Network Fundamentals — Hands-on in a Linux VM
Catch the TCP Handshake in Three Lines
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
- Start an
nc -lk 127.0.0.1 9000server in the background (send the received data to/root/tcp/recv.txt) and save the output ofss -ltnto/root/tcp/listen.txt. - Send one line,
hello, while capturing port 9000 onlowithtcpdump, and save the capture containing the handshake and the close to/root/tcp/handshake.txt. - 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. - Connect to 9000 once more, and right after closing, save the TIME-WAIT line from
ss -tanto/root/tcp/timewait.txt. - Save the ephemeral port range and the port the client used in the step 2 capture to
/root/tcp/ports.txtas two lines,range=·client_port=. - Use Python to try one more bind to
127.0.0.1:9000and save the error to/root/tcp/inuse.txt. - Use
ss -ltnpto find the process holding 9000 and save that line to/root/tcp/owner.txt. - 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
- Start the server like
setsid nc -lk 127.0.0.1 9000 > /root/tcp/recv.txt 2>&1 < /dev/null &.-kis the option that keeps listening after a connection ends. - Capture: first start
timeout 6 tcpdump -i lo -nn -l 'tcp port 9000' > 파일 2>&1 &(replace the file placeholder with the output file name), then runecho hello | nc -q 1 127.0.0.1 9000. - Flags:
[S]SYN,[S.]SYN+ACK,[.]ACK,[P.]data,[F.]FIN,[R.]RST. - Ephemeral port range:
sysctl net.ipv4.ip_local_port_range. - A common mistake: starting the server and leaving its standard output attached to the terminal. Other commands get mixed in and the file gets tangled — always send it to a file.
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.