Skip to content
0xrcosRyan Camargo — home
All posts
networking~7min readRyan Camargo

TCP/IP Notes: The Four Layers, the Handshake, and Reading a Packet

Study notes on the TCP/IP model — the four layers, the three-way handshake, TCP states, and a practical look at reading a packet capture with tcpdump and tshark.

Note: This is sample placeholder content created to demonstrate the blog. Replace it with your own writing.

These are working notes I keep coming back to whenever I need to explain TCP/IP to someone — including myself. They're not exhaustive; they're the parts that, in my experience, actually explain 90% of the behavior you see in practice.

The Four Layers

The TCP/IP model collapses the seven-layer OSI model into four. In practice, this is the model the internet actually speaks.

Layer Examples What it answers
Application HTTP, SSH, DNS, SMTP, TLS "What are we saying to each other?"
Transport TCP, UDP, QUIC "How do we make this reliable/fast?"
Internet IP (v4/v6), ICMP "How do we get from A to B?"
Network access Ethernet, Wi-Fi, ARP "How do we get to the next hop?"

A useful mental move: each layer wraps the one above it. An HTTP request becomes a TCP segment, which becomes an IP packet, which becomes an Ethernet frame. On the receiving end, each layer unwraps and hands the payload up.

The Three-Way Handshake

TCP is connection-oriented. Before any application data flows, the two endpoints have to agree on a sequence of numbers and a set of capabilities. This is the three-way handshake:

        Client                                Server
          |                                     |
          |  ----- SYN, seq=x ---------------> |
          |                                     |
          |  <---- SYN+ACK, seq=y, ack=x+1 ---- |
          |                                     |
          |  ----- ACK, ack=y+1 --------------> |
          |                                     |
          |  ===== application data ==========> |
          |                                     |

In words:

  1. The client sends SYN with a starting sequence number x.
  2. The server replies with SYN+ACK, its own starting sequence y, and an ack of x+1.
  3. The client replies with ACK and ack=y+1. The connection is now ESTABLISHED.

The sequence numbers exist so each side can acknowledge what it has received and detect gaps. They are not "packet numbers" — they are byte counters, and they roll over.

TCP Flags

The TCP header carries a small set of flags. Knowing them by heart makes reading captures much faster:

Flag Meaning Set when…
SYN Synchronize sequence numbers Opening a connection
ACK Acknowledgment field is valid After the first SYN, almost always
FIN Sender is finished sending Graceful close, one direction
RST Reset the connection Abort, refused, or error
PSH Push data up to the application immediately Interactive traffic (often set with ACK)
URG Urgent pointer is valid Rare; out-of-band signal within the stream
CWR Congestion window reduced ECN feedback
ECE ECN-echo Explicit Congestion Notification

SYN, ACK, FIN, and RST are the four you'll see constantly. PSH+ACK is what most interactive data segments look like. A bare RST is usually the most interesting one in a security context — it can mean "port closed," "this connection was rejected," or "something in the middle killed me."

TCP States (the short version)

The full state machine is genuinely useful but more than fits in a blog post. The states I actually think about:

  • LISTEN — server is waiting for SYNs.
  • SYN_SENT — client has sent SYN, waiting for SYN+ACK.
  • SYN_RECEIVED — server got SYN, sent SYN+ACK, waiting for ACK.
  • ESTABLISHED — handshake done, data flows.
  • FIN_WAIT_1, FIN_WAIT_2, CLOSE_WAIT, LAST_ACK, CLOSING, TIME_WAIT — the various phases of tearing down a connection.

TIME_WAIT is the one that bites people. After the side that initiates the close sends its final ACK, it sits in TIME_WAIT for roughly two maximum segment lifetimes (typically 60–120 seconds). This is so that a delayed duplicate of the final ACK doesn't confuse a new connection that reuses the same port pair. On a busy server with short-lived connections, TIME_WAIT sockets pile up; this is normal, not a leak.

You can see the current state of every connection on a Linux box with:

ss -tan | awk 'NR==1 || $1=="ESTAB" || $1=="TIME-WAIT"' | head

The ss command has replaced netstat for most use cases. It's faster and reads from netlink rather than scraping /proc.

A Practical Packet Inspection

The textbook material above only really clicks when you see it in a capture. tcpdump and tshark are the two tools I reach for, in that order. tcpdump is faster and on every Linux box; tshark is what I want when I need to look at decoded fields.

A simple capture of a single SSH connection:

# Capture the first 20 packets of TCP traffic to port 22, no DNS resolution.
sudo tcpdump -n -c 20 'tcp port 22' -i any

A typical tcpdump line for a SYN looks like:

10.0.0.5.54321 > 10.0.0.10.22: Flags [S], seq 1234567890, win 64240, options [mss 1460,sackOK,TS val 123 ecr 0,nop,wscale 7], length 0

Reading it:

  • 10.0.0.5.54321 > 10.0.0.10.22 — source IP.port to destination IP.port.
  • Flags [S] — SYN. [S.] would mean SYN+ACK (the dot is ACK).
  • seq 1234567890 — initial sequence number.
  • win 64240 — receive window the sender is offering.
  • length 0 — no payload, which makes sense for a SYN.

For a deeper look — decoded fields, layered nicely — tshark with JSON output is excellent for scripting and for sharing captures with people who don't want to install Wireshark:

# Decode the same traffic and emit JSON, one object per packet.
sudo tshark -i any -f 'tcp port 22' -c 3 -T json > handshake.json

A trimmed version of what you get back looks like this:

{
  "_index": "packets-2026-07-18",
  "_source": {
    "layers": {
      "ip": {
        "ip.src": "10.0.0.5",
        "ip.dst": "10.0.0.10",
        "ip.proto": "6"
      },
      "tcp": {
        "tcp.srcport": "54321",
        "tcp.dstport": "22",
        "tcp.flags": "0x00000002",
        "tcp.flags.syn": "1",
        "tcp.seq": "1234567890",
        "tcp.window_size": "64240"
      }
    }
  }
}

This is the same information tcpdump showed you, just structured. Useful for diffing captures, for building dashboards, or for piping into jq when you're hunting for a specific pattern.

A Note on MTU and Fragmentation

The Internet layer (IP) is responsible for getting a packet across multiple hops. Each hop has a maximum transmission unit — the largest frame it will carry. Ethernet's MTU is typically 1500 bytes. When a packet is bigger than the next hop's MTU, IP either fragments it (IPv4) or drops it and signals the sender (IPv6, with Packet Too Big ICMP).

Two practical consequences:

  1. Path MTU discovery matters. Most modern stacks do it; misconfigured firewalls that block ICMP can silently break it, producing the classic "small packets work, large packets hang" failure mode.
  2. TCP segments should fit. TCP uses MSS (maximum segment size) to negotiate a payload size that fits the path's MTU, typically 1460 bytes over standard Ethernet (1500 − 20 IP − 20 TCP). This is the number you'll see in the SYN's options.

Closing Notes

The TCP/IP model is small enough to fit in a blog post and deep enough to spend a career on. The four layers explain the structure; the handshake explains how connections begin; the flags and state machine explain how they behave (and misbehave); and tcpdump / tshark are how you actually see any of it. Once you can read a single tcpdump line confidently, you've got most of what you need to debug the network problems that don't have a nice error message.

For the canonical references, RFC 793 defines TCP (with many updates since), RFC 1122 is the host requirements doc that codifies what stacks actually do, and the tcpdump man page is one of the best-written pieces of documentation in the Unix world.

On this page

Related