TT Lab
Get started
Learn Learning paths Courses

Network Fundamentals — Hands-on in a Linux VM

Catch the TCP Handshake in Three Lines

Continue in TT Lab

This lab runs on a VM

You build a server and a client with nc on loopback (lo) and capture the traffic between them with tcpdump. No outside network is needed, so every packet you capture is your own.

Goal

Read how a connection opens and closes through the SYN, SYN-ACK, ACK, and FIN flags, and learn the difference between a closed port and an open port, TIME-WAIT, ephemeral ports, and how to find the process holding a port.

Why it matters

The single sentence "the connection doesn't work" mixes three different things. If you send a SYN and an RST comes back, the peer is alive and only the port is closed; if nothing comes back, a firewall dropped it or there is no route; and if it got as far as SYN-ACK but no data arrives, it is an application problem. To the user these three look the same, "it doesn't work", but the people who fix them are completely different. If you can read the flags, the three are separated in the first 30 seconds.

Steps

  1. Start an nc -lk 127.0.0.1 9000 server in the background (send the received data to /root/tcp/recv.txt) and save the output of ss -ltn to /root/tcp/listen.txt.
  2. Send one line, hello, while capturing port 9000 on lo with tcpdump, and save the capture containing the handshake and the close to /root/tcp/handshake.txt.
  3. Try to connect to port 9001, which nobody is listening on, and save the error message together with the capture from that moment (showing the RST) to /root/tcp/refused.txt.
  4. Connect to 9000 once more, and right after closing, save the TIME-WAIT line from ss -tan to /root/tcp/timewait.txt.
  5. Save the ephemeral port range and the port the client used in the step 2 capture to /root/tcp/ports.txt as two lines, range=·client_port=.
  6. Use Python to try one more bind to 127.0.0.1:9000 and save the error to /root/tcp/inuse.txt.
  7. Use ss -ltnp to find the process holding 9000 and save that line to /root/tcp/owner.txt.
  8. In /root/tcp/report.md, write two lines, client_port=·server_port=, together with an explanation of why the three-step handshake and TIME-WAIT exist.

Notes

A listening socket

Start an nc -lk 127.0.0.1 9000 server in the background (send the received data to /root/tcp/recv.txt) and save the output of ss -ltn to /root/tcp/listen.txt.

setsid nc -lk 127.0.0.1 9000 > /root/tcp/recv.txt 2>&1 < /dev/null &. ss -ltn shows TCP (-t) listening sockets (-l) in numeric form (-n). 127.0.0.1:9000 must show as LISTEN.

The three-line handshake

Send one line, hello, while capturing port 9000 on lo with tcpdump, and save the capture containing the handshake and the close to /root/tcp/handshake.txt.

Capture first: timeout 6 tcpdump -i lo -nn -l 'tcp port 9000' > /root/tcp/handshake.txt 2>&1 &. After 1 second, run echo hello | nc -q 1 127.0.0.1 9000. The file must have lines that start in the order [S], [S.], [.] and end with [F.], and hello arrives in recv.txt.

A closed port answers with RST

Try to connect to port 9001, which nobody is listening on, and save the error message together with the capture from that moment (showing the RST) to /root/tcp/refused.txt.

Start the capture timeout 5 tcpdump -i lo -nn -l 'tcp port 9001' >> /root/tcp/refused.txt 2>&1 & and run nc -zv -w 1 127.0.0.1 9001 >> /root/tcp/refused.txt 2>&1. The file must have both Connection refused and Flags [R.].

TIME-WAIT

Connect to 9000 once more, and right after closing, save the TIME-WAIT line from ss -tan to /root/tcp/timewait.txt.

Right after echo again | nc -q 1 127.0.0.1 9000, run ss -tan | grep TIME-WAIT > /root/tcp/timewait.txt. It remains on the side that closed first (the client). It disappears after 60 seconds, so look right away.

Ephemeral port

Save the ephemeral port range and the port the client used in the step 2 capture to /root/tcp/ports.txt as two lines, range=·client_port=.

The range is sysctl -n net.ipv4.ip_local_port_range (two numbers). The client port is the port in 127.0.0.1.<포트> > (the port placeholder) on the [S] line of handshake.txt. That value must be inside the range.

One process holds a port

Use Python to try one more bind to 127.0.0.1:9000, and save one line saying where you bound together with the error to /root/tcp/inuse.txt.

{ echo 'bind 127.0.0.1:9000'; python3 -c 'import socket; s=socket.socket(); s.bind(("127.0.0.1", 9000))' 2>&1; } > /root/tcp/inuse.txt. Address already in use must appear. Only one socket can have the same address and port. Which port the conflict happened on must remain in the file — the conflict message for a different port is not the answer to this step.

Who holds it

Use ss -ltnp to find the process holding 9000 and save that line to /root/tcp/owner.txt.

ss -ltnp | grep ':9000 '. -p adds users:(("nc",pid=…)). If you are not root, you cannot see other users' processes.

What you learned

In /root/tcp/report.md, write two lines, client_port=·server_port=, together with an explanation of why the three-step handshake and TIME-WAIT exist.

client_port is the value from step 5, and server_port is 9000. The body must include the words SYN and TIME-WAIT.