Keeping Tabs on Pesky VPN Users
I run a VPN server for my friends on an Ubuntu 10.04 box. Bandwidth usage went through the roof so to track down which of them was doing it I hacked up a 70 line Ruby script.
This assumes every user gets one unique IP address mentioned in the last column of /etc/ppp/chap-secrets
There needs to be an iptables chain called VPN_ACCT_IN and VPN_ACCT_OUT which has an entry for each of the VPN user IPs, like:
Chain VPN_ACCT_IN (1 references)
pkts bytes target prot opt in out source destination
245K 258M all -- * * 0.0.0.0/0 192.168.88.57
45687 56M all -- * * 0.0.0.0/0 192.168.88.50
27175 31M all -- * * 0.0.0.0/0 192.168.88.51
and
Chain VPN_ACCT_OUT (1 references)
pkts bytes target prot opt in out source destination
201K 29M all -- * * 192.168.88.57 0.0.0.0/0
34681 2721K all -- * * 192.168.88.50 0.0.0.0/0
20632 2758K all -- * * 192.168.88.51 0.0.0.0/0
all data that’s FORWARDED through the box to/from VPN IPs should go to these chains:
Chain FORWARD (policy ACCEPT 17M packets, 11G bytes)
pkts bytes target prot opt in out source destination
372K 408M VPN_ACCT_IN all -- * * 0.0.0.0/0 192.168.88.0/24
298K 38M VPN_ACCT_OUT all -- * * 192.168.88.0/24 0.0.0.0/0
Once that’s set up, make sure pptpd logs to wtmp - /etc/pptpd.conf should have the logwtmp line.
The ruby script takes the login durations through the wtmp log (by parsing the output of the “last” command), and gets the data transfer values from the iptables chains. You may have to reset the iptables counters when wtmp gets logrotated at the end of the month.
#!/usr/bin/env ruby
userip = {}
begin
IO.readlines("/etc/ppp/chap-secrets").each do |line|
next if /^#/ =~ line
(user, pptpd, passwd, ip) = line.split
userip[user] = ip
end
rescue
puts "Couldn't open chap-secrets file"
end
usageip_inbound = {}
IO.popen("iptables -nvL VPN_ACCT_IN").readlines.each do |line|
next if /Chain|pkts/ =~ line
cols = line.split
data = cols[1]
ip = cols[7]
usageip_inbound[ip] = data
end
usageip_outbound = {}
IO.popen("iptables -nvL VPN_ACCT_OUT").readlines.each do |line|
next if /Chain|pkts/ =~ line
cols = line.split
data = cols[1]
ip = cols[6]
usageip_outbound[ip] = data
end
totals = {}
totals.default = 0
IO.popen("last").readlines.each do |row|
next if not /ppp/ =~ row
cols = row.chomp.split
(user, term, ip) = cols[0..2]
(day, month, date, start_time) = cols[3..6]
end_time = cols[7..cols.length]
duration = cols[-1][1..-2]
next if /still logged in/ =~ end_time*' '
(start_hour, start_minute) = start_time.split(':')
(hours, minutes) = duration.split(':')
total = hours.to_i * 60 + minutes.to_i
totals[user] += total
#t = Time.local(2011, month, date, start_hour, start_minute)
end
puts "VPN Usage Summary"
puts "-----------------"
totals.sort_by { |k,v| v }.reverse.each do |user,x|
m = x % 60
h = x / 60
print "#{user}\t --> "
print "#{h} hours " if h > 0
print "#{m} minutes "
ip = userip[user]
inbound = usageip_inbound[ip]
outbound = usageip_outbound[ip]
puts "(#{inbound} received, #{outbound} sent)"
end