Cisco IOS Home Defender configuration

Like many, I've spent 2020 and 2021 trapped at home with teenagers doing virtual schooling. It's been a challenge to (a) police the internet to a sensible level while (b) allowing virtual schooling 8-12 hrs/day. On top of that, I have a Cisco router for a broadband gateway, and it has zero consumer-friendly web controls. Cisco is plenty powerful and robust, mind you, it's just not friendly. But since I'm a certified network enginer (ccie 2795, lapsed) I kind of like a challenge.

My router config is described below. It has several overlapping types of usage security and web policing:

  1. It acts as a DNS server, tied into OpenDNS's free Family Shield service.
  2. DHCP reservations so all devices use predictable IP addresses
  3. object-groups to identify all devices used by a group (eg, "kids", "school", ...)
  4. DNS views so that specific devices are subject to different rules. (eg, blacklist youtube from kids, but allow it for school).
  5. Fuzzy identification of Minecraft traffic
  6. Match Fortnite servers by IP address (see this article)
  7. Use QoS traffic shaping to both restrict bad apps and guarantee bandwidth fairness for my work traffic
  8. Normal NAT and zone-based firewall behavior
  9. Have a big hammer ready to block all internet access when needed
  10. Allow incoming traffic for hosting local minecraft and web servers
  11. Dynamic DNS updates to assist with incoming traffic
  12. ...
Some of the mechanisms are on 24x7. Others are only enabled by time-ranges -- like a bedtime policy. Much of the time, I manually turn things on and off based on family dynamics.
! Game ON!
  shape average 256000
! Game OFF!
  shape average 8000

IOS configuration walk-through

service tcp-keepalives-in

service tcp-keepalives-out

service timestamps debug datetime msec localtime show-timezone

service timestamps log datetime msec localtime show-timezone


hostname c1111


aaa new-model

username cisco password sanfran   ! not!


logging buffered 100000 notifications

clock timezone CST -6 0

clock summer-time CDT recurring


login on-failure log

login on-success log


ntp server ! use

ntp server

ntp server

ntp server


access-list 23 permit


line vty 0 4

 access-class 23 in

 privilege level 15      ! avoid enable pw

 transport input ssh


Global best-practice Cisco stuff

ip ddns update method noip


  add http://<email>:<password><h>&myip=<a>

 interval maximum 2 0 0 0


Dynamic DNS


I use See Gig0/0/0 for how this is invoked

ip dhcp pool HomeLAN





 lease 14


ip dhcp excluded-address

ip dhcp excluded-address


DHCP on my LAN


Set DNS server to self ( This is required to do DNS filtering in the router.

ip dhcp pool HomeLAN_LukasPhone


 client-identifier 01d0.7714.4c9f.a6


ip dhcp pool HomeLAN_Simi


 client-identifier 0128.cfe9.1a51.ff


ip dhcp pool HomeLAN_Chromebook1


 client-identifier 0198.2cbc.5582.3c


ip dhcp pool HomeLAN_Simi_iPhone




ip dhcp pool HomeLAN_Alenka_MacBook


 client-identifier 0114.7dda.ce48.84


<and so on...>


DHCP reservations


These are critical! For every device on your network, grab the client-id from “show ip dhcp bind” and build a static reservation for it. This ensures that the device has a predictable, consistent IP address.


A clever person can bypass DHCP reservations by either (a) configuring a static IP or (b) randomizing their MAC address. If you have that problem, expand the “ip dhcp excluded-address” range to include the entire subnet and turn on Cisco IP Source Guard. Unfortunately, this also makes it more difficult to add new devices and guest users to the network. A more mature approach is probably to use multiple SSID’s assigned to different VLANs. Luckily, my kids haven’t figured this out, so I’ve not needed such countermeasures.


I use a google spreadsheet to keep track of home devices and periodically review the ARP table to add new reservations.

object-group network Craig



ip access-list extended MATCH-CRAIG

 permit ip object-group Craig any


object-group network Alenka



ip access-list extended MATCH-ALENKA

 permit ip object-group Alenka any


object-group network kids






ip access-list extended MATCH-KIDS

 permit ip object-group kids any


Local Object-groups


Using IP assignments from the DHCP reservations, I create one object-group for each class that I want to apply policy to.


In my case, Craig is me,  Alenka is my wife, and “kids” are my kids.


The access-list for each object-group is used in other places. The object-group itself is also used in other places.

object-group network fortnite











ip access-list extended MATCH-FORTNITE

 permit ip any object-group fortnite


FORTNITE identification


As I find gaming servers, I add them to these object-groups.


See for a discussion of how harvesting server IPs is done.


Although this object-group is named “fortnite”, I wound up putting in xboxlive and twitch and other game IPs into it.

! method A


ip access-list extended MATCH-MINECRAFT

 permit tcp any any eq 25565


! method B


ip access-list extended FUZZY-MINECRAFT

 permit tcp object-group kids any gt 1024


route-map PBR permit 10

 match ip address FUZZY-MINECRAFT

 match length 52 52

 set ip qos-group 5


MINECRAFT identification


Minecraft was a challenge. First I used its well-known port 25565, but it took my kids about ten seconds to bypass that. Then I tried to harvest known server IPs like I do with fortnite, but they don’t identify themselves very well.


While looking at Minecraft traffic with Netflow (or Wireshark), I noticed that it sent a lot of packets that were exactly 52 bytes long. This was unusual, and no other protocol does that, certainly not at the same level.


On Cisco, matching packet length requires using a route-map and policy-based routing (PBR).


ip access-list extended MATCH-KIDS-VPN

 permit udp object-group kids any eq non500-isakmp

 permit udp object-group kids any eq 53


VPN identification


This is a simple access-list blocking IPSec NAT-T (UDP 4500), but only applied to the kids object-group.


Blocking VPN is whack-a-mole. The primary defense is DNS-based (see the "ip dns server" section below). THat stymies most casual attempts to use VPN. A secondary defense is to block common VPN protocol traffic, like "non500-isakmp" aka UDP 4500. I've also caught VPN services using UDP 53, pretending to be DNS traffic to get around hotspot walled gardens.

Unfortunately, a determined individual can bypass both quite easily. Browsers like firefox offer both encrypted DNS and browser-based VPN, a combination that is impossible to block using traditional means.

In the future, VPN will require IP address harvesting, the same technique I use for fortnite. I am also considering an EEM script to kill connections that have certain flow characteristics -- eg, long-lived and a packet rate that suggests gaming as opposed to audio/video conferencing. (it'd be based on a headless netflow collector EEM script project of mine)


class-map match-any CLASS-CRAIG

 match access-group name MATCH-CRAIG

 match qos-group 1


class-map match-any CLASS-ALENKA

 match access-group name MATCH-ALENKA

 match qos-group 2


class-map match-any CLASS-KIDS

 match access-group name MATCH-KIDS

 match qos-group 3


class-map match-any CLASS-GAMES

 match qos-group 5

 match access-group name MATCH-MINECRAFT

 match access-group name MATCH-FORTNITE


class-map match-any CLASS-KIDS-VPN

 match access-group name MATCH-KIDS-VPN

 match qos-group 6




Rather than blocking packets, I use QoS to shape bad traffic down to an unusable level.


QoS works on CLASSES. I use ambidextrous classes that can be equally used on the inside interface for classification (with access-lists) and on the outside interface for shaping/queueing (with qos-groups).


The next two sections show how inside/outside policy-maps use these classes.



  set qos-group 5


  set qos-group 1


  set qos-group 2


  set qos-group 6


  set qos-group 3

 class class-default

  set qos-group 4


QoS policy – inside interface


This policy matches the inside view of traffic and it assigns a unique qos-group to each class. The qos-group stays with the packet as it traverses the router. This is important for dealing with NAT, which changes the IPs and renders normal access-lists useless.

If any of these classes overlap, then order is important. E.g., CLASS-KIDS-VPN must occur above CLASS-KIDS in order to properly catch traffic.




  shape average 4000000      ! 4 mbps


  fair-queue queue-limit 50


  shape average 2000000      ! 2 mbps


  fair-queue queue-limit 50


  shape average 8000         ! 8 kbps / disabled


  shape average 8000         ! 8 kbps / disabled


  shape average 2000000      ! 2 mbps


  fair-queue queue-limit 50

 class class-default

  shape average 2000000      ! 2 mbps


  fair-queue queue-limit 50


QoS policy – outside interface


This shapes the various classes in the OUTBBOUND direction. There is no need for an INBOUND filter. By squeezing a protocol’s outbound bandwidth to 8 kbps, it is effectively stopped.


If you just want to restrict GAMES and KIDS-VPN, then the other classes wouldn’t need “shape average” commands.


I choose to shape ALL classes because my broadband speeds vary a lot by time-of-day and other home LAN traffic was hogging the circuit. E.g., cloud photo backup was causing my work Zoom calls to fail. This approach artificially lowers the bandwidth of each class so that the total committed outbound ratio is at a level that the broadband circuit can deal with, even during bad times.


E.g., say you have a broadband circuit with 100 mbps down and 30 mbps up, but you find that during peak hours you can reliably only get 10 mbps up. Unfortunately, Cisco has no way to detect and adjust the available bandwidth from 10-30 mbps based on conditions. So instead I manually carve up the reliable 10 Mbps into different classes. So now cloud backup (class-default) gets at most 2 mbps while I (CLASS-CRAIG) still have my 4 mbps.


The downside is that I can’t take advantage of >10 Mbps of upload speeds at the times that my broadband can handle 30 Mbps. But that’s OK. Download speeds are unrestricted, and most people care most about download speeds.


interface GigabitEthernet0/0/0

 description Spectrum broadband

 ip ddns update hostname

 ip ddns update noip host

 no ip dhcp client request domain-name

 no ip dhcp client request dns-nameserver

 ip address dhcp

 no ip unreachables

 ip nat outside

 zone-member security OUTSIDE-SPECTRUM

 load-interval 30

 negotiation auto

 service-policy output INTERNET-FAIRNESS-OUTSIDE


ip route dhcp


OUTSIDE interface (broadband)


Uses dhcp

Tied into the dynamic DNS update.

NAT outside

Zone-based firewall zone “OUTSIDE-SPECTRUM”

Applies the QoS policy described above


I also send the default route to the upstream as learned through DHCP.

interface Vlan10

 description Home Network

 ip flow monitor NETFLOW input

 ip flow monitor NETFLOW output

 ip address

 ip dns view-group NO-TUBE

 ip nat inside

 zone-member security INSIDE

 ip policy route-map PBR

 service-policy input INTERNET-FAIRNESS


INSIDE interface (LAN)


Netflow ingress/egress for monitoring

DNS views enforced

NAT inside

Zone-based firewall zone “INSIDE”

Policy-based routing for Fuzzy Minecraft identification

Applies the QoS policy described above

ip dns server


ip dns view default

 domain name-server

 domain name-server

 domain name


ip dns view NO-TUBE

 domain name-server

 domain name-server

 domain name


ip dns view-list NO-TUBE

 view NO-TUBE 10

  restrict source access-group NO-TUBE

 view default 20


ip host view NO-TUBE


ip access-list standard NO-TUBE







In addition to using QoS to restrict some game traffic, I also wanted to wholesale block web sites by DNS Name.


This section globally enables the router’s DNS server with OpenDNS Family Shield DNS servers as the upstream. THese servers provide blanket protection against malware and porn.


Next, I create the view-list NO-TUBE that my kids are subjected to. For this, I use OpenDNS Home DNS servers. This is also a free service, but it requires a bit more effort to set up. You must create a free account with OpenDNS and also ensure that you have dynamic DNS properly configured so that OpenDNS can identify which requests are coming from your home network even as your ISP changes your IP address. THe benefit is that you can then choose which categories of DNS queries to allow / disallow, such as Drugs, Pornography, Adware, Web Spam, etc. These are all noble and good categories, but the one you really need is Proxy/Anonymizer which blocks VPN and other circumvention techniques. (see screenshot of my OpenDNS settings)

(instead of OpenDNS, you can DNS filter locally using data from or you can move the DNS filtering to a separate device, like pi-hole.)

Next, I locally blacklist DNS entries by adding “ip host” statements for each problematic domain. The IP is a junk IP. The result is that when a device in the NO-TUBE view tries to reach, they get a junk IP and their browser hangs. Not pretty, but very effective.

If I had an OpenDNS paid subscription or pi-hole, I would implement this blacklist using those methods instead of local "ip host" entries.

Finally, the access-list determines which devices are subjected to the NO-TUBE DNS rules. I couldn’t get it to work with the kids object-group, but believe that’s a bug with my IOS version. Note that the access-list supports time-range to implement time-of-day / day-of-week restrictions


ip access-list extended EMBARGO

 deny ip object-group kids any

 permit ip any any


ip access-list extended EMBARGO-TIME

 deny ip object-group kids any time-range window

 permit ip any any


time-range window

 absolute start 13:00 13 March 2021 end 17:00 13 March 2021


So far, I’ve described ways to surgically identify and restrict certain game apps and wholesale block dns names like youtube.


EMBARGO is my big hammer, blocking all Internet access. I sometimes couple it with a time-of-date filter. One of the things greatest in life is applying the big hammer and hearing the lamentations of my kids:


interface Vlan 10

 ip access-group EMBARGO in



flow exporter NETFLOW-EXPORTER


 transport udp 2055


flow monitor NETFLOW


 cache timeout active 60

 statistics packet protocol

 statistics packet size

 record netflow-original


Netflow is also configured


I collect it on a linux server and do various network discovery and monitoring with it.


ip nat inside source static tcp 80 interface GigabitEthernet0/0/0 80


ip nat inside source static tcp 25565 interface GigabitEthernet0/0/0 25565



Like most, my broadband router gets a single public IP address.


I NAT two ports of it to inside services. Web traffic (TCP 80) goes to my web server which hosts several virtual domains that are all fronted by cloudflare.  MINECRAFT traffic (TCP 25565) goes to my son’s computer where he hosts a server.


ip nat inside source route-map NAT-SPECTRUM interface GigabitEthernet0/0/0 overload


route-map NAT-SPECTRUM permit 20

 match ip address LOCAL-SUBNETS

 match interface GigabitEthernet0/0/0


ip access-list extended LOCAL-SUBNETS

 permit ip any


The final NAT statement is the general purpose PAT for all outbound traffic. My route-map for more granular control since I have multiple ISP’s and tunnel interfaces. If you only have an inside and outside, there’s no need for a route-map.


ip access-list extended INSIDE-TO-SPECTRUM

 permit ip any any


ip access-list extended SPECTRUM-TO-INSIDE

 permit tcp any host eq 25565

 permit tcp any host eq www


class-map type inspect match-all INSIDE-TO-SPECTRUM

 match access-group name INSIDE-TO-SPECTRUM


class-map type inspect match-any SPECTRUM-TO-INSIDE

 match access-group name SPECTRUM-TO-INSIDE


policy-map type inspect INSIDE-TO-SPECTRUM

 class type inspect INSIDE-TO-SPECTRUM


 class class-default

  drop log


policy-map type inspect SPECTRUM-TO-INSIDE

 class type inspect SPECTRUM-TO-INSIDE


 class class-default

  drop log


policy-map type inspect BLIND-PERMIT

 class class-default



zone security INSIDE

zone security OUTSIDE-SPECTRUM


zone-pair security INSIDE-TO-SPECTRUM source INSIDE destination OUTSIDE-SPECTRUM

 service-policy type inspect INSIDE-TO-SPECTRUM


zone-pair security SPECTRUM-TO-INSIDE source OUTSIDE-SPECTRUM destination INSIDE

 service-policy type inspect SPECTRUM-TO-INSIDE


zone-pair security INSIDE-TO-SELF source INSIDE destination self

 service-policy type inspect BLIND-PERMIT


zone-pair security SELF-TO-INSIDE source self destination INSIDE

 service-policy type inspect BLIND-PERMIT




ZBF is Cisco’s way of punishing us.


I use the same name for the access-list, class-map, and policy-map, even though those are distinct objects.


The SPECTRUM-TO-INSIDE access-list is the only thing that does anything interesting – it permits outside traffic in for the two hosts services described under NAT.   Since ZBF happens after NAT, the access-lists must match the inside IPs.