Learn › The Network Stack › Connections
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.
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.
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 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 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.
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.
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.
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:
send-curl - sends an HTTP request over TCPsend-udp - sends a UDP message to port 5000send-ping - sends ICMP pings (neither TCP nor UDP)hold-open - opens a TCP connection to port 80 and keeps it open for 60 secondsTab 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.
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.
>>> The column headers and nothing under them. -t on its own lists sockets that are connected to something, and nothing is. A socket that is merely waiting for callers is a listening socket, and listing those takes the -l flag, which you will use on the UDP side in a moment. If you picked the port 80 socket, that is the most useful wrong answer here: the service is running, the door is open, and this command still shows you nothing, because you asked it a different question. If you picked the error, notice that an empty result is not an error anywhere in Linux. A tool that finds nothing prints nothing and exits happily. Reading that silence correctly is half of troubleshooting.
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.
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.
>>> UNCONN, short for unconnected. UDP never connects to anything, so there is no handshake to be halfway through and no conversation to be inside: the socket is simply bound to a port, and datagrams may arrive at any moment. If you picked LISTEN, that is the right instinct pointed at the wrong protocol. LISTEN is a TCP state, and it means the socket is prepared to complete a handshake. Nothing on the UDP side ever completes a handshake. If you picked ESTAB, watch for this one in the lab: a datagram arriving changes nothing about the socket. It is read and it is gone, and the State column reads the same before and after.
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.
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.
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.
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.
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.
>>> About ten for the web request, and one for the UDP message. The request and the response are only two of those ten packets. The rest are TCP doing its bookkeeping: opening the connection, acknowledging what arrived, and closing the connection down again. The UDP message needs none of that, so it is one packet and it is over. If you picked two each, you counted the conversation and not the protocol around it, which is exactly what the next screen fixes. If you picked ten each, hold on to the instinct that acknowledgment is expensive. It is. UDP simply never pays for it.
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.