5

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.

  • Nice, it looks like NTM would do the job, doesn't look like its CLI though :( – A.Adverse Oct 10 '17 at 9:14
  • How do you access the server from LAN - through the interface eth2 or through some other interface? I mean - is it okay to disable this interface programmatically when the daily limit is reached. – pa4080 Oct 10 '17 at 10:00
  • I have eth0, local Gb, and eth1 local Mb ethernets, and wlan0 all producing traffic routed out eth2. There is no problem disabling eth2 after the traffic limit is reached. – A.Adverse Oct 10 '17 at 10:46
  • 1
    @pa4080 I'm not actually using your script, it was looking overly complicated for me. However it gave me enough ideas to get where I wanted to go. I have a script probably v ugly, that chops up the output of vnstat and if its over 480Mb takes down eth2. I also had one that reset the interface at midnight, but disabled that in the end, as there was to much background use, that chewed the days allowance while the interface was up but not otherwise in use. – A.Adverse Nov 1 '17 at 23:53
5

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:

  • $1=LIMIT holds the value of the traffic limit in MiB, doesn't it? Else change to XM=$(( XB / ( 1000 * 1000 ) )). – dessert Oct 10 '17 at 17:54
  • 1
    To run a sudo command in the background, use sudo -b. Also, did you intend to truncate the log at the start of each loop? – muru Oct 11 '17 at 8:19
  • 1
    @muru, yes, with new record in every 5 seconds the log will grown rapidly, and nobody will read the history of this log. The important information is: how much traffic remains, and is the interface disabled. sudo -b is new for me, thanks! – pa4080 Oct 11 '17 at 8:25
  • @muru, do I need to use nohup with sudo -b? – pa4080 Oct 11 '17 at 8:41
  • @pa4080 probably not. – muru Oct 11 '17 at 8:42

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.