How do you decide whether an IPv4 address such as 172.16.45.9 falls inside a prefix such as 172.16.32.0/20, using the mask?
answer
- compare the leading bits only
- bitwise AND with the mask
- mask the prefix side too
- one partial octet decides
basics
~20 sAND the address with the prefix's mask and compare the result with the prefix's network bits. For /20 the mask is 255.255.240.0; 172.16.45.9 AND that mask is 172.16.32.0, equal to the prefix, so the address is inside.
solid answer
~40 sAn address is inside a prefix when its first *n* bits equal the prefix's first *n* bits, which is `(address AND mask) == (prefix AND mask)`. For `172.16.32.0/20` the mask is `255.255.240.0`. The first two octets are compared whole and match. The third octet is the only partial one: `45 AND 240` — `00101101 AND 11110000` — is `00100000`, which is `32`, the same as the prefix. The last octet is masked away entirely. So `172.16.45.9` is inside, and the prefix covers third-octet values 32 through 47; `172.16.48.1` is outside because `48 AND 240` is `48`. Masking the prefix side as well protects the test from a prefix written with host bits set.
code
pseudocode · 9 linesfunction in_prefix(addr, prefix, len):
# addr and prefix are 32-bit unsigned integers
if len == 0:
return true # 0.0.0.0/0 covers every address
mask = (0xFFFFFFFF << (32 - len)) AND 0xFFFFFFFF
return (addr AND mask) == (prefix AND mask)
# in_prefix(172.16.45.9, 172.16.32.0, 20) -> true
# in_prefix(172.16.48.1, 172.16.32.0, 20) -> falsego deeper
Remember the rule: AND the address with the mask and compare with the prefix; only the leading bits named by the length matter.
Work the partial octet in binary or by block size without hesitation, and explain why the prefix side must be masked too.
Spot membership bugs in real allow-lists and filters: text matching, unmasked prefixes with host bits set, and entries that are silently wider than intended.
Push for prefixes to be normalised at the point of entry across systems, so a reviewer reading an allow-list sees exactly the range that is enforced.
## The membership rule A **prefix** such as `172.16.32.0/20` names every IPv4 address whose first 20 bits equal the first 20 bits of `172.16.32.0`. The remaining 12 bits are free. So "is this address inside the prefix?" is a question about leading bits only, and the **subnet mask** is the tool that isolates them: a bitwise **AND** with the mask keeps the network bits and forces every host bit to zero. The test is: `(address AND mask) == (prefix AND mask)` Masking the prefix side as well is deliberate. If someone writes the prefix with host bits set, the comparison still uses only the bits the length says matter. ## Worked trace: 172.16.45.9 against 172.16.32.0/20 1. Convert the length to a mask: 20 = 8 + 8 + 4, so the mask is `255.255.240.0`. 2. AND the address with the mask, octet by octet. 3. AND the prefix with the mask the same way. 4. Compare the two results. | Octet | Address | Mask | Address AND mask | Prefix AND mask | |---|---|---|---|---| | 1 | 172 | 255 | 172 | 172 | | 2 | 16 | 255 | 16 | 16 | | 3 | 45 = `00101101` | 240 = `11110000` | `00100000` = 32 | 32 | | 4 | 9 | 0 | 0 | 0 | Both sides come out as `172.16.32.0`, so the address is **inside**. Run the same steps for `172.16.48.1`: the third octet gives `00110000 AND 11110000` = `00110000` = 48, which differs from 32, so that address is **outside**. Notice where the work happens: - Octets where the mask is `255` are compared whole. - Octets where the mask is `0` are ignored completely. - Only the **one partial octet** needs binary thinking. ## A shortcut for the partial octet The partial octet's mask value tells you the block size in that octet: 256 − 240 = **16**. A `/20` therefore spans 16 consecutive values of the third octet, starting on a multiple of 16. `32` is a multiple of 16, so the block runs from `172.16.32.0` through `172.16.47.255`. Any address whose first two octets are `172.16` and whose third octet is 32–47 is inside; 31 and 48 are not. The bitwise AND and this range check always agree; the range form is just quicker to do in your head. ## Edge cases worth knowing - **`/0`** has the mask `0.0.0.0`, so both sides of the comparison are always `0.0.0.0` — every address matches. `0.0.0.0/0` is the default route, which RFC 4632 says every implementation must accept. - **`/32`** has the mask `255.255.255.255`, so only the identical address matches. - **Host bits set in the prefix.** `172.16.45.0/20` is not a canonical prefix: 45 is not on a 16-boundary. Masking both sides treats it as `172.16.32.0/20`, which may be wider than its author meant. Implementations differ — some reject such an entry, some silently mask it — and RFC 1812 says routers should reject configuration inconsistent with the network-prefix model. Writing `172.16.45.9/20` is fine as an *interface* address, where it means "this address, on a /20"; it is the *route or filter* entry that should have zero host bits. - **Text matching is not prefix matching.** The string `172.16.45.9` "starts with" `172.16.4`, yet the address is not in `172.16.4.0/22`, whose third octets run 4 to 7. Characters are not bits, and the answer goes wrong whenever the prefix does not end on an octet boundary. ## The same rule in IPv6 IPv6 uses the identical test over 128 bits; RFC 4291 defines the prefix length as the number of leftmost contiguous bits that make up the prefix. Since each hexadecimal digit is 4 bits, a `/56` fixes 14 digits: `2001:db8:abcd:12ab::1` is inside `2001:db8:abcd:1200::/56` because both begin `2001:0db8:abcd:12`, while `2001:db8:abcd:1300::1` is not. ## Where the test shows up The same comparison sits under several everyday mechanisms, each with its own rules beyond the bit test: - an **allow-list or deny-list** entry deciding whether a client address is covered; - a **host deciding whether a destination is on its own link** or must go through a router; - a **router's forwarding lookup**, which runs this test against many prefixes and, when several match, prefers the longest one. Getting the bit test right, including the partial octet and the edge cases above, is what keeps all three honest.
- What changes if an IPv4 allow-list entry is written as 172.16.45.0/20 instead of 172.16.32.0/20?The entry has host bits set, because 45 is not on a 16-boundary. If both sides are masked it still means `172.16.32.0/20`, third octets 32 through 47, probably wider than the author intended. Implementations differ: some reject it, some silently mask it. A naive check that masks only the address compares against `172.16.45.0`, which no masked address can equal, so it matches nothing. Normalise prefixes when they are entered.
- How does the same test work for IPv6, say 2001:db8:abcd:12ab::1 against 2001:db8:abcd:1200::/56?Identically, over 128 bits. A `/56` fixes the first 56 bits, which is 14 hexadecimal digits at 4 bits each: `2001:0db8:abcd:12`. The address begins with the same 14 digits, so it is inside; `2001:db8:abcd:1300::1` is not. Lengths on a multiple of 4 line up with hex digits, which is one reason IPv6 address plans favour them.
saying these in an interview costs you the question
- Matches by text prefix, so 172.16.45.9 seems inside 172.16.4.0/22.
- Assumes a /20 covers only a single value of the third octet.
- Masks the address but not the prefix, so host bits in the prefix break it.
- Says a /20 starting at 172.16.32.0 runs to third octet 63.
- Thinks 0.0.0.0/0 matches only the address 0.0.0.0.