First-Match CIDR Firewall: Status of an IP Address or a Whole CIDR Block
DatabricksSoftware Engineer · OnsiteLast updated
Implement an ordered first-match IPv4 firewall that returns ALLOW or DENY for an address, then extend it so the target can be a whole CIDR block that is allowed only if every address inside it is. Tests bit-level prefix matching and reasoning about address ranges at scale.
A firewall is configured with an ordered list of rules. Each rule pairs an IPv4 pattern with an action, for example:
rules = [("192.168.1.0/24", "ALLOW"), ("8.8.8.8", "DENY")]
A pattern is either a CIDR block a.b.c.d/p, which covers every address whose first p bits equal the first p bits of a.b.c.d, or a bare address, which is treated as /32. The list is ordered, so the status of an address is the action of the first rule that covers it.
Write get_status(rules, target). In the base question the target is a single address: get_status(rules, "192.168.1.5") returns "ALLOW", because its first 24 bits (192.168.1) match the first rule. The follow-up asks what happens, once the rules are in place, when the target is itself a CIDR block. A block stands for many addresses, and it counts as allowed only if every address in it is allowed. Your function must handle both kinds of target.
Function Signature
def get_status(rules: list[tuple[str, str]], target: str) -> str:
Rules
- Read each address as a 32-bit number with the first octet most significant. An address matches the pattern
n/pwhen its firstpbits equal the firstpbits ofn./0matches every address. Bits ofnafter the firstpare ignored. - The status of a single address is the action of the lowest-index rule it matches.
- Assumption (not stated in the report): an address that matches no rule has status
"DENY". targetis either a bare address (a block of exactly one address) or a CIDR blocka.b.c.d/qcovering every address whose firstqbits equal those ofa.b.c.d.- Return
"ALLOW"if every address in the target block has status"ALLOW"; otherwise return"DENY". For a bare address this is simply that address's status.
Constraints
1 <= len(rules) <= 10^5- Every prefix length, in a rule or in
target, is an integer from 0 to 32; a pattern without/means/32. - Every address is four decimal octets from 0 to 255, separated by dots, with no leading zeros or whitespace.
- Every action is exactly
"ALLOW"or"DENY". - A target block can contain up to
2^32(4,294,967,296) addresses, which exceeds2^31 - 1. - The output is exactly
"ALLOW"or"DENY".
Examples
Example 1
rules = [("192.168.1.0/24", "ALLOW"), ("8.8.8.8", "DENY")]
target = "192.168.1.5"
Output: "ALLOW"
The first 24 bits of 192.168.1.5 match rule 0. With the same rules, "8.8.8.8" would return "DENY" (rule 1), and "10.0.0.1" would return "DENY" because no rule matches it.
Example 2
rules = [("10.0.0.7", "DENY"), ("10.0.0.0/24", "ALLOW")]
target = "10.0.0.0/28"
Output: "DENY"
The block covers 10.0.0.0 through 10.0.0.15. Address 10.0.0.7 matches the DENY rule first, so the block is not entirely allowed. The target "10.0.0.16/28" would return "ALLOW".
Example 3
rules = [("10.0.0.0/25", "ALLOW"), ("10.0.0.128/25", "ALLOW")]
target = "10.0.0.0/24"
Output: "ALLOW"
No single rule covers the whole block, but each of its halves, 10.0.0.0 through 10.0.0.127 and 10.0.0.128 through 10.0.0.255, is covered by an ALLOW rule.
Solution walkthrough is for members
Get the full walkthrough with reference code and complexity analysis, plus Kitty Coach at 300 messages a day. Hints, the code runner and community answers stay free.