折腾了一下VPN和SSH的流量记录方法。因为没有对外开放,只是我自己用,所以纯属自娱自乐,而网上的方法都是用freeradius管理,就有点太重量级了,所以自己搞两个小脚本解决。
OPENVPN
先说OpenVPN。OpenVPN的conf文件中可以定义连接时和断开时执行的脚本。所以在conf里加
client-connect /path-to-script/connect
client-disconnect /path-to-script/disconnect
management x.x.x.x port
把path-to-script改为connect和disconnect这两个脚本所在的目录,注意下执行openvpn的帐户需要对这两个文件有执行权限。把management后面的换成服务器自己的ip,可以用lo的,port自己设置。其中disconnect脚本的内容:
#!/bin/bash
now=`date +%Y-%m`
log=/etc/vpn/user
let q=`cat $log | grep "Quota" | awk -F'\t' '{print $NF}'`
e=`cat $log | grep $common_name | awk -F'\t' '{print $4}'`
old=`cat $log | grep $common_name | awk -F'\t' '{print $1}'`
let u1=`cat $log | grep $common_name | awk -F'\t' '{print $3}'`
if [ $old == $now ];then
let u=$u1+$bytes_received+$bytes_sent
else
let u=$bytes_received+$bytes_sent
fi
sed -i -e "s/$old\t$common_name\t$u1/$now\t$common_name\t$u/" $log
openvpn断开连接的时候会自动把$common_name,$bytes_sent,$bytes_received告诉disconnect脚本,然后disconnect就把这次连接所用的流量记录下来,并和/etc/vpn/user中的记录相加,得到新的流量,再记到/etc/vpn/user中。/etc/vpn/user里的格式我用的是:"年-月tab用户名tab流量tab邮箱tab加密后的密码"。年月是为了每个月把记录清零,而邮箱是可以在disconnect中添加比如mutt之类的命令,当流量用光了就发邮件通知。
而connect中则需要把记录中的流量读取出来,和限制的流量数目相比较,如果大于或等于,就把连接kill掉。而kill连接就需要用到management的interface了。
(sleep 1;echo kill $common_name;) | telnet -4 x.x.x.x port
sleep 1;很重要,否则telnet无法传递kill命令进去,而IP地址和端口号就是和conf文件中对应。这个命令还能用来在每天定时断开所有连接,只是把kill $common_name;换成signal SIGHUP;就行,SIGHUP信号会告诉openvpn断开所有连接再重新分配ip地址,相当于重启openvpn服务。这样就能每天至少统计一次流量。当然了,实际上网络问题会导致连接断开,几天几夜一直连着不断的情况好像挺难的,尤其是在某朝的网络状况。
PPTP/L2TP
这两个本质上是一样的,都是用的ppp,而ppp同样提供在连接和断开连接的时候执行脚本的功能。在/etc/ppp/ip-up.d/中放connect脚本,在/etc/ppp/ip-down.d/中放disconnect脚本。这两个脚本和OpenVPN的那两个没什么区别,可以把对应的直接拷过来,但是要把$common_name改为$PEERNAME,$bytes_sent和$bytes_received分别改为$BYTES_SENT和$BYTES_RCVD。
对于ppp,在connect脚本中,如果超了流量要拒绝连接,需要用
ifconfig $IFNAME down
关闭相应的interface,因为ppp每个连接都是单独的interface,不像openvpn都是同一个。而要定时断开所有连接的话直接
killall pppd
就行了。
SSH
Ok, now we are at the most tricky part. tricky是因为SSH并不支持断开连接的时候执行脚本。其实bash.bash_logout可以在logout的时候执行命令,但问题是如果没有logout,比如说直接断开连接,或者干脆就没用bash作为shell的话就不行了。所以还得用别的。
首先在/etc/ssh/sshrc中加入
#!/bin/bash
dir=/etc/vpn/ssh
pid=`ps x | grep sshd | grep -v grep | awk -F' ' '{print $1}' | tail -n 2 | head -n 1`
user=`ps -p $pid -o uname | tail -n 1`
ppid=`ps -p $pid -o ppid | tail -n 1`
crontab -l > $dir/$pid.cron 2>&1
if [ -z "`cat $dir/$pid.cron | grep MAILTO`" ];then
echo -e "MAILTO=\"\"" > $dir/$pid.cron
fi
echo "*/10 * * * * $dir/disconnect $pid $ppid $user > /dev/null 2>&1" >> $dir/$pid.cron
crontab $dir/$pid.cron
rm $dir/$pid.cron
没有sshrc就新建一个。当新的ssh连接建立的时候,会产生两个新的进程,其中一个是parent,一个是child。因为执行sshrc的是登录的帐户,所以先列出该帐户的进程,找到其中ssh相关的,而新的进程总是排在最下面,所以就能找到进程的pid,然后再以这个pid为基础找到ppid和username。然后新建一个cron任务,10分钟执行一次disconnect脚本,而这个脚本就是根据pid和ppid判断该连接是否断开,如果断开了就去ssh的log文件里找到相应的流量记录,并把运行自己的这个任务从cron中删除。所以pid,ppid和user都要告诉disconnect脚本,以便disconnect能准确的判断进程和进程的log,而不是把别的进程搞混。在disconnect脚本中主要靠以下的命令判断进程是否结束
ps -p "$1" | grep -v PID
其中$1就是第一个参数,也就是pid了。一旦该命令执行结果为空,就是说进程结束了,那就表示连接断开了,这时候再去ssh的log中读取该进程产生的流量。这是log中相应的记录:其他和vpn的disconnect脚本差不多,只是需要添加一个删除自己的cron任务的命令。
Mar 3 13:53:37 vps sshd[23782]: Connection closed by x.x.x.x
Mar 3 13:53:37 vps sshd[23782]: Transferred: sent 3048, received 1712 bytes
Mar 3 13:53:37 vps sshd[23782]: Closing connection to x.x.x.x port xxxx
Mar 3 13:53:37 vps sshd[23780]: pam_unix_session(sshd:session): session closed for user xxxx
所以就是根据ppid和用户名找到连接断开的时间,再找到该时间点对应pid的记录。ssh的sshd_config中要把LogLevel设为VERBOSE,否则不会记录下流量。所以disconnect里这么写:
time=`cat $log | grep "session closed for user $3" | grep "$2" | awk -F' ' '{print $3}'`
let bytes_sent=`cat $log | grep "$time" | grep "$1" | grep Transferred | awk -F' ' '{print $8}' | sed 's/,//'`
let bytes_rcvd=`cat $log | grep "$time" | grep "$1" | grep Transferred | awk -F' ' '{print $(NF-1)}'`
这样就有流量了,
crontab -l > $dir/$1.cron
sed -i "/$1/d" $dir/$1.cron
if [ -z "`cat $dir/$1.cron | sed '/MAILTO/d'`" ];then
crontab -r
else
crontab $dir/$1.cron
fi
rm $dir/$1.cron
先把该用户的cron列出来,然后删掉需要删掉的部分,如果删掉以后没有别的任务了,就直接把整个crontab给删掉了,否则就只是删掉当前的任务而已。
同样的,设置定时重启,但是不能直接删掉所有sshd进程,因为有一个是daemon的,需要保留。所以这么做:
l=`ps ax | grep sshd | grep -v grep | grep -v priv | grep -v sbin | awk -F' ' '{print $1}'`
for i in $l
do
kill -9 $i
done
这样不论是VPN还是SSH,不论是哪种VPN都能记录流量了。我突然有个广告画面在脑中,画外音:不管怎么翻,是这么翻,这么翻,还是这么翻,不会漏,就是不会漏。然后再加个妹纸,一定要穿一条白色的裤子,在床上滚来滚去,镜头还得对着屁股,末了对着镜头一笑,那几天,再也不用担心了,噢耶。。
Updated: 2012-Mar-30
SSH的流量计录还是有些问题。用putty的时候如果正常退出是可以在log中留下流量记录的,但是如果是linux下的openssh-client,不论客户端怎么退出都不会在server上留下流量记录。所以还是要用iptables来记录。
#!/bin/bash
#$1=ip
#$2=user
#$3=connect/disconnect
dir=/etc/vpn/ssh
if [ "$3" == "1" ];then
t=`/usr/sbin/iptables -L OUTPUT -nx | grep $1 | grep "$2"`
if [ -z "$t" ];then
/usr/sbin/iptables -I INPUT -p tcp --dport xx -s $1 -m comment --comment "$2"
/usr/sbin/iptables -I OUTPUT -p tcp --sport xx -d $1 -m comment --comment "$2"
fi
fi
if [ "$3" == "2" ];then
let n=`who | grep $2 | grep $1 -c`
if [ $n -eq 0 ];then
let in=`/usr/sbin/iptables -L INPUT -nxv | grep "$1" | grep "$2" | awk '{print $2}'`
let out=`/usr/sbin/iptables -L OUTPUT -nxv | grep "$1" | grep "$2" | awk '{print $2}'`
/usr/sbin/iptables -D INPUT -p tcp --dport xx -s $1 -m comment --comment "$2"
/usr/sbin/iptables -D OUTPUT -p tcp --sport xx -d $1 -m comment --comment "$2"
else
let in=0
let out=0
fi
echo "$in $out"
fi
用sshrc在用户登录的时候以root身份执行上面这个脚本,运行时候传递三个参数,第一个是用户的ip,第二个是用户名,第三个是数字1,1表示执行第一部分,也就是添加iptables规则。然后脚本会判断,如果这个ip的该用户还没有相应的规则,就添加,否则就不填加,因为一个ip只需要一条规则来计算流量。
而在用户退出以后,仍然用前面的cron每10分钟执行disconnect脚本的办法,只是把disconnect脚本中判断流量的部分换成以root身份执行上面这个脚本。同样是三个参数,只是把第三个参数换成数字2,脚本就会执行第二部分,记录流量,删除iptables规则。同样的,脚本也会判断,如果该ip的该用户全部连接都断开了,才会记录流量并删除规则。
添加iptables的时候一定要用-I,否则如果加在最后面的话,iptables先ACCEPT了到ssh端口的INPUT包就不会再继续执行后面这条记录流量的链了。而且iptables一定要用绝对路径,不然cron执行的时候不认。
用sshrc在用户登录的时候以root身份执行上面这个脚本
请问这个如何做?setuid?
@luguo: 不好意思,忘了更新。我现在没有用sshrc了,而是用root执行cron读取ssh的日志判断是否连接。因为客户端可以设置不执行sshrc的。
但其实要用root身份执行那个脚本我本来是用sudo的,把用户加到一个特定的组,再允许这个组以root权限执行那个脚本,虽然其实不是很安全。
@K. Huang: 感谢你的思路,给了我很多启发。一直在找办法统计ssh流量,原来只要更改log等级就能够分析流量了。不过我比较担心安全方面的问题以及log文件体积庞大的时候的分析效率。而我现在使用freeradius来管理多台服务器的ssh user,不知道有其他思路来统计各个低权限用户的流量不。请赐教。
@ruguo: 其实如果用log的话不是很好,因为在linux下的客户端退出时服务器不会记录流量。所以我最后还是转向了iptables。就是当用户登录时记录下ip地址和用户名并添加一条iptables规则,用户退出时根据这条iptables规则记录流量后删除该规则。这样也就不用分析日志了。freeradius不懂,感觉很麻烦,要搞数据库。
@K. Huang: 所以只要结合你上述所说的办法就行。首先通过log分析当前在线的用户。然后传递相关参数到脚本里设置iptables就行吧?
另外你所说的客户端可以不执行sshrc是怎么一回事?能否说一下?多谢。
@luguo: ssh -N 不执行远程命令
最终我打算用netstat 来判断是否在线。 然后用你的思路
传递参数到iptable里 嘿嘿
@luguo: 这个我曾经考虑过,但不知道怎么弄,能教下么?
netstat -tpn | grep ESTABLISHED | grep $ssh-port
剩下的就是awk +sed 就行了。
我还在测试中,你也可以尝试一下。
@luguo: 嗯,真不错。不用每次读取ssh日志那个大家伙。谢谢了。
交流嘛 嘿嘿。 加个友情否?以后没思路就找你了 哈哈
@luguo: 不好意思啊,我不太加友链的
看你的这个流量记录 好似 ssh 跟vpn帐号可以一号共用啊
可以实现么?
@Hong: 不能的。ssh是系统帐号,pptp的记录在/etc/ppp/chap-secret中,openvpn则是通过证书
你好博主。能共享一下你写的PPTP/L2TP的connect/disconnect的脚本吗?我是Linux小白,不太会改脚本。在文章中我没有找到对应的connect脚本,所以如果博主方便可以发我邮箱或者给我一个share的地址吗?万分感谢。
@iVan: connect脚本,https://vik.im/4d26,放在 /etc/ppp/ip-up.d/ 里。disconnect脚本,https://vik.im/de7a,放在 /etc/ppp/ip-down.d/ 里。记录用户流量的文件在 /etc/vpn/user,可以改。