[Network] 내가 만든 웹 서비스 WireShark로 패킷 분석하기 - 7계층 분석
개요
내가 만든 웹 서비스 WireShark로 패킷 분석하기 1편에 이어서, 2편에서는 본격적인 WireShark 패킷 내용을 살펴보자.
- 현재 서버는 로컬 컴퓨터에서 spring boot를 IntelliJ로 실행하고, 포트포워딩하여 15001 포트로 클라이언트가 접속할 수 있도록 세팅되어있다.
- 서버 위주의 패킷을 분석하고, 특정 상황에서는 서버와 클라이언트 양측의 패킷을 각각 분석한다.
유저 시나리오는 다음과 같다.
- 클라이언트의 접속(채팅방 이용자)
- 유저 1의 채팅방 만들기
- 유저 2의 채팅방 접속(다대다 채팅 가능)
- 서로 채팅하기
- 방 나가기
- 이메일 전송하기
- 클라이언트 브라우저 접속 종료
S1# 클라이언트(채팅방 이용자)의 메인 화면 접속
[서버 측 패킷]
두 클라이언트(51115, 51116)의 요청과 서버의 응답에 대한 패킷 전송과정을 확인할 수 있다. 가장 처음 요청을 한 클라이언트 1의 TCP 연결 요청은 서버측의 [SYN, ACK]를 받지 못해, 서버 측에서 지속적으로 TCP Retransmission을 실행하는 것을 확인할 수 있다.
TCP Retransmission
TCP는 RDT 통신을 한다.
- RDT(Reliable Data Transfer)란? 불안정한 네트워크 환경에서도 안정적으로 데이터를 전송하는 기술로, 신뢰성 있는 데이터 전송을 의미한다. UDP와 뚜렷하게 비교되는 TCP 특징 중 하나다.
TCP는 RDT 통신을 위해서 Retransmission 매커니즘을 사용한다.
- TCP는 데이터 수신이 잘 되었는지 확인하기 위한 ACK라는 일종의 flag를 사용한다. 통신을 하는 과정에서 ACK 패킷을 받지 못하면 일정 시간 내에 해당 패킷을 다시 전송한다. 불확실한 네트워크 상황에서 데이터 손실을 방지하기 위한 RDT 통신 기술이다!
- 이러한 Retransmission과 관련하여 timer, Dupicate ACK 등을 알아야 하지만, TCP 관련 포스팅에서 상세하게 다루도록 한다. 패킷 분석에서 서버 측의 SYN, ACK를 받지 못해 Retransmission을 지속적으로 실행하는 것을 확인하는 것이 핵심이다.
RST(Reset) 패킷
연결을 즉시 종료하기 위한 패킷이다. 비정상적이거나 예기치 않은 상황에서 연결을 재설정하거나 아예 끊기 위한 기술이다.
- 위 패킷 캡처내용에서 클라이언트 1(51115)의 연결 요청에 대해 Retransmission을 하다가, 제대로 연결이 되지 않자 RST로 끊고 511129라는 클라이언트로 재연결을 하는 것을 확인할 수 있다.
TCP 3way-handshaking과 HTTP Request/Response
한 번에 연결이 잘 된 클라이언트 2(51116)의 경우, 3 Way handshaking이 잘 되었고 TCP 연결 이후 HTTP 응답﹒요청을 주고 받는 걸 패킷 캡처를 통해 확인하였다.
GET /HTTP/1.1 살펴보기
- [APPLICATION Layer] 해당 패킷은 HTTP의 버전 1.1을 사용함을 알 수 있다.
- [TRANSPORT Layer] TCP 프로토콜을 사용하며 src/dst port는 각각 (51116, 8080)이다.
- [NETWORK/LINK Layer] 상세 내역을 보면 IPv4 버전을 이용하고, L2 계층에서는 Ethenet을 사용하며 src/dst MAC 주소를 각각(04:09:…, 64:79:…) 확인할 수 있다. IP Fragmentation은 사용되지 않았다. (flag를 통해 확인)
HTTP/1.1 304란?
해당 패킷에서 눈 여겨볼 점은 바로 아래에서 두 번째의 HTTP 상태코드 304 번이다.(No. 20052 패킷) 이는 요청된 자원에 대한 리디렉션, ‘Not Modified’를 뜻합니다. 요청 값에 대해 변경된 사항이 없으므로 캐시되어있는 자원을 이용하겠다는 뜻이다.
실제로 패킷을 캡처하기 전에 팀원들과 테스트를 진행했었다. 이 내용들이 WireShark에 반영되는 것이 신기했다. 만약 이 캐시를 지우려면 서버를 재시작 하거나, chrome 쿠키 삭제 기능을 활용할 수 있다.
[HTTP 304 패킷 상세 내용]
S2# 채팅방 만들기
[서버 측 패킷]
채팅방을 생성할 때 GET, POST 요청을 진행하고 endpoint를 반환받는 모습을 확인할 수 있다. 실제로 서버 측 코드로 구현한 것과 같이 요청이 주고 받는 모습이 캡처되었다.
같은 /chat/create 경로로 GET, POST 요청이 진행된다.
- GET: view 표시
- POST: view redirect 및 chatRoom DB 저장
S3# 채팅방 접속 및 웹소켓 연결(STOMP)
[클라이언트 측 패킷]
위 패킷 중 이 세가지 부분이 WebSocket(STOMP) 통신의 연결 시작 부분이다. 웹소켓은 처음 연결 방법에서 HTTP를 사용한다.(1편에 잘 나와있음) 하나씩 살펴보자.
HTTP/1.1 304란?
[패킷 상세 내용]
- Upgrade-Insecure-Requests: HTTP 요청을 자동으로 HTTPS로 리다이렉트
- Accept-Encoding: 콘텐츠 인코딩 방식은 gzip, deflate 압축을 지원한다는 뜻이다. 이러한 방식은 데이터 전송량을 줄여서 네트워크 효율을 높이는 방식이다. 실제로 서버 프로그래밍을 할 때도 대용량 데이터를 통신할 때는 JSON 데이터를 gzip으로 주고 받은 경험이 있어서 이를 직접 확인 해보는 것이 흥미로웠다.
- Accept-Language: 클라이언트가 선호하는 언어는 “ko” 즉, 한국어다.
이러한 헤더 정보를 통해 서버는 클라이언트에게 적합한 응답을 전송하고, 소통한다.
WebSocket 연결 설정 및 HTTP 101
HTTP 101는 클라이언트가 서버에게 프로토콜 전환을 요청했으며, 서버는 이를 승인한다는 상태 코드다. 이 패킷을 살펴보면 요청 헤더 부분에 WebSocket과 관련한 stomp 프로토콜 정보가 있음을 확인할 수 있다.
[패킷 상세 내용]
서버 구현부에서 사용한 @EnableWebScoketMessageBroker 애노테이션은 내장된 STOMP 메시징 브로커를 활성화한다. 이렇게 적용된 통신 방식이 WireShark에서도 반영되는 것을 확인할 수 있었다.
또한, 패킷을 통해 구현 내부 상세를 알 수 있었다. 바로 WebSocket 연결 확장 기능이다. 위 패킷 상세 내용을 살펴보면,
- permessage-deflate는 WebSocket 메시지 데이터를 압축하여 전송할 수 있게 하여, 네트워크 대역폭을 효율적으로 사용할 수 있도록 한다.
- client_max_window_bits=15 라는 필드로 최대 압축창 크기를 지정한다.
[WebSocket 패킷 상세 내용]
- FIN: 채팅의 마지막 부분이다
- MASK: WebSocket 프로토콜은 보안상의 이유로, 클라이언트에서 보내는 모든 데이터 프레임에 마스킹을 적용하도록 규정하고 있다.
- 중간자 공격(Man-in-the-Middle Attack) 방지
- 4byte 길이의 임의의 값으로, 데이터프레임에 XOR 연산을 적용
[추가 Layer 분석 내용]
해당 부분 또한 각 계층에서 사용되는 정보는 위 상황과 비슷했다.
- L2 계층에서는 Ethernet II를 사용하고 있으며, src/dst MAC 정보는 ac:49:db:dd:f1:ae와 88:3c:1c:ed:0b:40이다.
- Network Layer 프로토콜은 Ip이며 IPv4 버전을 사용한다.
- 클라이언트와 서버의 주소가 src/dst 주소인 192.168.219.105, 58.123.180.186으로 나타난다.
- IP Fragmentation은 사용되지 않으며 Offset 역시 0이다.
- 전송 계층의 프로토콜은 TCP이다.
- Checksum이 올바르게 되어 [correct] [Good] 등의 표시를 확인할 수 있으며, 문제 없이 통신이 진행되었다.
S3# 채팅하기
WebSocket 연결이 생성된 이후부터는 TCP 통신을 통해 안정적으로 채팅이 오가는 것을 확인할 수 있다.
S5# 이메일 전송하기
해당 API는 Google의 SMTP를 활용한 spring boot로 구현하였다. 따라서 구현한 사람의 gmail 계정과 앱 비밀번호를 사용하였기에, 보낸 사람(나)이 위와 같이 등록되어있다.
[클라이언트 측 패킷]
메일 전송을 위해서 클라이언트가 서버로 POST 요청을 보내는 것이 확인된다. ➡️ 간단하게 이메일 주소만 입력하면 해당 이메일로 소개메일을 보내는 것을 구현했기에, 브라우저에 입력된 이메일 주소를 POST로 서버로 전송하도록 구현하였다.
POST 요청 패킷을 확인해보면, 이메일 전송 값이 인코딩을 통해 헤더에 담긴 모습을 확인할 수 있다.
TCP ACKed unseen segment
이는 패킷이 정상적으로 송수신되었지만 WireShark 상에서 패킷을 찾을 수 없는 경우에 발생한다.
패킷을 자세히 살펴 보면 ACK에 대한 누락임을 알 수 있고, 경고창을 함께 보여준다.
[서버 측 패킷]
패킷을 통해 해당 API의 동작 과정을 알 수 있다.
- 서버측에서는 메일 전송 POST 요청을 받고 SMTP 프로토콜을 사용하여 자신의 이름을 알린다. (No.2038 패킷)
- 서버는 클라이언트에게 SMTP 기능을 지원하는 것을 알린다. (No.2047 패킷)
- 이후, 서버와 클라이언트는 보안 연결을 설정하고, google 서버가 메일을 전송한다.
[EHLO 상세 패킷]
SMTP 전송 프로토콜은 TCP를 이용하므로, 첫 번째 단계에서 클라이언트와 서버 간 연결을 시작한다.
이때, 클라이언트는 특화된 Hello 명령인 HELP or EHLO를 사용한다. 위 패킷에서는 EHLO를 사용했다.
이후 메일 서버와 요청 서버(웹 애플리케이션이 돌아가고 있는 곳)가 TLS 통신을 진행한다.
HTTPS/TLS 관련 패킷 분석은 과제 1 제출물을 통해 확인할 수 있습니다. 🙌
후기
HTTP에 관해...
서버 API를 짤 때, 항상 고민되었던 것이 어떤 HTTP method를 사용할 것인가?였습니다.
각 HTTP 요청/응답 메시지를 세부적으로 살펴보면서, 클라이언트와 서버 간의 통신 과정을 실제로 확인할 수 있었습니다. HTTP 메서드, 헤더, 페이로드 등 네트워크 통신의 핵심 요소들이 어떻게 동작하는지 깊이 있게 이해할 수 있었고, HTTP에 한층 가까워졌습니다.
평소에 Serialization과 관련하여 공부해야겠다 생각을 자주 했었는데, 이번 기회를 통해 HTTP 헤더를 직접적으로 분석할 수 있어, 통신에 대한 이해가 깊어짐을 체감했습니다.
네트워크 지식
Wireshark 분석을 통해 네트워크 계층부터 애플리케이션 계층까지 전체적인 통신 과정을 확인할 수 있었습니다. 직접 패킷 분석을 하면서 TCP/IP 스택의 동작 원리, 패킷 구조, 흐름 제어 등 네트워크 기술에 대한 이해도를 크게 향상할 수 있었습니다.