왜 계층이 필요한가
통신의 여러 일을 하나의 규격에 몰아넣었을 때의 문제를 보고, 계층과 인터페이스로 일을 나누면 무엇이 좋아지는지 아래 계층을 갈아 끼우는 모델로 확인합니다. 피어 사이의 논리적 통신과 실제 전달 경로, 계층 사이의 서비스를 정리하고, 장애를 계층별로 좁혀 가는 방법을 다룹니다.
1장에서 데이터가 패킷으로 나뉘어 전달된다는 것을 보았습니다. 그런데 그 과정을 자세히 들여다보면 할 일이 아주 많습니다. 전기나 전파 신호를 비트로 바꾸고, 비트를 프레임으로 묶어 같은 링크의 상대에게 보내고, IP 주소를 보고 어느 길로 갈지 정하고, 잃어버린 데이터는 다시 보내고, 마지막으로 응용 프로그램이 알아들을 수 있는 형태로 해석해야 합니다. 이 모든 일을 하나의 거대한 규격으로 처리하면 어떻게 될까요?
모든 일을 한 규격에 넣으면
신호를 만드는 일부터 경로를 정하는 일, 재전송, HTTP 요청의 해석까지 하나의 규격이 모두 맡는다고 해 봅시다. 집의 유선 이더넷을 Wi-Fi로 바꾸려면, 신호를 만드는 부분만 바꾸면 될 것 같지만 그 부분이 경로 결정이나 재전송과 뒤엉켜 있다면 규격 전체를 다시 살펴야 합니다. 새로운 응용 프로그램을 하나 만들 때마다 신호 처리부터 다시 짜야 할지도 모릅니다. 소프트웨어에서 모든 일을 한 덩어리가 맡는 이런 구조를 모놀리식(monolithic)이라고 부르는데, 네트워크도 소프트웨어와 마찬가지로 이 문제를 계층화(layering)로 풀었습니다.
계층으로 나누면 좋아지는 것
계층화는 통신의 일을 역할별로 나누어 여러 계층에 맡기는 것입니다. 각 계층은 자기 일만 하고, 위 계층에는 서비스를 제공하며, 아래 계층의 서비스를 쓸 뿐 그 내부가 어떻게 동작하는지는 알 필요가 없습니다.
이렇게 나누면 세 가지가 좋아집니다. 첫째, 각 계층을 따로 설계하고 구현할 수 있어서, 물리 계층을 설계하는 엔지니어는 HTTP를 몰라도 되고 웹 개발자는 전파의 변조 방식을 몰라도 됩니다. 둘째, 한 계층만 갈아 끼울 수 있어서, 이더넷을 Wi-Fi로 바꾸어도 IP와 TCP는 그대로 씁니다. 셋째, 계층 사이의 약속을 표준으로 정해 두면 서로 다른 회사의 장비와 소프트웨어가 함께 동작합니다.
다음 코드는 IP 계층이 아래의 링크 계층을 정해진 방법으로만 부르는 모델입니다. 실제 라우팅이나 주소 풀이는 하지 않고, 다음 홉의 주소는 고정된 값을 씁니다.
class Ethernet:
def send(self, data, dst_mac):
return f"[이더넷] {dst_mac}에게 {len(data)}바이트를 담은 프레임"
class WiFi:
def send(self, data, dst_mac):
return f"[Wi-Fi] {dst_mac}에게 {len(data)}바이트를 담은 무선 프레임"
class IPLayer:
def __init__(self, link, gateway_ip, gateway_mac):
self.link, self.gateway_ip, self.gateway_mac = link, gateway_ip, gateway_mac
def send(self, data, dst_ip):
packet = b"IPHDR" + data # 진짜 20바이트 헤더 대신 짧은 표시를 붙인다
print(f"[IP] {dst_ip}로 갈 패킷을 다음 홉 {self.gateway_ip}에 넘긴다")
print(self.link.send(packet, self.gateway_mac)) # 아래 계층이 무엇이든 같은 방법으로 부른다
for link in (Ethernet(), WiFi()):
IPLayer(link, "192.168.1.1", "AA:BB:CC:DD:EE:FF").send(b"Hello", "10.0.0.1")[IP] 10.0.0.1로 갈 패킷을 다음 홉 192.168.1.1에 넘긴다
[이더넷] AA:BB:CC:DD:EE:FF에게 10바이트를 담은 프레임
[IP] 10.0.0.1로 갈 패킷을 다음 홉 192.168.1.1에 넘긴다
[Wi-Fi] AA:BB:CC:DD:EE:FF에게 10바이트를 담은 무선 프레임IPLayer는 아래가 이더넷인지 Wi-Fi인지 모르고, send(data, dst_mac)라는 약속만 압니다. 그래서 링크 계층을 갈아 끼워도 IPLayer의 코드는 한 줄도 바뀌지 않았습니다. 5바이트의 데이터가 10바이트가 된 것은 IP 계층이 자기 헤더를 붙였기 때문으로, 이렇게 계층마다 헤더를 붙이는 것이 2장 4편의 캡슐화입니다.
출력에서 프레임의 목적지가 최종 목적지 10.0.0.1이 아니라 다음 홉인 게이트웨이의 MAC 주소라는 점도 눈여겨봅니다. 라우터를 거쳐 가는 경로에서 링크 계층은 같은 링크에 있는 다음 장비까지만 프레임을 보내며, 최종 목적지까지 가는 것은 IP 계층의 일입니다. 물론 현실에서 이더넷을 Wi-Fi로 바꾸면 주소를 받는 방법이나 MTU, 손실률은 달라질 수 있어서, 계층화가 모든 차이를 없애 주는 것은 아닙니다.
소프트웨어 설계와 같은 생각
프로그래머에게 계층화는 익숙한 생각입니다. 웹 서비스의 코드도 화면을 다루는 층, 업무 규칙을 다루는 층, 저장소를 다루는 층으로 나누고, 각 층은 인터페이스 뒤에 구현을 감춥니다. 저장소를 MySQL에서 PostgreSQL로 바꾸어도 업무 규칙의 코드가 그대로이기를 바라는 것처럼, 링크를 바꾸어도 IP가 그대로이기를 바라는 것입니다. 일을 나누어 각 부분이 하나의 관심사만 맡게 하는 이 원칙을 관심사의 분리(separation of concerns)라고 합니다.
피어끼리의 대화와 실제 전달
회사에서 다른 회사로 공식 문서를 보내는 과정을 생각해 봅시다. 작성자가 문서를 쓰고, 비서가 문서를 묶음으로 나누어 번호를 붙이고 빠진 묶음이 있으면 다시 보내 달라고 할 수 있게 관리하며, 우편실이 상대 회사의 주소를 보고 어느 우체국으로 넘길지 정하고, 배달원이 다음 우체국까지 실어 나릅니다. 각각 응용 계층, 전송 계층, 네트워크 계층, 데이터 링크와 물리 계층에 해당합니다.
피어 관계와 실제 전달 경로
응용이 아래 계층의 서비스를 통해 데이터를 보냅니다. 수신 스택을 올라 상대 응용에 전달합니다. 각 단계의 값과 전환 이유를 비교합니다.
LAYERING · LOGICAL PEERS / DATA PATH
논리적 피어 관계와 실제 데이터 전달 경로를 나누어 읽는다
응용·전송·IP의 종단 논리적 관계와 실제 전달 경로를 구분합니다. 데이터는 송신 스택·중간 링크와 라우터·수신 스택을 거칩니다. 링크 계층의 직접 상대는 각 링크의 다음 홉입니다.
점선은 이 예시의 종단 응용·전송·IP 규약 관계입니다. 링크·물리는 라우터 너머의 상대 종단과 같은 링크에서 직접 통신하는 관계가 아니므로 종단 간 점선을 그리지 않았습니다. 실선은 한 방향의 전달 순서입니다.
- 회색 점선: 종단 응용·전송·IP 규약 관계
- 청록 실선·화살표: 한 방향의 전달 순서
이 예의 조건
- 본문과 전체 도표의 예를 따라 송신 하강부터 수신 상승까지 진행합니다.
관계도의 보충 설명
| 구분 | 전체 도표의 내용 |
|---|---|
| 제목 | 피어 관계와 실제 전달 경로 |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 송신 하강 응용이 아래 계층의 서비스를 통해 데이터를 보냅니다. |
|
|
| 다음 홉 경로 각 링크의 다음 홉과 라우터를 거칩니다. |
|
|
| 수신 상승 수신 스택을 올라 상대 응용에 전달합니다. |
|
|
피어 관계와 실제 전달 경로
응용이 아래 계층의 서비스를 통해 데이터를 보냅니다. 수신 스택을 올라 상대 응용에 전달합니다. 각 단계의 값과 전환 이유를 비교합니다.
LAYERING · LOGICAL PEERS / DATA PATH
논리적 피어 관계와 실제 데이터 전달 경로를 나누어 읽는다
응용·전송·IP의 종단 논리적 관계와 실제 전달 경로를 구분합니다. 데이터는 송신 스택·중간 링크와 라우터·수신 스택을 거칩니다. 링크 계층의 직접 상대는 각 링크의 다음 홉입니다.
점선은 이 예시의 종단 응용·전송·IP 규약 관계입니다. 링크·물리는 라우터 너머의 상대 종단과 같은 링크에서 직접 통신하는 관계가 아니므로 종단 간 점선을 그리지 않았습니다. 실선은 한 방향의 전달 순서입니다.
- 회색 점선: 종단 응용·전송·IP 규약 관계
- 청록 실선·화살표: 한 방향의 전달 순서
이 예의 조건
- 본문과 전체 도표의 예를 따라 송신 하강부터 수신 상승까지 진행합니다.
관계도의 보충 설명
| 구분 | 전체 도표의 내용 |
|---|---|
| 제목 | 피어 관계와 실제 전달 경로 |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 송신 하강 응용이 아래 계층의 서비스를 통해 데이터를 보냅니다. |
|
|
| 다음 홉 경로 각 링크의 다음 홉과 라우터를 거칩니다. |
|
|
| 수신 상승 수신 스택을 올라 상대 응용에 전달합니다. |
|
|
받는 회사에서는 이 과정을 거꾸로 거쳐 문서가 수신자에게 닿습니다. 두 회사의 작성자와 수신자는 마치 직접 대화하는 것처럼 느끼지만, 실제로 문서는 보내는 쪽에서 계층을 내려가 배달되고 받는 쪽에서 계층을 올라갑니다. 네트워크에서도 같은 계층끼리, 예를 들어 양쪽의 HTTP끼리는 서로 직접 대화하는 것처럼 정해진 규약을 지키는데, 이를 피어(peer) 사이의 논리적 통신이라고 합니다. 실제 데이터는 그림처럼 아래 계층의 서비스를 거쳐, 중간의 라우터들을 링크 하나씩 지나 전달됩니다.
계층 사이의 서비스와 인터페이스
각 계층은 바로 아래 계층의 서비스를 쓰고, 바로 위 계층에 서비스를 제공합니다.
| 관계 | 뜻 | 예 |
|---|---|---|
| 서비스 제공 | 아래 계층이 위 계층에 해 주는 일 | IP가 TCP에게 패킷을 목적지 호스트까지 전달해 준다 |
| 서비스 사용 | 위 계층이 아래 계층에 맡기는 일 | TCP가 세그먼트를 IP에 맡겨 보낸다 |
| 인터페이스 | 두 계층이 주고받는 방법의 약속 | 응용 프로그램과 전송 계층 사이의 소켓 API |
인터페이스가 바뀌지 않는 한 그 아래의 구현은 얼마든지 바뀔 수 있습니다. 응용 프로그램은 소켓 API만 알면 되므로, 운영체제가 TCP의 혼잡 제어 알고리즘을 바꾸어도 응용 프로그램을 고칠 필요가 없습니다.
장애를 계층별로 좁히기
계층화가 실무에서 가장 쓸모 있는 순간은 장애를 찾을 때입니다. 아래 계층부터 하나씩 확인하면 문제가 있는 계층을 빨리 좁힐 수 있습니다.
ip -br link show eth0 # 링크: 인터페이스가 UP이고 케이블이 연결되었는가(LOWER_UP)
arping -c 3 192.168.1.1 # 데이터 링크: 같은 LAN의 게이트웨이가 ARP에 답하는가
ping -c 4 192.168.1.1 # 네트워크: IP로 게이트웨이에 닿는가
nc -zv example.com 443 # 전송: TCP 443번 포트로 연결되는가(이름 풀이도 필요하다)
curl -v https://example.com # 응용: TLS와 HTTP 응답이 올바른가장치 이름 eth0과 주소는 환경에 맞게 바꾸어야 하며, arping처럼 root 권한이 필요한 명령도 있습니다. 결과를 이어서 읽을 때는 각 명령이 같은 대상을 확인했는지 살펴야 합니다. 게이트웨이에 대한 ping이 성공했다고 example.com에 닿는다는 보장은 없고, ICMP와 TCP는 방화벽이 서로 다르게 다룰 수 있기 때문입니다. 위 계층의 실패는 아래 계층이 모두 정상일 때에야 그 계층의 문제라고 말할 수 있습니다.
더 깊이: 기능은 어느 계층에 두어야 하나
계층을 나누고 나면 신뢰성, 암호화, 중복 제거 같은 기능을 어느 계층에 둘지 정해야 합니다. 1984년 Saltzer, Reed, Clark의 논문 "End-to-End Arguments in System Design"은 이 질문에 대한 원칙으로 종단 간 논증(end-to-end argument)을 내놓았습니다. 어떤 기능이 통신의 양 끝에 있는 응용 프로그램의 지식과 도움이 있어야만 완전하고 정확하게 구현될 수 있다면, 그 기능을 통신 시스템 안에서 완성할 수는 없다는 것입니다. 아래 계층이 제공하는 불완전한 버전은 성능을 높이는 수단으로만 의미가 있습니다.
논문이 드는 예는 파일 전송입니다. 망이 홉마다 체크섬과 재전송으로 신뢰성을 높여도, 디스크에서 읽고 쓰는 과정이나 중간 장비의 메모리에서 생기는 오류는 막지 못하므로 결국 응용 프로그램이 양 끝에서 파일 전체의 체크섬을 비교해야 합니다. 실제로 MIT의 한 망에서는 게이트웨이가 버퍼를 복사하다가 대략 백만 바이트에 한 번꼴로 두 바이트의 순서를 바꾸는 고장이 있었습니다. 홉마다 하는 체크섬은 전송 중의 비트 오류만 검사했기 때문에 이 고장을 잡지 못했고, 그 게이트웨이를 거쳐 옮긴 운영체제 소스 파일 일부가 손상되었습니다.
그렇다고 아래 계층이 아무것도 하지 말라는 뜻은 아닙니다. 손실이 잦은 망에서 끝에서만 확인하고 처음부터 다시 보내면 파일이 길수록 성공하기 어려워지므로, 링크에서 어느 정도 복구해 주는 편이 전체 성능에 낫습니다. 즉 아래 계층의 신뢰성은 정확성의 조건이 아니라 성능을 위한 공학적 절충입니다. IAB가 인터넷의 설계 원칙을 정리한 RFC 1958도 이 논문을 인용하며, 망이 할 일은 데이터그램을 되도록 효율적이고 유연하게 전달하는 것이고 나머지는 모두 가장자리에서 해야 한다고 적습니다. IP가 재전송을 하지 않고 TCP와 응용 프로그램이 끝에서 신뢰성을 맡는 구조가 이 원칙을 따른 것입니다.
정리
- 통신의 모든 일을 하나의 규격에 넣으면 한 부분을 바꿀 때 전체를 다시 살펴야 하므로, 네트워크는 일을 계층으로 나누었습니다.
- 각 계층은 아래 계층의 서비스를 쓰고 위 계층에 서비스를 제공하며, 정해진 인터페이스만 지키면 내부 구현을 바꿀 수 있습니다.
- 같은 계층끼리는 직접 대화하는 것처럼 규약을 지키지만, 실제 데이터는 계층을 내려가 링크를 하나씩 지나 전달됩니다.
- 링크 계층은 같은 링크의 다음 장비까지, IP 계층은 최종 목적지 호스트까지를 맡습니다.
- 장애는 아래 계층부터 같은 대상에 대해 확인해 올라가면 원인이 있는 계층을 빨리 좁힐 수 있습니다.
연습 문제
1. 호출 인자만 바꾸면
본문 코드의 마지막 호출을 send(b"Hi", "10.0.0.2")로 바꾸면 출력에서 무엇이 달라지나요? 이 결과로 "실제 네트워크에서도 모든 목적지가 같은 다음 홉을 쓴다"고 말할 수 있나요?
해설 보기
IP 계층의 출력에서 목적지가 10.0.0.2로 바뀌고, 링크 계층의 출력에서 프레임에 담긴 바이트가 IPHDR 5바이트와 Hi 2바이트를 합한 7바이트로 바뀝니다. 다음 홉의 IP와 MAC은 그대로인데, 이 모델은 모든 목적지를 같은 게이트웨이로 보내도록 값을 고정했기 때문입니다. 실제 호스트는 라우팅 표를 보고 목적지마다 다음 홉을 고르므로, 같은 LAN 안의 목적지라면 게이트웨이를 거치지 않고 바로 보내는 등 결과가 달라집니다. 모델이 무엇을 단순화했는지 알고 있어야 그 결과를 현실에 옮기지 않습니다.
2. 계층 하나만 바꾸기
회사 사무실의 유선 이더넷을 모두 Wi-Fi로 바꾸었습니다. 웹 서비스의 코드와 TCP, IP는 그대로입니다. 계층화 덕분에 바뀌지 않아도 되는 것과, 그래도 확인해야 할 것을 하나씩 들어 보세요.
해설 보기
응용 프로그램과 TCP, IP는 링크 계층의 인터페이스만 쓰므로 코드를 고칠 필요가 없습니다. 브라우저와 서버는 이더넷이든 Wi-Fi든 같은 방법으로 소켓을 엽니다. 그래도 Wi-Fi는 무선이라 손실과 지연이 더 들쭉날쭉하므로 TCP의 처리량이 달라질 수 있고, 접속할 때의 인증과 주소 할당 방식, 링크의 MTU도 확인해야 합니다. 계층화는 코드를 고칠 필요를 없애 줄 뿐, 아래 계층의 성질이 위 계층의 성능에 미치는 영향까지 없애 주지는 않습니다.
3. 어디까지 확인되었나
같은 서버의 IP 주소에 대한 ping이 성공하고 그 서버의 TCP 443번 포트에도 연결되었지만, 서버 이름으로 HTTPS 요청을 보내자 인증서의 이름이 맞지 않는다는 오류로 중단되었습니다. 어느 계층까지는 정상이라고 말할 수 있고, 다음으로 무엇을 확인해야 하나요?
해설 보기
ICMP로 서버의 IP에 닿았고 TCP 연결도 맺어졌으므로, 그 서버까지의 네트워크 계층과 전송 계층은 정상이라고 볼 수 있습니다. 실패한 곳은 TLS 핸드셰이크 단계로, 서버가 보낸 인증서의 이름이 요청한 호스트 이름과 맞지 않았습니다. 요청한 이름이 인증서의 이름 목록에 있는지, 같은 IP에서 여러 사이트를 운영한다면 서버가 요청한 이름에 맞는 인증서를 고르고 있는지, DNS가 엉뚱한 서버의 IP를 돌려준 것은 아닌지를 확인합니다. HTTP 요청은 아직 보내지도 못했으므로 HTTP 계층이 정상인지는 알 수 없습니다.
다음 글 OSI 7계층 모델에서는 이 계층화의 생각을 가장 체계적으로 정리한 OSI 모델의 일곱 계층과 각 계층이 다루는 데이터의 단위를 살펴봅니다.