IP 헤더 읽어드립니다
원티드 프리온보딩 백엔드 인턴십을 진행하면서 IPv4 헤더 분석하기라는 발표 주제로 만든 PPT 자료와 설명을 작성하였습니다.
- IP는 인터넷 프로토콜 스택, 즉 TCP/IP 프로토콜 스택의 핵심 프로토콜로 네트워킹에서 데이터를 패킷으로 캡슐화하고 주소를 지정하며 라우팅을 통해 다른 네 트워크로 데이터를 전송하는 데 사용됩니다.
- IP는 연결형이 아닌 비연결형 프로토콜이며 이는 각 패킷이 독립적으로 처리되어 전송 순서나 신뢰성이 보장되지 않는 것을 의미합니다.
Version & Header Length
위 그림과 같이 IP 버전에는 32비트로 이루어진 IP Version 4가 존재하고 128비트로 이루어진 IP Version 6가 존재합니다. 또한 IPv6의 헤더는 현재 설명드리는 IPv4 헤더와는 다릅니다. 16진수로 이루어진 바이트 스트림을 확인하였을 때 첫 번째 바이트 스트림인 45는 16진수로 0x45를 나타내며 이를 2진수로 변환하였을 때 0100 0101 총 8비트로 표현할 수 있게 됩니다. 여기서 앞 부분 4비트는 버전을 의미하며 앞 부분을 10진수로 변환하였을 때 4가 나온다면 IPv4를, 6이 나온다면 IPv6를 의미합니다. 뒷 부분 4비트는 헤더의 길이로 4바이트 단위로 표현되기 때문에 뒷 부분 4비트를 10진수로 변환하고 4를 곱하여 헤더 길이를 확인할 수 있습니다.
뒷 부분 4비트는 2의 4승으로 최대값을 표현하면 1111이 나오게 되는데 이를 10진수로 변환하면 15라는 값이 나옵니다. 여기서 헤더의 길이는 4바이트 단위로 표현되므로 4를 곱하면 60으로 이는 IP 헤더의 최대 길이는 60바이트라는 것을 알 수 있게 합니다.
DS Field
다음으로 설명드릴 Differentiated Serviced Field는 위 그림에서 초록색으로 표시된 DSCP 6비트와 뒷 부분 ECN 2비트로 나뉩니다. ECN 2비트는 이전 TCP 헤더 분석하기에서 ECN 메커니즘을 사용할 때 같이 설명드린 부분이라서 이 부분에 대한 설명은 TCP 헤더 분석하기를 참조 부탁드립니다.
서비스 품질은 한정된 네트워크 용량으로 트래픽을 제어하고 주요 애플리케이션의 성능을 보장하기 위해 메커니즘이나 기술을 활용하는 것입니다. 이를 통해 조직은 특정 고성능 애플리케이션들의 우선 순위를 정하여 전체 네트워크 트래픽을 조정할 수 있습니다.
과거와는 달리, 지연에는 민감하지만 손실에는 덜 민감한 멀티미디어 응용서비스의 출현에 따라, 인터넷에서의 QoS의 보장은 매우 중요한 과제가 되었습니다.
IntServ(Integrated Services)에서 DiffServ를 사용하게 된 가장 큰 이유는 확장성 때문입니다. 이와 관련해서는 먼저 IntServ 작동원리와 DiffServ 작동원리에 대한 이해와 비교가 필요합니다.
IntServ
IntServ는 각 플로우마다 네트워크를 통한 자원 예약이 필요하고 이를 위해서 RSVP(Resource Reservation Protocol)같은 프로토콜을 사용해서 매 플로우마다 네트워크 상의 라우터에 자원 예약을 요청하게 됩니다. 요청을 받은 각 라우터 또는 네트워크 내의 각 라우터들은 이러한 플로우에 대한 상태 정보를 유지해야 하기 때문에 큰 규모의 네트워크일 경우 수많은 플로우가 존재할 것이고 이는 라우터 내의 많은 상태 정보의 유지를 필요로 하며 이로 인해 복잡성과 확장성의 문제가 발생합니다.
DiffServ
반면에 DiffServ는 패킷이 들어올 때마다 패킷에 대한 처리 방식을 행동으로 분류하고 상대정보를 유지하지 않는 대신 IP 헤더 내 DSCP 필드를 사용하여 차등화하여 패킷을 분류하고 이에 맞게 처리합니다. 즉, 개별 플로우마다 상태 정보를 유지할 필요가 없기 때문에 대규모의 네트워크에서도 확장하기에 용이합니다.
결론적으로 IntServ의 RSVP를 활용한 자원 예약 방식은 각 플로우마다 라우터들의 상태 정보 유지를 필요로 하고 이러한 작동 방식은 관리가 복잡하므로 DSCP 필드에 마킹하여 각 패킷별로 다르게 처리하는 DiffServ가 효율과 확장성에 있어서 훨씬 유리합니다.
QoS를 위해 실시간성이 중요한 음성 데이터를 보다 실시간성에 덜 민감한 일반데이터보다 우선순위를 높게 부여하여 처리하는 것입니다.
위 그림은 DiffServ 아키텍처이고 큰 두 원은 서로 다른 네트워크망을 의미합니다. 송신자가 수신자에게 패킷을 전달하기까지의 과정이며 동일한 네트워크 망안에서 라우터별로 이동할 때는 코어 라우터를 거치면서 이동하게 되고 두 네트워크 망 사이에서 위치한 엣지 라우터에서 트래픽을 분류, 마킹, 폴리싱합니다. 즉, 엣지 라우터에서 DSCP 값을 설정하고 이에 맞게 트래픽 흐름이나 대역폭을 조절하였다면 코어 라우터에서는 DSCP 값을 기반으로 패킷을 처리하게 되며 DSCP 값에 따라 라우터의 행동은 달라지게 됩니다.
엣지 라우터 내부에서는 Classifier와 Marker, Traffic Conditioner 기능을 하게 되고 코어 라우터는 엣지 라우터에서 트래픽 프로필에 따라 분류한 DSCP 값을 보고 구분하여 차별화된 처리를 하게 됩니다.
코어 라우터의 차별화된 처리 또는 행동들은 4가지로 구분됩니다. 가장 먼 저 기본적으로 적용되는 BE가 있고 EF, AF, CS가 존재합니다.
여기까지 이 DSCP가 어떻게 사용되는지와 왜 사용되는지에 대해서 알아보았다면 이번에는 이 DSCP 값들이 무엇을 의미하는지에 대해서 알아보겠습니다.
DSCP는 6비트를 사용하므로 2의 6승인 총 64가지 바이너리들로 표현되지만 일반적으로 사용되는 바이너리와 DSCP 이름, IP 우선순위를 위 표에서 확인할 수 있습니다.
다음으로 제가 캡쳐한 패킷의 아이피 헤더 내 DSCP는 모두 0으로 이는 기본 홉별행위인 BE를 사용하게 됩니다. BE의 특성으로 보았을 때 현재 이 패킷은 별도의 QoS 보장이 없으며 인터넷 내 라우터들을 지나면서 병목이나 과부하가 발생할 시에 패킷 손실이 발생할 수 있게 됩니다.
Total Length
Total Length 필드는 헤더와 데이터를 모두 포함한 길이(크기)를 나타내며 이전에 확인했다시피 아이피 헤더가 20바이트였으므로 전체 길이에서 헤더 길이를 제외한 32바이트가 데이터의 길이라는 것을 계산할 수 있습니다.
단편화에 사용되는 필드
연속으로 세 필드를 모두 보여드렸는데요. 이러한 이유는 Identification과 Flags 그리고 Fragment Offset은 모두 패킷의 단편화(분할)과 재조립과 관련된 필드이기 때문입니다. 여기서 Flags는 총 3비트로 가장 첫번째 비트는 예약된 필드이며 나머지 두 개의 비트를 사용하여 Flags값을 나타냅니다.
먼저, MTU와 MSS의 개념에 대해서 알아보고 가겠습니다. 위 그림에는 현재 이더넷 프레임 내에 캡슐화된 TCP/IP 패킷을 나타냅니다. 여기서 Preamble는 이더넷 프레임이 시작될 것임을 수신측에 알리기 위한 시퀀스로 수신 장치가 프레임의 시작임을 감지하게 해주고 FCS는 전송 중에 프레임에 발생할 수 있는 오류를 감지하기 위한 값을 나타내며 프레임의 마지막을 나타냅니다.
실제 물리적 네트워크를 통해 패킷을 전송하기 위해서는 물리 계층에 해당하는 정보가 필요합니다. 그래서 데이터 링크 계층 (TCP/IP 모델에서의 네트워크 인터페이스 계층)에서 MAC Header와 FCS가 추가됩니다. 간단히 말하면, 인터넷을 통과하는 패킷 자체에는 IP Header, TCP/UDP Header, Data만 포함될 수 있지만, 실제로 이 패킷을 물리적인 네트워크를 통해 전송하기 위해선 추가적인 헤더와 정보가 필요합니다.
따라서, 실제 패킷이 송신 호스트에서 라우터 또는 스위치 등의 네트워크 장비, 그리고 수신 호스트까지 전달되는 도중에는 데이터 링크 계층의 정보인 MAC 주소가 포함됩니다.
인터넷을 통과하는 패킷의 핵심적인 부분은 IP Header, TCP/UDP Header, 그리고 Data이며 이 정보들은 목적지까지 변하지 않습니다. 그러나, 이 패킷이 인터넷상의 라우터나 스위치 등의 네트워크 장비를 지날 때마다 데이터 링크 계층에서의 정보, 특히 MAC 주소는 갱신됩니다.
라우터가 패킷을 수신할 때, 원래의 MAC 헤더와 FCS를 제거하고, IP 헤더를 확인하여 패킷의 다음 목적지 (다음 홉)를 결정합니다. 이후 새로운 MAC 헤더 (새로운 목적지와 출발지 MAC 주소를 포함)와 FCS를 패킷에 추가한 후, 해당 패킷을 다음 홉으로 전송합니다.
이렇게 패킷은 네트워크를 건너는 동안 여러 번의 MAC 주소 갱신을 겪게 됩니다
라우터는 라우팅 테이블을 확인 후에 다음 라우터(다음 홉)으로 패킷을 전달하기 위해 ARP를 통해서 다음 라우터의 MAC 주소를 알아내고 MAC 헤더의 목적지 MAC 주소를 다음 라우터 장비의 MAC 주소로 갱신합니다.
MAC 주소가 갱신되며 다음 장치로 패킷이 전달될 때 IP 패킷의 크기가 해당 장치가 가지고 있는 MTU값보다 클 경우 해당 장치에서 패킷을 분할하게 됩니다.
Identification
실제로 단편화된 패킷을 캡쳐하기 위해서 IP 헤더 내 Flags 필드의 MF 부분이 1인 패킷으로 필터링하였지만 단편화된 패킷을 발견할 수 없어 직 접 단편화된 패킷을 구글 DNS 서버로 전송했습니다. 총 1500바이트의 Payload(Data)를 갖는 ICMP 패킷을 500바이트씩 단편화하여 전송하도록 했고 이렇게 500바이트씩 분할될 패킷들을 식별하기 위해 identification을 0x1111로 지정하였습니다.
0x1111로 필터링한 결과로 총 4개의 패킷을 발견할 수 있었는데 각 패킷은 530바이트의 길이를 갖는 패킷 3개와 54바이트의 길이를 갖는 패킷 1개로 분할된 것을 확인할 수 있었습니다. 여기서 530바이트의 길이를 갖는 패킷은 초록색으로 54바이트의 길이를 갖는 패킷은 빨간색으로 표시하였으며 MAC 헤더의 길이와 IP 헤더의 길이, Data길이를 각각 표시하였습니다.
실제로는 500바이트씩 단편화하도록 지정했지만 실제로는 496바이트의 페이로드로 단편화된 것을 확인할 수 있었습니다. 여기서 가장 중요한 것은 제가 분할된 각 4개의 패킷들이 모두 동일한 identification 값을 갖는다는 것을 알고 이를 식별자로 필터링하여 구분했다는 것입니다.
아래는 제가 사용한 파이썬 코드입니다.
from scapy.all import *
# 대상 IP 주소
dst_ip = "8.8.8.8"
# 큰 데이터를 포함하는 ICMP 패킷 생성
icmp = ICMP()
payload = "A" * 1500 # 1500 바이트의 데이터
packet = IP(dst=dst_ip, id=0x1111, flags="MF") / icmp / payload
# 패킷을 단편화하고 전송
frags = fragment(packet, fragsize=500) # 500바이트 단위로 단편화
for frag in frags:
send(frag)
# 각 단편화된 패킷의 요약을 출력
for frag in frags:
print(frag.summary())
Flags
앞 부분 세개의 패킷의 IP Header 내 Flags 필드엔 MF(More fragments)가 1로 표시되어 있고 마지막 패킷에는 DF(Don’t Fragment)가 1로 표시되어 있었습니다. 이것이 의미하는 것은 MF일 경우 현재 이 패킷이 분할된 패킷임을 나타내고 뒤에 더 분할된 패킷이 온다는 것을 알게 합니다.
반면에 DF일 경우 현재 이 패킷 뒤에는 더 이상 분할된 패킷이 없다는 것을 의미합니다. 여기서는 모두 분할된 패킷이므로 이 패킷이 분할된 패킷이자 마지막 패킷임을 나타내지만 분할되지 않은 패킷에서는 말 그대로 분할되지 않은 패킷을 의미하게 됩니다. 뒤에서 설명할 Fragment Offset 필드로 각 분할된 패킷의 순서를 알 수도 있지만 마지막 패킷을 찾아내기 위해서 식별자로 분할 패킷을 구분하고 플래그 값으로 마지막 패킷은 알 수 있게 됩니다.
Fragment Offset
중간 라우터에서 분할된 패킷은 수신자 입장에서 어떤 순서로 패킷이 분할되었는지 알 수 있어야 하고 알게 된다면 분할된 순서에 맞게 패킷을 다시 재조립하여 원본 패킷으로 데이터를 올바르게 확인할 수 있어야 합니다. 그래서 수신자는 Fragment Offset 필드의 값을 보고 패킷의 순서를 알 수 있게 됩니다.
이 Fragment offset은 13비트를 갖는데 이는 최대 8192로 표현할 수 있게 되지만 수가 작기 때문에 8바이트 단위로 표현하여 64KB까지 표현할 수 있습니다. 즉 최대 64KB까지 표현할 수 있다는 것은 단편화된 패킷이 가질 수 있는 최대 Payload 사이즈는 64KB임을 짐작하게 합니다.
실제로 와이어샤크로 분할된 패킷을 캡쳐하고 Fragment Offset을 확인하였을 때 이 2진수를 10진수로 변환 후 8바이트 단위로 표현하기 위해 8을 곱셈하여 496이라는 값을 얻었습니다. 이 숫자가 의미하는 바는 이전 패킷의 크기가 496바이트까지 였고 현재 패킷은 496바이트 다음부터 시작한다는 것을 알 수 있습니다.
IP 헤더의 분할 및 재조립에 사용되는 세 필드의 기능을 요약하기 위해 최소 2400 바이트의 Payload를 가진 패킷을 1200 바이트씩 3부분으로 분할하였을 때의 원본 패킷과 분할된 패킷의 그림을 예시로 들겠습니다. 분할된 세 패킷은 동일한 identification을 갖게 되고 첫 번째와 두 번째 패킷은 분할된 패킷이라는 의미로 MF(More Fragments)에 1을 가지고 마지막 패킷은 DF(Don’t Fragments)에 1을 가지게 됩니다. Fragment offset 값에는 8을 곱하여 현재 패킷의 시작 위치가 어디부터인지도 알 수 있게 됩니다.
즉 id로 패킷을 식별하고 Flag로 각 패킷의 단편화 여부를 확인하고 Fragment Offset으로 단편화된 패킷의 시작위치를 알 수 있는 것입니다.
Time To Live
만약 TTL 필드가 없다면 무한루프에 빠진 패킷들은 지나는 라우터마다 과부하를 일으키게 될 것입니다. 이러한 패킷들이 넘쳐난다면 인터넷 상에서 존재하는 라우터들은 동작하지 않게 되고 인터넷은 제대로 동작하지 않게 됩니다. 따라서 TTL 필드가 각 라우터를 지날 때 (다음 홉으로 전달될 때) 1씩 감소하게 되고 기본 TTL값이 0이 되면 패킷은 소멸됩니다. 즉 TTL은 해당 패킷이 네트워크 상에서 얼마나 오래 존재할 수 있는지를 알 수 있게 해줍니다.
Protocol
프로토콜은 TCP/IP 4계층에서 인터넷 계층에 사용되는 프로토콜을 의미하며 값이 1일 경우 ICMP를 나타냅니다.
Header Checksum
다음으로 해당 패킷이 유효한지 중간에 변조는 없었는지 확인하기 위한 체크섬 필드입니다.