Ubuntu – Daily Download limit

command linenetworking

I'm looking for a way to create a daily internet limit for my Ubuntu Server. All outbound traffic leaves Eth2, a usb dongle, this is prepaid and the kids eat it quickly. I need to divide my prepaid amount usually 12Gb into daily allotments and stop traffic for the day after this amount is reached. Maybe with a web page thrown in saying daily limit exceeded.

Preferably something from the CLI. It's a headless beast with only SSH access.

VNSTAT seems to do what I need, I just don't have the scripting skills to have it drive an ifdown command.

Thanks.

Best Answer

My suggestion is the following script that will get the data of incoming and outgoing traffic from ifconfig interface-name and will compare the sum with a predefined limit value. This action will be repeated every 5 seconds (for example).

When the amount of the traffic (income+outcome) becomes equal or greater than the limit, the script will disable the target interface and exit. The maximum discrepancy between the actual value at which the interface will be disabled and the limit value will be equal to 5s x MaxSpeed.

The script can be executed by Cron job. So you will be able to set different job for each day of the week, etc. Additionally when the limit is reached you can run the script manually with an additional amount of traffic.

The script name should be traffic-watch, otherwise you should change its 5th line. My suggestion is to place it in /usr/local/bin, thus it will be available as shell command. Don't forget to make it executable: chmod +x /usr/local/bin/traffic-watch.

The script should be executed as root (sudo). It creates a log file: /tmp/traffic-watch-interface-name.log, where you can check the last action. The script has two input variables:

  • $1=$LIMIT - the value of the traffic limit in MB - the default value is 400.
  • $2=$IFACE - the name of the target network interface - the default value is eth0.
  • If you want to override these values during the execution of the script, use these formats:

    traffic-watch "250" "enp0s25"
    traffic-watch "250"
    traffic-watch "" "enp0s25"
    

Use 'traffic-watch' with 'crontab'. If you want to run the script every morning at 6:30, open root's Crontab (sudo crontab -e) and add this line:

30 6 * * * /usr/local/bin/traffic-watch 2>/dev/null

Use 'traffic-watch' manually. To run the script as root and push it into the background we shall use sudo -b:

sudo -b traffic-watch "150" 2>/dev/null

The content of the script 'traffic-watch' is:

#!/bin/bash

# Initialize
[ -z "${1}" ] && LIMIT="400"  || LIMIT="$1" # Set the total traffic daily limit in MB
[ -z "${2}" ] && IFACE="eth0" || IFACE="$2" # Set the name of the target interface
LOG="/tmp/traffic-watch-$IFACE.log"         # Set the log file name
LANG=C                                      # Set envvar $LANG to `C` due to grep, awk, etc.
IPPT='[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+'       # Set IP address match pattern #IPPT='[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}'

NIC="$(/sbin/ethtool -i "$IFACE" | awk 'FS=": " {print $2; exit}')" # Get the $IFACE (NIC) driver

# Function: Get the current traffic
get_traffic(){
    RX="$(/sbin/ifconfig "$IFACE" | grep -Po "RX bytes:[0-9]+" | sed 's/RX bytes://')" # Get the incoming traffic
    TX="$(/sbin/ifconfig "$IFACE" | grep -Po "TX bytes:[0-9]+" | sed 's/TX bytes://')" # Get the outgoing traffic
    XB=$(( RX + TX ))                                                            # Calculate the total traffic
    XM=$(( XB / ( 1000 * 1000 ) ))                                               # Convert the total traffic in MB
}

# Functions: Disable the interface
interface_down(){ /sbin/ifconfig "$IFACE" down 2>/dev/null && exit; }

# Function: Reset the traffic and enable the interface
reset_traffic_interface_up(){ /sbin/modprobe -r "$NIC" 2>/dev/null && /sbin/modprobe "$NIC" 2>/dev/null && /sbin/ifconfig "$IFACE" up 2>/dev/null; }

# Function: Get the IP address
get_ip(){ /sbin/ifconfig "$IFACE" 2>/dev/null | grep -Po "${IPPT}" | head -1; }

# --- The main program ---

reset_traffic_interface_up

# Wait until the IP address is obtained
until [[ "$(get_ip)" =~ ${IPPT} ]]; do sleep 1; done

# While the interface has IP address == while it is up; check if it is up on every 5 seconds (the `time` of the cycle is about 75 ms)
while [[ "$(get_ip)" =~ ${IPPT} ]]; do

    get_traffic

    # Start logging
    printf '\n%s\n\nI-face:\t%s\nDriver:\t%s\nIP:\t%s\n' "$(date)" "$IFACE" "$NIC" "$(get_ip)" > "$LOG"
    printf '\nRX:\t%s\nTX:\t%s\nXB:\t%s\nXM:\t%s\n' "$RX" "$TX" "$XB" "$XM" >> "$LOG"

    if (( XM >= LIMIT )); then
        printf '\nThe daily limit of %s MB was reached.' "$LIMIT" >> "$LOG"
        printf '  The interface %s was disabled!\n\n' "$IFACE" >> "$LOG"
        interface_down
    else
            printf '\n%s MB remains on %s.\n\n' "$(( LIMIT - XM ))" "$IFACE" >> "$LOG"
    fi

    # Debug:    cat "$LOG"

    sleep 5 ## *Adjust this value* ##

done; interface_down

Notes:

  • Disable the script when you update and upgrade the system! The lack of internet could be cause of broken packages.

  • It is a good idea to attempt to kill the previous instance of the script (just in case its limit is not reached) before run a new:

    sudo pkill traffic-watch
    sudo -b traffic-watch "150" 2>/dev/null
    
    29 6 * * * /usr/bin/pkill traffic-watch 2>/dev/null 
    30 6 * * * /usr/local/bin/traffic-watch 2>/dev/null 
    
  • Probably 2>/dev/null is not obligatory, because, I think all, errors are redirected to /dev/null by the script itself.

  • To check the remaining traffic remotely you can use this command:

    ssh user@host.or.ip tail -n3 /tmp/traffic-watch-eth0.log
    

    Thanks to @Dessert for this idea! (Replace eth0 with the actual interface in use.)

  • To get back your network interface UP: First ise ifconfig -a to find its name. Then sudo ifconfig INTERFACE up.

  • This script could be recreated to work with iptables instead ofifconfig - up/down. This will be a powerful solution.

  • The script is available as GitHub repository at: https://github.com/pa4080/traffic-watch

  • Another script, based on the current, that only will get the traffic for a period of time is provided here: How to get the current network traffic via the commandline in a simple format.

References:

Related Question