서버·인프라

tcpdump 로 TCP 3-way handshake 보기 — 네트워크 처음 배우는 사람을 위한 패킷 읽기

tcpdump 로 TCP 3-way handshake 보기 — 네트워크 처음 배우는 사람을 위한 패킷 읽기

웹 브라우저나 curl 로 페이지를 열면 화면에는 결과만 보인다. 하지만 그 짧은 순간에
두 컴퓨터는 작은 메시지를 열 번쯤 주고받는다. 이 글에서는 그 메시지를 직접
붙잡아서 한 줄씩 읽어 본다.

네트워크를 처음 공부하는 사람을 기준으로 썼다. 용어는 나올 때마다 풀어서 설명한다.
다 따라 하면 이런 것을 눈으로 확인하게 된다.

상황 보게 되는 것
정상적으로 접속할 때 연결을 맺는 인사 세 번(3-way handshake), 요청과 응답, 연결을 끊는 인사
아무도 없는 포트에 접속할 때 서버가 곧바로 "없다" 고 거절한다 — Connection refused
대답이 중간에 사라질 때 같은 인사를 몇 번이고 다시 보낸다 — Connection timed out

글에 나오는 출력은 모두 집에 만든 실습용 가상 머신에서 실제로 잡은 것이다.

참고

집 내부망 주소는 예시 값으로 바꿔 적었다. 패킷을 잡는 실습 컴퓨터는
192.168.0.10, 접속할 웹 서버는 192.168.0.20 이다. 따라 할 때는 자기 주소로
바꿔 넣는다.

먼저 알아 둘 말 다섯 개#

이 다섯 개만 알면 글 전체를 읽을 수 있다.

말 쉽게 말하면
패킷 네트워크로 오가는 메시지 한 통. 큰 데이터도 작은 패킷 여러 개로 나뉘어 간다
IP 주소 컴퓨터의 집 주소. 192.168.0.20 같은 숫자 네 덩어리
포트 그 집 안의 방 번호. 웹 서버는 보통 80번(HTTP), 443번(HTTPS) 방에서 손님을 받는다
TCP 패킷이 빠짐없이, 순서대로 도착하게 챙기는 약속(프로토콜). 웹, SSH 등 대부분이 이 위에서 돈다
캡처 오가는 패킷을 복사해서 기록해 두는 것. 이 글에서는 tcpdump 라는 프로그램이 한다

TCP 는 전화 통화와 비슷하다. 바로 용건부터 말하지 않고, 먼저 "여보세요?"
"네, 들려요" "네, 저도 들려요" 를 주고받아 서로 연결된 것을 확인한다. 이 세 번의
인사가 3-way handshake 다. 다 말한 뒤에는 "끊을게요" "네, 끊어요" 로 마무리한다.
이 글은 그 인사를 실제 패킷으로 보는 것이 목표다.

실습 환경#

컴퓨터 두 대가 같은 공유기에 연결돼 있다.

 [실습 컴퓨터]  ── 패킷 ──▶  [웹 서버]
 192.168.0.10                192.168.0.20 (80번 포트)
 tcpdump 를 여기서 실행
  • 실습 컴퓨터: 리눅스면 무엇이든 된다. 여기서는 Proxmox 위에 만든 Ubuntu 24.04 가상 머신(2코어, 메모리 2 GB)을 썼다.
  • 웹 서버: 같은 네트워크에서 80번 포트로 응답하는 것이면 된다. 집에 블로그나 NAS 가 있으면 그걸 쓴다. 없으면 다른 리눅스 컴퓨터에서 sudo python3 -m http.server 80 한 줄로 임시 웹 서버를 띄울 수 있다.

명령은 모두 실습 컴퓨터의 터미널에 입력한다. 걸리는 시간은 30분 정도다.

참고

명령 앞의 sudo 는 "관리자 권한으로 실행" 이라는 뜻이다. 패킷을 엿보는 일은 아무나
하면 안 되는 일이라 관리자 권한이 필요하다. 처음 쓸 때 내 계정 비밀번호를 묻는다.

1. 도구를 설치한다#

sudo apt update
sudo apt install -y tcpdump tshark nftables curl netcat-openbsd

apt 는 우분투의 프로그램 설치 도구다. 설치하는 다섯 가지는 이렇다.

프로그램 하는 일
tcpdump 패킷을 잡아서 보여 준다. 이 글의 주인공
tshark 잡은 패킷을 더 친절하게 풀이해 준다. 유명한 Wireshark 의 터미널판
nftables 리눅스 방화벽. 6단계에서 패킷을 일부러 버릴 때 쓴다
curl 터미널에서 웹 페이지를 요청한다. 브라우저 대신 쓴다
netcat (nc) 특정 포트가 열려 있는지 두드려 본다

설치 도중 tshark 가 "Should non-superusers be able to capture packets?"(관리자가 아닌
사용자도 패킷을 잡게 할까요?) 라고 물으면 Yes 를 고른다. 그리고 내 계정을 그 권한
그룹에 넣는다.

sudo usermod -aG wireshark $USER

이 설정은 로그아웃했다 다시 로그인해야 적용된다. 설치된 버전은 이랬다.

tcpdump version 4.99.4
TShark (Wireshark) 4.2.2
curl 8.5.0

2. 네트워크 장치 이름을 확인한다#

tcpdump 에게 "어느 랜선을 지켜볼지" 알려 줘야 한다. 장치 이름부터 확인한다.

ip -4 -br addr
lo               UNKNOWN        127.0.0.1/8
eth0             UP             192.168.0.10/24 metric 100
  • lo 는 컴퓨터가 자기 자신과 이야기할 때 쓰는 가상 장치라서 무시한다.
  • eth0 이 실제 네트워크 장치고, 내 주소는 192.168.0.10 이다.

장치 이름은 컴퓨터마다 다르다. ens18, enp0s3 처럼 나오면 아래 명령의 eth0 을
그 이름으로 바꾼다.

3. 정상 접속을 잡는다#

순서는 이렇다. 녹화 시작 → 웹 페이지 요청 → 녹화 종료 → 녹화본 보기.

sudo tcpdump -i eth0 -nn -w http.pcap 'host 192.168.0.20 and tcp port 80' &
curl -s -o /dev/null -w 'http_code=%{http_code}n' http://192.168.0.20/
sudo pkill -INT tcpdump

한 줄씩 보면 이렇다.

첫 줄 — 녹화 시작

부분 뜻
tcpdump 패킷 캡처 프로그램을 실행한다
-i eth0 eth0 장치를 지켜본다
-nn 주소와 포트를 숫자 그대로 보여 준다. 이름으로 바꾸느라 생기는 추가 패킷이 섞이지 않는다
-w http.pcap 화면에 보여 주는 대신 http.pcap 파일에 녹화한다
'host 192.168.0.20 and tcp port 80' 필터. 이 서버의 80번 포트와 오가는 패킷만 녹화한다. 안 걸면 다른 패킷이 잔뜩 섞인다
맨 끝 & 이 명령을 뒤에서 돌게 두고 다음 명령을 바로 입력할 수 있게 한다

둘째 줄 — 웹 페이지 요청. curl 로 서버에 접속하고, 받은 내용은 버리고
(-o /dev/null) 응답 코드만 찍는다. 화면에 이렇게 나온다.

http_code=301

301 은 "다른 주소로 가세요" 라는 응답이다. 이 서버는 HTTP 접속을 HTTPS 로 넘기도록
설정돼 있어서 그렇다. 응답 코드는 중요하지 않다. 연결이 맺어지고 끊어지는 과정을
보는 것이 목적이다.

셋째 줄 — 녹화 종료. 뒤에서 돌던 tcpdump 에게 멈추라고 신호를 보낸다.

이제 녹화본을 읽는다.

tcpdump -nn -r http.pcap

-r 은 "파일에서 읽기" 다. 출력은 패킷 하나가 한 줄이고, 모두 열 줄이 나온다.
한 줄이 길어서 뒷부분의 options [...] 은 … 로 줄였다. 그 부분은 몰라도 된다.

07:51:10.186372 IP 192.168.0.10.37738 > 192.168.0.20.80: Flags [S], seq 3850998404, win 64240, options […], length 0
07:51:10.186619 IP 192.168.0.20.80 > 192.168.0.10.37738: Flags [S.], seq 568641128, ack 3850998405, win 65160, options […], length 0
07:51:10.186659 IP 192.168.0.10.37738 > 192.168.0.20.80: Flags [.], ack 1, win 502, options […], length 0
07:51:10.186722 IP 192.168.0.10.37738 > 192.168.0.20.80: Flags [P.], seq 1:74, ack 1, win 502, options […], length 73: HTTP: GET / HTTP/1.1
07:51:10.186821 IP 192.168.0.20.80 > 192.168.0.10.37738: Flags [.], ack 74, win 509, options […], length 0
07:51:10.186913 IP 192.168.0.20.80 > 192.168.0.10.37738: Flags [P.], seq 1:364, ack 74, win 509, options […], length 363: HTTP: HTTP/1.1 301 Moved Permanently
07:51:10.186924 IP 192.168.0.10.37738 > 192.168.0.20.80: Flags [.], ack 364, win 501, options […], length 0
07:51:10.187062 IP 192.168.0.10.37738 > 192.168.0.20.80: Flags [F.], seq 74, ack 364, win 501, options […], length 0
07:51:10.187185 IP 192.168.0.20.80 > 192.168.0.10.37738: Flags [F.], seq 364, ack 75, win 509, options […], length 0
07:51:10.187197 IP 192.168.0.10.37738 > 192.168.0.20.80: Flags [.], ack 365, win 501, options […], length 0

처음 보면 암호 같지만, 한 줄만 뜯어보면 나머지는 같은 모양이다.

한 줄 뜯어보기#

첫 줄을 조각내 보면 이렇다.

07:51:10.186372 IP 192.168.0.10.37738 > 192.168.0.20.80: Flags [S], seq 3850998404, win 64240, length 0
조각 뜻
07:51:10.186372 패킷을 잡은 시각. 소수점 아래 여섯 자리라 백만분의 1초까지 나온다
IP IP 패킷이라는 표시
192.168.0.10.37738 보낸 쪽. 주소 192.168.0.10 의 37738번 포트. 마지막 숫자 덩어리가 포트다
> 이 방향으로 갔다
192.168.0.20.80 받는 쪽. 웹 서버의 80번 포트
Flags [S] 이 패킷의 종류. 아래 표에 정리했다
seq 3850998404 순서 번호. 아래 "순서 번호" 에서 설명한다
win 64240 "지금 이만큼 더 받을 수 있다" 는 여유 공간
length 0 실어 나르는 데이터의 크기(바이트). 0 이면 내용 없이 인사만 한 것이다

참고

내 쪽 포트 37738 은 운영체제가 아무 빈 번호나 골라 준 것이다. 서버는 80번처럼
정해진 번호에서 기다리고, 접속하는 쪽은 매번 아무 번호나 쓴다. 그래서 따라 하면
이 숫자는 다르게 나온다.

Flags — 패킷의 종류#

표시 이름 통화로 치면
[S] SYN "여보세요?" — 연결하고 싶다
[S.] SYN + ACK "네, 들려요. 그쪽도 제 말 들리세요?"
[.] ACK "네, 잘 들려요" — 받았다는 확인
[P.] PUSH + ACK 진짜 용건(데이터)을 담았다
[F.] FIN + ACK "저는 할 말 다 했어요, 끊을게요"
[R.] RST + ACK "그런 사람 없어요" — 연결 거절. 5단계에서 나온다

점(.)은 ACK, 즉 "받았다" 는 표시다. 맨 처음 SYN 을 빼면 거의 모든 패킷에 붙어 있다.

열 줄을 우리말로 옮기면#

번호 방향 Flags 무슨 일
1 나 → 서버 [S] 여보세요? (연결 요청)
2 서버 → 나 [S.] 네, 들려요. 그쪽도 들리세요?
3 나 → 서버 [.] 네, 들려요. 여기까지가 3-way handshake
4 나 → 서버 [P.] 웹 페이지 주세요 (GET / HTTP/1.1, 73바이트)
5 서버 → 나 [.] 요청 잘 받았어요
6 서버 → 나 [P.] 여기 응답이요 (301 Moved Permanently, 363바이트)
7 나 → 서버 [.] 응답 잘 받았어요
8 나 → 서버 [F.] 저는 끝났어요, 끊을게요
9 서버 → 나 [F.] 네, 저도 끊어요
10 나 → 서버 [.] 알겠어요. 연결 종료

1번부터 10번까지 걸린 시간을 시각으로 계산해 보면 0.001초가 안 된다. 같은 집
안의 컴퓨터끼리라 그렇다.

순서 번호(seq)와 확인 번호(ack)#

TCP 는 보낸 데이터에 번호를 매긴다. 긴 편지에 쪽 번호를 적어 보내는 것과 같다.
받는 쪽은 "몇 번까지 받았으니 그다음 번호를 보내 달라" 고 답한다. 이것이 ack 다.

4~5번 줄에서 확인할 수 있다.

4  나 → 서버  [P.], seq 1:74, length 73   ← 1번부터 73번까지 73바이트를 보낸다
5  서버 → 나  [.],  ack 74                ← 73번까지 다 받았다. 다음은 74번부터

6~7번 줄도 같다. 서버가 363바이트를 보내고(seq 1:364), 내가 ack 364 로 답한다.

참고

1번 줄은 seq 3850998404 처럼 큰 숫자인데 3번 줄부터는 ack 1 처럼 작아진다.
시작 번호는 보안 때문에 아무 큰 수에서 시작하는데, 읽기 힘드니 tcpdump 가 두 번째
패킷부터 "시작 번호에서 몇 번째인지" 로 바꿔 보여 주기 때문이다. 원래 숫자를
보려면 -S 를 붙인다.

bash
tcpdump -nn -S -r http.pcap

07:51:10.186659 IP 192.168.0.10.37738 > 192.168.0.20.80: Flags [.], ack 568641129, …

2번 줄에서 서버가 보낸 시작 번호 568641128 에 1을 더한 값이다. SYN 은 데이터가
없어도 번호를 하나 쓴다. 그래서 "SYN 잘 받았다" 는 답이 +1 이 된다. 8~9번 줄의
FIN 도 마찬가지라, seq 74 로 보낸 FIN 에 서버가 ack 75 로 답한다.

4. tshark 로 다시 읽는다 (선택)#

같은 녹화본을 tshark 로 열면 더 읽기 쉽게 풀이해 준다.

tshark -r http.pcap -T fields -e frame.number -e frame.time_relative -e _ws.col.Info

-e 로 보고 싶은 칸을 고른다. 번호, 첫 패킷부터 지난 시간, 요약 설명 세 칸이다.
요약 칸 뒤쪽은 … 로 줄였다.

1	0.000000000	37738 → 80 [SYN] Seq=0 Win=64240 Len=0 …
2	0.000247000	80 → 37738 [SYN, ACK] Seq=0 Ack=1 Win=65160 Len=0 …
3	0.000287000	37738 → 80 [ACK] Seq=1 Ack=1 Win=64256 Len=0 …
4	0.000350000	GET / HTTP/1.1
5	0.000449000	80 → 37738 [ACK] Seq=1 Ack=74 Win=65152 Len=0 …
6	0.000541000	HTTP/1.1 301 Moved Permanently  (text/html)
7	0.000552000	37738 → 80 [ACK] Seq=74 Ack=364 Win=64128 Len=0 …
8	0.000690000	37738 → 80 [FIN, ACK] Seq=74 Ack=364 Win=64128 Len=0 …
9	0.000813000	80 → 37738 [FIN, ACK] Seq=364 Ack=75 Win=65152 Len=0 …
10	0.000825000	37738 → 80 [ACK] Seq=75 Ack=365 Win=64128 Len=0 …

[S.] 대신 [SYN, ACK] 처럼 이름을 풀어 쓰고, 4번과 6번은 HTTP 내용으로 보여 준다.
순서 번호도 처음부터 0 으로 시작해서 더 보기 편하다.

팁

마우스로 보는 화면이 편하면 http.pcap 파일을 내 PC 로 가져와 Wireshark 로 연다.
파일을 가져오는 방법은 SSH 키로 비밀번호 없이 서버 접속하기에
정리해 두었다. 이 글의 실습은 터미널만으로 충분하다.

5. 아무도 없는 포트를 두드린다 — Connection refused#

이번에는 서버에서 아무 프로그램도 기다리지 않는 81번 포트로 접속해 본다.
아무도 안 사는 방 문을 두드리는 셈이다.

sudo tcpdump -i eth0 -nn -w rst.pcap 'host 192.168.0.20 and tcp port 81' &
curl -sS -m 3 http://192.168.0.20:81/
sudo pkill -INT tcpdump
tcpdump -nn -r rst.pcap

curl 은 바로 실패한다.

curl: (7) Failed to connect to 192.168.0.20 port 81 after 0 ms: Couldn't connect to server

녹화본에는 딱 두 줄이 남는다.

07:51:25.364900 IP 192.168.0.10.57504 > 192.168.0.20.81: Flags [S], seq 1538215172, win 64240, options […], length 0
07:51:25.365196 IP 192.168.0.20.81 > 192.168.0.10.57504: Flags [R.], seq 0, ack 1538215173, win 0, length 0

"여보세요?"([S]) 에 서버가 곧바로 "그런 사람 없어요"([R.]) 라고 답했다.
nc 로 두드려 보면 더 익숙한 문장이 나온다.

nc -vz -w 2 192.168.0.20 81
nc: connect to 192.168.0.20 port 81 (tcp) failed: Connection refused

Connection refused 는 RST 를 받았다는 뜻이다. 서버 컴퓨터까지는 잘 도착했고,
서버가 대답도 했다. 다만 그 포트에서 기다리는 프로그램이 없다. 이런 오류를 보면
네트워크보다 서버 프로그램이 켜져 있는지, 포트 번호가 맞는지를 먼저 본다.

6. 대답을 버려서 재전송을 만든다 — Connection timed out#

연결이 안 되는 경우는 하나 더 있다. 대답이 아예 오지 않는 경우다. 보통 중간의
방화벽이 패킷을 조용히 버릴 때 이렇게 된다.

참고

방화벽은 패킷을 검사해서 통과시킬지 버릴지 정하는 문지기다. 리눅스에는
nftables 라는 방화벽이 들어 있다. 이번 실습에서는 일부러 규칙을 하나 넣어서
이 상황을 만든다.

실습 컴퓨터에 "서버에서 오는 SYN-ACK 는 버려라" 라는 규칙을 넣는다. 서버는
"네, 들려요" 라고 대답하지만, 그 대답이 내 컴퓨터 문 앞에서 버려진다.

sudo nft add table inet lab
sudo nft add chain inet lab in '{ type filter hook input priority 0; policy accept; }'
sudo nft add rule inet lab in ip saddr 192.168.0.20 tcp sport 80 tcp flags '& (syn|ack) == syn|ack' counter drop

세 줄이 하는 일은 이렇다.

  1. lab 이라는 이름의 규칙 묶음(table)을 만든다.
  2. 그 안에 들어오는 패킷(hook input)을 검사하는 목록(chain)을 만든다. 기본값은 통과(policy accept)다.
  3. 규칙 하나를 넣는다. 보낸 쪽이 192.168.0.20 의 80번 포트이고, SYN 과 ACK 표시가 둘 다 켜진 패킷이면, 개수를 세고(counter) 버린다(drop).

잘 들어갔는지 확인한다.

sudo nft list table inet lab
table inet lab {
	chain in {
		type filter hook input priority filter; policy accept;
		ip saddr 192.168.0.20 tcp sport 80 tcp flags syn,ack / syn,ack counter packets 0 bytes 0 drop
	}
}

이제 녹화하면서 접속해 본다. -m 8 은 8초까지만 기다리라는 뜻이다.

sudo tcpdump -i eth0 -nn -w retx.pcap 'host 192.168.0.20 and tcp port 80' &
curl -sS -m 8 -o /dev/null http://192.168.0.20/
sudo pkill -INT tcpdump

이번에는 바로 실패하지 않고 8초를 기다린 뒤 포기한다.

curl: (28) Connection timed out after 8002 milliseconds

tshark 로 녹화본을 읽는다. 뒷부분은 줄였다.

tshark -r retx.pcap -T fields -e frame.number -e frame.time_relative -e _ws.col.Info
1	0.000000000	47244 → 80 [SYN] Seq=0 …
2	0.000237000	80 → 47244 [SYN, ACK] Seq=0 Ack=1 …
3	1.000877000	[TCP Retransmission] 80 → 47244 [SYN, ACK] Seq=0 Ack=1 …
4	1.044813000	[TCP Retransmission] 47244 → 80 [SYN] Seq=0 …
5	1.045084000	[TCP Retransmission] 80 → 47244 [SYN, ACK] Seq=0 Ack=1 …
6	2.068998000	[TCP Retransmission] 47244 → 80 [SYN] Seq=0 …
7	2.069344000	[TCP Retransmission] 80 → 47244 [SYN, ACK] Seq=0 Ack=1 …
8	3.092999000	[TCP Retransmission] 47244 → 80 [SYN] Seq=0 …
9	3.093322000	[TCP Retransmission] 80 → 47244 [SYN, ACK] Seq=0 Ack=1 …
10	4.116799000	[TCP Retransmission] 47244 → 80 [SYN] Seq=0 …
11	4.117096000	[TCP Retransmission] 80 → 47244 [SYN, ACK] Seq=0 Ack=1 …
12	5.140992000	[TCP Retransmission] 47244 → 80 [SYN] Seq=0 …
13	5.141390000	[TCP Retransmission] 80 → 47244 [SYN, ACK] Seq=0 Ack=1 …
14	7.145083000	[TCP Retransmission] 80 → 47244 [SYN, ACK] Seq=0 Ack=1 …
15	7.188976000	[TCP Retransmission] 47244 → 80 [SYN] Seq=0 …
16	7.189306000	[TCP Retransmission] 80 → 47244 [SYN, ACK] Seq=0 Ack=1 …

Retransmission 은 "재전송", 즉 같은 패킷을 다시 보냈다는 뜻이다. 통화로 옮기면
이렇다.

  • 나: "여보세요?"
  • 서버: "네, 들려요!" — 그런데 이 말이 내 방화벽에서 버려진다.
  • 나: (대답이 없네…) 1초 뒤 다시 "여보세요?"
  • 서버: (대답을 했는데 확인이 없네…) "네, 들려요!" — 또 버려진다.
  • 이렇게 계속 반복하다가 8초가 지나 curl 이 포기한다.

몇 가지를 더 읽을 수 있다.

  • 서버는 멀쩡하다. SYN-ACK 를 매번 제대로 보냈다. 받는 쪽에서 버렸을 뿐이다.
  • 같은 SYN 을 다시 보낸 것이다. Seq=0 이 매번 같은 것이 "새 연결이 아니라 같은 인사의 반복" 이라는 표시다.
  • 다시 보내는 간격은 1, 2, 3, 4, 5초째에 한 번씩, 그다음은 7초째다. 처음 몇 번은 1초 간격으로 빠르게 다시 보내고, 그 뒤로는 간격을 점점 늘린다.

참고

버려지는 SYN-ACK 가 녹화본에 보이는 이유. tcpdump 는 패킷이 방화벽에 도착하기
전에
복사한다. 그래서 방화벽이 버린 패킷도 녹화된다. 이 성질 덕분에 "패킷은
도착했는데 방화벽이 막는 것인지" 를 가려낼 수 있다.

참고

교과서에는 재전송 간격이 1초, 2초, 4초… 로 두 배씩 늘어난다고 나온다. 여기서
처음에 1초 간격이 이어진 것은 최근 리눅스(6.6 이후)에 생긴 설정 때문이다.

$ sysctl net.ipv4.tcp_syn_linear_timeouts net.ipv4.tcp_syn_retries
net.ipv4.tcp_syn_linear_timeouts = 4
net.ipv4.tcp_syn_retries = 6

처음 몇 번은 같은 간격으로 빨리 다시 보내고, 그래도 안 되면 간격을 두 배씩 늘린다.
포기하기 전까지 최대 6번 다시 보낸다.

실습이 끝나면 규칙을 지운다#

주의

이 규칙을 남겨 두면 이 컴퓨터에서 그 웹 서버로 영영 접속이 안 된다. 꼭 지운다.

지우기 전에 규칙이 몇 개를 버렸는지 본다.

sudo nft list chain inet lab in | grep counter
		ip saddr 192.168.0.20 tcp sport 80 tcp flags syn,ack / syn,ack counter packets 9 bytes 540 drop

packets 9 — 9개를 버렸다. 녹화본의 SYN-ACK(2, 3, 5, 7, 9, 11, 13, 14, 16번)도
정확히 9개다. 이제 지운다.

sudo nft delete table inet lab
sudo nft list tables

마지막 명령의 결과에 lab 이 없으면 원래대로 돌아온 것이다.

확인#

다 따라 했다면 아래 명령의 결과가 오른쪽 # 뒤의 숫자와 같아야 한다.

tcpdump -nn -r http.pcap | wc -l                                       # 10 — 정상 접속은 패킷 10개
tcpdump -nn -r http.pcap 'tcp[tcpflags] & tcp-fin != 0' | wc -l        # 2  — 끊는 인사 FIN 은 양쪽에서 한 번씩
tcpdump -nn -r rst.pcap 'tcp[tcpflags] & tcp-rst != 0' | wc -l         # 1  — 닫힌 포트는 RST 한 번
tshark -r retx.pcap -Y tcp.analysis.retransmission | wc -l             # 14 — 재전송이 14번
sudo nft list tables | grep -c lab                                     # 0  — 실습 규칙이 남아 있지 않다

wc -l 은 줄 수를 세는 명령이다. 패킷 수는 환경에 따라 한두 개 다를 수 있다.
정상 접속에서 [S] [S.] [.] 로 시작해 [F.] 두 개와 [.] 로 끝나면 된다.

정리#

  • TCP 연결은 인사 세 번으로 시작한다. [S] → [S.] → [.] 를 3-way handshake 라고 부른다. 끊을 때도 양쪽이 [F.] 를 한 번씩 보낸다.
  • Connection refused 와 Connection timed out 은 원인이 다르다.
    • refused: RST 를 받았다. 서버까지는 갔다. 서버 프로그램과 포트 번호를 본다.
    • timed out: 아무 대답도 못 받았다. 중간의 방화벽이나 경로를 본다.
    • 어느 쪽인지 헷갈리면 tcpdump 로 한 번 잡아 보면 바로 갈린다.
  • tcpdump 는 방화벽보다 앞에서 본다. 그래서 "패킷은 왔는데 막혔다" 를 확인할 수 있다.

용어 정리#

용어 뜻
SYN 연결하자는 첫 인사
ACK 받았다는 확인
FIN 할 말 다 했으니 끊자는 인사
RST 연결을 즉시 거절하거나 끊는 신호
seq 내가 보내는 데이터의 순서 번호
ack (번호) "여기까지 받았으니 다음은 이 번호부터"
재전송 확인을 못 받아서 같은 패킷을 다시 보내는 것
필터 원하는 패킷만 골라 잡는 조건 (host …, tcp port …)

다음 실습에서는 리눅스 안에 가상 컴퓨터 여러 대를 만들고, 서로 다른 네트워크를
라우터로 이어 본다. 오늘 쓴 tcpdump 로 그 사이를 오가는 패킷도 다시 들여다본다.

공유
욱콩

욱콩

서버와 배포를 만지면서 겪은 것을 기록합니다. 직접 돌려 보고 나온 결과만 씁니다.

댓글

댓글 남기기