Wednesday, August 21, 2013

Blocking Internet at specific times using Netfilter / iptables on OpenWrt Router

From the title, it should already be obvious that this will apply to a very specific case, but I'm sure there are parts of it that will be helpful in other scenarios too.  As is typical with Linux, the power is there, but the lengthy list of vaguely documented pieces and parts make it a challenge to get all the proverbial planets aligned.

What motivated me to sort all this out was a goal of implementing a reasonably cheap, reasonably reliable way to shut off internet access on the tablets my kids have during certain blocks of time each day.  I found a few things like DansGuardian and MIA (Mom's Internet Access) but they were either too complicated to set up, or didn't work on my hardware, or maybe there was some other reason I passed them by... can't remember now.  I also searched for this kind of step by step tutorial that would cover the major points for using iptables (a.k.a. netfilter) firewall rules in Linux to simply shut off traffic from certain devices.  I didn't find anything, so now I'm writing it...  so here goes...

Hardware

* This will probably work on most any wireless access point / router that has been loaded with OpenWRT, but for me it's all set up on a Netgear WNDR3700 v2.
* The rules are set up using the hardware / MAC address on the wireless tablets, which is something all WiFi capable tablets have, so there's nothing special about the devices that are being blocked.

Firmware

On the NetGear router, the stock firmware has been replaced with OpenWrt Barrier Breaker r34054. The version of OpenWrt that would apply to other wireless access points / routers may be different.  See the references below for a link to the OpenWrt compatibility page.  Getting OpenWrt loaded isn't too tough, but I'm not covering that here.

Skills

Most people who get past loading custom firmware on a router will be able to handle the rest of this, but it would help to know your way around an SSH client like PuTTY (ref below), have a basic understanding of networky things like IP addresses, MAC addresses, and understand a little about how timezones and UTC time relate.

Overview

When a wireless device connects to an access point, the network traffic is generally routed (also sometimes called "forwarded") through it to an internet connection.  When the operating system that runs on the access point / router hardware is derived from Linux, as OpenWrt is, there is usually a low level firewall technology built into the core (kernel) of the OS called iptables.  Understanding iptables rules takes a deeper understanding of network protocols and the specifics of packet level data, so there is often a higher level "firewall" product that simplifies how the rules are defined and generates more complicated rules for iptables.  This is also how OpenWrt is set up by default.

Details

iptables "time" module

iptables rules can be defined with -m time --timestart HH:MM:SS --timestop HH:MM:SS to limit the start and end time block within which the rule will be applied.
  • NOTE: The :SS part of the times may be omitted if it is just :00.  Example: --timestart 17:30 --timestop 18:45 is equivalent to --timestart 17:30:00 --timestop 18:45:00
  • WARNING: Somewhere along the way, the default interpretation of HH:MM:SS CHANGED FROM Local-TimeZone TO UTC.  The iptables version distributed with OpenWrt r34054 apparently uses the Local-TimeZone by default.  Documentation on newer versions of iptables says that the default interpretation of HH:MM:SS in iptables is UTC but that may not be the case.  Experiment to see how it actually behaves.
  • WARNING: timestart and timestop cannot cross midnight.  Newer versions of the "time" module for iptables support a --contiguous parameter, but that wasn't supported in the OpenWrt r34054.  If it is supported, it must be specified for a time range like the following to work --timestart 23:00 --timestop 01:00
  • WARNING: If the "Kernel TimeZone" is not in synch with the "Local TimeZone," iptables rules using local times may not behave as you'd expect.  If you have changed the "Local TimeZone" (maybe it was set incorrectly) and you have not rebooted the device (i.e. Linux) since changing it, execute the 'date -k' command to synch things up (or if you're a fan of how M$ Windows works, you could do a full reboot).
  • WARNING: In older versions of iptables, using UTC time requires --utc to be specified in the iptables rule.  The rules operate more clearly based on the UTC time (displayed with 'date -u'), but the tradeoff is that the HH:MM:SS times would not automatically adjust for Daylight Saving Time.
  • WARNING: (...ok this is really a repeat, but...) Older versions of iptables default to LocalTZ, so any documentation referring to the '--localtz' parameter may not apply to an older version of iptables.  Experiment to see how iptables is using the timestart and timestop before concluding that the rule just isn't working.
  • EXAMPLE: 8pm to 7am Mountain Daylight Time (MDT / UTC-6) does not cross midnight in UTC, so it could be specified in one rule:
                  "-m time --utc --timestart 02:00 --timestop 13:00"
  • EXAMPLE: 8pm to 7am Mountain Daylight Time (MDT / UTC-6) does not cross midnight in UTC, so it could be specified in two rules (not sure how you keep packets from squeaking by between 23:59:59 and 00:00, but that one second gap probably won't hurt anything):
                  "-m time --localtz --timestart 20:00 --timestop 23:59:59"
                  "-m time --localtz --timestart 00:00 --timestop 07:00"

iptables "mac" module

Many iptables rule examples you may find use the '-s' parameter to specify a source IP address to match the packets that are to be affected by the rule.  Many access points are set up to use DHCP, which automatically assigns an IP address from a pool of available addresses.  Unless special configuration has been added on the A/P / router, to assign a "static DHCP address lease" to a certain device, the device you're trying to block could end up having a different IP address and iptables rules that use '-s' or '--source' would block the wrong device.  Instead of relying on a dynamically assigned IP address, it is preferable to define rules based on the hardware / MAC address of the device, which will not change.

So, "-m mac --mac-source 9A:BC:DE:F0:12:34" would match packets from a specific hardware device with that MAC address.

NOTE: Some portable devices could have more than one MAC address if they had, for instance, separate radios for different speed WiFi connections (like one MAC address for 802.11B and a different MAC address for 802.11N)  Be sure to make a rule for all possibilities.

iptables "comment" module
Documenting what you've done is always a good idea.  There is a way you can build some commentary right into the rule so you can easily find it when you look at the output of 'iptables --list'  The comment module does the work.
-m comment --comment "This rule blocks Suzy's midnight surfing."

Putting it all Together

This example rule blocks a specific device from accessing the internet between 8pm and 7am MDT (UTC-6).

iptables -I forwarding_rule -m comment --comment "Shut off Georgie's tablet at night" -p tcp -m mac --mac-source 9A:BC:DE:F0:12:34 -m time --utc --timestart 02:00 --timestop 13:00 -j zone_wan_REJECT

NOTE: The target "zone_wan_REJECT" is probably specific to OpenWrt.  It may work in a normal iptables setup to simply specify the "REJECT" target.

Adding the Rule(s)

Using the LuCI web based administrative console for OpenWRT, navigate to the "Network" tab, then within that to the "Firewall" tab, then within that to the "Custom Rules" tab.  Enter the rules here as text and click the "Submit" button.  This just saves the new rules but the firewall must still be restarted to pick up the change.

Restarting the Firewall

There may be a way to do this in LuCI, but I never did find a way.  I found it was easier to SSH into the router using PuTTY and run "/etc/init.d/firewall restart"  An SSH/PuTTY session is also a good place to verify the clock. timezone. etc are set up correctly on the A/P / router.  The "date" command will show many of the clock related things of interest.

An Alternative Way to Generate Firewall Rules

OpenWrt has a higher level firewall configuration tool that generates iptables rules using a config file at /etc/config/firewall.  Some may find it more intuitive to define firewall rules this way so here's the reference page if that's what you prefer.  I abandoned defining my firewall rules this way because I couldn't figure out how to pass through much of what I wanted without just declaring it as "option extra" anyway.
* http://wiki.openwrt.org/doc/uci/firewall

References

4 comments:

Timo John said...

Thanks for your blog. It helped me a lot to restrickt Internet acces of my children.
I had to modify the last option on my openWRT:
"-j zone_wan_REJECT" => -j REJECT

Tanks

Unknown said...

Thank you! This is exactly what I was looking for. My version of OpenWRT (r45545) does support --contiguous BTW.

Like Timo, I also used REJECT instead of zone_wan_REJECT.

Finally, some web pages these days seem to use UDP to transmit some data (notably Google search), so I also used -p all instead of -p tcp

Unknown said...

Excellent Howto!

For those who prefer to use /etc/config/firewall method here is a sample section:


config rule
option src 'lan'
option dest 'wan'
option src_mac '00:15:58:6A:5C:C2'
option target 'REJECT'
option name 'Sahara'
option extra '-m time --weekdays Mon,Tue,Wed,Thu,Fri --kerneltz --timestart 09:00 --timestop 17:00'
option enabled '1'

Unknown said...

"date -k" - the answer to my problem and more, thank you!