关于我
 

xjpvictor's Blog
小老鼠,上灯台,两只耳朵竖起来

VPN和SSH简单流量记录


各种折腾·archlinuxlinuxscriptvpnvps

本文发表于超过180天以前,部分内容可能已经失效

折腾了一下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中相应的记录:
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)}'`
这样就有流量了,
其他和vpn的disconnect脚本差不多,只是需要添加一个删除自己的cron任务的命令。

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执行的时候不认。

本文 "VPN和SSH简单流量记录" 由 K. Huang 首先发表于 xjpvictor's Blog 并以 CC BY-NC 4.0 许可证发布 © 2012
转载注明引用来源 https://blog.xjpvictor.info/2012/03/vpn-ssh-traffic-log/

赞助我

16条评论

  1. 用sshrc在用户登录的时候以root身份执行上面这个脚本

    请问这个如何做?setuid?

    回复
    • @luguo: 不好意思,忘了更新。我现在没有用sshrc了,而是用root执行cron读取ssh的日志判断是否连接。因为客户端可以设置不执行sshrc的。
      但其实要用root身份执行那个脚本我本来是用sudo的,把用户加到一个特定的组,再允许这个组以root权限执行那个脚本,虽然其实不是很安全。

      回复
      • @xjpvictor Huang: 感谢你的思路,给了我很多启发。一直在找办法统计ssh流量,原来只要更改log等级就能够分析流量了。不过我比较担心安全方面的问题以及log文件体积庞大的时候的分析效率。而我现在使用freeradius来管理多台服务器的ssh user,不知道有其他思路来统计各个低权限用户的流量不。请赐教。

        回复
        • @ruguo: 其实如果用log的话不是很好,因为在linux下的客户端退出时服务器不会记录流量。所以我最后还是转向了iptables。就是当用户登录时记录下ip地址和用户名并添加一条iptables规则,用户退出时根据这条iptables规则记录流量后删除该规则。这样也就不用分析日志了。freeradius不懂,感觉很麻烦,要搞数据库。

          回复
  2. 最终我打算用netstat 来判断是否在线。 然后用你的思路
    传递参数到iptable里 嘿嘿

    回复
  3. netstat -tpn | grep ESTABLISHED | grep $ssh-port

    剩下的就是awk +sed 就行了。

    我还在测试中,你也可以尝试一下。

    回复
  4. 交流嘛 嘿嘿。 加个友情否?以后没思路就找你了 哈哈

    回复
  5. 看你的这个流量记录 好似 ssh 跟vpn帐号可以一号共用啊
    可以实现么?

    回复
  6. 你好博主。能共享一下你写的PPTP/L2TP的connect/disconnect的脚本吗?我是Linux小白,不太会改脚本。在文章中我没有找到对应的connect脚本,所以如果博主方便可以发我邮箱或者给我一个share的地址吗?万分感谢。

    回复

评论

你的邮箱地址不会被公开。必填项以 * 标出

无意义或不相关评论将被删除

取消 上传图片 或拖拽上传

* Some files will not be uploaded. Only png, gif, jpg are allowed and maximum 10 images, each file less than 10M.

你可以添加 10 个 png/gif/jpg 图片 (每个文件小于 10M)

允许使用以下html标签:<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

你可以上传文件,粘贴代码或长文至 Drop.it.r

更多相似文章