The Network Layer
IP Addressing and Subnetting
The dots are for readability. The address is one
JrCodex·7 min read
Jr Codex Computer Networks Notes
Level: Intermediate Prerequisites: Module 2, Chapter 4: Switching, ARP and VLANs Time to complete: ~25 minutes
Table of Contents
- The Structure of an Address
- Subnet Masks
- CIDR Notation
- Network, Broadcast and Usable Addresses
- Subnetting
- Special Address Ranges
- Summary & Next Steps
1. The Structure of an Address
32 Bits, Written for Humans
─────────────────────────────────────────
192.168.1.10
Four DOTTED-DECIMAL octets, each 0-255:
192 168 1 10
11000000 10101000 00000001 00001010
The dots are for readability. The address is one
32-bit number, and every operation on it is
bitwise.
─────────────────────────────────────────
THE KEY IDEA
─────────────────────────────────────────
An IP address has TWO PARTS:
NETWORK portion which network
HOST portion which machine on it
192.168.1 . 10
└ network ┘ └host┘
This split is what makes routing possible. A
router stores ONE entry for a whole network,
covering millions of hosts — instead of one entry
per machine, which Module 2, Chapter 3 showed is
why MAC addresses cannot be routed.
─────────────────────────────────────────
2. Subnet Masks
The Question the Mask Answers
─────────────────────────────────────────
WHERE does the network portion end?
The address alone does not say. The SUBNET MASK
does: 1 bits mark the network portion, 0 bits
mark the host portion.
address 192.168.1.10
11000000.10101000.00000001.00001010
mask 255.255.255.0
11111111.11111111.11111111.00000000
└────── network ──────┘ └─ host ─┘
─────────────────────────────────────────
Extracting the Network — a bitwise AND
─────────────────────────────────────────
address 11000000.10101000.00000001.00001010
mask 11111111.11111111.11111111.00000000
AND ────────────────────────────────────
network 11000000.10101000.00000001.00000000
= 192.168.1.0
─────────────────────────────────────────
def to_int(ip): return int.from_bytes(bytes(int(o) for o in ip.split('.')), 'big')
def to_ip(n): return '.'.join(str(b) for b in n.to_bytes(4, 'big'))
def network_address(ip, prefix):
mask = (0xFFFFFFFF << (32 - prefix)) & 0xFFFFFFFF # `prefix` ones, then zeros
return to_ip(to_int(ip) & mask)
print(network_address("192.168.1.10", 24)) # 192.168.1.0
print(network_address("192.168.1.10", 16)) # 192.168.0.0
print(network_address("10.45.200.17", 20)) # 10.45.192.0 ← not an octet boundaryTHE DECISION EVERY HOST MAKES
─────────────────────────────────────────
Before sending any packet:
Is the DESTINATION's network the same as MINE?
SAME ──► deliver DIRECTLY. ARP for the
destination (Module 2, Chapter 4).
DIFFERENT ──► send to the DEFAULT GATEWAY. ARP
for the router.
That comparison — mask both addresses, compare
the results — happens for every single packet
your machine sends.
─────────────────────────────────────────
3. CIDR Notation
The Shorthand
─────────────────────────────────────────
192.168.1.0/24
The /24 is the PREFIX LENGTH — how many leading
bits are the network portion.
/24 = 255.255.255.0
/16 = 255.255.0.0
/8 = 255.0.0.0
/26 = 255.255.255.192
/30 = 255.255.255.252
─────────────────────────────────────────
Why CIDR Replaced Classes
─────────────────────────────────────────
The old scheme fixed the split at octet
boundaries:
Class A /8 16,777,214 hosts
Class B /16 65,534 hosts
Class C /24 254 hosts
An organisation needing 300 addresses had to
take a Class B — 65,534 addresses, of which
65,234 were WASTED and unusable by anyone else.
CIDR (Classless Inter-Domain Routing) allows ANY
prefix length, so that organisation gets a /23
— 510 addresses. This is the main reason IPv4
lasted as long as it did.
─────────────────────────────────────────
Prefix Length and Size
─────────────────────────────────────────
addresses = 2^(32 - prefix)
/24 ──► 256 addresses (254 usable)
/25 ──► 128
/26 ──► 64
/28 ──► 16
/30 ──► 4 (2 usable — point-to-point
links)
/31 ──► 2 (both usable — a special case
for links)
/32 ──► 1 (a single host)
Each extra bit of prefix HALVES the network.
─────────────────────────────────────────
4. Network, Broadcast and Usable Addresses
Two Addresses Are Reserved
─────────────────────────────────────────
For 192.168.1.0/24:
NETWORK ADDRESS 192.168.1.0
all host bits 0. Names the network itself.
Cannot be assigned to a device.
BROADCAST ADDRESS 192.168.1.255
all host bits 1. Reaches every host on this
network.
Cannot be assigned to a device.
USABLE 192.168.1.1 - 192.168.1.254
254 addresses = 2^8 - 2
─────────────────────────────────────────
def subnet_info(ip, prefix):
mask = (0xFFFFFFFF << (32 - prefix)) & 0xFFFFFFFF
net = to_int(ip) & mask
bcast = net | (~mask & 0xFFFFFFFF)
total = 2 ** (32 - prefix)
return {
"network": to_ip(net),
"broadcast": to_ip(bcast),
"first": to_ip(net + 1) if total > 2 else to_ip(net),
"last": to_ip(bcast - 1) if total > 2 else to_ip(bcast),
"usable": max(total - 2, 0) if total > 2 else total,
}
print(subnet_info("192.168.1.10", 24))
# {'network': '192.168.1.0', 'broadcast': '192.168.1.255',
# 'first': '192.168.1.1', 'last': '192.168.1.254', 'usable': 254}
print(subnet_info("10.0.5.130", 26))
# {'network': '10.0.5.128', 'broadcast': '10.0.5.191',
# 'first': '10.0.5.129', 'last': '10.0.5.190', 'usable': 62}5. Subnetting
The Task
─────────────────────────────────────────
You have 192.168.1.0/24 and need FOUR separate
networks — one per department, each its own
broadcast domain (Module 2, Chapter 4).
Borrow HOST bits to make more NETWORK bits.
4 subnets = 2² ──► borrow 2 bits
/24 + 2 = /26
Each /26 has 2^(32-26) = 64 addresses, 62 usable.
─────────────────────────────────────────
The Four Subnets
─────────────────────────────────────────
192.168.1.0/26 .0 - .63 usable .1 -.62
192.168.1.64/26 .64 - .127 usable .65 -.126
192.168.1.128/26 .128 - .191 usable .129-.190
192.168.1.192/26 .192 - .255 usable .193-.254
The BLOCK SIZE is 64 — so subnets start at
multiples of 64. That pattern holds generally:
block size = 2^(32 - prefix), and networks always
begin at a multiple of it.
─────────────────────────────────────────
def subnets(base_ip, old_prefix, new_prefix):
"""Split a network into equal subnets."""
start = to_int(base_ip) & ((0xFFFFFFFF << (32 - old_prefix)) & 0xFFFFFFFF)
block = 2 ** (32 - new_prefix)
count = 2 ** (new_prefix - old_prefix)
return [f"{to_ip(start + i * block)}/{new_prefix}" for i in range(count)]
print(subnets("192.168.1.0", 24, 26))
# ['192.168.1.0/26', '192.168.1.64/26', '192.168.1.128/26', '192.168.1.192/26']VLSM — Variable Length Subnet Masking
─────────────────────────────────────────
Equal subnets waste space when needs differ.
VLSM lets you size each one.
Requirements: 100 hosts, 50 hosts, 20 hosts, and
two point-to-point links.
ALLOCATE LARGEST FIRST:
100 hosts ──► /25 (126 usable) 192.168.1.0/25
50 hosts ──► /26 (62 usable) 192.168.1.128/26
20 hosts ──► /27 (30 usable) 192.168.1.192/27
link ──► /30 (2 usable) 192.168.1.224/30
link ──► /30 (2 usable) 192.168.1.228/30
Largest first is essential — allocating a small
subnet in the middle of the space fragments it so
a large one no longer fits on its required
boundary.
─────────────────────────────────────────
6. Special Address Ranges
Ranges Worth Memorising
─────────────────────────────────────────
PRIVATE (RFC 1918) — not routable on the
internet; used behind NAT (Chapter 5)
10.0.0.0/8 16,777,216 addresses
172.16.0.0/12 1,048,576
192.168.0.0/16 65,536
LOOPBACK
127.0.0.0/8 127.0.0.1 = this machine
LINK-LOCAL (APIPA)
169.254.0.0/16 self-assigned when DHCP
FAILS
── seeing this address means DHCP did not
answer (Chapter 5)
MULTICAST
224.0.0.0/4 one-to-many groups
DOCUMENTATION — safe to use in examples
192.0.2.0/24, 198.51.100.0/24, 203.0.113.0/24
DEFAULT ROUTE
0.0.0.0/0 "everything" — Chapter 3
─────────────────────────────────────────
The Diagnostic Worth Knowing Now
─────────────────────────────────────────
A machine with a 169.254.x.x address has NOT
received a DHCP lease.
It assigned itself a link-local address because
no DHCP server replied. So the problem is DHCP,
the cable, or the VLAN — not routing, not DNS,
not the application.
This one recognition saves a great deal of
diagnostic time.
─────────────────────────────────────────
7. Summary & Next Steps
Key Takeaways
- An IP address splits into a network portion and a host portion, and that hierarchy is what lets one routing entry cover millions of hosts.
- The subnet mask defines where the split falls, and every host masks both its own and the destination address to decide between direct delivery and the gateway.
- CIDR replaced fixed classes with arbitrary prefix lengths, which stopped the enormous address waste that classful allocation caused.
- Subnets always begin at a multiple of their block size, and VLSM requires allocating the largest subnet first to avoid fragmenting the space.
Concept Check
- Given 10.20.30.45/20, compute the network address, the broadcast address and the usable range.
- Why does every host perform a bitwise AND before sending each packet?
- A machine has the address 169.254.12.9. What has failed, and what have you ruled out?
Next Chapter
→ Chapter 2: IPv4, IPv6 and Address Exhaustion
Jr Codex — 1-on-1 Personalized Coaching | Back to Module Index | Back to Computer Networks Index