├── .gitignore ├── README.md ├── changelog ├── control ├── copyright ├── debian ├── .gitignore ├── changelog ├── compat ├── control ├── copyright ├── install └── rules ├── example.png ├── fileMap ├── init └── wrtbwmon ├── install.sh ├── makefile ├── mkipk.sh ├── postinst ├── readDB.awk ├── release-notes ├── todo ├── usage.htm1 ├── usage.htm2 └── wrtbwmon /.gitignore: -------------------------------------------------------------------------------- 1 | *.db 2 | *.htm 3 | *.log 4 | *.old 5 | *~ 6 | .RData 7 | \#* 8 | old 9 | TAGS 10 | *.tgz 11 | *.ipk 12 | .gawk* 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # wrtbwmon 2 | Modified from https://code.google.com/p/wrtbwmon/. 3 | 4 | ## Testers needed for IPv6 support 5 | Experimental release: https://github.com/pyrovski/wrtbwmon/releases/tag/0.37_ipv6 6 | 7 | ## Features 8 | - "User" column has mouseover text containing MAC and IP addresses 9 | - "First seen" and "Total" columns in usage table 10 | - Monitoring of locally generated traffic on a per-interface basis 11 | - `remove` function to delete `iptables` rules 12 | 13 | ### What does it do? 14 | `wrtbwmon` was designed to track bandwidth consumption on home routers. 15 | It accomplishes this with `iptables` rules, which means you don't need to run an extra process just to track bandwidth. 16 | `wrtbwmon` conveniently tracks bandwidth consumption on a per-IP address basis, 17 | so you can easily determine which user/device is the culprit. 18 | 19 | Here is an example usage table: 20 | ![image](example.png) 21 | 22 | ### How do I use it? 23 | - Install: Download and install ipk from the [releases page](https://github.com/pyrovski/wrtbwmon/releases/) 24 | - Setup: `wrtbwmon setup` 25 | - Update table: `wrtbwmon update /tmp/usage.db` (you can place the data table anywhere) 26 | - Create html page: `wrtbwmon publish /tmp/usage.db /tmp/usage.htm` 27 | - Dump table to terminal: `wrtbwmon dump /tmp/usage.db` 28 | - Remove: `wrtbwmon remove` 29 | 30 | ### Installation options 31 | - Install ipk 32 | - e.g.,: 33 | - `cd /tmp` 34 | - HTTPS: `curl -LO https://github.com/pyrovski/wrtbwmon/releases/download/0.36/wrtbwmon_0.36_all.ipk` 35 | - HTTP: you're on your own :( Busybox wget usually doesn't have SSL support. 36 | - OpenWrt: `opkg install /tmp/wrtbwmon_0.36_all.ipk` 37 | - Install deb from [the releases page](https://github.com/pyrovski/wrtbwmon/releases) 38 | - Or, if you don't want to use an ipk or a deb: 39 | - `cd /tmp` 40 | - HTTPS: `curl -L https://github.com/pyrovski/wrtbwmon/archive/0.36.tar.gz | tar xvz` 41 | - `cd wrtbwmon-0.36` 42 | - `./install.sh wrtbwmon readDB.awk usage.htm1 usage.htm2 wrtbwmon` 43 | - Currently, this depends on the `install` program. OpenWrt chose to provide this as the "coreutils-install" package. 44 | - Or, if you have `make`, just `make install` as root after cloning/unpacking. 45 | 46 | ### Configuring the published table 47 | - `wrtbwmon` checks a few files for MAC -> name maps: 48 | - 4th argument to `wrtbwmon publish ` 49 | - `/tmp/dhcp.leases` 50 | - `/tmp/dnsmasq.conf` 51 | - `/etc/dnsmasq.conf` 52 | - `/etc/hosts` 53 | - If all of the above do not yield a match, the script will optionally perform a reverse DNS lookup directed at the DNS server specified in the `DNS` variable. If `DNS` is blank or unset, the script will not perform such lookups. 54 | 55 | ### Regular updates 56 | - Add the following to root's crontab: 57 | 58 | # adapt PATH to your needs 59 | PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin 60 | 61 | * * * * * 12 |

Total Usage:

13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
UserDownloadUploadTotalFirst seenLast seen
13 |
This page was generated on (date) 14 | 15 | -------------------------------------------------------------------------------- /wrtbwmon: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # wrtbwmon: traffic logging tool for routers 4 | # 5 | # Peter Bailey (peter.eldridge.bailey+wrtbwmon AT gmail.com) 6 | # 7 | # Based on work by: 8 | # Emmanuel Brucy (e.brucy AT qut.edu.au) 9 | # Fredrik Erlandsson (erlis AT linux.nu) 10 | # twist - http://wiki.openwrt.org/RrdTrafficWatch 11 | 12 | trap "rm -f /tmp/*_$$.tmp; kill $$" INT 13 | binDir=/usr/sbin 14 | dataDir=/usr/share/wrtbwmon 15 | lockDir=/tmp/wrtbwmon.lock 16 | pidFile=$lockDir/pid 17 | networkFuncs=/lib/functions/network.sh 18 | uci=`which uci 2>/dev/null` 19 | nslookup=`which nslookup 2>/dev/null` 20 | nvram=`which nvram 2>/dev/null` 21 | 22 | chains='INPUT OUTPUT FORWARD' 23 | DEBUG= 24 | interfaces='eth0 tun0' # in addition to detected WAN 25 | DB=$2 26 | mode= 27 | 28 | # DNS server for reverse lookups provided in "DNS". 29 | # don't perform reverse DNS lookups by default 30 | DO_RDNS=${DNS-} 31 | 32 | header="#mac,ip,iface,in,out,total,first_date,last_date" 33 | 34 | createDbIfMissing() 35 | { 36 | [ ! -f "$DB" ] && echo $header > "$DB" 37 | } 38 | 39 | checkDbArg() 40 | { 41 | [ -z "$DB" ] && echo "ERROR: Missing argument 2 (database file)" && exit 1 42 | } 43 | 44 | checkDB() 45 | { 46 | [ ! -f "$DB" ] && echo "ERROR: $DB does not exist" && exit 1 47 | [ ! -w "$DB" ] && echo "ERROR: $DB is not writable" && exit 1 48 | } 49 | 50 | checkWAN() 51 | { 52 | [ -z "$wan" ] && echo "Warning: failed to detect WAN interface." 53 | } 54 | 55 | lookup() 56 | { 57 | MAC=$1 58 | IP=$2 59 | userDB=$3 60 | for USERSFILE in $userDB /tmp/dhcp.leases /tmp/dnsmasq.conf /etc/dnsmasq.conf /etc/hosts; do 61 | [ -e "$USERSFILE" ] || continue 62 | case $USERSFILE in 63 | /tmp/dhcp.leases ) 64 | USER=$(grep -i "$MAC" $USERSFILE | cut -f4 -s -d' ') 65 | ;; 66 | /etc/hosts ) 67 | USER=$(grep "^$IP " $USERSFILE | cut -f2 -s -d' ') 68 | ;; 69 | * ) 70 | USER=$(grep -i "$MAC" "$USERSFILE" | cut -f2 -s -d,) 71 | ;; 72 | esac 73 | [ "$USER" = "*" ] && USER= 74 | [ -n "$USER" ] && break 75 | done 76 | if [ -n "$DO_RDNS" -a -z "$USER" -a "$IP" != "NA" -a -n "$nslookup" ]; then 77 | USER=`$nslookup $IP $DNS | awk '!/server can/{if($4){print $4; exit}}' | sed -re 's/[.]$//'` 78 | fi 79 | [ -z "$USER" ] && USER=${MAC} 80 | echo $USER 81 | } 82 | 83 | detectIF() 84 | { 85 | if [ -f "$networkFuncs" ]; then 86 | IF=`. $networkFuncs; network_get_device netdev $1; echo $netdev` 87 | [ -n "$IF" ] && echo $IF && return 88 | fi 89 | 90 | if [ -n "$uci" -a -x "$uci" ]; then 91 | IF=`$uci get network.${1}.ifname 2>/dev/null` 92 | [ $? -eq 0 -a -n "$IF" ] && echo $IF && return 93 | fi 94 | 95 | if [ -n "$nvram" -a -x "$nvram" ]; then 96 | IF=`$nvram get ${1}_ifname 2>/dev/null` 97 | [ $? -eq 0 -a -n "$IF" ] && echo $IF && return 98 | fi 99 | } 100 | 101 | detectLAN() 102 | { 103 | [ -e /sys/class/net/br-lan ] && echo br-lan && return 104 | lan=$(detectIF lan) 105 | [ -n "$lan" ] && echo $lan && return 106 | } 107 | 108 | detectWAN() 109 | { 110 | [ -n "$WAN_IF" ] && echo $WAN_IF && return 111 | wan=$(detectIF wan) 112 | [ -n "$wan" ] && echo $wan && return 113 | wan=$(ip route show 2>/dev/null | grep default | sed -re '/^default/ s/default.*dev +([^ ]+).*/\1/') 114 | [ -n "$wan" ] && echo $wan && return 115 | [ -f "$networkFuncs" ] && wan=$(. $networkFuncs; network_find_wan wan; echo $wan) 116 | [ -n "$wan" ] && echo $wan && return 117 | } 118 | 119 | lock() 120 | { 121 | attempts=0 122 | while [ $attempts -lt 10 ]; do 123 | mkdir $lockDir 2>/dev/null && break 124 | attempts=$((attempts+1)) 125 | pid=`cat $pidFile 2>/dev/null` 126 | if [ -n "$pid" ]; then 127 | if [ -d "/proc/$pid" ]; then 128 | [ -n "$DEBUG" ] && echo "WARNING: Lockfile detected but process $(cat $pidFile) does not exist !" 129 | rm -rf $lockDir 130 | else 131 | sleep 1 132 | fi 133 | fi 134 | done 135 | mkdir $lockDir 2>/dev/null 136 | echo $$ > $pidFile 137 | [ -n "$DEBUG" ] && echo $$ "got lock after $attempts attempts" 138 | trap '' INT 139 | } 140 | 141 | unlock() 142 | { 143 | rm -rf $lockDir 144 | [ -n "$DEBUG" ] && echo $$ "released lock" 145 | trap "rm -f /tmp/*_$$.tmp; kill $$" INT 146 | } 147 | 148 | # chain 149 | newChain() 150 | { 151 | chain=$1 152 | # Create the RRDIPT_$chain chain (it doesn't matter if it already exists). 153 | iptables -t mangle -N RRDIPT_$chain 2> /dev/null 154 | 155 | # Add the RRDIPT_$chain CHAIN to the $chain chain if not present 156 | iptables -t mangle -C $chain -j RRDIPT_$chain 2>/dev/null 157 | if [ $? -ne 0 ]; then 158 | [ -n "$DEBUG" ] && echo "DEBUG: iptables chain misplaced, recreating it..." 159 | iptables -t mangle -I $chain -j RRDIPT_$chain 160 | fi 161 | } 162 | 163 | # chain tun 164 | newRuleIF() 165 | { 166 | chain=$1 167 | IF=$2 168 | 169 | #!@todo test 170 | if [ "$chain" = "OUTPUT" ]; then 171 | cmd="iptables -t mangle -o $IF -j RETURN" 172 | eval $cmd " -C RRDIPT_$chain 2>/dev/null" || eval $cmd " -A RRDIPT_$chain" 173 | elif [ "$chain" = "INPUT" ]; then 174 | cmd="iptables -t mangle -i $IF -j RETURN" 175 | eval $cmd " -C RRDIPT_$chain 2>/dev/null" || eval $cmd " -A RRDIPT_$chain" 176 | fi 177 | } 178 | 179 | update() 180 | { 181 | #!@todo could let readDB.awk handle this; that would place header 182 | #!info in fewer places 183 | createDbIfMissing 184 | 185 | checkDB 186 | checkWAN 187 | 188 | > /tmp/iptables_$$.tmp 189 | lock 190 | # only zero our own chains 191 | for chain in $chains; do 192 | iptables -nvxL RRDIPT_$chain -t mangle -Z >> /tmp/iptables_$$.tmp 193 | done 194 | # the iptables and readDB commands have to be separate. Otherwise, 195 | # they will fight over iptables locks 196 | awk -v mode="$mode" -v interfaces=\""$interfaces"\" -f $binDir/readDB.awk \ 197 | $DB \ 198 | /proc/net/arp \ 199 | /tmp/iptables_$$.tmp 200 | unlock 201 | } 202 | 203 | ############################################################ 204 | 205 | case $1 in 206 | "dump" ) 207 | checkDbArg 208 | lock 209 | tr ',' '\t' < "$DB" 210 | unlock 211 | ;; 212 | 213 | "update" ) 214 | checkDbArg 215 | wan=$(detectWAN) 216 | interfaces="$interfaces $wan" 217 | update 218 | rm -f /tmp/*_$$.tmp 219 | exit 220 | ;; 221 | 222 | "publish" ) 223 | checkDbArg 224 | [ -z "$3" ] && echo "ERROR: Missing argument 3 (output html file)" && exit 1 225 | 226 | # sort DB 227 | lock 228 | 229 | # busybox sort truncates numbers to 32 bits 230 | grep -v '^#' $DB | awk -F, '{OFS=","; a=sprintf("%f",$4/1e6); $4=""; print a,$0}' | tr -s ',' | sort -rn | awk -F, '{OFS=",";$1=sprintf("%f",$1*1e6);print}' > /tmp/sorted_$$.tmp 231 | 232 | # create HTML page 233 | rm -f $3.tmp 234 | cp $dataDir/usage.htm1 $3.tmp 235 | 236 | #!@todo fix publishing 237 | while IFS=, read PEAKUSAGE_IN MAC IP IFACE PEAKUSAGE_OUT TOTAL FIRSTSEEN LASTSEEN 238 | do 239 | echo " 240 | new Array(\"$(lookup $MAC $IP $4)\",\"$MAC\",\"$IP\", 241 | $PEAKUSAGE_IN,$PEAKUSAGE_OUT,$TOTAL,\"$FIRSTSEEN\",\"$LASTSEEN\")," >> $3.tmp 242 | done < /tmp/sorted_$$.tmp 243 | echo "0);" >> $3.tmp 244 | 245 | sed "s/(date)/`date`/" < $dataDir/usage.htm2 >> $3.tmp 246 | mv $3.tmp $3 247 | 248 | unlock 249 | 250 | #Free some memory 251 | rm -f /tmp/*_$$.tmp 252 | ;; 253 | 254 | "setup" ) 255 | checkDbArg 256 | [ -w "$DB" ] && echo "Warning: using existing $DB" 257 | createDbIfMissing 258 | 259 | for chain in $chains; do 260 | newChain $chain 261 | done 262 | 263 | #lan=$(detectLAN) 264 | wan=$(detectWAN) 265 | checkWAN 266 | interfaces="$interfaces $wan" 267 | 268 | # track local data 269 | for chain in INPUT OUTPUT; do 270 | for interface in $interfaces; do 271 | [ -n "$interface" ] && [ -e "/sys/class/net/$interface" ] && newRuleIF $chain $interface 272 | done 273 | done 274 | 275 | # this will add rules for hosts in arp table 276 | update 277 | 278 | rm -f /tmp/*_$$.tmp 279 | ;; 280 | 281 | "remove" ) 282 | iptables-save | grep -v RRDIPT | iptables-restore 283 | rm -rf "$lockDir" 284 | ;; 285 | 286 | *) 287 | echo \ 288 | "Usage: $0 {setup|update|publish|remove} [options...] 289 | Options: 290 | $0 setup database_file 291 | $0 update database_file 292 | $0 publish database_file path_of_html_report [user_file] 293 | Examples: 294 | $0 setup /tmp/usage.db 295 | $0 update /tmp/usage.db 296 | $0 publish /tmp/usage.db /www/user/usage.htm /jffs/users.txt 297 | $0 remove 298 | Note: [user_file] is an optional file to match users with MAC addresses. 299 | Its format is \"00:MA:CA:DD:RE:SS,username\", with one entry per line." 300 | ;; 301 | esac 302 | --------------------------------------------------------------------------------