immortalwrt/package/lean/luci-app-wrtbwmon/root/usr/sbin/wrtbwmon
2018-10-19 13:06:16 +08:00

302 lines
7.1 KiB
Bash
Executable File

#!/bin/sh
#
# wrtbwmon: traffic logging tool for routers
#
# Peter Bailey (peter.eldridge.bailey+wrtbwmon AT gmail.com)
#
# Based on work by:
# Emmanuel Brucy (e.brucy AT qut.edu.au)
# Fredrik Erlandsson (erlis AT linux.nu)
# twist - http://wiki.openwrt.org/RrdTrafficWatch
trap "rm -f /tmp/*_$$.tmp; kill $$" INT
binDir=/usr/sbin
dataDir=/usr/share/wrtbwmon
lockDir=/tmp/wrtbwmon.lock
pidFile=$lockDir/pid
networkFuncs=/lib/functions/network.sh
uci=`which uci 2>/dev/null`
nslookup=`which nslookup 2>/dev/null`
nvram=`which nvram 2>/dev/null`
chains='INPUT OUTPUT FORWARD'
DEBUG=
interfaces='eth0 tun0' # in addition to detected WAN
DB=$2
mode=
# DNS server for reverse lookups provided in "DNS".
# don't perform reverse DNS lookups by default
DO_RDNS=${DNS-}
header="#mac,ip,iface,in,out,total,first_date,last_date"
createDbIfMissing()
{
[ ! -f "$DB" ] && echo $header > "$DB"
}
checkDbArg()
{
[ -z "$DB" ] && echo "ERROR: Missing argument 2 (database file)" && exit 1
}
checkDB()
{
[ ! -f "$DB" ] && echo "ERROR: $DB does not exist" && exit 1
[ ! -w "$DB" ] && echo "ERROR: $DB is not writable" && exit 1
}
checkWAN()
{
[ -z "$wan" ] && echo "Warning: failed to detect WAN interface."
}
lookup()
{
MAC=$1
IP=$2
userDB=$3
for USERSFILE in $userDB /tmp/dhcp.leases /tmp/dnsmasq.conf /etc/dnsmasq.conf /etc/hosts; do
[ -e "$USERSFILE" ] || continue
case $USERSFILE in
/tmp/dhcp.leases )
USER=$(grep -i "$MAC" $USERSFILE | cut -f4 -s -d' ')
;;
/etc/hosts )
USER=$(grep "^$IP " $USERSFILE | cut -f2 -s -d' ')
;;
* )
USER=$(grep -i "$MAC" "$USERSFILE" | cut -f2 -s -d,)
;;
esac
[ "$USER" = "*" ] && USER=
[ -n "$USER" ] && break
done
if [ -n "$DO_RDNS" -a -z "$USER" -a "$IP" != "NA" -a -n "$nslookup" ]; then
USER=`$nslookup $IP $DNS | awk '!/server can/{if($4){print $4; exit}}' | sed -re 's/[.]$//'`
fi
[ -z "$USER" ] && USER=${MAC}
echo $USER
}
detectIF()
{
if [ -f "$networkFuncs" ]; then
IF=`. $networkFuncs; network_get_device netdev $1; echo $netdev`
[ -n "$IF" ] && echo $IF && return
fi
if [ -n "$uci" -a -x "$uci" ]; then
IF=`$uci get network.${1}.ifname 2>/dev/null`
[ $? -eq 0 -a -n "$IF" ] && echo $IF && return
fi
if [ -n "$nvram" -a -x "$nvram" ]; then
IF=`$nvram get ${1}_ifname 2>/dev/null`
[ $? -eq 0 -a -n "$IF" ] && echo $IF && return
fi
}
detectLAN()
{
[ -e /sys/class/net/br-lan ] && echo br-lan && return
lan=$(detectIF lan)
[ -n "$lan" ] && echo $lan && return
}
detectWAN()
{
[ -n "$WAN_IF" ] && echo $WAN_IF && return
wan=$(detectIF wan)
[ -n "$wan" ] && echo $wan && return
wan=$(ip route show 2>/dev/null | grep default | sed -re '/^default/ s/default.*dev +([^ ]+).*/\1/')
[ -n "$wan" ] && echo $wan && return
[ -f "$networkFuncs" ] && wan=$(. $networkFuncs; network_find_wan wan; echo $wan)
[ -n "$wan" ] && echo $wan && return
}
lock()
{
attempts=0
while [ $attempts -lt 10 ]; do
mkdir $lockDir 2>/dev/null && break
attempts=$((attempts+1))
pid=`cat $pidFile 2>/dev/null`
if [ -n "$pid" ]; then
if [ -d "/proc/$pid" ]; then
[ -n "$DEBUG" ] && echo "WARNING: Lockfile detected but process $(cat $pidFile) does not exist !"
rm -rf $lockDir
else
sleep 1
fi
fi
done
mkdir $lockDir 2>/dev/null
echo $$ > $pidFile
[ -n "$DEBUG" ] && echo $$ "got lock after $attempts attempts"
trap '' INT
}
unlock()
{
rm -rf $lockDir
[ -n "$DEBUG" ] && echo $$ "released lock"
trap "rm -f /tmp/*_$$.tmp; kill $$" INT
}
# chain
newChain()
{
chain=$1
# Create the RRDIPT_$chain chain (it doesn't matter if it already exists).
iptables -t mangle -N RRDIPT_$chain 2> /dev/null
# Add the RRDIPT_$chain CHAIN to the $chain chain if not present
iptables -t mangle -C $chain -j RRDIPT_$chain 2>/dev/null
if [ $? -ne 0 ]; then
[ -n "$DEBUG" ] && echo "DEBUG: iptables chain misplaced, recreating it..."
iptables -t mangle -I $chain -j RRDIPT_$chain
fi
}
# chain tun
newRuleIF()
{
chain=$1
IF=$2
#!@todo test
if [ "$chain" = "OUTPUT" ]; then
cmd="iptables -t mangle -o $IF -j RETURN"
eval $cmd " -C RRDIPT_$chain 2>/dev/null" || eval $cmd " -A RRDIPT_$chain"
elif [ "$chain" = "INPUT" ]; then
cmd="iptables -t mangle -i $IF -j RETURN"
eval $cmd " -C RRDIPT_$chain 2>/dev/null" || eval $cmd " -A RRDIPT_$chain"
fi
}
update()
{
#!@todo could let readDB.awk handle this; that would place header
#!info in fewer places
createDbIfMissing
checkDB
checkWAN
> /tmp/iptables_$$.tmp
lock
# only zero our own chains
for chain in $chains; do
iptables -nvxL RRDIPT_$chain -t mangle -Z >> /tmp/iptables_$$.tmp
done
# the iptables and readDB commands have to be separate. Otherwise,
# they will fight over iptables locks
awk -v mode="$mode" -v interfaces=\""$interfaces"\" -f $binDir/readDB.awk \
$DB \
/proc/net/arp \
/tmp/iptables_$$.tmp
unlock
}
############################################################
case $1 in
"dump" )
checkDbArg
lock
tr ',' '\t' < "$DB"
unlock
;;
"update" )
checkDbArg
wan=$(detectWAN)
interfaces="$interfaces $wan"
update
rm -f /tmp/*_$$.tmp
exit
;;
"publish" )
checkDbArg
[ -z "$3" ] && echo "ERROR: Missing argument 3 (output html file)" && exit 1
# sort DB
lock
# busybox sort truncates numbers to 32 bits
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
# create HTML page
rm -f $3.tmp
cp $dataDir/usage.htm1 $3.tmp
#!@todo fix publishing
while IFS=, read PEAKUSAGE_IN MAC IP IFACE PEAKUSAGE_OUT TOTAL FIRSTSEEN LASTSEEN
do
echo "
new Array(\"$(lookup $MAC $IP $4)\",\"$MAC\",\"$IP\",
$PEAKUSAGE_IN,$PEAKUSAGE_OUT,$TOTAL,\"$FIRSTSEEN\",\"$LASTSEEN\")," >> $3.tmp
done < /tmp/sorted_$$.tmp
echo "0);" >> $3.tmp
sed "s/(date)/`date`/" < $dataDir/usage.htm2 >> $3.tmp
mv $3.tmp $3
unlock
#Free some memory
rm -f /tmp/*_$$.tmp
;;
"setup" )
checkDbArg
[ -w "$DB" ] && echo "Warning: using existing $DB"
createDbIfMissing
for chain in $chains; do
newChain $chain
done
#lan=$(detectLAN)
wan=$(detectWAN)
checkWAN
interfaces="$interfaces $wan"
# track local data
for chain in INPUT OUTPUT; do
for interface in $interfaces; do
[ -n "$interface" ] && [ -e "/sys/class/net/$interface" ] && newRuleIF $chain $interface
done
done
# this will add rules for hosts in arp table
update
rm -f /tmp/*_$$.tmp
;;
"remove" )
iptables-save | grep -v RRDIPT | iptables-restore
rm -rf "$lockDir"
;;
*)
echo \
"Usage: $0 {setup|update|publish|remove} [options...]
Options:
$0 setup database_file
$0 update database_file
$0 publish database_file path_of_html_report [user_file]
Examples:
$0 setup /tmp/usage.db
$0 update /tmp/usage.db
$0 publish /tmp/usage.db /www/user/usage.htm /jffs/users.txt
$0 remove
Note: [user_file] is an optional file to match users with MAC addresses.
Its format is \"00:MA:CA:DD:RE:SS,username\", with one entry per line."
;;
esac