pfSense hard limit

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.