Traffic shaping per IP based on cumulative time per day

Hi Everyone,

I've been trying to work this out for a while now, but keep on coming up short. My kids are allowed a certain amount of time on their devices per day. This results in managing parental controls across multiple different ecosystems, which is problematic, given they don't talk to each other and sometimes don't work (looking at you, Apple!).

I therefore wish to roll my own solution which tracks the average bytes of traffic a given client is using over X amount of time and if it is over a given threshold, their available time is reduced by one unit. Once they have exhausted their daily limited, I will throttle all devices associated with that user. I am fine with the scripting aspect of this and the throttling (using tc), but I can't work out how best to obtain real-time traffic (bytes) of individual clients - I just can't get iptables to report the statistics I need. I would also like to have the stats recorded across all outbound interfaces I have - wlan, vpn0 and vpn1.

Any suggestions very gratefully received!

Cheers

Tim

Technically 'possible', but not going to be easy - you'd have to do most of it yourself, from scratch.

Using a captive portal solution might save you some time and get you 'close enough' to your desired setup, but that's not quite trivial either.

2 Likes

Hey, thanks for your input :slight_smile: Like I said, I (think I) have the skills/knowledge to accomplish most of this, but determining the best way to get the bytes traffic from a given client is eluding me. I'm not anticipating that I can accomplish any of this through a GUI interface and that it will initially (I might try and build a UI later) rely on one or more scripts.

You could create a chain of firewall rules which match against traffic to/from each client (based on their MAC addresses, or an ipset of MAC addresses), but do nothing except keep a count of the bytes matched. You could then have a script that checks the byte counts for the rules in that chain, running once a minute with a cron job.

So here is my silly little proposal: create one SSID per kid and have them each register all devices in the same SSID, then you have a single point you need to monitor for the traffic levels per kid. This will also making the throttling part easier, since you have natural places to attach the traffic shapers to.

EDIT: make clear that "silly" only refers to the proposal in this post. I apologize for being unclear before.

1 Like

Silly proposal

Slightly harsh :slight_smile:

The iptables approach for keeping counts of bytes/packets per client is pretty standard isn't it? Or is there some horrible flaw with this that I'm not aware of?

I see no flaw in iptables at all, it is pretty much orthogonal to my proposal. You still can use iptables with IP and/or MAC matches to do the accounting, but you will have single obvious points where to instantiate the traffic shapers. I know iptables has a hash limit module, but I am not sure whether that can compete with cake or [HTB|HFSC]+fq_codel in low latency performance...

Just to be explicit, with silly I referred to my SSID idea :wink:

Just to be explicit, with silly I referred to my SSID idea :wink:

Haha, I see :slight_smile:

FWIW I don't the SSID part is silly at all. It didn't occur to me because my router doesn't have wifi. If this approach is used you probably don't need iptables at all - you could parse packet/byte counts from ifconfig output.

1 Like

I thought it was harsh too, until the clarification! :rofl:

Thanks for the input both :slight_smile:

1 Like

Using nftables you could set up quotas for each device and tag packets over quota so as to be able to slow them down with tc.

It's possible that iptables has this functionality but nftables is way easier to use for this kind of thing.

You could take a look at openNDS as it allows setting both data quotas and data rate quotas by user device. It also allows you to add your own scripting to control just about anything.

You should check out nlbw to help tracking bandwidth per host. You'll likely still have to script a lot of this, but thats where I would start looking...

Steps:
put a rc.local script with a ip table rule that disables internet to the network where you kids connects.
touch a file, and with an if check it the same on firewall.user

create a .html page with a green button that trigger that cgi script.

then You can make a cgi bin script to: add 1 to a file (counter) and remove that iptable rule that blocks internet and set a new cron task to disable it after 1hr, if the contents of file (counter) is above your threshold then generate again .html page replacing color of button with red. also you can put the number of available hours in the button

put an icon on your kid device to access that .html like http://192.168.1.1/enable_internet.html
so your kid can use it and see the green button "enable +1hs of internet" / "internet is enabled" below: "you have no more hs of internet available" / "you have x hs available"

but the better is to just find another way bc finally your kid will be angry and will just be trying to use some neighbor wifi, or will even plug his own small router on some ethernet plug socket or something or will learn OpenWRT and how to hack into ssh and edit your script... true story...

Quick update... I have managed to achieve what I set out in the OP - yay. I now have the following:

  • SQLite database containing users, devices (of a user), schedule (per user), scheduleActiveTime (per schedule), config and logs
  • bash script that runs via cron (ever minute) that determines if a user is using the internet on one of their devices and logs appropriately

For completeness, the working code I have is:

function set_tcf() {
	TCF="${TC} filter add dev ${1} parent 1: protocol ip prio 5 u32 match u16 0x0800 0xFFFF at -2"
}

function create_chains() {
	DEV=$1
	
	if [[ `${TC} qdisc show dev ${DEV} | grep "qdisc htb 1" | wc -l` -eq 0 ]]; then
		set_tcf $DEV
		${TC} qdisc add dev ${DEV} root       handle 1:    htb default 0xA
		${TC} class add dev ${DEV} parent 1:  classid 1:1  htb rate ${MAX_BW}
		${TC} class add dev ${DEV} parent 1:1 classid 1:10 htb rate ${MAX_BW}
	 
		${TC} class add dev ${DEV} parent 1:1 classid 1:20 htb rate ${DNLD}
	 
		#${TCF} flowid 1:10
	fi
}

function filter_mac() {
	DEV=$3
	M0=$(echo $1 | cut -d : -f 1)$(echo $1 | cut -d : -f 2)
	M1=$(echo $1 | cut -d : -f 3)$(echo $1 | cut -d : -f 4)
	M2=$(echo $1 | cut -d : -f 5)$(echo $1 | cut -d : -f 6)
	
	if [[ `${TC} filter show dev ${DEV} | grep -i "${M0}${M1}/ffffffff at -8" | wc -l` -eq 0 ]]; then
		set_tcf $DEV
		${TCF} match u16 0x${M2} 0xFFFF at -4 match u32 0x${M0}${M1} 0xFFFFFFFF at -8 flowid $2
		${TCF} match u32 0x${M1}${M2} 0xFFFFFFFF at -12 match u16 0x${M0} 0xFFFF at -14 flowid $2
	fi
}

-----
for IF in ${INTERFACES}; do
	for MAC in ${MACS}; do
		create_chains ${IF}
		filter_mac ${MAC} "1:20" ${IF}
	done
done

Now I need to create a frontend UI.

Thanks all for your input :slight_smile:

2 Likes