개요
컴퓨터 네트워크의 목표는 간단하다. 컴퓨터로부터 다른 컴퓨터로 데이터를 전송하는 것.
하지만 이는 매우 복잡한 과정을 거친다.
- 전송 계층에서 신뢰성 있는 전송 서비스를 제공.
- 네트워크 계층에서 네트워크 노드 간의 라우팅 서비스를 제공.
- 데이터 링크 계층에서 물리적으로 연결된 노드에게 데이터를 전송하는 서비스를 제공.
- 물리 계층에서 실제로 신호로 비트를 전송하는 서비스를 제공.
이 모든 것이 잘 이뤄져야 비로소 호스트에게 데이터가 전송된다.
먼저 용어를 정리해보자.
- 컴퓨터 네트워크 (Computer Network) : 컴퓨터와 컴퓨터를 통신망으로 연결한 것
- 노드 (Node) : 컴퓨터 네트워크 상에 연결된 장치
- 호스트 (Host) : 고유 IP 주소를 가진 노드
- 링크 (Link) : 물리적으로 노드와 노드를 연결하는 통로
- 홉 (Hop) : 거리의 단위, 보통 한 링크를 이동하면 한 홉이라고 한다.
- 경로 (Path) : 네트워크 상의 두 노드 간의 이동 경로
- 프로토콜 (Protocol): 데이터 통신을 원활하게 하기 위해 필요한 통신 규약
각 층별로 데이터의 단위가 어떻게 다른지 정리해보자.
- 응용 계층에서 응용 프로그램이 소켓을 통해 보내는 데이터 단위는 메시지.
- 전송 계층에서 TCP 통신에서의 데이터 단위는 세그먼트, UDP 통신에서의 데이터 단위는 데이터그램.
- 네트워크 계층에서는 패킷.
- 데이터 링크 계층에서는 프레임.
- 물리 계층에서는 비트.
이 단위는 프로토콜 데이터 단위로 알려진 것인데, OSI 모델에서의 단위이다. 인터넷 프로토콜 스위트에서는 다르게 정의한다.
각 계층에서는 하위 계층으로부터 문제없이 서비스를 잘 받고, 실제로 어떻게 작동하는지 알 필요가 없다는 가정하에 진행된다.
연결 지향 프로토콜과 비연결 프로토콜
- 연결 지향 프로토콜
- 통신 연결이 유지되는 것을 지향하는 프로토콜.
- 연결을 계속 유지하기 위한 비용이 들기 때문에 더 비싸다.
- 이미 연결되어 있기 때문에 어떤 사람이 질의를 보냈는지 연결을 이용하여 알 수 있다.
- 비 연결 프로토콜
- 통신 연결을 유지하지 않는 프로토콜.
- 연결 유지 비용이 들지 않기 때문에 저렴하다.
- 매번 새롭게 연결이 성립되기 때문에 필요한 경우 매 연결 시 자신이 누구인지 알려줘야 한다. ex) HTTP 프로토콜을 이용한 웹 환경의 경우 쿠키나 세션을 통해 매번 자신을 식별할 수 있는 정보를 함께 전송한다.
IP 프로토콜은 비연결 프로토콜이지만 IP 프로토콜을 이용하는 TCP 프로토콜은 연결 지향 프로토콜이다. 또다시 TCP 프로토콜을 이용하는 HTTP 프로토콜은 비연결 프로토콜이다. 이렇듯 프로토콜을 어떻게 활용하느냐에 따라 연결 지향과 비연결 프로토콜로 바뀔 수 있다.
전송 계층 (Transport Layer)
전송 계층의 역할은 목적지까지 데이터를 잘 도착하도록 하는 것이다. 연결 지향 데이터 스트림 지원, 신뢰성 있는 데이터 전송, 흐름 제어, 그리고 다중화와 같은 편리한 서비스를 제공한다. 전송 계층에서 가장 널리 알려진 프로토콜이 바로 TCP, UDP 프로토콜이다. 앞서 나열한 편리한 서비스들은 거의 다 TCP 프로토콜로 제공되는 것이다. 이러한 편리한 서비스들을 제공하기 위해서는 복잡한 처리를 요구하고, 이는 TCP 프로토콜을 이용하는 비용이 크다는 것을 뜻한다. 때로는 이러한 서비스들이 필요 없는 경우도 있다. 그런 때에는 UDP 프로토콜을 이용하여 저비용으로 통신할 수도 있다.
TCP 프로토콜은 호스트에서만 작동하고, 중간 라우터 노드들에서는 작동하지 않는다. 앞서 언급했듯이 TCP 프로토콜은 복잡한 처리를 요구하기 때문에 모든 중간 라우터 노드들에서까지 TCP 프로토콜을 작동하게 한다면 지금처럼 많은 데이터 전송을 처리할 수 없기 때문이다.
TCP 프로토콜
TCP 프로토콜의 기능은 다음과 같다.
- 신뢰성 있는 데이터 전송 (Reliable Data Transfer, RDT)
- 연결 제어 (Connection Control)
- 흐름 제어 (Flow Control)
- 혼잡 제어 (Congestion Control)
TCP 프로토콜의 신뢰성 있는 데이터 전송 기능 덕분에 수신자는 전송자가 보낸 데이터를 신뢰할 수 있다.
각 기능을 제공하기 위한 자세한 방법을 알아보자.
Multiplexing / Demultiplexing
응용 프로그램이 소켓을 통해 데이터를 전송하는 것부터 TCP 프로토콜이 시작된다. 한 컴퓨터에서 여러 개의 소켓과 프로세스가 존재할 수 있기 때문에 OS에서는 소켓과 프로세스를 식별하기 위해 포트번호를 따로 둔다. 그러나 보통 컴퓨터에 연결된 통신 링크는 하나이기 때문에 하나의 링크를 통해 여러 소켓의 데이터를 주고받아야 한다. 이를 위해 OS에서는 전송하려는 데이터를 TCP/UDP 세그먼트로 만들 때 헤더를 추가하여 공통적으로 출발지 포트 번호와 목적지 포트 번호를 둔다. 이러한 작업을 Multiplexing이라 하고, 목적지에 도착한 세그먼트는 헤더를 확인하여 Demultiplexing 된 뒤 대상 소켓에게 데이터를 전달한다.
그림 출처 : https://medium.com/coderscorner/transport-layer-multiplexing-and-demultiplexing-670e16b6bfc
신뢰성 있는 통신
신뢰성 없는 통신을 기반으로 신뢰성 있는 통신을 지원하기 위한 과정을 알아보자. (원문)
신뢰성 있는 통신을 한다는 것은 잘못된 데이터를 전송받지 않고, 데이터의 순서 또한 유지되는 통신을 보장한다는 뜻이다.
아래 그림은 신뢰성 있는 통신 서비스를 어떻게 제공해주는지 보여주는 그림이다. 신뢰성 있는 데이터 전송 함수인 rdt_send 함수는 내부적으로 신뢰성 없는 채널을 이용하는 udt_send 함수를 이용하여 구현된다.
첫 시작인 rdt1.0에서는 신뢰성 있는 채널을 통해 데이터 전송이 이뤄진다고 가정한다. 데이터 전송에 에러가 발생하지 않는다는 가정 하의 전송이기 때문에 rdt_send 함수는 받은 데이터를 패킷으로 만들고 udt_send 함수로 패킷을 전송하기만 해도 신뢰성 있는 통신이 된다. 수신자 측은 받은 패킷을 합쳐 데이터로 돌리면 된다.
rdt2.0에서는 비트 에러가 존재할 수 있는 상황까지 고려한다. 전화 통화로 받아쓰기를 한다고 가정해보자. 아마 상대방이 한 문장씩 말할 때마다 잘 들었다는 응답(OK) 혹은 다시 말해달라는 응답(Please repeat that)을 통해 받아쓰기를 잘하고 있는지 확인할 수 있을 것이다. rdt2.0에서는 이 방식을 모티브로 삼아 작동하며, 이러한 응답을 통한 통신 조절을 ARQ(Automatic Repeat reQuest)라고 한다.
rdt2.0에서는 여기에 두 가지 추가적인 기능이 요구된다.
- 신뢰성 없는 채널을 통한 통신이 이뤄지기 때문에 비트 에러가 있는지 감지할 수 있어야 한다. -> 체크섬.
- 수신자로부터 피드백을 받을 수 있어야 한다. -> 성공적으로 받았다면 ACK, 잘못 받았다면 NAK.
이런 식으로 송신 후 기다리기 때문에 rdt2.0은 stop-and-wait 프로토콜로 알려져 있다.
그러나 ACK와 NAK 응답도 에러가 발생할 수 있다. ACK도 NAK도 오지 않으면 송신자 측에서는 무한정 기다리게 된다. 송신 측에서는 에러가 났는지 알 수 없다. 또한 중복 송신을 하게 되면 중복 수신을 하게 된다.
rdt2.1에서는 순서 번호(Sequence number)를 추가함으로써 중복 수신을 방지한다. 순서 번호만 확인하면 중복된 데이터를 받았는지 알 수 있으므로 중복 수신을 방지할 수 있다. stop-and-wait 프로토콜 상으로는 방금 전공한 패킷이 잘 전송되었는지만 알면 되기 때문에 순서 번호가 0 혹은 1만 있으면 된다. 0->1->0->1 순으로 패킷을 전송한다. rdt2.0의 가정상 아직 패킷이 유실될 수 있는 상황은 아니기 때문에 이 방법은 문제가 없다.
여기서 잘못된 수신에 대해서 NAK 응답이 아닌 ACK를 이용한 방법이 가능하다. rdt2.2는 NAK를 사용하지 않는 방법이다. ACK에 순서 번호를 추가함으로써 잘못된 데이터를 받은 것을 다시 송신하게 할 수 있다.
이제는 심지어 패킷이 사라질 수도 있는 채널을 통해 통신한다고 가정하자. (끔찍)
이 경우엔 패킷 손실을 어떻게 감지할지, 그리고 패킷 손실이 발생하면 어떻게 해야 할지 생각해봐야 한다. 패킷 손실이 됐을 때 대응 방법은 이미 rdt2.2까지의 고민을 통해 알 수 있다. 다시 보내는 것. 아주 간단하다. rdt3.0에서는 수신자가 ACK를 받는 것에 대한 타임 아웃 타이머를 둬서 이를 해결한다. 시간 내에 데이터를 성공적으로 수신했다는 응답을 받지 못하면 데이터를 다시 보내는 것이다. 중복된 데이터 수신에 대해서는 이미 rdt2.2 상에서 처리된다.
rdt3.0까지 발전시켜나감으로써 이제 신뢰성 있는 통신을 할 수 있게 됐다. 하지만 이 방법은 한 번에 한 패킷씩밖에 보내지 못하기 때문에 성능상으로 만족스럽지 못하다. 매우 먼 거리에 패킷을 보내려 한다면 패킷이 왔다 갔다(Round trip)하는 것을 기다리는 시간 때문에 전송 속도가 매우 떨어질 것이다. 이제는 여러 패킷을 보낼 방법을 고민해야 한다. ACK를 받기 전에 다수의 패킷을 전송하는 방법을 통틀어 파이프라인 프로토콜이라고 하며, 세부적으로 GBN(Go-Back-N), SR(Selective Repeat) 프로토콜이 있다.
Go-Back-N 프로토콜
- 수신자 측에서 지금까지 성공적으로 받은 패킷 순서에 대한 ACK를 전송하며, 순서에 맞지 않는 패킷은 성공적으로 수신한 것으로 간주하지 않는다. 따라서, 송신자는 ACK를 받지 못한 가장 오래된 패킷부터 모두 재전송하게 된다.
Selective Repeat 프로토콜
- 잘못된 순서의 패킷을 받았더라도 버퍼에 저장해둔다. 중간에 수신 실패한 패킷만 다시 전송하게끔 수신자 측은 개별적으로 ACK를 전송하며, 송신자 역시 패킷마다 타임 아웃 타이머를 가진다.
- Selective Repeat 프로토콜상으로 상위 계층에게 현재 성공적으로 수신한 패킷까지만 제공함으로써 해당 패킷까지 신뢰성 있게 통신되었음을 보장한다.
이로써 신뢰성 없는 채널을 통한 신뢰성 있는 통신을 구현하는 방법에 대해 알아보았다. 실제 TCP에서는 Go-Back-N 프로토콜과 Selective Repeat 프로토콜을 하이브리드 하여 사용한다고 한다.
적절한 타임 아웃 시간 예측
타임아웃 시간은 어떻게 결정할까? 너무 짧으면 재전송 요청을 많이 하게 되고, 너무 길면 기다리는 시간이 길어질 것이다. 적절한 타임 아웃 시간을 설정하기 위한 방법으로 그때그때 확인된 RTT(Round Trip Time, 패킷이 갔다가 되돌아오는데 걸린 시간)인 SampleRTT를 이용하여 다음 RTT인 EstimatedRTT를 예측한다. 원리는 이전까지의 RTT와 다음 RTT는 비슷할 것이고, 변화가 생기면 소폭 반영하는 것이다. 식으로 나타내면 다음과 같다.
적절한 a, 예를 들면 0.125 정도를 설정하면 과거의 데이터에 더 비중을 둬서 변화를 수용하게 된다.
연결 제어
TCP 프로토콜은 연결 지향 프로토콜이고, 신뢰성 있는 통신을 제공하기 위하여 순서 번호를 사용한다고 하였다. TCP 프로토콜에서 통신이 이루어지기 위해서는 먼저 서로 연결이 되어야 하고, 서로 임의의 시작 순서 번호를 알려줘야 한다. 그 과정을 3-way handshaking이라고 한다. 그럼 과정을 살펴보자.
Giri ---> Seflix SYNchronize with my Initial Sequence Number of X
Giri <--- Seflix I received your SYN, I ACKnowledge that I am ready for [X+1]
Giri <--- Seflix SYNchronize with my Initial Sequence Number of Y
Giri ---> Seflix I received your SYN, I ACKnowledge that I am ready for [Y+1]
TCP 프로토콜에서는 연결하자는 뜻으로 SYN 패킷을 보낸다. SYN은 SYNchronize의 약자로 Giri는 SYN 패킷과 함께 초기 순서 번호(Initiali Sequence Number, ISN)를 Seflix에게 보낸다. Seflix는 Giri의 초기 순서 번호가 X인 것을 알게 되고, Giri에게 SYN, ACK가 합쳐진 패킷을 보내면서 자신의 초기 순서 번호가 Y임을 알려준다. 마지막으로 Giri는 Seflix로부터 초기 순서 번호를 잘 받았다는 의미로 ACK를 보낸다. 이 과정을 통해 두 호스트는 신뢰성 있는 통신을 할 수 있는 준비를 마치게 된다.
실제로는 3-way handshaking 과정에서 초기 순서 번호 외에도 수신 기본 윈도우 크기(rwnd), 여러 가지 추가적인 옵션 등을 교환하게 된다. 왜 2-way로는 안 되는 걸까? 아래는 2-way handshaking의 실패 시나리오다. 호스트 A가 호스트 B에 연결을 요청한 것이 딜레이가 많이 되어 다시 연결을 요청하는 상황이 발생할 수 있다. 그 경우 호스트 B는 과거의 연결 요청에 대한 순서 번호로 응답하게 되고, 호스트 A는 잘못된 순서의 패킷이 왔기 때문에 그 패킷을 버리게 된다. 따라서 성공적으로 통신을 할 수 없게 된다.
이제 연결 종료를 위한 4-way handshaking에 대해 알아보자.
- 호스트 A가 연결 종료를 하자는 FIN 패킷을 호스트 B에게 보낸다.
- 호스트 B가 응답으로 ACK 패킷을 호스트 A에게 보낸다.
- 호스트 B는 이제 남은 데이터를 모두 전송한 뒤 FIN 패킷을 호스트 A에게 보낸다.
- 호스트 A는 응답으로 ACK 패킷을 호스트 B에게 보낸다.
FIN 패킷을 보낸 시점부터 더 이상 데이터 전송이 불가능하다는 것이 핵심이다.
흐름 제어와 혼잡 제어
송신량과 수신 처리량을 일치시키는 것을 흐름 제어라고 한다. 수신 측이 처리할 수 있는 양만큼만 전송하도록 제어하는 서비스가 흐름 제어다. 이를 위해 크게 Stop and wait 방식과 Sliding Window 방식이 사용된다. Stop and wait 방식은 1개씩 프레임을 전송하는 방식이기 때문에 효율성이 나쁘고 거의 이용되지 않으므로 Sliding Window 방식을 알아보자.
- 수신 측은 여유 버퍼 공간 크기를 rwnd(TCP 패킷 헤더의 window size 필드)라는 값으로 송신 측에게 알려준다.
- 송신 측은 (마지막으로 보낸 데이터 순서 번호(LastByteSent) - 마지막으로 성공적으로 수신한 데이터 순서 번호(LastByteAcked))가 rwnd를 넘지 않도록 데이터를 보낸다.
이 방식은 윈도우 기반의 방식이라는 점에서 신뢰성 있는 데이터 전송을 위한 Selective Repeat 방식과 비슷하다고 볼 수 있다.
반면에 네트워크가 혼잡하다고 판단될 때 데이터 송신량을 떨어뜨리는 것을 혼잡 제어라고 한다.
if 네트워크망이 처리 가능한 데이터량을 넘어서는 데이터를 전송하려한다. :
초과되는 양의 데이터는 전송에 실패한다.
신뢰성 있는 데이터 전송 알고리즘에 의해 전송 실패한 데이터를 재전송한다.
이러한 악순환이 반복되어 네트워크망이 혼잡해지는 문제가 일어나지 않도록 혼잡 제어 서비스를 제공한다.
혼잡 상태가 일어났을 땐 보통 두 가지로 나뉜다.
- 라우터의 버퍼가 오버플로우 되어 패킷이 유실되는 경우.
- 라우터의 버퍼에 대기함으로 인해 평소보다 큰 딜레이가 발생되는 경우.
두 경우 모두 ACK를 받지 못하는 패킷에 대한 타임아웃으로 알 수 있게 된다. AIMD(Additive Increase/Multiplicative Decrease), Slow Start, Fast Retransmit, Fast Recovery 등 몇 가지 방식들이 있지만 공통적으로 송신 측의 전송 버퍼의 크기 cwnd를 차례로 키워나가다 전송 실패가 발생할 경우 전송량을 줄인다는 점은 비슷하다. rwnd는 수신 측이 알려주는 반면 cwnd는 송신 측이 앞서 예를 든 방식들을 이용하여 적절한 값을 찾아나간다.
결국 흐름 제어와 혼잡 제어 모두 송신 측이 데이터 전송량을 조절해야 한다는 점에서는 같다. 결과적으로 rwnd보다 많은 데이터를 전송해서도, cwnd보다 많이 데이터를 전송해서도 안 되기 때문에, rwnd와 cwnd 중 작은 값만큼의 양을 전송하면 된다.
UDP 프로토콜
지금까지 알아본 TCP 프로토콜에 비해 UDP 프로토콜은 매우 간략하다. TCP 프로토콜에서 제공하는 Multiplexing/Demultiplexing, 신뢰성 있는 데이터 전송, 연결 제어, 흐름 제어, 혼잡 제어 등의 서비스를 제공하지 않으면 UDP 프로토콜이라고 볼 수 있다. 이러한 서비스들을 제공하지 않기 때문에 부하(Overhead)가 매우 적다. 그렇기 때문에 많은 요청을 처리해줘야 하고, 신뢰성이 떨어져도 괜찮은 서비스들에서 유용하게 쓰일 수 있다. 대표적인 예로 DNS(Domain Name Service), VoIP(음성 인터넷 프로토콜), 온라인 게임 서버 등에서 쓰인다.
UDP 프로토콜에서는 수신자가 메시지를 수신했는지 알 수 없고, 보낸 순서대로 받도록 하지도 않는다. 이러한 기능이 필요하다면 성위 프로토콜 상의 처리가 필요하다. 예를 들어 DNS 질의 요청에 대한 응답이 일정 시간 내에 오지 않으면 다시 질의를 보낸다. 실시간 스트리밍이라면 오래된 순서 번호의 패킷은 그냥 버린다. 어차피 과거의 패킷이므로 굳이 사용할 필요가 없기 때문이다.
전송 계층 정리
전송 계층에서는 소켓으로 어떻게 데이터를 잘 전달할 것인지에 대해 다뤘다. 비용이 더 들긴 하지만 보낸 데이터를 그대로 받을 수 있도록 해주는 TCP 프로토콜을 이용하거나, 낮은 비용으로 데이터를 보낼 수 있지만 신뢰성이 떨어지는 UDP 프로토콜을 선택할 수도 있다. 모든 경우에 적합한 방법은 없기 때문에 문제에 따라 적절한 선택을 해야 한다. 프로토콜을 통해 프로세스에게 데이터를 잘 전달해주는 것은 OS가 도와준다. 받은 데이터를 어떻게 활용하는지는 응용 계층에게 달려있다.
네트워크 계층 (Network Layer)
네트워크 계층의 역할은 전송 계층에서 보내려는 패킷을 목적지 노드까지 도착시키는 것이다. 네트워크 계층에서는 중간 라우터를 통한 라우팅(Routing)과 포워딩(Forwarding), 필요하다면 연결 설정을 담당한다. 가능한 빠르게 데이터를 전송하기 위해서 최적의 경로로 데이터를 전송해야 하지만 그것은 쉬운 일이 아니다. 그러한 일을 해주는 IP 프로토콜에 대해서 알아볼 것이다.
라우팅과 포워딩의 차이점은 무엇일까? 라우터는 또 다른 라우터들과 물리적으로 연결되어 있다고 보면 된다. 라우팅은 현재 라우터에서 연결된 라우터 중 어떤 라우터로 보내는 것이 목적지로 가는데 최적인지 경정하는 것이다. 라우팅 알고리즘이 수행되고 나면 어떤 헤더를 가진 패킷을 어떤 링크로 전송하라는 정보가 정리된 포워딩 테이블이 만들어진다. 만들어진 포워딩 테이블을 기반으로 라우터는 전달받은 패킷을 다른 라우터로 포워딩한다.
네트워크 계층의 ATM, Frame Relay, X.25와 같은 모델들은 다른 라우터까지의 대역폭을 예약해둘 수도 있다. 데이터 그램이 전송되기 전 두 호스트 간의 경로를 가상 연결(Virtual Connection)로 설정하여 두 호스트가 충분한 서비스를 제공받을 수 있도록 한다. 이 글에서는 인터넷 모델을 다루기 때문에 이에 대한 설명을 생략하고, 라우팅에 대해서만 자세히 다루고자 한다.
우리가 관심 있는 네트워크 계층의 프로토콜은 크게 라우팅 프로토콜, 인터넷 프로토콜, ICMP 프로토콜로 나뉜다.
- 라우팅 프로토콜은 포워딩 테이블을 만들기 위한 프로토콜이다.
- 인터넷 프로토콜은 주소 체계를 위한 프로토콜이다.
- ICMP 프로토콜은 에러 메시지를 전달받기 위해 주로 쓰이는 프로토콜이다.
데이터그램 네트워크 (Datagram Network)
데이터그램이 전송되기 전 연결을 먼저 설정하는 방식과 달리 데이터그램 네트워크에서는 그때그때 받은 데이터그램의 목적지 주소를 확인하여 포워딩 테이블에 따라 출력 링크로 전송한다. 포워딩 테이블의 각 항목은 주소 범위와 출력 링크를 가진다. 왜 주소 범위인지 궁금하다면 IPv4의 주소 체계는 2의 32승, 40억 개 가량의 항목을 가질 수 있기 때문임을 생각해보면 된다. 만약 범위가 아니라 각 주소로 포워딩 테이블을 만든다면 테이블의 크기가 너무 커서 현실적이지 않을 것이다.
받은 데이터그램에 대해 포워딩 테이블에 있는 엔트리 중 어떤 것을 선택할지 결정하기 위해 Longest Prefix Matching 방법이 이용된다. 개념적으로 생각해보면 더 세부적으로 결정되어 있는 것을 따르는 선택 규칙이라고 보면 된다. (IP 패킷의 목적지 IP 주소가 라우팅 테이블에 있는 수많은 목적지 IP 주소 중 일치하는 부분이 가장 긴 곳으로 포워딩(라우팅)하는 규칙.)
인터넷 프로토콜 (Internet Protocol, IP)
인터넷 프로토콜은 호스트의 주소 지정과 패킷 분할 및 조립 기능을 담당한다. 데이터 링크 계층에서 전달할 수 있는 데이터의 최대 크기를 MTU(Maximum Transmission Unit)이라고 하는데, 전송하고자 하는 데이터그램이 MTU보다 크다면 데이터그램을 분할하여 offset을 지정해준다. 분할된 데이터그램들은 최종 목적지에서 재조립된다.
전송 계층 프로토콜의 패킷에서는 포트 번호까지만 나타났지만 인터넷 프로토콜부터는 IP 주소가 나타난다. 우리가 익숙하게 알고 있던 192.168.0.1 같은 주소는 IPv4 주소 체계에 의한 주소이다. 인터넷 프로토콜은 과거부터 꾸준히 발전되어 왔는데, IPv4는 전 세계적으로 사용된 첫 번째 인터넷 프로토콜이다. 현재는 주소 공간의 고갈로 인해 IPv6로 대체되고 있다. 개념상의 이해를 쉽게 하기 위해 설명은 IPv4를 기준으로 하겠다.
네트워크망 상에서 호스트 또는 라우터 간의 물리 링크 연결을 인터페이스라고 한다. 먼저 IP 주소는 호스트에 부여되는 것이 아닌 인터페이스에 부여되는 것이라는 사실을 알아둬야 한다. 라우터는 보통 여러 노드들과 연결되어 있으므로 여러 개의 인터페이스를 갖고 있고, 호스트는 보통 하나의 유선 연결이나 무선 연결이 되어 있으므로 하나 혹은 두 개의 인터페이스를 가진다. 실제 네트워크 망에서 모든 호스트가 라우터와 직접 연결되어 있진 않고, 중간에 이더넷 스위치(Ethernet Switch)라는 것들이 여러 개의 호스트와 연결되어 있고, 이더넷 스위치와 라우터가 연결되어 있다.
IP 주소로 표현 가능한 주소가 매우 많기 때문에 관리의 효율성을 위해 서브넷이라는 개념이 도입된다. 이에 따라 IP 주소는 서브넷 부분(상위 비트), 포스트 부분(하위 비트)으로 나뉘게 된다. 서브넷에 대해 다음 항목에서 좀 더 자세히 알아보자.
서브넷 (Subnet)
IP 주소의 서브넷 부분이 같은 노드들을 모아 서브넷이라고 한다. 다르게는 중계하는 라우터 없이 물리적으로 연결된 (이더넷 스위치 포함) 네트워크망이라고 말할 수 있다. 서브(sub)와 네트워크(network)의 합성어인 것을 보면 알겠지만 계층적으로 하위에 존재하는 네트워크이다. 서브넷을 활용하는 것은 관리상의 이점을 가진다. 방대한 양의 네트워크망을 그냥 관리하는 것보다는 계층으로 나누어 관리하는 것이 훨씬 수월하다. 이처럼 서브넷의 관리를 맡겨버리는 방법을 서브 네트워킹이라고 한다.
서브넷을 활용하기 위해서는 IP 주소에서 어디까지가 서브넷 부분인지 알려줄 필요가 있다. "223.1.1.0/24"와 같은 표현은 "223.1.1.0" 주소의 상위 24bit가 서브넷 부분임을 알려주고, "24"를 서브넷 마스크라고 한다. 상위 24bit가 서브넷 부분이므로 이를 위한 서브넷 마스크를 주소로 표현하면 "255.255.255.0"이 되고, 서브넷 마스크와 IP 주소를 AND 연산하면 서브넷 부분만 얻을 수 있게 된다.
클래스 주소 체계는 IP 주소의 서브넷 부분을 정확히 8bit, 16bit, 24bit로 나누는 방식이다. 따라서 A클래스는 "/8", B클래스는 "/16", C클래스는 "/24"를 서브넷 마스크로 가진다. 그러나 이 방법은 C클래스는 최대 254개 호스트, B클래스는 최대 65534개의 호스트를 가질 수 있는 것에서 볼 수 있듯이 유연성이 떨어진다. 대신 "a.b.c.d/x"와 같은 형식으로 서브넷 부분이 임의의 길이를 가질 수 있도록 하는 방법을 CIDR(Classless Inter Domain Routing)이라고 한다.
호스트 주소와 주소 블록 획득
지금까지의 공부로 IP 주소가 어떤 것인지 대충 이해했다. 그렇다면 호스트는 어떻게 IP 주소를 부여받을 수 있는가? 기본적으로 고정 IP와 유동 IP로 알려진 두 방법이 있다. 고정 IP는 특정 IP를 아예 호스트에 지정해버리는 방법이고, 유동 IP는 IP를 동적으로 획득하는 방법이다. 네트워크에 연결됐을 때만 주소를 가지므로 IP주소를 재활용할 수 있기 때문에 큰 장점이 있다. 특히 요즘은 모바일 기기로 잦은 네트워크 접속과 종료가 있는 상황에서는 더욱 유용하다. 유동 IP를 지원하는 가장 대중적인 방법은 DHCP (Dynamic Host Configuration Protocol)을 사용하는 것이다. 아마 네트워크나 공유기 설정에서도 쉽게 찾아볼 수 있을 것이다.
기관에서 여러 호스트로 이뤄진 서브넷을 만들어 관리하고 싶다면 주소 블록이 필요하다. 주소 블록은 어떻게 획득할까? 답은 "인터넷 서비스 공급자(Internet Service Provider)에게 돈 주고 산다."이다. ISP는 더 상위에 있는 IP 주소 관리 단체(Internet Corporation for Assigned Names and Numbers, ICANN)로부터 주소 블록을 발급받는다. 그리고 발급받은 주소 블록을 쪼개서 고객들에게 주소 블록을 판다.
ISP는 인터넷 망에게 자신의 주소 블록을 목적지로 하는 패킷들을 모두 보내라고 광고한다. 넓은 서브넷상으로 라우팅 된 패킷이 ISP 망 내에 도착하면 또다시 더 좁은 서브넷으로 라우팅 되기를 반복할 것이다. 이러한 방식을 Hierarchical Addressing이라고 한다.
그런데 만약 어떤 기관이 다른 ISP로 이전한다면?
이전한 ISP가 아예 다른 주소 블록을 라우팅 하도록 광고하고 있었다면 Hierarchical Addressing으로 해당 기관에게 라우팅 하지 못하게 된다. 이 경우 ISP는 인터넷 망에게 추가적인 주소 블록도 광고한다. 더 좁은 서브넷은 서브넷의 길이가 더 길어질 것이므로 앞서 언급했던 Longest Prefix Matching 방법에 의해 적절한 ISP로 라우팅 될 것이다.
NAT (Network Address Translation)
NAT는 IP 주소 하나를 갖고 여러 호스트를 관리하는 방법이다. NAT 장비 내부에는 지역 네트워크가 형성되고, 지역 네트워크 상의 주소를 NAT 장비가 관리한다. 지역 네트워크에서 패킷이 외부로 나갈 때 NAT 장비를 거치게 되고, 이때 패킷의 출발 주소가 NAT 장비의 주소로 변경된다. 하나의 IP로 여러 호스트를 관리하기 때문에 NAT 장비는 출력하는 패킷의 포트 번호를 다른 번호로 바꿔버린다. 이렇게 하면 나중에 지역 네트워크로 진입하고자 하는 패킷의 목적지 포트 번호를 확인하여 적절한 지역 네트워크 호스트를 찾을 수 있게 된다.
NAT는 IP 주소를 효율적으로 사용할 뿐만 아니라 내부 장비들의 IP 주소를 숨김으로써 보안상의 이점도 가진다. 멀리 찾을 필요 없이 우리에게 가장 가까운 NAT 장비는 무선 기지국이다. 스마트폰이 셀룰러 네트워크에 연결되는 것이 기지국이라는 NAT에 연결되는 것이라고 보면 된다.
ICMP (Internet Control Message Protocol)
ICMP 프로토콜은 호스트와 라우터 사이의 네트워크 계층 정보를 통신하기 위해 사용되는 프로토콜이다. ping, traceroute 프로그램에서 쓰이는 것이 익숙하다. 특히 traceroute는 어떤 경로를 거쳐 호스트에 도착하는지 확인할 수 있어서 네트워크 관련 문제를 디버깅하고자 할 때 유용하게 쓰이곤 한다.
라우팅 알고리즘
라우팅 알고리즘은 크게 Link State, Distance Vector, Hierarchical Routing 세 가지로 나뉜다. Link State는 주기적으로 링크의 비용을 주변 라우터들에게 알려주면 (Link state broadcast) 최소 비용 경로를 다시 계산하는 방식이다. 그 익숙한 다익스트라의 최단 경로 알고리즘이 쓰인다. 라우터가 모든 목적지까지의 경로를 저장해야 하기 때문에 메모리가 많이 소모되고, 복잡한 그래프에 대해 최단 경로 알고리즘을 수행해야 하기 때문에 CPU가 많이 쓰인다. 그러나 모든 목적지까지의 경로를 알고 있으므로 라우팅 테이블 변화에 따른 전파 속도가 빠르다. OSPF(Open Shortest Path First) 프로토콜이 대표적이다.
Hierarchical Routing은 앞서 언급한 Hierarchical Addressing과 비슷하다. 수많은 목적지에 대해 모두 라우팅 테이블을 저장하고 링크 상태를 알려주는 것은 너무 어렵다. 좀 더 쉽게 문제를 해결하기 위해 인터넷을 AS(Autonomous System)라는 좀 더 작은 구역으로 나눈다. 각 AS는 관리자에 의해 자치적으로 운영되며, 각자의 라우팅 프로토콜을 사용한다. AS 내에서의(Intra-AS) 라우팅을 위한 프로토콜을 IGP (Interior Gateway Protocol)이라고 하며 OSPF 프로토콜과 RIP 프로토콜이 주로 쓰인다. 반면 AS 외부로의 (Inter-AS) 라우팅을 위해서는 먼저 다른 AS로 찾아갈 수 있도록 하는 라우팅이 필요하며, 이를 EGP(Exterior Gateway Protocol)이라고 한다. EGP로는 BGP(Border Gateway Protocol)이 주로 쓰인다.
네트워크 계층 정리
이로써 네트워크 계층에서 주소 관리와 라우팅을 위한 부분들을 알아보았다. 전체 인터넷은 계층적 네트워크로 이루어져 있으며 하위 네트워크를 서브넷이라고 한다. 라우터는 전달받은 패킷의 목적지를 보고 포워딩 테이블에서 가장 적합한 인터페이스를 찾아 그 인터페이스로 패킷을 출력한다. 목적지 전부를 테이블로 만들기 어렵기 때문에 포워딩 테이블은 정확한 목적지 주소가 아닌 서브넷들로 구성되어 있다. 포워딩 테이블을 만들기 위해서 여러 가지 라우팅 알고리즘이 사용된다. 외부의 AS로 이동하기 위한 라우팅과 AS 내에서의 라우팅을 위한 프로토콜로 나뉜다. 공통적으로 라우터들은 서로 정보를 공유하며(Interworking) 최적의 라우팅을 위해 노력한다.
데이터 링크 계층과 물리 계층
데이터 링크 계층부터는 실제로 인터페이스를 통해 데이터 전송이 이뤄진다. 데이터 링크 계층에서는 흐름 제어(전송 계층과 다르다), 오류 제어, 반이중과 전이중 서비스를 제공한다. 인터페이스는 하나이고 양측이 함께 사용하기 때문에 양측이 골고루 사용할 수 있게끔 조율을 해줘야 공평할 것이다. 또한, 물리 계층에서 데이터 전송 시에는 물리적인 문제로 인해 잘못된 비트가 전송될 수도 있다. 이를 감지할 수 있도록 하는 서비스가 오류 감지이며, 어떤 오류 감지 기법은 수정 기능까지도 가진다.
데이터 링크 계층에서 사용되는 주소는 MAC 주소이다. IP주소를 바뀔 수 있는 반면 MAC 주소는 네트워크 인터페이스에 할당된 고유 식별자이다. IP 패킷을 전송하기 위해 물리적 네트워크 주소인 MAC 주소가 필요한데, 이를 모를 때 ARP(Address Resolution Protocol)을 이용한다.
물리 계층에서는 물리적으로 비트를 전송하기 위한 일들을 한다. 그렇기에 당연히 하드웨어와 밀접한 관계에 있을 수밖에 없는 계층이다. 이더넷, 토큰 링 등이 이 계층에 포함되며 전기 신호 혹은 광 통신 등이 있다. 전기 신호는 멀리 이동할수록 약해지는데, 전기 신호를 다시 증폭시키기 위해 리피터가 사용된다. 전화선에 사용되던 모뎀도 이 계층에 속한다. 모뎀은 이름에 나타나 있듯이 디지털 신호를 아날로그 신호로 바꾸어 전송하고, 아날로그 신호를 다시 디지털 신호로 읽어내는 역할을 했었던 기계이다.
세션 계층 (Session Layer)
지금까지 패킷이 네트워크망을 거쳐 호스트에게 도착하는 여행을 알아보았다. 투명성의 원칙에 따라 호스트에 어떻게든 패킷이 올바르게 도착했다는 가정 하에 시작해보자. 네트워크 망을 통한 통신은 OS에 의해 관리되며, 실제로 우리가 프로그래밍하는 것은 OS 위에서 돌아가는 프로세스이다. 세션 계층은 프로세스가 통신을 관리하기 위한 방법에 대한 계층이다. 또한, TCP/IP 세션을 만들고 없애는 책임을 수행하는 계층이기도 하다.
프로세스가 OS에게서 데이터를 전달받기 위해서는 앞서 이미 언급한 소켓을 이용한다. 세션 계층은 추상적으로 논리적 연결에 대해 다루는 계층이므로 소켓 외에 명확히 콕 집어 이야기할만한 주제는 거의 없지만, 그 개념 자체는 응용 계층에서 재활용될 것이다.
표현 계층 (Presentation Layer)
소켓을 통해 전달받은 데이터는 아직 바이트 덩어리일 뿐이다. 문자만 해도 ASCII 코드 혹은 유니코드 등 다양한 방법으로 표현될 수 있고, 어떤 시스템은 정수 값을 16bit만으로 표현할 수도 있다. 이렇듯 시스템마다 데이터 표현 방식이 다르기 때문에 이를 같은 데이터로 인식하기 위한 처리를 해주는 계층이 필요하며, 표현 계층이 그 기능을 수행한다. 말하자면 번역기와 같은 일을 하는 계층이다.
압축과 암호화 또한 표현 계층에서의 서비스이다. 압축을 통해 전송하고자 하는 데이터를 좀 더 작게 만들어 보낼 수도 있고, 보안을 위해 암호화된 데이터로 보낼 수도 있다. TLS(Transport Layer Security)는 널리 쓰이고 있는 보안을 위한 암호 규약이다. 또는 RPC(Remote Procedure Call)를 위한 데이터 직렬화 등을 필요로 하기도 한다. XDR(eXternal Data Representation) 프로토콜이 직렬화 프로토콜의 대표적인 예이다.
응용 계층 (Application Layer)
표현 계층까지 거쳐 "이해할 수 있는" 데이터가 되었다. 이제 데이터를 주고받는 방법을 이용한 어떠한 일도 할 수 있다. 가장 많이 예를 드는 응용 계층의 프로토콜은 전자 메일과 관련된 SMTP(Simple Mail Transfer Protocol), 파일 전송을 위한 FTP(File Transfer Protocol), WWW 세상의 정보를 주고받는 HTTP(HyperText Transfer Protocol)이다.
특히 HTTP 프로토콜은 가장 널리 쓰인다고 볼 수 있는 프로토콜 중 하나이다. 꼭 브라우저가 아니더라도 HTTP 프로토콜이 워낙 잘 정의되어 있는 프로토콜이기 때문에 서버와 통신하기 위한 프로토콜을 별도로 정의하지 않고 HTTP 프로토콜을 그냥 이용하는 경우가 많다. 그러나 알아둬야 할 것은 뭐든지 프로토콜을 해석한 결과라는 것이다. 예를 들어 RESTful API는 요청 결과를 JSON(JavaScript Object Notation) 포맷으로 돌려주는 경우가 많다. 라이브러리를 이용할 때는 HTTP 응답의 Body를 JSON으로 파싱 하여 Dictionary 객체를 얻어 사용하곤 한다. 이미 HTTP 응답의 Body 가 JSON 포맷으로 쓰여있다는 것을 알고 있어야 이 방법이 통한다. 실제 HTTP 응답의 Body는 바이트 덩어리일 뿐이고, 바이트 덩어리만 봐서는 무슨 내용인지 알 수 없다. 만약 서버가 XML 포맷의 데이터를 줬는데 JSON으로 파싱 하려 한다면 에러가 날 것이다. 이를 명확하게 알려주기 위해 HTTP Header에 Content-Type 필드가 있는 것이다.
응용 계층에서의 프로그래밍을 위해서는 소켓 프로그래밍을 알아보면 된다. 무엇보다 강조하고 싶은 것은 데이터를 다루는 규칙인 프로토콜을 정의하는 것과 그 프로토콜에 따라 작동하도록 프로그램을 작성하는 것이 네트워크 프로그래밍의 기본이라는 것이다.
이 글은 개념정리 - (8) 컴퓨터 네트워크편 을 바탕으로 정리한 글입니다.
'0 > web' 카테고리의 다른 글
뒤늦은 React 입문기 정리 (라이프 사이클, 렌더링 과정) (0) | 2019.04.15 |
---|---|
Flux (React를 보완하자) (0) | 2019.04.12 |
브라우저의 작동원리 (3) (How browsers work) (0) | 2019.04.02 |
브라우저의 작동 원리 (2) (How browsers work) (0) | 2019.04.01 |
문맥 자유 문법 (Context Free Grammar, CFG) (0) | 2019.04.01 |