LearnThe Network StackConnections

TCP vs UDP

ss -tun - a hands-on Linux lab on a real virtual machine.

TCP is a conversation. UDP is a shout. ss shows you both, live, right now.

Two doors are open on that server right now

Inside this VM there is a small network, and the machine on the far end of it is holding two doors open. One of them expects a conversation: a greeting, an agreement, an acknowledgment for every message. The other expects nothing at all. Whatever arrives is read, and nothing is ever sent back.

Both doors sit on the same cable, in the same kernel, one command apart. The same tool lists them. The same tool watches them. Yet fetching one small web page through the first door costs ten packets, and a six byte message through the second door costs exactly one.

Those two doors are TCP and UDP. Every network service you have ever used picked one of them. By the end of this lesson you will have listed both, watched both on the wire, and be able to say which one a service should use without looking it up.

The black boxes below are a practice terminal: a safe sandbox that checks the one command each step teaches. The real VM comes at the end, under Ready to practice. Your progress in the real lab is tracked automatically, so type commands naturally and watch the checkmarks appear.

What a transport protocol is

In earlier lessons, you saw how Ethernet frames carry IP packets across a network. But IP only gets data from one machine to another. It does not know which program on the machine should receive the data.

That is the job of the transport layer. It sits on top of IP and adds two things: port numbers (to identify the program) and a delivery strategy (reliable or fast). The two transport protocols are TCP and UDP.

The delivery strategy is not a detail. It decides what happens when a packet goes missing, and that single decision changes the entire shape of the traffic you are about to capture.

TCP: the conversation

TCP stands for Transmission Control Protocol. Think of it as a phone call. Before any data flows, the two sides set up a connection. During the conversation, every piece of data is tracked. If something gets lost, TCP retransmits it. When the conversation is done, both sides hang up.

Key properties of TCP:

TCP is used for HTTP (web), SSH (remote access), SMTP (email), and anything where every byte matters.

UDP: the shout

UDP stands for User Datagram Protocol. Think of it as shouting across a room. You send your message and hope the other side hears it. There is no connection setup. There is no acknowledgment. There is no retransmission.

Key properties of UDP:

UDP is used for DNS (name lookups), NTP (time sync), video streaming, and online gaming, anywhere speed matters more than perfect delivery.

What each one costs at the front of the packet

The difference shows up immediately in the headers, the block of fields at the front of every packet that says where it is going and what to do with it. A TCP header is 20 bytes minimum and contains sequence numbers, acknowledgment numbers, flags (SYN, ACK, FIN), window size, and more. A UDP header is only 8 bytes and contains just four fields:

TCP Header (20+ bytes):
  Source Port | Dest Port | Sequence Number | Ack Number |
  Data Offset | Flags | Window Size | Checksum | Urgent Ptr |

UDP Header (8 bytes):
  Source Port | Dest Port | Length | Checksum |

TCP needs all that extra data to track the conversation. UDP keeps it minimal because it does not track anything.

Why there are two of them at all

There was only one at first. When Vint Cerf and Bob Kahn published the original design in the 1970s, addressing and reliability lived together in a single protocol. Then people began asking for things that reliability made worse. Packet voice was the famous case: if a fragment of speech goes missing, resending it a second later is useless, because the conversation has already moved on. Waiting for it is worse than losing it.

So the design was split. Addressing became IP, and the reliable part became TCP riding on top of it. That left room for a second, much smaller passenger. UDP was published as RFC 768 in 1980, and the specification is about three pages long. The TCP specification that followed a year later runs to roughly eighty five pages.

That page count is the whole lesson in one number. Nearly everything in those extra pages exists to answer a single question: what should happen when a packet does not arrive? UDP never answers it. That is why UDP is three pages, and why the answering falls to whoever writes the application, or to nobody at all when the answer is that it does not matter.

The little network in this VM

This lab has a two-machine network with a server that runs both protocols:

  [client]  10.0.1.10  <-------->  10.0.1.20  [server]
  MAC: 02:00:0a:00:01:0a           MAC: 02:00:0a:00:01:14

  Server services:
    TCP port 80   - HTTP (web server)
    UDP port 5000 - UDP listener (accepts datagrams, sends nothing back)

The client and the server are network namespaces. A network namespace is a separate, isolated network stack inside one Linux machine: its own interfaces, its own routing table, its own list of sockets. Two namespaces on one VM behave like two machines on a cable.

That matters for every command in this lesson. ss only reports sockets that belong to the namespace it runs in. Type ss at your normal prompt and you see the sockets of the VM itself, not the sockets of the lab network. To look inside a namespace, run the command through ip netns exec:

sudo ip netns exec server ss -tn

Read that as: "in the namespace called server, run ss -tn." Every ss command below uses that form.

Four helper commands are on the VM:

Tab completion works on command names, not just on paths. Type send- and press Tab twice: the shell lists all three traffic generators. Type hold- and press Tab once and it finishes the word for you. Build the habit now. A command that completes is a command that exists, spelled correctly, which is half of the errors in this lesson gone before you press Enter.

Commit: what does ss show before anything connects?

ss stands for "socket statistics". It replaces the older netstat command and shows every socket the kernel is tracking in the current namespace.

The -t flag means "TCP only". The -n flag means "numbers instead of names", so you see 80 instead of http.

The web server inside the server namespace is running right now, on TCP port 80. Nobody has connected to it. Take a position before you type anything.

Give it something to look at

To see a TCP socket you need a connection that stays open long enough to look at. hold-open does exactly that: it opens a TCP connection from the client to port 80 on the server, holds it for 60 seconds, and prints one line to confirm.

hold-open

Now ask the server side what it is holding. The output has five columns:

State    Recv-Q  Send-Q  Local Address:Port  Peer Address:Port

Before you press Enter, decide which address lands in which column. You are asking the server namespace, so which of the two machines counts as local?

sudo ip netns exec server ss -tn

prompt: student@transport-lab:~$ answer: sudo ip netns exec server ss -tn ||| sudo ip netns exec server ss -nt ||| sudo ip netns exec server ss -t -n output: State Recv-Q Send-Q Local Address:Port Peer Address:Port ESTAB 0 0 10.0.1.20:80 10.0.1.10:<client port> hint: Run it inside the namespace, then ask for TCP only and numeric ports: sudo ip netns exec server ss -tn

One row, and its State is ESTAB, short for established: the connection is open and data can flow. That is the point of TCP in one line. The socket has a state, and the kernel is tracking both ends of a conversation.

You asked the server, so 10.0.1.20:80 is the local end and 10.0.1.10 is the peer. The peer port is printed above as a label rather than a number for a reason. The kernel picks a fresh high-numbered port for every outgoing connection, so on your screen it is a real number in the tens of thousands, and it is different every time you run hold-open. Both queue columns read 0, because hold-open opens the connection and then sends nothing down it. Nothing is waiting to be read, nothing is waiting to be acknowledged.

This is the first of the five things your lab grades: TCP sockets asked for on their own, with no UDP flag in the same command.

hold-open keeps the connection alive for 60 seconds and then lets it go. If ss prints only the column headers, the connection has already ended. Run hold-open again and look straight away.

Commit: what state does a UDP socket sit in?

Now the other door. The server also has a UDP listener bound to port 5000. It has never spoken to anyone, and it never will speak first.

ss prints a State column for every socket it knows about, whatever the protocol. So it has to print something for this one.

The other door

The -u flag means "UDP only". Add -l to list the sockets that are bound and waiting rather than connected, which for UDP is the only kind there is.

sudo ip netns exec server ss -uln

prompt: student@transport-lab:~$ answer: sudo ip netns exec server ss -uln ||| sudo ip netns exec server ss -lun ||| sudo ip netns exec server ss -u -l -n output: State Recv-Q Send-Q Local Address:Port Peer Address:Port UNCONN 0 0 0.0.0.0:5000 0.0.0.0:* hint: Same namespace form, then the UDP flag on its own plus listening and numeric: sudo ip netns exec server ss -uln

One row, for the listener on port 5000, and every column is telling you something different from the TCP row you just read.

0.0.0.0 in the local column means "any address on this machine". 0.0.0.0:* in the peer column means there is no peer at all.

Notice the difference from TCP. A TCP socket carries a state like ESTAB or LISTEN. This UDP socket says UNCONN, short for unconnected, because UDP never connects to anything. There is no handshake, no tracking, no state machine. The socket exists, and datagrams can arrive at any time.

That is the second of the five things your lab grades: UDP asked for on its own, with no TCP flag in the same command.

Both protocols on one screen

You have asked two separate questions and got two different-looking answers. Now put them side by side in a single command, because the flags combine:

sudo ip netns exec server ss -tuln

When you ask for more than one protocol at once, ss adds a Netid column at the front so you can tell the rows apart. Watch for it, and watch the two State values sitting one above the other.

prompt: student@transport-lab:~$ answer: sudo ip netns exec server ss -tuln ||| sudo ip netns exec server ss -tlun ||| sudo ip netns exec server ss -t -u -l -n output: Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port tcp LISTEN 0 5 0.0.0.0:80 0.0.0.0:* udp UNCONN 0 0 0.0.0.0:5000 0.0.0.0:* hint: Both protocol flags in one command, plus listening and numeric: sudo ip netns exec server ss -tuln

Two rows, one screen, and the delta between them is the whole lesson.

The web server on TCP port 80 is LISTEN: it is waiting for connections, and the Send-Q column on a LISTEN row is not a byte count, it is the accept backlog (how many completed connections the kernel will hold for the program). The UDP listener on port 5000 is UNCONN, because there is nothing to listen for. Datagrams simply arrive.

Same tool, same output format, two completely different protocols. That is the third of the five things your lab grades.

Milestone: you can read both doors now

Count what you can do that you could not do fifteen minutes ago. You can walk into an isolated network stack and ask it what it is holding. You can ask for one protocol at a time or both at once. And one word in the State column now tells you which kind of socket you are looking at: in a conversation, waiting to start one, or not the sort of socket that has conversations at all.

That is the view from the kernel: sockets, sitting still. The rest of this lesson is the view from the wire, where the two protocols stop looking merely different and start looking like opposites.

Watching them on the wire

cap is a wrapper that runs tshark on the client side of the link. You met it in the Watching Traffic lesson, along with -c, which stops the capture after a count of packets.

One new flag here. -Y is a display filter: tshark captures everything the interface sees, then prints only the packets that match. That is the other kind of filter from the one Watching Traffic used. A trailing capture filter drops packets inside the kernel before tshark ever receives them, while a display filter keeps everything and narrows what reaches your screen. For picking one protocol out of a quiet lab wire, either works, and -Y is the one that takes a protocol name on its own.

Order matters when you run a capture. tshark needs a second or two to load its dissectors and start listening, so schedule the traffic to fire slightly later and start the capture immediately:

(sleep 5; send-curl) &

Read that as "wait 5 seconds, then run send-curl, and do it in the background". The & hands your prompt straight back so you can start capturing while the timer runs down.

Commit: what does one web page cost in packets?

send-curl fetches one small page from the web server. send-udp sends one six byte message to the UDP listener. Both cross the same cable, and you are about to watch each one.

Challenge: catch the whole TCP conversation

Scaffolding off. No command is printed from here on.

You want to watch one web request live, from the first packet to the last, with nothing else on your screen. Start the delayed traffic first with (sleep 5; send-curl) &, then capture ten packets and show only the TCP ones. You know the capture shortcut and the count flag from Watching Traffic, and the display filter flag from two steps ago. The filter itself is just the name of the protocol, in lower case.

You will see the full TCP lifecycle. Timestamps, the client port and the exact frame sizes will differ on your VM, and each real line is longer than what fits here (every TCP line also carries Seq, Win and Len values), but the shape is this:

prompt: student@transport-lab:~$ answer: cap -c 10 -Y tcp ||| cap -Y tcp -c 10 ||| sudo ip netns exec client tshark -i veth-c -c 10 -Y tcp output: 1 0.000000 10.0.1.10 -> 10.0.1.20 TCP 74 43218 > 80 [SYN] 2 0.000045 10.0.1.20 -> 10.0.1.10 TCP 74 80 > 43218 [SYN, ACK] 3 0.000067 10.0.1.10 -> 10.0.1.20 TCP 66 43218 > 80 [ACK] 4 0.000123 10.0.1.10 -> 10.0.1.20 HTTP 143 GET / HTTP/1.1 5 0.000456 10.0.1.20 -> 10.0.1.10 TCP 66 80 > 43218 [ACK] 6 0.000789 10.0.1.20 -> 10.0.1.10 HTTP 245 HTTP/1.0 200 OK 7 0.000812 10.0.1.10 -> 10.0.1.20 TCP 66 43218 > 80 [ACK] 8 0.001023 10.0.1.20 -> 10.0.1.10 TCP 66 80 > 43218 [FIN, ACK] 9 0.001045 10.0.1.10 -> 10.0.1.20 TCP 66 43218 > 80 [FIN, ACK] 10 0.001067 10.0.1.20 -> 10.0.1.10 TCP 66 80 > 43218 [ACK] hint: The capture shortcut, a count of ten, and the display filter flag with the protocol name after it: cap -c 10 -Y tcp

Look at the flags in brackets. Packets 1-3 are the three-way handshake: SYN, SYN-ACK, ACK. Packets 4-7 are the data exchange: the HTTP request and response with acknowledgments. Packets 8-10 are the teardown: FIN, FIN-ACK, ACK.

TCP is chatty. One simple web request produced ten packets. Most of them are just bookkeeping.

Only two lines out of the ten carry anything a person asked for: packet 4, the r

Practice TCP vs UDP in a real Linux terminal at The Linux Camp. Progress is verified automatically as you type commands on the machine.

More lessons in Connections