Core Question
Core Question
"Destination Unreachable" sounds like one failure. Why does it split into four codes, and why does the sender of the message change depending on which one you get?
Outcome
Outcome
By the end of this session, the learner should be able to:
- name the sender implied by each of the four
UnreachableCodevalues - explain why port-unreachable is a different *kind* of failure from net/host-unreachable, not just a different degree
- describe what
next_hop_mtuin a frag-needed message is for - build a valid port-unreachable message from a quote and confirm the quote round-trips unchanged
Read Order
Read Order
- Read
UnreachableCode - Read
_SENDER_BY_CODE - Read
who_sends() - Read
make_port_unreachable() - Read
make_frag_needed() - Run
examples/icmp/session_02_walkthrough.py
Read It Like Code
Read It Like Code
who_sends(code: UnreachableCode) -> str make_port_unreachable(quoted: QuotedPacket) -> IcmpMessage make_frag_needed(quoted: QuotedPacket, next_hop_mtu: int) -> IcmpMessage
Fields That Matter
Fields That Matter
| Field | Why it matters |
|---|---|
UnreachableCode.PORT_UNREACHABLE | The one code in this table sent by the *destination*, not a router along the path. The packet arrived; nobody was listening on that port. |
UnreachableCode.NET_UNREACHABLE / HOST_UNREACHABLE | Both come from a router that ran out of route — the packet never got close to the destination. |
next_hop_mtu (in make_frag_needed) | Not a status flag — a number the sender is expected to read and act on. It's the entire mechanism behind Path MTU Discovery. |
quoted (on every builder) | Carried through unchanged from caller to IcmpMessage. The builder's job is to attach the right icmp_type/code, not to touch the quote. |
Decision Flow
Decision Flow
who reported this? NET_UNREACHABLE / HOST_UNREACHABLE -> a router along the path (no route left to try) PORT_UNREACHABLE -> the destination host itself (packet arrived, nobody listening) FRAG_NEEDED -> the router whose outbound link is too small for the packet
Reading Lens
Reading Lens
The important move in this session is to stop reading "Destination Unreachable" as a single failure and start asking, for any given code:
- did this packet ever reach the destination host, or did a router give up first?
- is this message reporting a routing problem, a listening problem, or a size problem?
- if this is frag-needed, what number is riding along, and who is supposed to read it?
Toy Model Boundary
Toy Model Boundary
Real ICMP generation is rate-limited by the sending router or host, so a burst of unreachable packets doesn't turn into a flood back at the source — this lesson has no rate limiting at all. This module is also IPv4-only; ICMPv6 folds "too big" into its own type rather than a Destination-Unreachable code, so make_frag_needed's shape doesn't carry over directly.
Code Landmarks
Code Landmarks
_SENDER_BY_CODE
A plain dict, one line per code. Read the comment above it before the dict itself — it states the sender for all four codes in prose, and the dict is just that prose made queryable.
who_sends()
One line: a dict lookup. All the teaching content lives in the table it reads from, not in this function.
make_port_unreachable()
Builds a DEST_UNREACHABLE message with code=PORT_UNREACHABLE.value. The docstring frames it as a confession: "I got it, nobody was home" — worth reading as the destination host's own voice, not a router's.
make_frag_needed()
The code string embeds next_hop_mtu directly (f"{...} (next-hop MTU {next_hop_mtu})"). That's the one-sentence version of Path MTU Discovery: the sender reads this number out of the error and shrinks future packets to fit.
Failure Questions
Failure Questions
Use the source file to answer these:
- Which two codes in
_SENDER_BY_CODEare reported by "a router along the path," and what do they have in common that separates them fromPORT_UNREACHABLE? - Why is port-unreachable described as a *different kind* of failure rather than just another routing failure — what has to be true about the packet's journey for this code to be sent at all?
- What does
make_frag_needed()do withnext_hop_mtuthatmake_port_unreachable()doesn't do with any equivalent value? - If you call
make_port_unreachable(quoted), whaticmp_typedoes the resultingIcmpMessagecarry, and where in the source is that decided? - Does
who_sends()know anything about a specific packet, or only about the code? What does that tell you about what this function can and can't answer?
Walkthrough
Walkthrough
Run this:
PYTHONPATH=src python3 examples/icmp/session_02_walkthrough.py
The walkthrough reads who_sends() for all four codes, then builds a frag-needed message and a port-unreachable message and checks what each one carries.
Done When
Done When
The learner can say all of the following without looking at notes:
- "Net-unreachable and host-unreachable come from a router giving up; port-unreachable comes from the destination itself."
- "Port-unreachable means the packet arrived — that's a fundamentally different failure from a router with no route."
- "Frag-needed isn't just a rejection — the MTU it carries is the whole mechanism Path MTU Discovery runs on."
References
References
- RFC 792 (Internet Control Message Protocol)
- RFC 1191 (Path MTU Discovery) — for the
next_hop_mtufield inmake_frag_needed()
Continue