A connection is not one thing that works or fails. It is a stack of independent steps — find the name, reach the address, open the transport, agree on encryption, ask the question — and each fails with its own signature. That is what makes layering worth learning: it turns "the network is broken" into a search order. So this guide keeps asking what each mechanism looks like when it breaks, until symptoms start naming their own layer.
Each chapter opens with the short version. Tap one to read the detail.
Layering, and why it is a diagnostic tool
~2 min
Four layers: link, internet, transport, application. Each adds a header and treats what is above it as opaque. The payoff is not the diagram — it is that each layer fails distinctively, so a symptom points at one.
The Internet suite has four layers, not seven: a link layer to reach the next box, an internet layer to reach a distant host, a transport layer to reach a process, and an application layer that carries meaning. The seven-layer Open Systems Interconnection (OSI) model is a teaching overlay from another family, and the host specification folds its top two layers into one.
Each layer wraps what it is handed and reads only its own header. The Internet Protocol (IP) therefore promises nothing: a datagram may be corrupted in flight, delivered twice, reordered, or simply lost. Ordering and retransmission are built by the two endpoints, which is why routers can stay simple and forward each packet on its own.
The confusion this creates is that people diagnose the stack as a unit. Watch one web request and it is four things: resolve a name, connect to an address, complete an encryption handshake, send the request. Each fails alone. A name that will not resolve and a certificate that will not verify are not both "the site is down" — they are different faults with different owners, and they look alike only because one command performs all four.
Rule: name the last step that succeeded before changing anything; the fault is in the next one.
Addresses, masks, and getting onto the wire
~2 min
Before sending, a host masks the destination, masks itself, and compares. Same result means talk directly on the local link; different means hand it to a router. Most addressing bugs are that comparison going the wrong way.
A mask is not a permission list. It answers one question, and the host specification makes the algorithm a requirement: mask the destination, mask your own address, compare. Equal means the destination is on your connected network and you send directly; unequal means it is reachable only through a gateway.
That comparison explains a wrong mask's odd symptoms. Too wide, and the host treats a remote address as a neighbor, asks the local link for a machine that is not there, and gets silence: a hang, not a rejection. Too narrow, and local traffic detours via the router, which usually still works. Neither looks like an addressing fault; both look like "some hosts work and some don't".
A prefix such as /24 says how many leading bits are the network. One example machine sits at 192.168.1.109 with a 255.255.255.0 mask, inside one of the ranges reserved for private use and never routed publicly: 10/8, 172.16/12, 192.168/16. Its table holds a default route and an entry for its own /24. Forwarding takes the longest matching prefix, so 0.0.0.0/0 is the last resort by construction.
"Directly" is where the local link comes in. A frame is addressed to a 48-bit hardware address, so a host holding only an IP address broadcasts "who has this address?" and caches the hardware address from the reply. That machine's table shows each cached entry with an expiry counter.
Nothing authenticates that reply, and an unsolicited one overwrites the cache. That is deliberate: it lets a standby machine take over an address in seconds. It equally lets an attacker on your network become the middle of your conversation, with nothing in the frame to tell the two apart.
Rule: when local works and remote does not, check the mask and gateway; when only some hosts on one subnet are unreachable, check the address-to-hardware mapping.
Not IPv4 with longer addresses
~2 min
The notation is the easy part. What changes is the machinery: hosts build their own addresses, address resolution moved into the protocol, and routers may no longer split oversized packets — so discovering the path size is mandatory, not an optimization.
Addresses are eight groups of four hex digits, with one run of zero groups compressible to :: — one run only, because two would be ambiguous. Interfaces are typically given a /64.
The first surprise is how many addresses one interface holds. The machine used here has six: a mandatory link-local address beginning fe80, one stable global address it built itself from the prefix its router advertised, several temporary ones that rotate so a fixed identifier cannot track the user, and older ones marked deprecated but still accepted. More striking, its default route points at an fe80 address — the gateway is named by an address that is not routable off the link at all. Normal here; nonsense in the older protocol.
Address resolution moved into the protocol too, and a host must probe for a duplicate before it may use an address at all.
The fragmentation change is what breaks deployments. Routers on the path may not split an oversized packet; only the source may. Every link must carry at least 1280 bytes, and the fixed header is 40 bytes rather than 20, so a segment size computed here subtracts 60 where the older protocol subtracted 40. Since no router will fix an oversized packet for you, discovering the path size is not tuning — it is how the protocol works.
Rule: if a service works for some clients and hangs for others, test each protocol separately; you almost certainly have two different paths.
Address translation, and the connections it quietly forbids
~2 min
A translator creates its mapping when the first packet goes out. Everything surprising follows: unsolicited inbound traffic matches nothing and is dropped, the box is stateful where a router is not, and protocols that quote their own address break.
Basic translation swaps one address for another; the common form rewrites ports too, so many private hosts share one public address and are told apart by port. The binding is created when the first outgoing session starts, and that is the whole model.
Two familiar behaviors drop out of that direction. Unsolicited inbound traffic matches no entry and is discarded: filtering as a side effect, not a security design, and treating it as a firewall is the most expensive misconception here. The mapping is state, so unlike a router — which forwards each datagram independently and can restart unnoticed — a translator that loses its table kills every connection through it.
Protocols carrying an address inside their own payload are the classic casualty. The box rewrites headers, not payloads, so an inside address travels out intact and the peer tries to reach something unroutable. The workaround is a helper that parses that protocol and edits the payload too — and encrypting the payload blinds the helper, which is one reason newer designs stopped quoting addresses.
For peer-to-peer traffic one property decides everything: a translator should keep the same external port for a given internal socket whoever it is talking to. Boxes that pick a fresh mapping per destination defeat the usual trick of learning your public address and telling a peer about it. And an uplink address inside 100.64.0.0/10 is carrier-side translation space — you are behind another layer, and inbound is not yours to open.
Rule: translation breaks anything that assumes your address is reachable, or worth quoting.
Refused, hung, reset — and why "slow" is two problems
~2 min
Setup fails in two ways that mean opposite things: a fast refusal proves the host is reachable and nothing is listening, a timeout proves nothing. Once open, throughput is capped by the smaller of two windows, and short transfers are governed by round trips.
The opening handshake agrees a starting sequence number each way, and its failure modes are the most useful diagnostic in networking.
A port with nothing listening rejects your opening request with a reset, because the specification answers any segment arriving where no connection exists with one. In one test, a closed local port failed the connection in 0.018 seconds with Connection refused.
A dropped connection (by a filter, a wrong route, an absent host) produces nothing, and the sender retries until it gives up. The same test against an unrouted address took 75 seconds. The tool's own six-second timeout did not bound it, because that option governs idle time on an open connection, not the operating system's connect timeout. Slow failures are usually somebody else's timer.
So refused proves routing worked and the host is up, leaving a process or a port, while timed out proves nothing. A reset mid-connection is a third case: an abort discarding buffered data, not the orderly shutdown that closes each direction separately. And do not expect the stack to notice a dead peer: keepalive must default to off, with an interval defaulting to no less than two hours.
Throughput is then capped by two independent windows, and the smaller governs. The receiver advertises its free buffer, which says nothing about the network; the sender keeps a congestion window, its own guess about the path. That guess starts at a handful of segments and grows per acknowledgment until loss appears. Three duplicate acknowledgments are read as one lost segment and resent at once, not on a timer.
A short transfer never leaves that ramp, so round trips, not link capacity, set its duration; more bandwidth changes nothing for a small response over a long path. And since congestion is inferred from drops, an oversized queue delays the signal. A drop-when-full queue reports congestion only once full, so it can sit near-full for long periods, turning congestion into delay that no error reports.
Rule: separate "answered no" from "did not answer", then separate a rate problem from a round-trip problem — the fixes have nothing in common.
Datagrams, and what QUIC builds on them
~2 min
The datagram protocol is eight bytes of header and no promises: no ordering, no retransmission, no connection. One send is one receive. QUIC takes that blank slate and rebuilds the missing guarantees in application code, where they can actually be changed.
The header is two ports, a length and a checksum; anything else your application needs it must build. In exchange, message boundaries survive: one send is exactly one receive, unlike a byte stream that may coalesce or split your writes. The checksum covers a pseudo-header holding the addresses from the layer below, which is why a box that rewrites addresses must recompute it.
QUIC treats that blankness as a foundation. It runs over datagrams so it can live in application code and be updated without waiting for operating systems, and it rebuilds reliability, ordering and congestion control per stream. One connection carries many streams, and a lost packet stalls only the streams whose data it held — where a single loss on a byte-stream transport stalls everything queued behind it.
The nuance most summaries drop is in the specification itself: when several streams' data shares one packet, losing it blocks all of them. The benefit is real but proportional to how the data was packed, not automatic.
Identity moved as well: a connection is keyed by an identifier the endpoints choose rather than by the address pair, so it survives an address change — a download continues when a phone moves from wireless to cellular instead of resetting.
Datagrams also invite abuse, since a small forged request drawing a large reply turns an open service into an amplifier aimed at whoever the attacker names as the source. Hence a hard rule: until a peer's address is validated, a server may send at most three times what it received.
Rule: choosing datagrams means choosing to implement retransmission, ordering and congestion control yourself.
How a packet finds its way, and who really decides
~2 min
Forwarding is a lookup — the most specific matching prefix wins. Routing is the separate business of building that table, and between networks it is settled by policy rather than distance.
Keep two things apart. Forwarding is what happens to each packet: look up the destination, take the most specific matching prefix, send. Routing is the machinery that fills that table in, and confusing the two makes routing look arbitrary.
The most-specific rule has teeth. Providers keep the global table finite by advertising one short prefix instead of many long ones — but a longer prefix announced by anybody else inside that range attracts its traffic, because longest match does not care who is entitled to it. That is the mechanism behind route hijacks, and a design property rather than a bug: nothing in the protocol establishes who owns an address range.
Between networks, path selection is not a distance calculation. The decision process computes a degree of preference from locally configured policy first, and only routes that tie on preference reach the tie-breakers where path length is considered. Since that policy encodes commercial relationships, the route your traffic takes is a business decision expressed in a protocol. Loop prevention is separate and neat: each network prepends its own identifier to the path, and a route already containing your identifier is discarded.
Rule: forwarding is mechanical and knowable, routing is political — and because each direction is chosen independently, never assume the return path mirrors the forward one.
Nothing propagates; caches expire
~2 min
The name system is a hierarchy of delegations plus aggressive caching. There is no push and no way to hurry a change: your new record waits out the lifetime of the one it replaced, and "does not exist" is cached too.
Resolving a name means following delegations from the root down until something authoritative answers, then caching that result for the lifetime the zone attached to it. The most useful correction here is that nothing propagates. A change is distributed to nobody; caches keep serving the old answer until it expires. Two queries for the same name four seconds apart, made while writing this, returned lifetimes of 36 then 32 seconds — one cached copy counting down. The delay after a change is governed by the lifetime published before the change, which is why lowering it afterwards accomplishes nothing.
Absence is cached as well. A query for a name that does not exist returns a denial plus the zone's authority record, and that record sets how long the denial may be reused — so looking a name up before you create it can leave it broken for whoever tried early, with nothing wrong on your side.
Aliases carry one strict rule: a name holding an alias may hold nothing else, so the two can never disagree. That is why the bare apex of a zone cannot be one — it already carries its own delegation and authority records. Asking for the address of an alias returns both hops, as seen live: the alias record, then the address it pointed at.
Queries go over datagrams first, capped by the original specification at 512 bytes, with a truncation flag telling the client to retry over a connection. A firewall permitting the datagram port but blocking the connection-oriented one therefore gives a name service that works until an answer grows.
Rule: lower a record's lifetime, wait out the old one, then change it.
Semantics, retries, and what a cache may do
~2 min
The method is a contract about repetition, which is what makes an automatic retry safe or dangerous. Caching turns on two questions — who may store this, and how will they learn it changed.
Every request stands alone, so continuity is whatever the client resends. On that base, the method is a promise about repeating. Retrieval is safe, and replacing or deleting a resource is idempotent — twice leaves the same result as once — which is exactly what lets a client repeat a request automatically when a connection died before the response arrived. Submission is neither, and that is why an interrupted payment is genuinely hard rather than a bug in somebody's client.
The same subtlety shows up in redirects. 307 exists specifically because 302 carries a documented history of user agents turning a submission into a retrieval, and 307 forbids that change outright; choose 302 for a form post and the body silently disappears.
Caching starts with a distinction people get backwards. Telling a cache not to reuse a stored response without checking first is not the same as telling it never to write it down, and reaching for the first when you meant the second leaves private data stored. Revalidation is the payoff: the same address returned 5,896 bytes to a plain request and 0 bytes when the request carried a validator, because nothing had changed. That validator was a weak one, claiming equivalence rather than byte-identity — which matters as soon as you serve ranges of a file.
Finally, if a response was selected using request headers, an encoding or a language, the response must say so, or a shared cache hands the next client somebody else's variant.
Rule: for every response answer both cache questions — who may store it, and how will they find out it changed.
What the handshake proves, and what it does not
~2 min
Encryption in transit buys confidentiality, integrity, and proof that you reached the name you asked for — nothing about who owns that name or whether they are honest. Most real failures are chain and name-matching problems, not cryptography.
The everyday misconception is that a padlock says something about the site's honesty. It certifies the name you asked for, not the operator behind it, and anyone can get a certificate for a name they control.
Transport Layer Security (TLS) version 1.3 completes in one round trip because the client guesses a key share in its opening message, and everything after the server's reply is encrypted. The obsolete key exchanges are gone, so every connection is forward-secret: stealing the server's key later decrypts nothing captured earlier. A real handshake shows the shape: one message from the client; hello, extensions, certificate, signature and finish from the server in a single flight; then the client's finish.
Two properties cause most operational pain. First, names match against the certificate's subject alternative name, not its legacy common name. On one real connection, the requested host matched a wildcard entry in that list while the common name was a different, shorter name, so a client checking the old field would have rejected a valid certificate. Second, the server must send the intermediates linking its certificate to a trusted root. A client trusts only roots it already holds, so a root offered by the server is worthless. "Works in my browser, fails everywhere else" is nearly always a missing intermediate one client had cached.
Two more things ride inside the handshake. The application protocol is negotiated at no extra round trip; the client above offered two options and the server chose the newer one. And the requested name travels in the very first message, before any key exists, so the hostname is visible on the path even though the request is not.
Rule: a valid certificate proves you reached the name you typed, not that the operator deserves your trust.
Proving who may send as your domain
~2 min
Mail's own protocol lets any server claim any sender. Three checks published in the name system fill the gap: SPF lists the servers allowed to send, DKIM signs the message, and DMARC ties either result to the From address a reader actually sees.
The mail transfer protocol never verifies a sender, so any server can write any address into any field. Sender authentication is three separate checks a domain owner publishes in the name system, and the usual mistake is assuming each one protects the address on screen.
Sender Policy Framework (SPF) lists the hosts allowed to use a domain in the envelope sender, the bounce address handed over before any message text. It never reads the From line a person sees, so a forger can pass SPF for a domain they own while displaying yours. It also breaks on forwarding, because the forwarder becomes the connecting host and the original domain never listed it. Evaluation allows at most ten terms that cost a lookup, include among them; an eleventh turns the whole record into a permanent error.
DomainKeys Identified Mail (DKIM) signs chosen headers and the body. The signature names a domain and a selector, and the verifier fetches the key from <selector>._domainkey.<domain>, so you can publish a new key under a new selector before retiring the old one. A relay that leaves the signed content alone keeps the signature valid. A mailing list that adds a footer breaks it.
Domain-based Message Authentication, Reporting, and Conformance (DMARC) closes the gap. A message passes when SPF or DKIM passes for a domain aligned with the visible From domain, and one aligned pass is enough. The record at _dmarc.<domain> asks receivers to do nothing, quarantine or reject failures, and rua asks for aggregate reports. One large provider's records, read while writing this, show the split: its SPF ends in a soft ~all while its DMARC record says p=reject. The firm line lives in DMARC.
None of this encrypts mail or judges whether a message is wanted.
Rule: publish all three, start DMARC at p=none with reports on, and tighten only once the reports show every legitimate sender aligned.
Reading a symptom back to its layer
~2 min
Control messages are the network explaining itself, and expiring hop counters are what make tracing a path possible. The diagnosis nobody guesses is packet size: "small works, large hangs" is almost always the path maximum.
The control protocol carries the diagnoses. Its unreachable message separates a network with no route, a host that did not answer, a port with no listener, and a packet too large to forward when the sender forbade splitting it — four faults that look identical from above. A separate message reports an expired hop counter.
Tracing a path is built on that second one: send with a hop limit of one, then two, then three, and each router in turn announces itself by discarding your packet and complaining. Five hops of a real trace here, from the local gateway outward, took under ten milliseconds. The caveat matters more than the technique — each line arrives over that hop's own return path, so an ugly line may be a reverse-direction problem. And a filtered echo is not evidence of an outage; plenty of hosts refuse pings and serve traffic perfectly.
Packet size is the diagnosis that gets missed, because it does not look like a network fault. A sender marks its packets unsplittable and depends on a router returning "too large, and here is what fits". Block those control messages at a firewall and the sender never learns: small packets pass, the connection opens, and the first large response vanishes into silence. It reads as an application bug — a page that loads while uploads hang.
The local half is easy to see. With splitting forbidden, a 1,472-byte payload — exactly 1500 bytes once headers are added — drew a reply in 11 milliseconds, while 1,473 bytes never left the machine: ping: sendto: Message too long. That is a link refusing politely; mid-path, you simply get no message.
Rule: works small, hangs large, means packet size until you have ruled it out.
Written by Keentune. We are not affiliated with or endorsed by the organizations whose documentation informs this guide, and any linked sources belong to their respective owners.
All exam, test, and product names and trademarks are the property of their respective owners and are used here for identification and reference only. Keentune is independent study practice — not affiliated with, authorized, or endorsed by any of these organizations.