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.