학사 나부랭이

Network - Internet Protocol 본문

自習/Network

Network - Internet Protocol

태양왕 해킹 (14세) 2021. 7. 1. 03:33

IP

 용량이 큰 파일을 서버에서 내려받을 때, 파일은 패킷 단위로 쪼개져 전송되며, 도착지 호스트 기기에서 CPU가 재조립해요. IP 프로토콜은 패킷의 분할과 재조합에서 사용되며 출발지와 목적지의 IP와 라우터 설정을 포함해요.

 

IP 헤더 구조

그림에서 한 칸은 1bit인데 네트워크 정보를 나타내는 단위에는 4가지가 있어요.

  • 1bit = 0|1
  • 1byte = 8bits
  • 4bits = 1nibble
  • 1word = 4bytes

IP 헤더의 각 항목은 다음과 같아요.

  • Version: IPv4나 IPv6를 나타내요.
  • IHL (IP Header Length): IP 헤더의 길이를 워드 단위로 나타내요.
  • TOS (Type Of Service): 서비스에서 우선순위를 지원하도록 라우터에게 요구해요.
  • Total Length: 헤더의 총길이를 바이트 단위로 나타내요.
  • Identification: 패킷을 결합할 때 사용되는 고윳값이에요. 예를 들어, 분할된 패킷끼리는 같은 값을 가지죠.
  • IP Flags: 첫 번째 비트는 0으로, 사용되지 않아요. 두 번째 필드는 Do Not Fragment이며, 값이 1인 경우, 패킷을 분할하지 않죠. 세 번째 필드인 More Fragment는 분할된 패킷들 중, 마지막 패킷이라면 0, 아니면 1을 가져요.
  • Fragment Offset: 원본 데이터의 바이트 범위를 나타내요. {단편화할 패킷 크기 - 20bytes(ip 헤더)}/8bytes 크기만큼 증가해요.
  • Time To Live: 패킷은 라우터를 거치면서 TTL 값이 1씩 줄어들어요. 네트워크에서 패킷이 계속 라우터를 떠도는 것을 방지하죠. 1byte며, 최댓값은 255이에요.
  • Protocol: 상위 계층의 프로토콜을 나타내요. ICMP(=1), TCP(=6), UDP(=17), ESP(==50), AH(=51) 등이 있어요.
  • Header Checksu,: 패킷의 오류를 검출해요.
  • Source Address: 패킷 출발지의 IP 주소예요. 4bytes며, 각 byte마다 0~255를 나타내므로 0.0.0.0~255.255.255.255 만큼의 IP 주소를 나타낼 수 있어요.
  • Destination Address: 패킷의 목적지 IP 주소예요.
  • IP Option: 주로 테스트나 디버깅 용도로 사용되며 통신에는 관여하지 않아요. 대부분 사용하지 않는 편이에요.

IP Option 필드를 제외하면 IP 헤더는 주로 20bytes로 고정되어 있어요.

# IP 헤더 분석
from socket import *
import os
import struct

# 패킷을 byte 형태로 받으면 헤더와 나머지를 리턴
def ParseIpHeader(ip_header):
    # struct 모듈로 byte를 편하게 다룸, unpack의 첫 번째 인자의 알파벳에 따라 앞에서부터 byte를 끊어 튜플 형태로 반환, 두 번째 인자는 언팩 할 byte를 받음
    # B: 정수-1byte, H: 정수-2bytes, s: bytes-1byte, L: 정수-4bytes, Q: 정수-8bytes
    # byte 배열에서 'BBHHHBBH4s4s'는 '2B3H2BH4s4s'로 표현
    ip_headers = struct.unpack("!BBHHHBBH4s4s", ip_header[:20])
    ip_payloads = ip_header[20:]
    return ip_headers, ip_payloads


# 숫자를 byte 형태로 변환 후, bit 형태로 다시 출력
def FlagsAndOffset(int_num):
    # .to_bytes()는 정수를 byte 배열로 리턴, 뒤의 인자는 2bytes 길이의 Big endian 방식을 의미
    byte_num = int_num.to_bytes(2, byteorder="big")
    x = bytearray(byte_num)
    # 1byte는 8bits므로 zfill() 함수로 자릿수를 맞춰 줌
    flags_and_fragment_offset = bin(x[0])[2:].zfill(8) + bin(x[1])[2:].zfill(8)
    return (flags_and_fragment_offset[:3], flags_and_fragment_offset[3:])


def parsing(host):
    # NT계열 운영체제면(Windows)
    if os.name == "nt":
        socket_protocol = IPPROTO_IP
    # 리눅스나 유닉스 계열이면
    else:
        socket_protocol = IPPROTO_ICMP
    # AF_INET: IPv4 주소(AF_INET6: IPv6), Raw 소켓을 사용하겠다, 프로토콜 지정|생략(IP방식 = 0, ICMP방식 = 1)
    sock = socket(AF_INET, SOCK_RAW, socket_protocol)
    # 호스트의 IP주소와 포트를 연결(인자: ('호스트', '포트 번호')<=튜플 형태|자동 지정)
    sock.bind((host, 0))
    # 소켓에 옵션 추가(대상 소켓, 옵션(여기서는 IP헤더를 포함하는 옵션), 옵션의 설정 값(여기서는 True, if 0: 커널이 자동으로 IP헤더 작성))
    sock.setsockopt(IPPROTO_IP, IP_HDRINCL, 1)

    # Promiscuous mode, 윈도우에서 필요, 목적지 Network Interface Card의 MAC 주소가 자신이 아니더라도 패킷을 수신
    if os.name == "nt":
        sock.ioctl(SIO_RCVALL, RCVALL_ON)
    # 소켓에서 데이터를 수신할 버퍼의 크기, data변수에 넣음
    packet_number = 0
    try:
        while True:
            packet_number += 1
            data = sock.recvfrom(65535)
            # IP 헤더 20bytes의 ip_headers와 나머지 데이터인 ip_payloads를 튜플 형태로 반환 받음
            ip_headers, ip_payloads = ParseIpHeader(data[0])
            print(f"{packet_number}th packet\n")
            # ip_headers[0]는 1byte이며 bit 연산자를 이용해 특정한, 왼쪽 4bits에 해당하는 값이 IP의 버전
            print("version: ", ip_headers[0] >> 4)
            # IP 헤더의 길이는 ip_headers[0]의 오른쪽 4bits이며 출력한 값은 word 단위이므로 byte로 계산하려면 4를 곱해줘야 함
            print("header length: ", ip_headers[0] & 0x0F)
            print("TOS (Type Of Service): ", ip_headers[1])
            print("total length: ", ip_headers[2])
            print("identification: ", ip_headers[3])
            # IP 플레그와 프레그먼트 오프셋은 bit 형태로 출력, Do not Fragment가 참이면 010이 출력된다.
            print("IP flags, fragment offset: ", FlagsAndOffset(ip_headers[4]))
            print("TTL (Time To Live): ", ip_headers[5])
            print("protocol: ", ip_headers[6])
            print("header checksum: ", ip_headers[7])
            # inet_ntoa: byte 형을 읽을 수 있는 IP 주소 체계(*.*.*.*)로 보여줌
            print("source address: ", inet_ntoa(ip_headers[8]))
            print("destination address: ", inet_ntoa(ip_headers[9]))
            print("=" * 50)
    except KeyboardInterrupt:  # Ctrl+C
        # Off Promiscuous mode
        if os.name == "nt":
            sock.ioctl(SIO_RCVALL, RCVALL_OFF)
        sock.close()


if __name__ == "__main__":
    host = "192.168.0.9"
    print(f"Listening at [{host}]")
    parsing(host)

 

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

Network - Ping sweep scaner  (0) 2021.07.12
Network - Internet Control Message Protocol  (0) 2021.07.01
Network - IDS, IPS, Snort, UTM  (0) 2021.05.23
Network - Firewall  (0) 2021.05.23
Network - OSI 7 Layer Model  (0) 2021.05.23
Comments