View from the Trenches: Virtual Curfews

by Glenn Stone

Recently a client had an interesting request for me. It seems that his teenage daughter had done a very strange thing: she had requested a curfew on her Internet time, as it was interfering with her studies and sleep. Dad had been doing this simply by pulling the cat5 leading to her machine out of the hub, but this was clumsy and mechanical and it involved crawling under a desk. A better solution, involving his Debian Woody firewall, was in order. Not being a total firewall guru, he turned to me.

I took a bottom-up approach to the problem. First, using my own LAN, I perfected an ipchains rule that would block access from a given machine to the outside world, while still allowing intra-LAN access for things like printing to a networked printer.

ipchains -I input -s $VICTIM -d ! $SUBNET -i eth0 -j REJECT

where VICTIM is her IP address and SUBNET is something like 192.168.10.0/24, that is, the IP address with the last octet zero (usually) and the correct number of bits after the slash. Oddball subnets are left as an exercise for the reader; likewise, one could substitute a subnet for VICTIM as well.

So far, so good, except you have to make sure VICTIM really is VICTIM all the time. With plain vanilla DHCP, that's not guaranteed unless you make it so. Look at your logs (/var/log/syslog or /var/log/messages) for the MAC address of your target machine, and then add a static host to your dhcpd.conf, like this:

  host artoo {
     hardware ethernet 01:23:45:ab:cd:ef;
     fixed-address 192.168.10.10;
     default-lease-time 43200;
  }

Don't forget the semicolons at the end of the lines after editing, or DHCP won't start. /etc/init.d/dhcpd restart works on most systems to do the restart; make sure to test your work. The best way to do so is to refresh the interface on the client machine and check the logs for the appropriate DHCP ACK. Not doing so can be embarrassing.

Now, we have to script this thing. You could take the easy way out and write two little scriptlets, one to insert the rule and one to delete it. Having been a parent before, however, I knew that one should make allowances for both groundings and rewards or special circumstances. Furthermore, a single initscripts-style script would be easy to maintain, once all the details were fleshed out.

I came up with five cases: UP, DOWN, GROUND, FREE and STATUS. GROUND and FREE are used if the kid is grounded or, conversely, allowed to surf all night. I used touch files in /var/run to indicate the status. Normally, writing this big case statement would be a chore, but Debian includes /etc/init.d/skeleton, which provides a nice framework for such a thing. SuSE does too; Red Hatters and others can use DHCPD's fairly straightforward init script instead, if you want to play along instead of skipping to the end.

One more piece to this puzzle, and we're ready to install. We have to have a way to run these scripts whether or not Dad is around. Particularly because, under house rules, one is allowed to arise at 4am and finish one's homework. Cron is the obvious choice for this; here are the entries:

30 23 * * * /usr/local/bin/kidnet down
00 04 * * * /usr/local/bin/kidnet up

For those who are rusty, the fields are minute, hour, day of the month, month, day of the week (0 and 7 are both Sunday) and command. The easy way to install these lines is to use crontab -e and cut and paste. First, though, make sure root's EDITOR value is set the way you want. Debian throws you into nano if you don't, and if you have long lines in the file, it could be painful.

Having tested all this, I show up at my friend's house ready to drop this kit in and have it just work, only to discover that, no, he doesn't have ipchains, he has iptables. Oops. Well, we just have to fix this on the fly. Shouldn't be a big deal, should it? Simply change

ipchains -I input -s $VICTIM -d ! $SUBNET -i eth0 -j REJECT

to

iptables -I INPUT -s $VICTIM -d ! $SUBNET -i eth0 -j REJECT

and the matching delete rule likewise, and all is well, right?

Wrong.

There's a little subtlety in iptables that says you can't jump to REJECT on INPUT, nor can you (as I discovered) specify the inbound interface on FORWARD or OUTPUT. With greater flexibility comes greater complexity. What I ended up doing is specifying the outbound interface in the FORWARD rule, thereby making the rule a variable:

RULE="FORWARD -s $VICTIM -o eth0 -j REJECT"
#...
iptables -I $RULE

By putting the cutoff in the FORWARD rule, I ensured that only masquerading would be affected and not anything inside the LAN. Thus, the -d parameter would be redundant.

Listing 1. Final Script for iptables

Of course, there is room for improvement here. If the system goes down when the curfew arrives and comes back up after it should be down, it stays up until the next day. Also, if the status files get confused, you have to clean out /var/run manually. You could catch the error caused when doing kidnet up when the interface is up already, or you could do a little dance with grep and prevent it from happening. But my particular customer already is Linux-savvy from a user standpoint, and he takes good notes. If your audience is similar, this should be quite sufficient. If you're getting paid to make it really slick, then you've got another hour of work you can bill.

I learned a couple of things from this little adventure. One is that real-world applications rarely touch only one area of the system. In this case, we touched shell programming, crontab, DHCP and firewalling. It's a fairly simple task for the experienced sysadmin, but you won't find it in the average HOWTO. The other is thing I learned is to make sure what I think is the client's configuration actually is the client's configuration before going on site--it saves a lot of shotgun programming. On the other hand, even if you do screw up, if you're honest about it and fix it in a timely manner, it can earn you repeat business, perhaps more so than if you simply dropped it in. You're showing clients you'll be there for them. Repeat business, delightful words for a penguin-head to hear, down here in the trenches.

Glenn Stone is a Red Hat Certified Engineer, sysadmin, technical writer, cover model and general Linux flunkie. He has been hand-building computers for fun and profit since 1999, and he is a happy denizen of the Pacific Northwest.

email: liawol.org!gs

Load Disqus comments