TCP ハンドシェイクを3行で捕まえる
한국어 원문으로 표시합니다.
이 실습은 VM 에서 돕니다
루프백(lo) 위에서 nc 로 서버와 클라이언트를 만들고 tcpdump 로 그 사이를
잡습니다. 바깥 네트워크가 필요 없어서 잡히는 패킷이 전부 내 것입니다.
목표
연결이 열리고 닫히는 과정을 SYN·SYN-ACK·ACK·FIN 플래그로 읽고, 닫힌 포트와 열린 포트의 차이, TIME-WAIT, 임시 포트, 그리고 포트를 잡은 프로세스를 찾는 법을 익힙니다.
왜 중요한가
"연결이 안 된다" 는 한 문장에 서로 다른 세 가지가 섞여 있습니다. SYN 을 보냈는데 RST 가 오면 상대는 살아 있고 포트만 닫힌 것이고, 아무것도 안 오면 방화벽이 버리거나 경로가 없는 것이고, SYN-ACK 까지 왔는데 데이터가 안 오면 애플리케이션 문제입니다. 이 셋은 사용자에게 똑같이 "안 됨" 으로 보이지만 고치는 사람은 완전히 다릅니다. 플래그를 읽을 줄 알면 첫 30초에 그 셋이 갈립니다.
단계
nc -lk 127.0.0.1 9000서버를 백그라운드로 띄우고(받은 데이터는/root/tcp/recv.txt로)ss -ltn출력을/root/tcp/listen.txt에 저장하세요.lo의 9000 포트를tcpdump로 잡으면서hello한 줄을 보내, 핸드셰이크와 종료가 담긴 캡처를/root/tcp/handshake.txt에 저장하세요.- 아무도 듣지 않는 9001 포트로 접속을 시도해 오류 메시지와 그때의 캡처(RST 가 보이는)를
/root/tcp/refused.txt에 함께 저장하세요. - 9000 으로 한 번 더 접속하고 닫은 직후
ss -tan에서 TIME-WAIT 줄을/root/tcp/timewait.txt에 저장하세요. - 임시 포트 범위와 2단계 캡처에서 클라이언트가 쓴 포트를
/root/tcp/ports.txt에range=·client_port=두 줄로 저장하세요. - 파이썬으로
127.0.0.1:9000에 한 번 더 bind 를 시도해 그 오류를/root/tcp/inuse.txt에 저장하세요. ss -ltnp로 9000 을 잡은 프로세스를 찾아 그 줄을/root/tcp/owner.txt에 저장하세요./root/tcp/report.md에client_port=·server_port=두 줄과 함께 세 단계 핸드셰이크와 TIME-WAIT 가 왜 있는지를 적으세요.
참고
- 서버는
setsid nc -lk 127.0.0.1 9000 > /root/tcp/recv.txt 2>&1 < /dev/null &처럼 띄우세요.-k는 연결이 끝나도 계속 듣는 옵션입니다. - 캡처:
timeout 6 tcpdump -i lo -nn -l 'tcp port 9000' > 파일 2>&1 &를 먼저 걸고,echo hello | nc -q 1 127.0.0.1 9000. - 플래그:
[S]SYN,[S.]SYN+ACK,[.]ACK,[P.]데이터,[F.]FIN,[R.]RST. - 임시 포트 범위:
sysctl net.ipv4.ip_local_port_range. - 흔한 실수: 서버를 띄우고 표준 출력을 터미널에 물려 두는 것. 다른 명령이 섞여 파일이 엉킵니다 — 반드시 파일로 보내세요.
듣는 소켓
nc -lk 127.0.0.1 9000 서버를 백그라운드로 띄우고(받은 데이터는 /root/tcp/recv.txt 로) ss -ltn 출력을 /root/tcp/listen.txt 에 저장하세요.
setsid nc -lk 127.0.0.1 9000 > /root/tcp/recv.txt 2>&1 < /dev/null &. ss -ltn 은 TCP(-t) 듣는 소켓(-l)을 숫자로(-n) 보여 줍니다. 127.0.0.1:9000 이 LISTEN 으로 보여야 합니다.
세 줄의 핸드셰이크
lo 의 9000 포트를 tcpdump 로 잡으면서 hello 한 줄을 보내, 핸드셰이크와 종료가 담긴 캡처를 /root/tcp/handshake.txt 에 저장하세요.
캡처를 먼저: timeout 6 tcpdump -i lo -nn -l 'tcp port 9000' > /root/tcp/handshake.txt 2>&1 &. 1초 뒤 echo hello | nc -q 1 127.0.0.1 9000. 파일에 [S], [S.], [.] 순서로 시작해 [F.] 로 끝나는 줄들이 있어야 하고, recv.txt 에 hello 가 들어옵니다.
닫힌 포트는 RST 로 답한다
아무도 듣지 않는 9001 포트로 접속을 시도해 오류 메시지와 그때의 캡처(RST 가 보이는)를 /root/tcp/refused.txt 에 함께 저장하세요.
캡처 timeout 5 tcpdump -i lo -nn -l 'tcp port 9001' >> /root/tcp/refused.txt 2>&1 & 를 걸고 nc -zv -w 1 127.0.0.1 9001 >> /root/tcp/refused.txt 2>&1. 파일에 Connection refused 와 Flags [R.] 둘 다 있어야 합니다.
TIME-WAIT
9000 으로 한 번 더 접속하고 닫은 직후 ss -tan 에서 TIME-WAIT 줄을 /root/tcp/timewait.txt 에 저장하세요.
echo again | nc -q 1 127.0.0.1 9000 직후 ss -tan | grep TIME-WAIT > /root/tcp/timewait.txt. 먼저 닫은 쪽(클라이언트)에 남습니다. 60초 뒤 사라지니 바로 보세요.
임시 포트
임시 포트 범위와 2단계 캡처에서 클라이언트가 쓴 포트를 /root/tcp/ports.txt 에 range=·client_port= 두 줄로 저장하세요.
범위는 sysctl -n net.ipv4.ip_local_port_range (두 숫자). 클라이언트 포트는 handshake.txt 의 [S] 줄에서 127.0.0.1.<포트> > 의 포트입니다. 그 값이 범위 안에 있어야 합니다.
포트는 하나가 잡는다
파이썬으로 127.0.0.1:9000 에 한 번 더 bind 를 시도해, 어디에 bind 했는지 한 줄과 그 오류를 /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 가 나와야 합니다. 같은 주소·포트는 한 소켓만 가질 수 있습니다. 어느 포트에서 난 충돌인지가 파일에 남아 있어야 합니다 — 다른 포트의 충돌 메시지는 이 단계의 답이 아닙니다.
누가 잡았나
ss -ltnp 로 9000 을 잡은 프로세스를 찾아 그 줄을 /root/tcp/owner.txt 에 저장하세요.
ss -ltnp | grep ':9000 '. -p 가 users:(("nc",pid=…)) 를 붙여 줍니다. root 가 아니면 남의 프로세스는 안 보입니다.
무엇을 배웠나
/root/tcp/report.md 에 client_port=·server_port= 두 줄과 함께 세 단계 핸드셰이크와 TIME-WAIT 가 왜 있는지를 적으세요.
client_port 는 5단계의 값, server_port 는 9000. 본문에 SYN 과 TIME-WAIT 라는 낱말이 들어가야 합니다.