본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.
본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.
이미지 로딩 중...
AI Generated
2025. 12. 5. · 15 Views
IP 프로토콜과 ARP 완벽 가이드
네트워크의 핵심인 IP 프로토콜과 ARP의 동작 원리를 초급 개발자도 쉽게 이해할 수 있도록 설명합니다. 데이터가 인터넷을 통해 목적지까지 어떻게 찾아가는지, 그 여정을 함께 따라가 봅시다.
목차
1. 데이터 링크 계층의 한계
김개발 씨는 회사에서 두 건물 간의 네트워크를 연결하는 프로젝트를 맡게 되었습니다. 같은 건물 내에서는 컴퓨터끼리 통신이 잘 되는데, 다른 건물에 있는 서버와는 왜 직접 통신이 안 되는 걸까요?
선배에게 물어보니 "데이터 링크 계층의 한계 때문이야"라는 답이 돌아왔습니다.
데이터 링크 계층은 같은 네트워크 내에서만 통신할 수 있다는 근본적인 한계를 가지고 있습니다. 마치 같은 아파트 단지 내에서만 사용할 수 있는 내선 전화와 같습니다.
이 한계를 극복하기 위해 상위 계층인 네트워크 계층이 필요하게 되었습니다.
다음 코드를 살펴봅시다.
# 데이터 링크 계층의 특성을 보여주는 예시
class DataLinkLayer:
def __init__(self, network_id):
self.network_id = network_id # 소속 네트워크 식별자
self.mac_table = {} # MAC 주소 테이블
def can_communicate(self, target_network_id):
# 같은 네트워크 내에서만 직접 통신 가능
if self.network_id == target_network_id:
return True, "직접 통신 가능"
else:
return False, "다른 네트워크 - 라우터 필요"
def send_frame(self, dest_mac, data):
# MAC 주소 기반 프레임 전송
print(f"프레임 전송: {dest_mac} -> {data[:20]}...")
김개발 씨는 네트워크 기초를 배우면서 OSI 7계층이라는 개념을 접했습니다. 그중에서도 2계층인 데이터 링크 계층은 이더넷이나 와이파이 같은 기술이 동작하는 곳입니다.
하지만 이 계층에는 치명적인 한계가 있었습니다. 박시니어 씨가 화이트보드에 그림을 그리며 설명을 시작했습니다.
"데이터 링크 계층은 마치 아파트 단지 내 내선 전화 시스템과 같아요." 같은 아파트 단지에 사는 주민들끼리는 내선 번호만으로 통화할 수 있습니다. 101호에서 505호로 전화하려면 그냥 505를 누르면 됩니다.
하지만 옆 단지에 있는 친구에게 전화하려면 어떻게 해야 할까요? 내선 번호만으로는 불가능합니다.
데이터 링크 계층도 마찬가지입니다. 이 계층에서 사용하는 MAC 주소는 같은 네트워크 내에서만 의미가 있습니다.
MAC 주소는 48비트로 구성된 물리적 주소로, 네트워크 장비 제조 시 고유하게 부여됩니다. 문제는 MAC 주소가 계층적 구조를 가지지 않는다는 점입니다.
마치 주민등록번호처럼 고유하긴 하지만, 그 번호만 보고 그 사람이 어디 사는지 알 수 없는 것과 같습니다. 서울에 사는 사람인지, 부산에 사는 사람인지 주민등록번호만으로는 알 수 없습니다.
실제로 MAC 주소 AA:BB:CC:DD:EE:FF를 가진 컴퓨터가 어디에 있는지 전 세계 네트워크에서 찾으려면 어떻게 해야 할까요? 모든 네트워크에 물어봐야 합니다.
이것은 현실적으로 불가능합니다. 또한 데이터 링크 계층은 브로드캐스트 도메인이라는 개념으로 묶여 있습니다.
같은 브로드캐스트 도메인 안에서는 한 컴퓨터가 보낸 브로드캐스트 메시지가 모든 컴퓨터에 전달됩니다. 하지만 이 영역을 벗어나면 메시지가 전달되지 않습니다.
스위치라는 장비가 있습니다. 스위치는 데이터 링크 계층에서 동작하며, MAC 주소를 학습하여 효율적으로 프레임을 전달합니다.
하지만 스위치도 자신이 관리하는 네트워크 범위를 벗어난 목적지로는 데이터를 보낼 수 없습니다. 김개발 씨가 고개를 끄덕였습니다.
"그래서 라우터가 필요한 거군요?" 박시니어 씨가 미소를 지었습니다. "맞아요.
그리고 라우터가 사용하는 것이 바로 IP 프로토콜이에요." 데이터 링크 계층의 한계를 정리하면 이렇습니다. 첫째, 같은 네트워크 내에서만 통신 가능합니다.
둘째, MAC 주소는 위치 정보를 담고 있지 않습니다. 셋째, 전 세계적인 라우팅이 불가능합니다.
이러한 한계를 극복하기 위해 네트워크 계층이 등장했고, 그 핵심이 바로 IP 프로토콜입니다.
실전 팁
💡 - MAC 주소는 하드웨어에 고정된 물리적 주소이고, IP 주소는 논리적으로 할당되는 주소입니다
- 같은 네트워크 내 통신은 MAC 주소로, 다른 네트워크 간 통신은 IP 주소로 이루어집니다
2. 인터넷 프로토콜 IP 개요
김개발 씨는 집에서 미국에 있는 서버에 접속할 때마다 신기했습니다. 어떻게 내 컴퓨터가 지구 반대편에 있는 서버를 찾아갈 수 있는 걸까요?
그 비밀은 바로 **IP(Internet Protocol)**에 있었습니다.
IP는 서로 다른 네트워크 간에 데이터를 전달하기 위한 프로토콜입니다. 마치 전 세계 어디든 배달할 수 있는 국제 택배 시스템과 같습니다.
IP 주소라는 논리적 주소 체계를 사용하여 목적지를 식별하고, 라우팅을 통해 최적의 경로로 데이터를 전달합니다.
다음 코드를 살펴봅시다.
import socket
# IP 프로토콜의 기본 개념을 보여주는 예시
class IPProtocol:
def __init__(self):
self.version = 4 # IPv4 사용
self.ttl = 64 # Time To Live 기본값
def create_packet(self, src_ip, dest_ip, data):
# IP 패킷 생성
packet = {
"version": self.version,
"src_ip": src_ip, # 출발지 IP
"dest_ip": dest_ip, # 목적지 IP
"ttl": self.ttl, # 생존 시간
"data": data # 페이로드
}
return packet
def get_my_ip(self):
# 현재 컴퓨터의 IP 주소 확인
hostname = socket.gethostname()
return socket.gethostbyname(hostname)
이제 본격적으로 IP 프로토콜의 세계로 들어가 봅시다. 김개발 씨가 카페에서 노트북을 열고 구글에 접속합니다.
이 단순한 행동 뒤에는 IP 프로토콜이 묵묵히 일하고 있습니다. **IP(Internet Protocol)**는 이름 그대로 인터넷의 핵심 프로토콜입니다.
인터넷이라는 거대한 네트워크에서 데이터가 목적지를 찾아갈 수 있도록 해주는 내비게이션 시스템이라고 할 수 있습니다. 박시니어 씨가 비유를 들어 설명했습니다.
"택배를 생각해봐요. 택배를 보내려면 뭐가 필요하죠?" 김개발 씨가 대답했습니다.
"보내는 사람 주소와 받는 사람 주소요." 바로 그것입니다. IP도 마찬가지입니다.
데이터를 보내려면 출발지 IP 주소와 목적지 IP 주소가 필요합니다. 하지만 IP의 역할은 단순히 주소를 붙이는 것만이 아닙니다.
IP는 라우팅이라는 중요한 기능을 수행합니다. 라우팅이란 데이터가 목적지까지 가는 최적의 경로를 찾는 것입니다.
서울에서 부산으로 가는 길이 여러 가지 있듯이, 인터넷에서도 출발지에서 목적지까지 가는 경로가 여러 가지입니다. IP 프로토콜과 라우터들은 이 중에서 가장 효율적인 경로를 선택합니다.
IP의 특징 중 하나는 비연결형 프로토콜이라는 점입니다. 전화를 걸면 상대방과 연결이 유지되는 것과 달리, IP는 각 패킷을 독립적으로 처리합니다.
마치 편지를 보내는 것과 같습니다. 편지를 보내고 나면 그 편지가 어떤 경로로 갔는지 신경 쓰지 않습니다.
또한 IP는 최선형(Best-Effort) 서비스를 제공합니다. 이 말은 데이터가 목적지에 반드시 도착한다고 보장하지 않는다는 뜻입니다.
패킷이 중간에 손실될 수도 있고, 순서가 뒤바뀔 수도 있습니다. 김개발 씨가 놀랐습니다.
"그러면 데이터가 제대로 안 갈 수도 있다는 건가요?" 박시니어 씨가 고개를 끄덕였습니다. "그래서 상위 계층에 TCP가 있는 거예요.
IP는 전달에만 집중하고, 신뢰성은 TCP가 담당해요." 이것이 바로 계층화의 장점입니다. 각 계층이 자신의 역할에만 집중함으로써 전체 시스템이 효율적으로 동작합니다.
IP 프로토콜은 패킷이라는 단위로 데이터를 전송합니다. 큰 데이터는 여러 개의 작은 패킷으로 나누어지고, 각 패킷은 독립적으로 목적지를 향해 떠납니다.
목적지에 도착하면 다시 원래 데이터로 조립됩니다. 현재 인터넷에서는 두 가지 버전의 IP가 사용됩니다.
IPv4와 IPv6입니다. IPv4가 오랫동안 사용되어 왔지만, 주소 고갈 문제로 인해 IPv6로의 전환이 진행 중입니다.
실전 팁
💡 - IP는 데이터 전달만 담당하고, 신뢰성 보장은 TCP의 역할입니다
- 패킷은 독립적으로 전송되므로 같은 데이터의 패킷들도 다른 경로로 갈 수 있습니다
3. IP 주소의 형태와 구조
김개발 씨가 서버 설정을 하다가 192.168.1.100이라는 숫자를 보게 되었습니다. "이게 IP 주소라는 건 알겠는데, 왜 이런 형태인 거죠?" 평소에 아무 생각 없이 사용하던 IP 주소, 그 안에는 치밀한 설계가 숨어 있었습니다.
IP 주소는 네트워크에서 장치를 식별하는 논리적 주소입니다. IPv4 주소는 32비트로 구성되며, 점으로 구분된 4개의 숫자(옥텟)로 표현됩니다.
각 옥텟은 0부터 255까지의 값을 가지며, 네트워크 부분과 호스트 부분으로 나뉩니다.
다음 코드를 살펴봅시다.
import ipaddress
# IP 주소 구조 분석
def analyze_ip_address(ip_str, prefix_length):
# IP 주소 객체 생성
network = ipaddress.ip_network(f"{ip_str}/{prefix_length}", strict=False)
ip = ipaddress.ip_address(ip_str)
print(f"IP 주소: {ip}")
print(f"네트워크 주소: {network.network_address}")
print(f"브로드캐스트 주소: {network.broadcast_address}")
print(f"서브넷 마스크: {network.netmask}")
print(f"사용 가능한 호스트 수: {network.num_addresses - 2}")
# 이진수로 변환
binary = '.'.join(format(int(x), '08b') for x in ip_str.split('.'))
print(f"이진수 표현: {binary}")
# 예시 실행
analyze_ip_address("192.168.1.100", 24)
우리가 흔히 보는 192.168.1.100 같은 IP 주소, 이것이 어떻게 구성되어 있는지 자세히 살펴봅시다. 박시니어 씨가 종이에 IP 주소를 적으며 설명을 시작했습니다.
"IP 주소는 마치 우편 주소와 같아요. 서울시 강남구 역삼동처럼 계층적인 구조를 가지고 있죠." IPv4 주소는 총 32비트로 구성됩니다.
32개의 0과 1로 이루어진 숫자인 것입니다. 하지만 11000000101010000000000101100100 이렇게 쓰면 사람이 읽기 어렵겠죠?
그래서 32비트를 8비트씩 4개로 나누고, 각각을 10진수로 변환하여 점으로 구분합니다. 이것이 우리가 흔히 보는 **점으로 구분된 10진수 표기법(Dotted Decimal Notation)**입니다.
8비트가 표현할 수 있는 숫자의 범위는 0부터 255까지입니다. 따라서 각 옥텟은 0.0.0.0부터 255.255.255.255까지의 값을 가질 수 있습니다.
이론적으로 약 43억 개의 주소가 가능합니다. IP 주소의 핵심은 네트워크 부분과 호스트 부분으로 나뉜다는 점입니다.
우편 주소로 비유하면 네트워크 부분은 도시 이름이고, 호스트 부분은 상세 주소입니다. 예를 들어 192.168.1.100/24라는 주소가 있다면, /24는 앞의 24비트가 네트워크 부분이라는 뜻입니다.
즉 192.168.1까지가 네트워크 주소이고, 마지막 100이 호스트 주소입니다. 서브넷 마스크는 어디까지가 네트워크 부분인지 알려주는 역할을 합니다.
/24는 서브넷 마스크 255.255.255.0과 같습니다. 255인 부분이 네트워크, 0인 부분이 호스트입니다.
김개발 씨가 질문했습니다. "그러면 같은 192.168.1.x 주소를 가진 컴퓨터들은 같은 네트워크에 있는 건가요?" 정확합니다.
네트워크 부분이 같으면 같은 네트워크에 속한 것입니다. 같은 네트워크 내에서는 라우터 없이 직접 통신이 가능합니다.
특별한 주소들도 있습니다. 호스트 부분이 모두 0인 주소는 네트워크 주소입니다.
192.168.1.0은 192.168.1.0/24 네트워크 자체를 가리킵니다. 호스트 부분이 모두 1인 주소는 브로드캐스트 주소입니다.
192.168.1.255로 보낸 데이터는 해당 네트워크의 모든 장치에 전달됩니다. 사설 IP 주소라는 개념도 있습니다.
10.x.x.x, 172.16.x.x~172.31.x.x, 192.168.x.x 대역은 사설 네트워크에서만 사용하도록 예약되어 있습니다. 이 주소들은 인터넷에서 직접 라우팅되지 않으므로, 여러 조직에서 중복해서 사용할 수 있습니다.
우리 집 공유기가 192.168.0.1이고, 옆집 공유기도 192.168.0.1일 수 있는 이유가 바로 이것입니다. 사설 IP는 각 네트워크 내에서만 유효하기 때문입니다.
공인 IP 주소는 인터넷에서 고유한 주소입니다. 인터넷에 직접 연결되려면 공인 IP가 필요합니다.
ISP(인터넷 서비스 제공자)가 이 주소를 할당해줍니다.
실전 팁
💡 - 192.168.x.x, 10.x.x.x는 사설 IP로 내부 네트워크에서 자유롭게 사용할 수 있습니다
- 서브넷 마스크를 이해하면 네트워크 설계와 문제 해결이 쉬워집니다
4. IP의 핵심 기능
김개발 씨가 traceroute 명령어를 실행해 보았습니다. 자신의 컴퓨터에서 구글 서버까지 데이터가 어떤 경로로 가는지 보여주는 명령어입니다.
화면에는 수십 개의 라우터가 나열되었습니다. "와, 이렇게 많은 곳을 거쳐서 가는 거였어?"
IP의 핵심 기능은 주소 지정, 라우팅, 단편화와 재조립입니다. 주소 지정을 통해 목적지를 식별하고, 라우팅을 통해 최적 경로를 찾으며, 단편화를 통해 다양한 네트워크를 통과할 수 있습니다.
다음 코드를 살펴봅시다.
import subprocess
# IP의 핵심 기능을 보여주는 예시
class IPCoreFunctions:
def __init__(self, mtu=1500):
self.mtu = mtu # Maximum Transmission Unit
def addressing(self, src, dest):
# 주소 지정: 출발지와 목적지 식별
return {"source": src, "destination": dest}
def should_fragment(self, packet_size):
# 단편화 필요 여부 판단
if packet_size > self.mtu:
fragments_needed = (packet_size // self.mtu) + 1
return True, fragments_needed
return False, 1
def trace_route(self, destination):
# 경로 추적 (라우팅 경로 확인)
try:
result = subprocess.run(
["traceroute", "-m", "10", destination],
capture_output=True, text=True, timeout=30
)
return result.stdout
except Exception as e:
return f"추적 실패: {e}"
IP 프로토콜이 어떤 일을 하는지 좀 더 구체적으로 알아봅시다. IP의 핵심 기능은 크게 세 가지로 나눌 수 있습니다.
첫 번째는 **주소 지정(Addressing)**입니다. 앞서 살펴본 것처럼 IP는 32비트(IPv4) 또는 128비트(IPv6) 주소를 사용하여 네트워크상의 모든 장치를 고유하게 식별합니다.
박시니어 씨가 설명했습니다. "주소 지정이 왜 중요하냐면, 이것이 없으면 데이터가 어디로 가야 할지 모르기 때문이에요.
마치 주소 없는 편지와 같죠." 두 번째 핵심 기능은 **라우팅(Routing)**입니다. 라우팅은 데이터 패킷이 출발지에서 목적지까지 가는 경로를 결정하는 과정입니다.
인터넷은 수많은 네트워크가 연결된 거대한 그물망입니다. 서울에서 뉴욕까지 가는 경로가 하나만 있는 것이 아닙니다.
태평양을 건너갈 수도 있고, 유럽을 거쳐갈 수도 있습니다. 라우터라는 장비가 이 경로 결정을 담당합니다.
각 라우터는 라우팅 테이블을 가지고 있어서, 목적지 주소를 보고 "이 패킷은 저쪽으로 보내야겠다"라고 판단합니다. 김개발 씨가 터미널에서 traceroute google.com을 실행했습니다.
결과를 보니 자신의 패킷이 약 15개의 라우터를 거쳐 구글 서버에 도착하는 것을 알 수 있었습니다. 세 번째 핵심 기능은 **단편화와 재조립(Fragmentation and Reassembly)**입니다.
네트워크마다 한 번에 전송할 수 있는 데이터 크기가 다릅니다. 이것을 **MTU(Maximum Transmission Unit)**라고 합니다.
이더넷의 MTU는 보통 1500바이트입니다. 만약 3000바이트 크기의 데이터를 보내야 한다면 어떻게 할까요?
IP는 이 데이터를 여러 개의 작은 조각으로 나눕니다. 이것이 단편화입니다.
각 조각에는 원래 데이터에서 몇 번째 조각인지를 나타내는 정보가 포함됩니다. 목적지에 도착하면 이 정보를 바탕으로 원래 데이터로 재조립됩니다.
김개발 씨가 궁금해했습니다. "조각이 중간에 하나라도 손실되면 어떻게 되나요?" 박시니어 씨가 답했습니다.
"모든 조각이 도착해야 재조립이 가능해요. 하나라도 없으면 전체 패킷을 버리게 됩니다." 이 외에도 IP는 TTL(Time To Live) 기능을 제공합니다.
TTL은 패킷이 네트워크에서 살아있을 수 있는 시간을 의미합니다. 실제로는 시간이 아니라 거칠 수 있는 라우터 수를 나타냅니다.
패킷이 라우터를 지날 때마다 TTL 값이 1씩 감소합니다. TTL이 0이 되면 패킷은 폐기됩니다.
이것은 잘못 설정된 라우팅으로 인해 패킷이 무한히 네트워크를 떠도는 것을 방지합니다. 이러한 기능들이 조화롭게 동작하여 우리가 인터넷을 자유롭게 사용할 수 있게 해줍니다.
실전 팁
💡 - traceroute(Windows에서는 tracert) 명령어로 패킷의 경로를 확인할 수 있습니다
- MTU 불일치로 인한 문제는 네트워크 트러블슈팅에서 자주 마주치는 이슈입니다
5. IPv4 프로토콜 상세
서버 로그를 분석하던 김개발 씨가 이상한 점을 발견했습니다. 어떤 패킷은 정상적으로 처리되었는데, 어떤 패킷은 중간에 손실된 것 같습니다.
"IP 패킷 안에는 도대체 뭐가 들어있는 거지?" IPv4 헤더의 세계로 들어가 봅시다.
IPv4 헤더는 최소 20바이트로 구성되며, 버전, 헤더 길이, 서비스 타입, 전체 길이, 식별자, 플래그, TTL, 프로토콜, 체크섬, 출발지 주소, 목적지 주소 등의 필드를 포함합니다. 이 정보들이 패킷 전달의 모든 과정을 제어합니다.
다음 코드를 살펴봅시다.
import struct
# IPv4 헤더 구조 분석
class IPv4Header:
def __init__(self):
self.version = 4
self.ihl = 5 # Internet Header Length (20 bytes)
self.ttl = 64
self.protocol = 6 # TCP
def parse_header(self, raw_data):
# 첫 20바이트에서 헤더 정보 추출
header = struct.unpack('!BBHHHBBH4s4s', raw_data[:20])
version_ihl = header[0]
version = version_ihl >> 4 # 상위 4비트: 버전
ihl = (version_ihl & 0xF) * 4 # 하위 4비트: 헤더 길이
return {
"version": version,
"header_length": ihl,
"total_length": header[2],
"ttl": header[5],
"protocol": header[6], # 6=TCP, 17=UDP, 1=ICMP
"src_ip": self.bytes_to_ip(header[8]),
"dest_ip": self.bytes_to_ip(header[9])
}
def bytes_to_ip(self, addr_bytes):
return '.'.join(str(b) for b in addr_bytes)
IPv4 패킷의 구조를 상세히 살펴보겠습니다. IP 패킷은 헤더와 **페이로드(데이터)**로 구성됩니다.
헤더에는 패킷을 목적지까지 전달하는 데 필요한 모든 정보가 담겨 있습니다. 박시니어 씨가 화이트보드에 IPv4 헤더 구조를 그렸습니다.
"택배 상자에 붙어있는 송장이라고 생각하면 돼요. 거기에 보내는 사람, 받는 사람, 취급 주의 사항 등이 적혀있잖아요?" IPv4 헤더는 최소 20바이트입니다.
옵션 필드가 있으면 최대 60바이트까지 늘어날 수 있습니다. 각 필드를 하나씩 살펴봅시다.
버전(Version) 필드는 4비트로, IP 프로토콜의 버전을 나타냅니다. IPv4이므로 당연히 4입니다.
헤더 길이(IHL, Internet Header Length) 필드도 4비트입니다. 헤더의 길이를 4바이트 단위로 나타냅니다.
옵션이 없으면 5(20바이트), 옵션이 있으면 더 커집니다. 서비스 타입(Type of Service) 필드는 8비트로, 패킷의 우선순위나 서비스 품질(QoS)을 나타냅니다.
요즘은 DSCP(Differentiated Services Code Point)라고 불리기도 합니다. 전체 길이(Total Length) 필드는 16비트로, 헤더와 데이터를 포함한 전체 패킷 크기를 바이트 단위로 나타냅니다.
최대 65,535바이트까지 가능하지만, 실제로는 MTU 제한 때문에 그보다 작습니다. 식별자(Identification), 플래그(Flags), 단편 오프셋(Fragment Offset) 필드는 단편화와 관련된 필드입니다.
큰 패킷을 나눌 때 어떤 패킷에서 나온 조각인지, 몇 번째 조각인지를 나타냅니다. 김개발 씨가 질문했습니다.
"플래그에는 뭐가 있나요?" 박시니어 씨가 답했습니다. "DF(Don't Fragment)와 MF(More Fragments)가 있어요.
DF가 설정되면 이 패킷은 절대 쪼개지 말라는 뜻이에요." TTL(Time To Live) 필드는 8비트로, 패킷의 수명을 나타냅니다. 라우터를 지날 때마다 1씩 감소하고, 0이 되면 패킷은 폐기됩니다.
보통 64나 128로 설정합니다. 프로토콜(Protocol) 필드는 8비트로, 페이로드에 어떤 상위 프로토콜의 데이터가 담겨있는지 나타냅니다.
TCP는 6, UDP는 17, ICMP는 1입니다. 헤더 체크섬(Header Checksum) 필드는 16비트로, 헤더의 오류를 검출합니다.
데이터는 검사하지 않습니다. 라우터를 지날 때마다 TTL이 바뀌므로 매번 다시 계산됩니다.
**출발지 주소(Source Address)**와 목적지 주소(Destination Address) 필드는 각각 32비트로, IP 주소를 담고 있습니다. 이것이 IP 헤더에서 가장 중요한 필드입니다.
마지막으로 옵션(Options) 필드가 있습니다. 이것은 선택 사항으로, 보안, 소스 라우팅 등 특수한 기능에 사용됩니다.
하지만 대부분의 패킷에서는 사용하지 않습니다. 이러한 필드들이 조합되어 패킷이 인터넷을 통해 정확하게 전달될 수 있도록 합니다.
실전 팁
💡 - Wireshark 같은 도구로 실제 패킷을 캡처하면 이 헤더 구조를 직접 확인할 수 있습니다
- TTL 값을 보고 패킷이 어디서 왔는지 대략 추측할 수 있습니다 (Linux는 64, Windows는 128이 기본)
6. IPv6 프로토콜과 차이점
어느 날 김개발 씨가 뉴스를 보다가 "IPv4 주소 고갈"이라는 기사를 접했습니다. 43억 개나 되는 주소가 어떻게 다 사라진 걸까요?
그리고 그 대안으로 등장한 IPv6는 무엇이 다른 걸까요?
IPv6는 IPv4의 주소 고갈 문제를 해결하기 위해 등장한 차세대 인터넷 프로토콜입니다. 128비트 주소를 사용하여 사실상 무한한 주소 공간을 제공하며, 헤더 구조가 단순화되고 보안과 자동 설정 기능이 강화되었습니다.
다음 코드를 살펴봅시다.
import ipaddress
# IPv6 주소 다루기
def analyze_ipv6():
# IPv6 주소 생성
ipv6 = ipaddress.ip_address("2001:0db8:85a3:0000:0000:8a2e:0370:7334")
print(f"전체 표기: {ipv6.exploded}")
print(f"축약 표기: {ipv6.compressed}")
# IPv6 네트워크
network = ipaddress.ip_network("2001:db8::/32")
print(f"네트워크: {network}")
print(f"가능한 주소 수: {network.num_addresses}")
# IPv4와 IPv6 비교
ipv4_max = 2 ** 32 # 약 43억
ipv6_max = 2 ** 128 # 천문학적 숫자
print(f"\nIPv4 최대 주소: {ipv4_max:,}")
print(f"IPv6 최대 주소: {ipv6_max}")
print(f"IPv6는 IPv4보다 {ipv6_max // ipv4_max:,}배 많은 주소 제공")
analyze_ipv6()
인터넷이 처음 설계될 때 43억 개의 주소면 충분할 것이라고 생각했습니다. 하지만 스마트폰, IoT 기기 등이 폭발적으로 늘어나면서 IPv4 주소는 고갈되기 시작했습니다.
박시니어 씨가 역사를 설명했습니다. "1990년대부터 이 문제를 예견하고 IPv6 개발이 시작됐어요.
하지만 전환이 생각보다 오래 걸리고 있죠." IPv6의 가장 큰 특징은 128비트 주소를 사용한다는 점입니다. 이것이 얼마나 많은 주소인지 상상하기 어렵습니다.
지구 표면의 모든 제곱밀리미터에 수십억 개의 주소를 할당할 수 있는 양입니다. IPv6 주소는 16비트씩 8개 그룹으로 나누어 콜론으로 구분합니다.
예를 들면 2001:0db8:85a3:0000:0000:8a2e:0370:7334 같은 형태입니다. 너무 길죠?
그래서 축약 규칙이 있습니다. 첫 번째 규칙은 각 그룹에서 앞의 0을 생략할 수 있다는 것입니다.
두 번째 규칙은 연속된 0 그룹을 ::로 한 번만 대체할 수 있다는 것입니다. 위 주소는 2001:db8:85a3::8a2e:370:7334로 줄일 수 있습니다.
김개발 씨가 질문했습니다. "주소 길이 말고 다른 차이점은 뭐가 있나요?" 헤더 구조가 크게 단순화되었습니다.
IPv4 헤더에는 많은 필드가 있었지만, IPv6는 고정 40바이트 헤더를 사용합니다. 옵션은 확장 헤더로 분리했습니다.
이로 인해 라우터의 처리 속도가 빨라집니다. 특히 체크섬 필드가 제거되었습니다.
상위 계층(TCP, UDP)과 하위 계층(이더넷)에서 이미 오류 검출을 하기 때문에 중복이라고 판단한 것입니다. 단편화 처리 방식도 달라졌습니다.
IPv4에서는 라우터가 패킷을 쪼갤 수 있었지만, IPv6에서는 오직 출발지만 단편화할 수 있습니다. 중간 라우터의 부담을 줄인 것입니다.
IPsec이 기본으로 포함되었습니다. IPv4에서는 선택 사항이었던 보안 기능이 IPv6에서는 필수입니다.
이로 인해 종단 간 암호화가 기본적으로 지원됩니다. 자동 설정(Auto-configuration) 기능도 강화되었습니다.
IPv6 장치는 네트워크에 연결되면 자동으로 고유한 주소를 생성할 수 있습니다. DHCP 서버 없이도 가능합니다.
NAT가 필요 없어집니다. IPv4에서는 주소 부족으로 인해 사설 IP와 NAT를 사용해야 했습니다. 하지만 IPv6는 주소가 충분하므로 모든 장치가 공인 주소를 가질 수 있습니다.
그렇다면 왜 아직도 IPv6로 완전히 전환하지 못했을까요? 가장 큰 이유는 호환성입니다.
IPv4와 IPv6는 서로 직접 통신할 수 없습니다. 전 세계의 수많은 장비와 시스템을 한꺼번에 교체하는 것은 현실적으로 불가능합니다.
현재는 듀얼 스택(IPv4와 IPv6 동시 지원), 터널링(IPv6 패킷을 IPv4로 감싸기) 등의 기술을 사용하여 점진적으로 전환하고 있습니다.
실전 팁
💡 - 새로운 서비스를 개발할 때는 IPv6 지원을 고려해야 합니다
- localhost의 IPv6 버전은 ::1입니다
7. ARP 동작 원리
김개발 씨가 궁금해졌습니다. "IP 주소는 알겠는데, 결국 데이터가 실제로 전달되려면 MAC 주소가 필요하잖아요.
IP 주소에서 MAC 주소는 어떻게 알아내죠?" 바로 여기서 ARP가 등장합니다.
**ARP(Address Resolution Protocol)**는 IP 주소를 MAC 주소로 변환하는 프로토콜입니다. 네트워크 계층의 논리적 주소와 데이터 링크 계층의 물리적 주소를 연결하는 다리 역할을 합니다.
브로드캐스트를 통해 원하는 IP의 MAC 주소를 찾아냅니다.
다음 코드를 살펴봅시다.
import subprocess
import re
# ARP 테이블 조회 및 관리
class ARPManager:
def __init__(self):
self.arp_cache = {}
def get_arp_table(self):
# 시스템 ARP 테이블 조회
result = subprocess.run(['arp', '-a'], capture_output=True, text=True)
return result.stdout
def simulate_arp_request(self, target_ip):
# ARP 요청 시뮬레이션
print(f"ARP 요청 브로드캐스트:")
print(f" 발신: 내 MAC, 내 IP")
print(f" 수신: FF:FF:FF:FF:FF:FF (브로드캐스트)")
print(f" 질문: '{target_ip}'의 MAC 주소는?")
def simulate_arp_reply(self, target_ip, target_mac):
# ARP 응답 시뮬레이션
print(f"\nARP 응답 수신:")
print(f" 발신: {target_mac}, {target_ip}")
print(f" 응답: '내 MAC은 {target_mac}입니다'")
# 캐시에 저장
self.arp_cache[target_ip] = target_mac
print(f" -> ARP 캐시에 저장됨")
이제 마지막 퍼즐 조각인 ARP에 대해 알아봅시다. 앞서 배웠듯이 IP 주소는 네트워크 계층에서 사용되고, MAC 주소는 데이터 링크 계층에서 사용됩니다.
하지만 실제로 데이터를 전송하려면 둘 다 필요합니다. 박시니어 씨가 예를 들어 설명했습니다.
"192.168.1.100에 데이터를 보내고 싶다고 해봐요. IP 주소는 알지만 MAC 주소는 몰라요.
이때 어떻게 할까요?" 바로 **ARP(Address Resolution Protocol)**를 사용합니다. ARP는 "IP 주소를 알려줄 테니, MAC 주소를 알려줘"라고 네트워크에 물어보는 프로토콜입니다.
ARP의 동작 방식은 마치 학교 교실에서 선생님이 학생을 찾는 것과 비슷합니다. "김철수 있니?"라고 전체 학생에게 물어보면, 김철수만 "네, 저 여기 있어요"라고 대답하는 것입니다.
구체적인 과정을 살펴봅시다. 먼저 컴퓨터 A가 컴퓨터 B(192.168.1.100)에게 데이터를 보내려고 합니다.
A는 B의 MAC 주소를 모릅니다. A는 **ARP 요청(ARP Request)**을 보냅니다.
이 요청은 브로드캐스트로 전송됩니다. 즉, 같은 네트워크의 모든 장치가 이 메시지를 받습니다.
내용은 "192.168.1.100의 MAC 주소가 뭐야?"입니다. 네트워크의 모든 장치가 이 요청을 받습니다.
하지만 자신의 IP가 192.168.1.100이 아닌 장치들은 그냥 무시합니다. 오직 IP가 192.168.1.100인 컴퓨터 B만 응답합니다.
B는 **ARP 응답(ARP Reply)**을 보냅니다. 이번에는 유니캐스트로, 오직 A에게만 보냅니다.
내용은 "192.168.1.100은 나야, 내 MAC 주소는 AA:BB:CC:DD:EE:FF야"입니다. 김개발 씨가 질문했습니다.
"매번 이렇게 물어보면 너무 비효율적이지 않나요?" 좋은 질문입니다. 그래서 ARP 캐시가 있습니다.
한 번 알아낸 IP-MAC 매핑 정보는 일정 시간 동안 캐시에 저장됩니다. 다음에 같은 IP로 통신할 때는 캐시를 먼저 확인합니다.
터미널에서 arp -a 명령어를 입력하면 현재 ARP 캐시 내용을 볼 수 있습니다. 게이트웨이, 최근에 통신한 장치들의 IP-MAC 정보가 저장되어 있을 것입니다.
ARP 캐시 항목은 보통 몇 분에서 몇 시간 후에 만료됩니다. 만료되면 다시 ARP 요청을 보내야 합니다.
이렇게 하는 이유는 네트워크 구성이 바뀔 수 있기 때문입니다. 하지만 ARP에는 보안 취약점이 있습니다.
ARP 응답을 인증하지 않기 때문에, 악의적인 공격자가 가짜 ARP 응답을 보낼 수 있습니다. 이것을 ARP 스푸핑 또는 ARP 포이즈닝이라고 합니다.
예를 들어 공격자가 "192.168.1.1(게이트웨이)의 MAC은 내 MAC이야"라고 거짓 응답을 보내면, 피해자의 모든 인터넷 트래픽이 공격자를 거쳐가게 됩니다. 중간자 공격(Man-in-the-Middle)이 가능해지는 것입니다.
IPv6에서는 ARP 대신 **NDP(Neighbor Discovery Protocol)**를 사용합니다. NDP는 ICMPv6를 기반으로 하며, 보안이 강화되었습니다.
다시 처음 질문으로 돌아가 봅시다. IP 주소에서 MAC 주소를 어떻게 알아낼까요?
ARP 브로드캐스트로 물어보고, 응답을 받아 캐시에 저장합니다. 이것이 네트워크 계층과 데이터 링크 계층을 연결하는 핵심 메커니즘입니다.
실전 팁
💡 - arp -a 명령어로 현재 ARP 캐시를 확인할 수 있습니다
- 보안을 위해 정적 ARP 항목을 설정하거나 ARP 스푸핑 탐지 도구를 사용할 수 있습니다
이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!
댓글 (0)
함께 보면 좋은 카드 뉴스
Helm 마이크로서비스 패키징 완벽 가이드
Kubernetes 환경에서 마이크로서비스를 효율적으로 패키징하고 배포하는 Helm의 핵심 기능을 실무 중심으로 학습합니다. Chart 생성부터 릴리스 관리까지 체계적으로 다룹니다.
보안 아키텍처 구성 완벽 가이드
프로젝트의 보안을 처음부터 설계하는 방법을 배웁니다. AWS 환경에서 VPC부터 WAF, 암호화, 접근 제어까지 실무에서 바로 적용할 수 있는 보안 아키텍처를 단계별로 구성해봅니다.
AWS Organizations 완벽 가이드
여러 AWS 계정을 체계적으로 관리하고 통합 결제와 보안 정책을 적용하는 방법을 실무 스토리로 쉽게 배워봅니다. 초보 개발자도 바로 이해할 수 있는 친절한 설명과 실전 예제를 제공합니다.
AWS KMS 암호화 완벽 가이드
AWS KMS(Key Management Service)를 활용한 클라우드 데이터 암호화 방법을 초급 개발자를 위해 쉽게 설명합니다. CMK 생성부터 S3, EBS 암호화, 봉투 암호화까지 실무에 필요한 모든 내용을 담았습니다.
AWS Secrets Manager 완벽 가이드
AWS에서 데이터베이스 비밀번호, API 키 등 민감한 정보를 안전하게 관리하는 Secrets Manager의 핵심 개념과 실무 활용법을 배워봅니다. 초급 개발자도 쉽게 따라할 수 있도록 실전 예제와 함께 설명합니다.