학사 나부랭이

Network - TCP/UDP 본문

自習/Network

Network - TCP/UDP

태양왕 해킹 (14세) 2021. 7. 13. 04:26

 이전에 본 것처럼 패킷은 전송 도중 유실되거나 오차가 있을 수 있는데, 이는 당연히 네트워크의 신뢰성에 문제가 생길 수 있죠.

여기서, TCP는 이러한 패킷 전달에서 신뢰성을 보장하기 위해 고안된 프로토콜이에요. 통신 전에 항상 세션을 성립시키며, 패킷을 전송한 후, 패킷을 받은 측에서 패킷이 잘 도착했다고 알리죠. 그러니 속도가 상대적으로 느려요.

그에 비해 라이브 방송, 게임과 같은 약간 내용이 깨지는 정도는 감수하더라도 실시간 통신이 중요한 때가 있겠죠? 이렇게 속도가 정확도보다 중요한 경우 UDP를 사용해요.

TCP UDP
연결형 서비스 비연결형 서비스
高 신뢰성 低 신뢰성
低 속도 高 속도
수신 여부 확인 수신 여부 확인 X
전송 순서 보장 전송 순서 보장 X

TCP 헤더

 위는 TCP 헤더 구조의 도식인데, IP 헤더와 마찬가지로 20bytes가 기본 길이에요.

Source port: 출발지 포트 번호예요. 0~655335의 값을 가질 수 있죠.

Destination port: 도착지 포트 번호예요. 마찬가지로 0~65535의 값을 가질 수 있어요.

Sequence number: TCP 패킷의 순서를 나타내는 숫자예요. Identifiable 하며, 패킷이 생성될 때, 무작위 값으로 초기화되죠.

Acknowledgment number: 다음에 받을 Sequence number를 상대측에게 알려줘요.

Offset: IP 헤더의 IHL처럼 TCP 헤더의 길이를 나타내요. 이게 없으면 보통 20bytes = 5words이죠.

Reserved: 예약된 항목이에요.

TCP flags: TCP 패킷의 역할이 정해지는 필드예요. CEUAPRSF로 앞 글자로 각 필드를 알아야 해요.

 - CWR, ECE: TCP 혼잡 제어와 관련된 필드예요.

 - URF(URgent): Urgent pointer 필드의 값이 유효한 지 나타내요.

 - ACK(ACKnowledge): TCP 3-way handshake 시 사용되는 플래그예요.

 - PSH(PuSH): 수신 애플리케이션에 버퍼링 된 데이터를 푸시해 줄지 나타내요.

 - RST(ReSeT): TCP 연결을 강제 종료해요.

 - SYN(SYNchronize): 동기화 시퀀스 번호, 최초 TCP 3-way handshake 시 사용되어요.

 - FIN(FINish): TCP 연결  종료 시 4-way handshake 시 사용되어요. 더는 데이터가 없음을 알려주죠.

Window: 수신 윈도우 크기, 패킷 내용의 사이즈를 나타내요.

Checksum: 헤더 내용의 오류 검출을 위해 존재해요.

Urgent pointer: URG 플래그가 설정되면 시퀀스 번호에서 오프셋을 나타내요.

TCP options: 초기 연결 시 Maximum Segment Size 값의 설정, Window-scaling, Timestamp 설정 등에 대한 추가 옵션을 설정할 수 있어요.

 

TCP 3-way handshake

 TCP/IP 프로토콜을 이용해 통신하는 애플리케이션이 데이터를 전송하기 전에 정확한 전송을 보장하기 위해 상대방과 사전에 세션을 수립시키는 과정을 의미해요.

① 먼저 클라이언트가 서버에 접속을 요청하는 SYN 패킷을 보내고, SYN_SENT 상태가 되어요.

② 서버는 처음에 포트를 열고 LISTENING 상태로 클라이언트의 요청을 기다려요. 클라이언트의 SYN 요청을 받으면 SYN, ACK 플래그가 설정된 패킷으로 응답하고 SYN_RECEIVED 상태로 변경되어요.

③ 클라이언트가 서버에서 SYN. ACK 패킷을 받으면 ACK 패킷으로 응답해 데이터 송수신이 가능한 상태 즉, 세션이 맺어진 ESTABLISHED 상태가 되죠.

 

TCP 4-way handshake

 위의 3-way handshke가 TCP의 연결을 초기화할 때 수행된다면, 4-way handshake는 세션을 종료할 때 수행되는 절차예요.

① 클라이언트가 연결을 종료하겠다는 FIN, ACK 플래그를 전송해요.

② 서버는 ACK 응답을 보내 애플리케이션 종료 과정을 수행해요.

#1. 애플리케이션이 종료할 준비가 되면, FIN, ACK 패킷을 전송해요.

#2. 마지막으로 ACK 패킷을 보내고 TIME-WAIT 상태로 대기해요. 이는 Maximum Segment Lifetime * 2, 즉 약 120초 정도 기다린 후 완전히 종료해요. 이 때 기다리는 이유는 마지막 클라이언트의 ACK 패킷이 유실되었을 때 서버가 다시 보내는 FIN, ACK 패킷에 대응하지 못하기 때문이죠. 그러니 완전히 종료하기까지 일정 시간 기다려요.

 

UDP 헤더

 UDP는 TCP와 달리 8bytes의 기본 헤더로 이루어져 있어요.

Source port: 출발지 포트 번호예요. 0~65535의 값을 가질 수 있어요.

Destination port: 도착지 포트 번호예요. 마찬가지로 0~65535의 값을 가질 수 있죠.

Length: 패킷의 길이예요. UDP 헤더의 시작부터 상위 계층까지의 모든 길이를 포함해요.

Checksum: 헤더 내용의 오류 검출을 위해 존재해요.

from socket import *
import os
import struct


def parseIPHeader(payload):  # more accurate then before
    pre_ip_headers = struct.unpack("!2B3H2BH4s4s", payload[:20])
    ihl = (pre_ip_headers[0] & 0x0F) * 4  # IP Header Length, 4bytes = 1word
    ip_payloads = payload[ihl:]
    return pre_ip_headers, ip_payloads


def parseICMPHeader(icmp_data):
    icmp_headers = struct.unpack("!2B3H", icmp_data[:8])
    icmp_payloads = icmp_data[8:]
    return icmp_headers, icmp_payloads


def parseTCPHeader(payload):  # Calculate TCP Header's Length
    pre_tcp_headers = struct.unpack("!2H2L2B3H", payload[:20])
    tcp_header_length = (pre_tcp_headers[4] >> 4) * 4
    return pre_tcp_headers, payload[tcp_header_length:]


def parseUDPHeader(payload):
    pre_udp_headers = struct.unpack("!4H", payload[:8])
    return pre_udp_headers, payload[8:]


def tcpFlags(int_num):
    return str(bin(int_num))[2:].zfill(8)  # return tcp flag as binary numbers


def parsing(host):  # creating and binding raw socket
    if os.name == "nt":
        sock_protocol = IPPROTO_IP
    else:
        sock_protocol = IPPROTO_ICMP
    sock = socket(AF_INET, SOCK_RAW, sock_protocol)
    sock.bind((host, 0))

    sock.setsockopt(IPPROTO_IP, IP_HDRINCL, 1)  # socket option

    if os.name == "nt":  # turn on the promiscuous mode
        sock.ioctl(SIO_RCVALL, RCVALL_ON)

    packet_number = 0
    try:
        while True:
            packet_number += 1
            data = sock.recvfrom(65535)
            ip_headers, ip_payloads = parseIPHeader(data[0])
            ip_source_address = inet_ntoa(ip_headers[8])
            ip_destination_address = inet_ntoa(ip_headers[9])
            if ip_headers[6] == 6:  # only TCP
                print(
                    f"{packet_number}th packet\n[TCP] {ip_source_address} -> {ip_destination_address}"
                )
                tcp_headers, tcp_payloads = parseTCPHeader(ip_payloads)

                print("Source port: ", tcp_headers[0])
                print("Destination port: ", tcp_headers[1])
                print("Sequence number: ", tcp_headers[2])
                print("Acknowledgment number: ", tcp_headers[3])
                print("Offset(Length): ", tcp_headers[4] >> 4)
                print("TCP flags:")
                print("    CEUAPRSF")
                print("   ", tcpFlags(tcp_headers[5]))
                print("Window size: ", tcp_headers[6])
                print("Checksum: ", tcp_headers[7])
                print("Urgent pointer: ", tcp_headers[8])
                print("TCP payloads: ")
                print(tcp_payloads.decode("utf-8", "ignore"))
            elif ip_headers[6] == 17:  # only UDP
                print(
                    f"{packet_number}th packet\n[UDP] {ip_source_address} -> {ip_destination_address}"
                )
                udp_headers, udp_payloads = parseUDPHeader(ip_payloads)
                print("Source port: ", udp_headers[0])
                print("Destination port: ", udp_headers[1])
                print("Length: ", udp_headers[2])
                print("Checksum: ", udp_headers[3])
                print("UDP payloads: ", udp_payloads.decode("utf-8", "ignore"))
            print("=" * 30)

    except KeyboardInterrupt:  # input Ctrl+C
        if os.name == "nt":
            sock.ioctl(SIO_RCVALL, RCVALL_OFF)


def main():
    host = "192.168.0.9"
    print(f"Start sniffing at {host}")
    parsing(host)


if __name__ == "__main__":
    main()

위 그림은 IP 헤더의 첫 1byte이며, 왼쪽 4bits는 IP Version, 오른쪽 4bits는 IP Header Length라고 이전에 설명했죠? 코드의 AND 연산(0x0F = 1111)을 통해 IHL에 해당하는 비트를 구할 수 있답니다.

'自習 > Network' 카테고리의 다른 글

Network - HTTP  (0) 2021.07.30
Network - Web  (0) 2021.07.30
Network - Ping sweep scaner  (0) 2021.07.12
Network - Internet Control Message Protocol  (0) 2021.07.01
Network - Internet Protocol  (0) 2021.07.01
Comments