The Orbi allows you to block devices connected to it (by wireless or ethernet) from the web-based control page:

Similar functionality is provided from the Orbi app:

The ability to selectively block devices is very useful. Whether it is keeping your kids off the internet during homework time or preventing software you don’t control from “phoning home”, this functionality is now an assumed function of a modern router.

When I discovered my oldest son was waking up in the middle of the night to play on-line video games and then falling asleep during the day, I decided I needed to review my ability to schedule his connectivity.

What I wanted was a way to automatically turn off the internet to the kids computers and devices at a pre-established time, and then turn them back on in the morning.

Netgear has built-in options for doing this. The Disney/Circle software seems to require you to use an app on your phone and follows a ‘freemium’ model: I looked into it and it would seem to require paying a $5/month subscription fee to get the functionality I wanted. $5 isn’t awful, but when all the ‘partners’ that touch your digital life are trying to get a slice of your wallet, you can easily get the sense of being nickle-and-dimed to death.

My first attempt at disconnecting the kids’ devices used linux’s built-in iptables:

sub kidsnight { my $state = shift; my $TABLENAME = "INPUT"; print STDERR "going to "; if ( $state == 0 ) { print STDERR "disconnect"; } elsif ( $state ==1 ) { print STDERR "connect"; } print STDERR " the kid's devices from the internet

"; foreach my $name ( keys %kids_device_mac_hash ) { if ( $state == 0 ) { print STDERR "disconnecting $name

"; $expect->send("$IPTABLES -I $TABLENAME -m mac --mac-source $kids_device_mac_hash{$name} -j DROP

"); sleep 1; } else { $expect->clear_accum(); $expect->send("$IPTABLES -L $TABLENAME --line-numbers | /bin/grep DROP | /bin/grep MAC | /usr/bin/sort -r

"); $expect->expect( $timeout, [ qr/\d+\s+DROP/, sub { my $fh = shift; my $match = $fh->match(); if ( $match =~ /^(\d+)/ ) { my $linenumber = $1; print STDERR "deleting line # $linenumber

"; $fh->send("$IPTABLES -D $TABLENAME $linenumber

"); } exp_continue; } ] ); } }

That subroutine uses Expect.pm over the telnet interface (established in a previous entry) to put in entries in the kernel’s iptables to block (and remove) access to specific MAC addresses.

This was useful, but it seemed that sometimes it wouldn’t cut off established network connections for some games. This almost certainly reflects on my inadequate understanding of networking and packet handling. My failures here prompted my next exploration of directly manipulating the Orbi via its web interface.

The access control interface (in the above screenshot) uses a specific page, “/AccessControl_show.htm”, in an iframe. My control program has a hash of the kids’ computers/devices and associated MAC addresses (%kids_device_mac_hash). The subroutine takes one or two arguments: the first is 0 or 1, and decides whether we’re blocking or unblocking; the second is which specific device’s access we’re manipulating (all the kids’ devices, if left empty).

sub kids_block { my $state = shift; my $blocked = 'all'; if ( scalar $_[0] ) { $blocked = shift; }

Then, we create a list of all the MAC addresses we will be blocking/unblocking:

my @maclist; if ( $blocked eq 'all' ) { foreach my $key ( keys %kids_device_mac_hash ) { push @maclist, $kids_device_mac_hash{$key}; } } else { push @maclist, $kids_device_mac_hash{$blocked}; }

We read the webpage (and scrape the ‘ts’ variable):

$mech->get( "http://" . $router . "/AccessControl_show.htm" ); my $ts; if ( $mech->{content} =~ /var ts=\'(\d+)\'/ ) { $ts = $1; print STDERR "ts is $ts

"; } else { die "couldn't scrape 'ts' from /AccessControl_show.htm

"; }

…and select the web form we’re going to be manipulating

You need to set the field ‘submit_flag’ to ‘acc_control_block’ or ‘acc_control_unblock’, depending on whether you’re blocking access or unblocking it. For example:

$mech->field('submit_flag',"acc_control_block");

The following was determined by trial and error. The hidden input named ‘hidden_change_list’ is to be set to a ‘#’ separated and terminated list of MAC addresses to be blocked/unblocked. The hidden input named ‘hidden_change_num’ is to be set to the number of MAC addresses enumerated in ‘hidden_change_list’. Then, there’s a bunch of input fields that need to be set to something just so things go through. In the program, this looks like this:

my $L = join( '#', @maclist) . '#'; $mech->field('hidden_change_list', $L); $mech->field('hidden_change_num', scalar @maclist); $mech->set_fields( 'hid_able_block_device' => 1, 'hid_new_device_status' => 'Allow', 'hid_allow_no_connect_sta' => '', 'hid_block_no_connect_sta' => '', 'hidden_del_list' => '', 'hidden_del_num' => 0, 'select_edit' => '', 'enable_acl' => 1, 'access_all' => 'allow_all' );

Finally, we have WWW::Mechanize submit the form:

my $form = $mech->current_form(); $form->action( "http://" . $router . "/apply.cgi?/access_control_plsWait.htm timestamp=" . $ts ); $mech->submit();

Putting it all together, the subroutine looks like this:

sub kids_block { my $state = shift; my $blocked = 'all'; if ( scalar $_[0] ) { $blocked = shift; } my @maclist; if ( $blocked eq 'all' ) { foreach my $key ( keys %kids_device_mac_hash ) { push @maclist, $kids_device_mac_hash{$key}; } } else { push @maclist, $kids_device_mac_hash{$blocked}; } $mech->get( "http://" . $router . "/AccessControl_show.htm" ); my $ts; if ( $mech->{content} =~ /var ts=\'(\d+)\'/ ) { $ts = $1; print STDERR "ts is $ts

"; } else { die "couldn't scrape 'ts' from /AccessControl_show.htm

"; } $mech->form_number(1); my $L = join( '#', @maclist) . '#'; $mech->field('hidden_change_list', $L); print STDERR "DEBUG: hidden_change_list: $L

"; $mech->field('hidden_change_num', scalar @maclist); print STDERR "DEBUG: hidden_change_num: " . ( scalar @maclist ) . "

"; $mech->set_fields( 'hid_able_block_device' => 1, 'hid_new_device_status' => 'Allow', 'hid_allow_no_connect_sta' => '', 'hid_block_no_connect_sta' => '', 'hidden_del_list' => '', 'hidden_del_num' => 0, 'select_edit' => '', 'enable_acl' => 1, 'access_all' => 'allow_all' ); print STDERR "going to "; if ( $state == 0 ) { print STDERR "block"; $mech->field('submit_flag',"acc_control_block"); } elsif ( $state == 1 ) { print STDERR "unblock"; $mech->field('submit_flag',"acc_control_allow"); } print STDERR " the kid's devices on the router

"; my $form = $mech->current_form(); $form->action( "http://" . $router . "/apply.cgi?/access_control_plsWait.htm timestamp=" . $ts ); $mech->submit(); }

Ta’dah!

The whole thing lets me script blocking and unblocking from a cron job on another computer, and ends up making the Orbi a much richer tool for me.