The problem: data caps. Data traffic over mobile networks comes at a cost. So, when X MB or GB of data have been downloaded in a time period (day or week), all “client” traffic stops. Traffic shapers don’t help in this case, and vnstat is not versatile enough. pfSense has to be reachable from the outside for admin purposes, but the users will have no more access to the internet. When the time period expires pfSense resumes normal operation. It should also be reboot resilient, since reboots zero out all internal counters. Interestingly, instead of the usual culprits, python and php, awk came to the rescue. Who would have guessed. So, after some tinkering and reading, the following script came to existence. The function gecko() below is not necessary, but I wanted to try an awk function.
#!/usr/bin/awk -f
function gecko(filename)
{
printf("0\n0\n0\n0\n") > filename
}
BEGIN {
# First positional argument -> file where traffic data is stored
# Second positional argument -> traffic limit (in MB)
# Third positional argument -> base period (defaults to weekly)
# line 1 (date0) -> last time we operated on this file
# line 2 (date1) -> current time of operation on this file
# line 3 (old_data) -> bytes moved before last counter reset
# line 4 (data) -> data moved (new measurement)
statfile = ARGV[1]
limit = ARGV[2]
period = ARGV[3]
daily = 86400
weekly = 604800
if(period == "daily"){
period=daily
} else {
period=weekly
}
getline date0 < statfile
getline date1 < statfile
getline old_data < statfile
getline data < statfile
# Epoch time: Thu 197001010000
"date +%s" | getline t_epoch
# In this case wa want a weekly reset every Saturday at 00:00, hence 86400*2=172800
t_since_zero = (t_epoch-172800) % period
if(date0 > t_since_zero || date0 == "" || date1 < date0){
gecko(statfile)
exit
} else {
date0 = date1
date1 = t_since_zero
# ***** WARNING *****
# The interface monitored in this configuration is `ue0`
# Change manually according to current configuration
while(("pfctl -s Interfaces -i ue0 -vv" | getline data_new) > 0){
if(data_new ~ /In4\/Pass/){
gsub(/^.*Bytes: /, "", data_new)
gsub(/ +\]$/, "", data_new)
data_new = data_new / 1048576
if(data_new < data){
old_data = old_data + data
}
data = data_new
}
}
if(data + old_data > limit){system("/sbin/pfctl -d")} else {system("/sbin/pfctl -e")};
printf("%d\n%d\n%d\n%d\n", date0, date1, old_data, data) > statfile;
}
}
Put the above in a file, i.e. /root/caps.awk, and
chmod 755 /root/caps.awk
Then put in crontab a line like this:
*/2 * * * * root /root/caps.awk /root/vol.txt 2000
which means “turn off pf if traffic exceeds 2000MB.