Jamyy's Weblog

Linux Shell Script: 自動限制高流量使用者頻寬

by Jamyy on 九月.07, 2012, under Linux

在 Linux NAT 設置 IPFM 統計使用者每五分鐘網路流量, 利用 Shell Script 分析統計結果, 自動管制超過指定流量的使用者頻寬。

環境

Linux Server / NAT: Fedora 9, ipfm-0.11.5
Linux Server IP Address: 192.168.1.1
Local Area Network: 192.168.1.0/24
LAN-to-LAN VPN: 192.168.0.0/16

寬頻 6Mbps 雙向, 五分鐘內上傳或下載超過 120MB 者 (排除 VPN) 限速 2Mbps, 一小時後解除限制。

安裝需求套件

# yum install libpcap-devel byacc flex

安裝 IPFM

# wget http://robert.cheramy.net/ipfm/archive/ipfm-0.11.5.tgz
# tar zxf ipfm-0.11.5.tgz
# cd ipfm-0.11.5
# ./configure --prefix=/opt/ipfm-0.11.5
# make
# make install
# cd /opt
# ln -s ipfm-0.11.5 ipfm
# echo "MANPATH /opt/ipfm/man" >> /etc/man.config

編輯 IPFM 設定檔

# vi /opt/ipfm/etc/ipfm.conf

# IPFM can monitor only one device.
DEVICE eth0

# log subnet 192.168.1.0 when not in relation with subnet 192.168.0.0
LOG 192.168.1.0/255.255.255.0 NOT WITH 192.168.0.0/255.255.0.0

# do not log 192.168.1.1 (self)
LOG NONE 192.168.1.1

# dump results /path/to/filename
FILENAME "/var/log/ipfm/%Y_%m_%d/%H_%M"

# log every 5 minutes
DUMP EVERY 5 minutes

# clear statistics every 5 minutes
CLEAR EVERY 5 minutes

# sort by TOTAL bytes (desc); 
SORT TOTAL

# resolve ip to hostname
RESOLVE

設置開機啟動 cbq 與 IPFM

# vi /etc/rc.d/rc.local

/sbin/cbq start
/opt/ipfm/sbin/ipfm

立即啟動 IPFM

# /opt/ipfm/sbin/ipfm

設定 cbq 頻寬限制規則

# vi /etc/sysconfig/cbq-0002.limit

# 若網卡做 bridge 也要用網卡裝置名稱 (如: eth0) 才能正常管制
DEVICE=eth0,100Mbit,10Mbit
RATE=2Mbit
WEIGHT=200Kbit
PRIO=5
MARK=10

# cbq restart

設定 iptables 管制區

# iptables -t mangle -N limit
# iptables -t mangle -A limit -j RETURN
# iptables -t mangle -I FORWARD 1 -j limit
# iptables -t mangle -I POSTROUTING 1 -j limit # for proxy download limit
# service iptables save

分析 IPFM log file, 自動管制高頻寬使用者

# vi /root/bin/limit.sh

#!/bin/bash

# iptables 指令路徑
IPT=/usr/local/sbin/iptables

# IPFM log file 路徑
BASEPATH=/var/log/ipfm/$(date +%Y_%m_%d)
if [ ! -d "$BASEPATH" ]; then exit 1; fi

# 取得最新 log file
FILE=$(ls -t $BASEPATH | head -n 1)
if [ ! -e "$BASEPATH/$FILE" ]; then exit 1; fi

# 用以判別本次執行結果是否有使用者遭到限制頻寬
FLAGFILE=/tmp/limit.flag

# 允許每五分鐘最大傳輸量, 單位: MBytes
MAX_MB=120

# 找出 下載(IN) 或 上傳(OUT) 超過 $MAX_MB 的使用者
# 限制連線頻率, 並將其網路封包標上 MARK
# 交給 cbq 限制頻寬, 一小時後自動解除頻寬限制
tail -n +3 $BASEPATH/$FILE | awk -v MAX_MB=$MAX_MB \
'{ if ($2/1000/1000 > MAX_MB || $3/1000/1000 > MAX_MB) print $1 }' | \
while read IP; do \
$IPT -t mangle -N $IP; \
$IPT -t mangle -A $IP -s $IP -p tcp ! -d 192.168.0.0/16 --dport 1024:65535 -m state --state RELATED,ESTABLISHED -m limit --limit 50/sec --limit-burst 50 -j ACCEPT; \
$IPT -t mangle -A $IP -s $IP -p udp ! -d 192.168.0.0/16 --dport 1024:65535 -m state --state RELATED,ESTABLISHED -m limit --limit 50/sec --limit-burst 50 -j ACCEPT; \
$IPT -t mangle -A $IP -s $IP -p tcp ! -d 192.168.0.0/16 --dport 1024:65535 -m state --state RELATED,ESTABLISHED -j DROP; \
$IPT -t mangle -A $IP -s $IP -p udp ! -d 192.168.0.0/16 --dport 1024:65535 -m state --state RELATED,ESTABLISHED -j DROP; \
$IPT -t mangle -A $IP -s $IP ! -d 192.168.0.0/16 -j MARK --set-mark 10; \
$IPT -t mangle -A $IP -d $IP ! -s 192.168.0.0/16 -j MARK --set-mark 10; \
$IPT -t mangle -A $IP -d $IP -p tcp --sport 3128 -j MARK --set-mark 10; \
$IPT -t mangle -A $IP -j RETURN; \
$IPT -t mangle -I limit -s $IP -j $IP; \
$IPT -t mangle -I limit -d $IP -j $IP; \
echo "/root/bin/release.sh $IP" | at now + 1 hour; \
echo $IP >> $FLAGFILE; \
done

# 若有使用者被限制頻寬則郵件通知系統管理員
if [ -e $FLAGFILE ]; then
	cat $FLAGFILE $BASEPATH/$FILE | /bin/mail -s "limit" "admin@mycompany.com"
	rm -f $FLAGFILE
fi

# chmod +x /root/bin/limit.sh

撰寫解除限制 Shell Script

# vi /root/bin/release.sh

#!/bin/bash

if [ -z "$1" ]; then
	echo "Usage: $0 ip_address or hostname"
	exit 1;
fi

IP=$1
IPT=/usr/sbin/iptables

$IPT -t mangle -F $IP
$IPT -t mangle -D limit -s $IP -j $IP
$IPT -t mangle -D limit -d $IP -j $IP
$IPT -t mangle -X $IP

# chmod +x /root/bin/release.sh

排程自動執行

# crontab -e

# 於 IPFM log 時間 (0,5,10,15...) 後一分鐘分析統計資料
1,6,11,16,21,26,31,36,41,46,51,56 * * * * /root/bin/limit.sh

撰寫簡易 IPFM Log Parser

# vi /root/bin/topuser.sh

#!/bin/bash

# 顯示參數說明
echo -e "Usage: $0 [MB] [Date] [IN|OUT|TOTAL]\n"

# IPFM Log File 路徑 (預設取用當天日期)
BASEPATH=/var/log/ipfm/$(date --date "$2" +%Y_%m_%d)

# 找不到 IPFM Log File 路徑的處理
if [ ! -d "$BASEPATH" ]; then
        echo "$BASEPATH not found"
        exit 1
fi

# 若沒有給參數, 預設顯示當天最新 Log 檔案的前五名, 傳輸量換算為 MBytes
if [ -z "$1" ]; then
        FILE=$(ls -t $BASEPATH | head -n 1)
        if [ -f $BASEPATH/$FILE ]; then
                echo -e "$BASEPATH/$FILE\n"
                cat $BASEPATH/$FILE | tail -n +3 | head -n 5 | \
                awk '{ print $1,$2/1000/1000,$3/1000/1000,$4/1000/1000 }'
                echo
                exit
        else
                echo "There is no file in $BASEPATH"
                exit 1
        fi
fi

# 將 IN/OUT/TOTAL 參數轉換為對應欄位編號, 預設比對第四個欄位 (TOTAL)
case $3 in
        "IN" ) COL=2 ;;
        "OUT" ) COL=3 ;;
        * ) COL=4 ;;
esac

# 設定 MBytes 值
MB=$1

# 挑出指定欄位 (IN|OUT|TOTAL, 預設 TOTAL) 大於 $MB 的資料
# 輸出結果為: Log_FileName IP_or_Hostname IN_in_MB OUT_in_MB TOTAL_in_MB
echo -e "$BASEPATH\n"
cd $BASEPATH
for i in *; do
        tail -n +3 $i | sort -k $COL -nr | \
        awk -v MB=$MB \
        '{ if ($"'$COL'"/1000/1000 > MB) print "'$i'",$1,$2/1000/1000,$3/1000/1000,$4/1000/1000 }'
done

echo

# chmod +x /root/bin/topuser.sh

topuser.sh 操作範例

查看今天最近一次統計數據的前五名
# topuser.sh

Usage: /root/bin/topuser.sh [MB] [Date] [IN|OUT|TOTAL]

/var/log/ipfm/2012_09_06/09_10

adan1 46.5151 39.6769 86.1919
jason4 15.5354 0.111663 15.6471
saul1 0.162877 7.46824 7.63112
eric2 0.307504 5.01511 5.32261
spring 0.750812 0.011906 0.762718

查看今天每五分鐘總流量超過 80MB 的記錄
# topuser.sh 80

Usage: /root/bin/topuser.sh [MB] [Date] [IN|OUT|TOTAL]

/var/log/ipfm/2012_09_06

08_50 adan1 96.1725 3.50315 99.6756
09_10 adan1 46.5151 39.6769 86.1919
09_15 adan1 46.7223 53.1282 99.8504
09_20 adan1 46.5669 51.3431 97.91
09_25 adan1 46.6401 52.2849 98.925
09_30 adan1 152.87 28.407 181.277
09_35 adan1 114.225 7.63813 121.863
09_40 adan1 76.7918 4.87232 81.6641
09_45 adan1 73.0095 7.02855 80.038
10_25 adan1 76.9286 3.92587 80.8544

查看昨天每五分鐘總流量超過 190MB 的記錄
# topuser.sh 190 yesterday

Usage: /root/bin/topuser.sh [MB] [Date] [IN|OUT|TOTAL]

/var/log/ipfm/2012_09_05

09_55 adan1 167.637 25.8104 193.447
12_40 joe 229.067 20.697 249.764
12_55 joe 13.4598 179.731 193.191
13_05 joe 139.561 107.615 247.176
13_10 joe 99.4451 164.564 264.009
13_15 joe 7.56162 197.875 205.436
13_25 joe 8.61918 201.638 210.257
13_35 joe 121.724 70.3481 192.072

查看某日期每五分鐘下載流量超過 100MB 的記錄
# topuser.sh 100 2012-9-4 IN

Usage: /root/bin/topuser.sh [MB] [Date] [IN|OUT|TOTAL]

/var/log/ipfm/2012_09_04

08_25 adan1 120.288 8.6715 128.96
12_25 jason3 117.803 0.065849 117.869
12_30 jason3 107.993 0.02682 108.019

附錄: 寬頻頻寬 (Mbps) / 每秒最高傳輸理論值 (MBps) / 每五分鐘傳輸最大理論值 (MB per 5 minutes)

100 Mbps / 8 = 12.50 MBps * 60 * 5 = 3,750 MB per 5 minutes
 50 Mbps / 8 =  6.25 MBps * 60 * 5 = 1,875 MB per 5 minutes
 20 Mbps / 8 =  2.50 MBps * 60 * 5 =   750 MB per 5 minutes
 12 Mbps / 8 =  1.50 MBps * 60 * 5 =   450 MB per 5 minutes
  8 Mbps / 8 =  1.00 MBps * 60 * 5 =   300 MB per 5 minutes
  6 Mbps / 8 =  0.75 MBps * 60 * 5 =   225 MB per 5 minutes

IPFM 官方網頁: http://robert.cheramy.net/ipfm/
awk 指令參考: UNIX BASH scripting: Accessing external variable in AWK and SED



:,