Thuta Learning
ExercisesDevOps & Toolsbeginner

Exercise: Debug a Subnet Misconfiguration

What you'll walk away with

  • Explain the core ideas behind Exercise: Debug a Subnet Misconfiguration
  • Read the diagram and trace how data actually moves through it
  • Run the sample code and verify its output

Build the mental model

Most bugs announce themselves: a crash, a stack trace. Subnet bugs do not. Subnet math is the one place in networking where human intuition and machine arithmetic look identical right up until they diverge. We read 192.168.10.20 and 192.168.10.130 and think "same network, they both start with 192.168.10" -- and for a /24 we would be right. Every host in 192.168.10.0/24 really does share a broadcast domain, so code that decides membership by comparing the first three octets passes every test anyone writes. Then someone carves that /24 into four /26 blocks to separate warehouse scanners from finance machines, and the same code keeps returning the same confident answer while the truth underneath has quietly changed.

That is the bug class: subnet membership decided by octet intuition instead of by the mask the interface is actually configured with. It shows up as a hardcoded /24 in a helper, as a string comparison of address prefixes, as a network address computed by zeroing the last octet. All three are one mistake wearing different clothes -- the mask was assumed rather than read.

What makes it dangerous is exactly that it does not crash. A crash gets a ticket; a confidently wrong reachability report gets believed. An engineer reads DIRECT, concludes the switch path is healthy, and burns a day hunting a firewall that was never in the path. The habit that catches it is mechanical: never derive a network from an address by hand. Hand both the address and its real prefix to Python's ipaddress module and let it do the AND. Then test across a mask boundary, because a /24-only test suite cannot see this failure at all.

text
ONE /24 OR FOUR /26 BLOCKS?
---------------------------
  Real config: 192.168.10.0/24 carved into four /26 blocks of 64
  .0 ------ .63   .64 ----- .127   .128 ---- .191   .192 ---- .255
  +-------------+ +-------------+ +-------------+ +-------------+
  | SUBNET A    | | SUBNET B    | | SUBNET C    | | SUBNET D    |
  | reception   | | warehouse   | | server-nas  | | (unused)    |
  |   .20       | |   .70       | |   .130      | |             |
  | sales .50   | |             | |             | |             |
  +------+------+ +------+------+ +------+------+ +-------------+
         |               |               |
         +-------+-------+-------+-------+
                 |               |
            +--------------------------------+
            |  GATEWAY 192.168.10.1          |
            |  routes between the /26 blocks |
            +--------------------------------+

  The script assumes /24, so it sees ONE flat subnet:
  +-----------------------------------------------------------+
  | 192.168.10.0/24   everybody DIRECT, gateway never needed   |
  +-----------------------------------------------------------+
                        ^ this is the lie

Connect it to a real scenario

Here is the office you are debugging. A single 192.168.10.0/24 range was subdivided into four /26 blocks when the shop floor was separated from the back office. Reception (.20) and the sales laptop (.50) live in 192.168.10.0/26. The warehouse scanner (.70) sits in 192.168.10.64/26. The NAS (.130) sits in 192.168.10.128/26. The gateway at 192.168.10.1 is what routes between them.

Run the script below exactly as written. It does not raise, it does not warn, and it prints a report that looks entirely professional: a host table, a rule of dashes, then one verdict per host pair. Read the host table first, because that is where the evidence is. Every row reports the same net= value, even though every row was configured with a different /26. Then read the verdicts underneath: all six pairs say DIRECT.

Now do the arithmetic by hand for one pair. A /26 is 64 addresses, so the block boundaries fall at .0, .64, .128 and .192. Which block holds .70? Which block holds .130? Once you have those two numbers written down, the report has contradicted itself in front of you, and all that is left is finding the line of code that made it lie.

Try the working example

python
"""Office network reachability checker.

Every host is configured in CIDR notation. Two hosts can talk directly
through the switch when they share a subnet; otherwise the packet has to
be handed to the gateway. This script prints a report -- check it against
the configured masks by hand before you trust it.
"""

import ipaddress

HOSTS = {
    "reception-pc": "192.168.10.20/26",
    "sales-laptop": "192.168.10.50/26",
    "warehouse-scanner": "192.168.10.70/26",
    "server-nas": "192.168.10.130/26",
}

GATEWAY = "192.168.10.1"

# The office is "a 192.168.10.x network", so a /24 is close enough. Right?
ASSUMED_PREFIX = 24


def network_of(cidr):
    """Return the network this host address belongs to."""
    addr = cidr.split("/")[0]
    return ipaddress.ip_network(addr + "/" + str(ASSUMED_PREFIX), strict=False)


def same_subnet(cidr_a, cidr_b):
    """True when both hosts sit in the same subnet."""
    return network_of(cidr_a) == network_of(cidr_b)


def main():
    print("HOST TABLE")
    print("-" * 62)
    for name in HOSTS:
        cidr = HOSTS[name]
        print("{:<20} {:<20} net={}".format(name, cidr, network_of(cidr)))

    print()
    print("REACHABILITY REPORT")
    print("-" * 62)
    names = list(HOSTS)
    for i in range(len(names)):
        for j in range(i + 1, len(names)):
            a = names[i]
            b = names[j]
            if same_subnet(HOSTS[a], HOSTS[b]):
                verdict = "DIRECT (switch only)"
            else:
                verdict = "VIA GATEWAY " + GATEWAY
            print("{:<20} -> {:<20} {}".format(a, b, verdict))


main()
You should see
HOST TABLE
--------------------------------------------------------------
reception-pc         192.168.10.20/26     net=192.168.10.0/24
sales-laptop         192.168.10.50/26     net=192.168.10.0/24
warehouse-scanner    192.168.10.70/26     net=192.168.10.0/24
server-nas           192.168.10.130/26    net=192.168.10.0/24

REACHABILITY REPORT
--------------------------------------------------------------
reception-pc         -> sales-laptop         DIRECT (switch only)
reception-pc         -> warehouse-scanner    DIRECT (switch only)
reception-pc         -> server-nas           DIRECT (switch only)
sales-laptop         -> warehouse-scanner    DIRECT (switch only)
sales-laptop         -> server-nas           DIRECT (switch only)
warehouse-scanner    -> server-nas           DIRECT (switch only)

5-minute try-it

The symptom: the script runs clean and reports that all six host pairs can talk directly through the switch. The network team insists the warehouse scanner cannot reach the NAS without going through the router. One of them is wrong.

Start with the two rows that should look most suspicious to you: warehouse-scanner -> server-nas, and reception-pc -> server-nas. Take the configured CIDR for each of those hosts out of the HOSTS dict and work out its network address by hand, using the prefix that is actually written there -- not the one the report prints. Then compare your four network addresses against the single net= value the host table shows for every host.

When you have found the line responsible, fix it so that each host's own configured prefix is used, and re-run. A corrected report shows DIRECT for exactly one pair and VIA GATEWAY for the other five. Finally, prove the fix is real: change every host to a /24 and confirm the report flips back to all-DIRECT. If it does, you have also just demonstrated why the original bug survived testing for so long.

One important caution

Hardcoding a prefix length (almost always /24) inside a helper instead of reading each interface's configured mask. The code is correct until the day somebody subnets, and confidently wrong forever after.

Comparing IP addresses as strings. '192.168.1.10' < '192.168.1.9' is True as text and False as addresses, so sorting, range checks and ACL matching all quietly misbehave. Convert with ipaddress.ip_address() before comparing anything.

Python documentation - ipaddress: IPv4/IPv6 manipulation libraryComputer Networking

Easy traps

  • Hardcoding a prefix length (almost always /24) inside a helper instead of reading each interface's configured mask. The code is correct until the day somebody subnets, and confidently wrong forever after.
  • Comparing IP addresses as strings. '192.168.1.10' < '192.168.1.9' is True as text and False as addresses, so sorting, range checks and ACL matching all quietly misbehave. Convert with ipaddress.ip_address() before comparing anything.
  • Validate sample code in a local or test environment before applying it to a production network.

Exercise

The symptom: the script runs clean and reports that all six host pairs can talk directly through the switch. The network team insists the warehouse scanner cannot reach the NAS without going through the router. One of them is wrong.

Start with the two rows that should look most suspicious to you: warehouse-scanner -> server-nas, and reception-pc -> server-nas. Take the configured CIDR for each of those hosts out of the HOSTS dict and work out its network address by hand, using the prefix that is actually written there -- not the one the report prints. Then compare your four network addresses against the single net= value the host table shows for every host.

When you have found the line responsible, fix it so that each host's own configured prefix is used, and re-run. A corrected report shows DIRECT for exactly one pair and VIA GATEWAY for the other five. Finally, prove the fix is real: change every host to a /24 and confirm the report flips back to all-DIRECT. If it does, you have also just demonstrated why the original bug survived testing for so long.

You'll know it worked when: HOST TABLE -------------------------------------------------------------- reception-pc 192.168.10.20/26 net=192.168.10.0/24 sales-laptop 192.168.10.50/26 net=192.168.10.0/24 warehouse-scanner 192.168.10.70/26 net=192.168.10.0/24 server-nas 192.168.10.130/26 net=192.168.10.0/24 REACHABILITY REPORT -------------------------------------------------------------- reception-pc -> sales-laptop DIRECT (switch only) reception-pc -> warehouse-scanner DIRECT (switch only) reception-pc -> server-nas DIRECT (switch only) sales-laptop -> warehouse-scanner DIRECT (switch only) sales-laptop -> server-nas DIRECT (switch only) warehouse-scanner -> server-nas DIRECT (switch only)

Exercise: Debug a Subnet Misconfiguration | Thuta Learning