[네트워크] 와이어샤크(Wireshark)로 TCP/UDP 패킷 분석해보기

실습 정보

  • SERVER: Ubuntu 22.04
    • IP: 192.168.219.113
    • PORT: 65432
  • CLIENT: Windows 11
    • IP: 192.168.219.164
    • PORT: 51112

TCP 패킷 분석

TCP 헤더 구조

image

  • Source Port/ Detination Port: 출발지 포트와 목적지 포트를 나타냄
  • Sequence Number: 올바른 순서로 데이터를 보내기 위한 고유 일련 번호
  • Acknowledgement number: 다음 세그먼트를 수신할 준비가 된 상태를 나타내는 번호
  • Header length: Header 길이를 32bit 단위로 나타낸다. 최소 필드 값은 20 bytes 이다.
  • Window size Field: 흐름 제어를 위해 송신 시스템의 가용 가능 버퍼 크기를 나타냄 (Byte 단위)
  • Checksum Field: TCP segment의 내용이 유효한지 검증

TCP 연결을 위한 준비

  • TCP 연결을 테스트해보기 위해 파이썬으로 간단한 TCP 소켓 프로그래밍을 해볼 것이다.

server.py

import socket

# 서버 IP, 포트 설정
HOST :str = "192.168.219.113"
PORT :int = 65432

# 소켓 객체 생성
with socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM) as s:
    # 소켓을 HOST와 PORT에 바인딩
    s.bind((HOST, PORT))
    
    # 클라이언트의 연결 요청 대기
    s.listen()
    print("서버가 시작됨. 클라이언트의 연결을 기다리는 중 ..")
    
    while True:
        # 연결 수락
        conn, addr = s.accept()
        with conn:
            print('연결 되었음!', addr)
            while True:
                data = conn.recv(1024)
                if not data:
                    break
                print("받은 데이터: ", data.decode())
                conn.sendall(data) # 받은 데이터를 그대로 클라이언트에 전송

client.py

import socket

# 서버 IP, 포트 설정
HOST :str = "192.168.219.113" 
PORT :int = 65432

# 소켓 객체 생성
with socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM) as s:
    # 서버에 연결 요청
    s.connect((HOST,PORT))
    
    # 메시지 생성
    message :bytes = input("input string ..\n:").encode()

    # 서버에 메시지 전송
    s.sendall(message)
    
    data = s.recv(1024)

print("받은 데이터: ", data.decode())

  • Client와 Server 측 코드를 위와 같이 작성하였다.
  • 위 프로그램을 실행하여 패킷을 캡쳐해보자.
  • 위 방법 말고도, 웹 사이트에 접속하는 것만으로도 3-way, 4-way Handshake 과정 패킷 캡쳐가 가능하다.

코드를 실행하고 Client 측에서 포트를 확인해보자.
image
왼쪽 192.168.219.164(Client), 오른쪽 192.168.219.113(Server) 연결이 서로 성립(Established) 된 것을 볼 수 있다.

이제 패킷을 확인해보자.


TCP 패킷

image


3-Way Handshake 과정 - 연결 수립

image
사진에 나온 패킷 순서대로 살펴보도록 하자.

  • 9. 192.168.219.164(Client)측에서 192.168.219.113(Server)와 연결을 위해 SYN 패킷을 보내고 있다.
  • 10. .113(Server).164(Client)의 연결을 받았다는 ACK 응답을 하였다. 그리고 .113(Server)도 연결 요청(SYN)을 보낸다.
  • 11. .164(client)는 최종적으로 ACK 응답을 하게 됨으로써 서로 1:1 연결이 되었다.

Data Send/Receive 과정

image

  • 55. PSH flag의 의미는 데이터를 버퍼에 쌓지 말고 즉시 보내라 라는 의미이다.
    • 단순히 ACK만을 보내면 중간중간 데이터가 버퍼에 쌓이기 시작하여 PSH flag와 함께 보내면 즉시 보내지게 된다.

클라이언트단에서 “Hello?” 라는 문자열을 전송하였다. image
패킷을 자세히 확인해보자.


image

  • ACK, PSH flag가 Set이 된 상태이다
  • 또한 내가 Hello? 라는 문자열을 보낸 상태이고 Data Size는 6 bytes 인 것을 확인할 수 있다.
  • 처음 Seq Number를 보면 1로 설정되어 있고 다음 Seq number는 Data Size(=6)를 더한 7을 다음 Seq number로 맞추고 있다.

image

  • 56. 192.168.219.113(Server)192.168.219.164(Client)가 보낸 메시지를 잘 받았다는 ACK 응답을 보내고 있다.
    • ACK 번호를 7로 맞추고 있음.

  • 57. 192.168.219.113(Server)192.168.219.164(Client)에게로 메시지를 보내고 있다. 55번 패킷과 유사하게 PSH, ACK flag도 함께 보내고 있다.

4-Way Handshake 과정 - 연결 해제

image

  • 58. .164(Client).113(Server)에게 연결 종료를 위해 FIN/ACK 패킷을 보내고 있다.
  • 59. .113(Server).164(Client)에게 ACK 확인 응답을 보내고 세션이 끝날 때까지 기다린다 (CLOSE_WAIT)
  • 60. 마찬가지로 통신이 끝나면 Server도 Client에게 FIN/ACK 패킷을 보낸다.
  • 61. Client에서 수신 되었다는 ACK 메시지를 보내며 통신이 종료된다.

UDP 패킷 분석

UDP 연결을 위한 준비

  • UDP 연결을 테스트해보기 위해 파이썬으로 간단한 UDP 소켓 프로그래밍을 해볼 것이다.

server.py

import socket

# 서버 IP, 포트 설정
UDP_IP :str = "192.168.219.113"
UDP_PORT :int = 5005

# UDP 소켓 생성
server_socket = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM)

# 소켓을 주소에 바인딩
server_socket.bind((UDP_IP, UDP_PORT))
print("UDP 서버 시작됨")

while True:
    # 클라이언트로부터 데이터 수신
    data, addr = server_socket.recvfrom(1024)
    print(f"수신된 데이터 : {data.decode()}")
    
    # 클라이언트에게 데이터 전송
    server_socket.sendto(data, addr)

client.py

import socket

# 서버 IP, 포트 설정
UDP_IP :str = "192.168.219.113"
UDP_PORT :int = 5005

# UDP 소켓 생성
with socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM) as s:
    while True:
        message :str = input("전송할 메시지 입력\n:")
        
        # 서버에 데이터 전송
        s.sendto(message.encode(), (UDP_IP, UDP_PORT))
        
        # 서버로부터 데이터 수신
        data, addr = s.recvfrom(1024)
        print(f"서버로부터 수신된 데이터: {data.decode()}")

  • Client와 Server 측 코드를 위와 같이 작성하였다.
  • 위 프로그램을 실행하여 패킷을 캡쳐해보자.

image

  • client 측에서 문자열을 입력하고 Server에 전달하면 서버는 그대로 반환하는 일종의 Echo system 형태이다.
  • client에서 hi, hello를 보내고 서버로부터 그대로 반환 받았다.

UDP 패킷

image

  • UDP 패킷의 경우 TCP 패킷과 다르게 매우 단순하다. TCP 에서는 연결 수립과 해제에서 handshake 과정이 필요하였다.
  • 그러나 UDP의 경우 handshake 과정 없이 data 만을 전송한다.

관련 글

지식 공유4분 읽기

IDC 엔지니어를 위한 OSI 7계층 트러블슈팅 실전 랩!

개요 서버를 설치하고, 인프라를 구축하고, 장애를 해결하는 모든 업무의 근간에는 바로 OSI 7계층 이라는 강력한 나침반이 있습니다. 이른으로만 들었던 OSI 7계층을 실제 업무에서 어떻게 활용할 수 있는지, 두 가지 시나리오를 기반...

네트워크