본문 바로가기
컴퓨터/Server

패킷 여행 in TCP/IP 네트워크 스택

by 김짱쌤 2015. 4. 12.

패킷 여행

지금까지 네트워크 서버를 만드는 법을 열심히 설명했지만, 실제 컴퓨터 내부적으로 데이터가 어떻게 전송되고 수신되는지는 이야기하지 않았다. 우리가 WSASend나 WSARecv라고 별 생각없이 코딩을 치고 빌드해서 프로그램을 실행하면, 컴퓨터는 그 작업을 수행하기위해서 수많은 작업을 수행한다. 송/수신시 데이터는 추상적으로 구분된 여러 네트워크 레이어를 거쳐 정해진 형식을 갖춰가면서 네트워크 process를 지나간다. 이 레이어들은 크게 어플리케이션에서 관리하는 유저 영역, 그리고 운영체제가 다루는 File, Socket, TCP/IP, 이더넷의 커널 영역, 마지막으로 네트워크 디바이스인 NIC(Network Interface Card, LAN카드)로 구분할 수 있다. 이 과정을 상세히 알아보면서 네트워크 동작 원리를 공부해보자.

데이터 전송

우선 어플리케이션이 데이터를 생성하여 write 시스템 콜을 호출하는 경우를 생각해보자. write할때 우리는 미리 생성된 socket을 통해 데이터 송신을 요청할 것이다. Linux계열 OS는 소켓을 File Descriptor 상태로 App과 연결한다. 그래서 데이터는 파일 I/O의 형태로 Socket에게 전달된다.

전달된 데이터는 Socket에게 할당된 send buffer로 복사된다. 이 버퍼는 커널영역에서 관리하는 메모리에 위치한다. 전송 순서를 유지하기위해서 전달된 데이터는 send buffer의 맨 뒤에 저장된다(push_back).

send buffer에 전송가능한 데이터가 들어오면 이제 TCP의 동작이 수행된다. 소켓별로 TCP 정보를 관리하는 Control Block(TCB)가 있고, TCP 레이어에서는 TCB의 정보를 토대로 데이터 flow를 결정한다. (TCB의 정보 : connection state, recieve window, congestion window, sequence number, 재전송 타이머 등..)
TCP가 전송가능하다고 판단되면 TCP레이어에서는 버퍼에서 전송할 데이터에 TCP헤더를 붙여 캡슐화된 TCP segment(패킷)를 생성한다. 데이터(페이로드)는 TCB의 recieve window, congestion window, maximum segment size중 최대값만큼 담긴다. 그리고 TCP checksum을 계산한다. (최신 네트워크 스택은 똑똑한 NIC를 사용하여 대신 TCP checksum을 계산해준다.)

생성된 세그먼트는 IP레이어로 전달된다. IP레이어에서는 패킷에 IP헤더를 추가한다. 그리고 목적지를 찾는 IP routing을 수행한다. 도착 지점까지 찾는것이 아니라 다음 스텝의 IP(next hop IP) 주소를 재귀적으로 찾아 목적지에 도달하는 것이다. 목적지를 찾으면 IP checksum을 계산하여 패킷에 추가한뒤 Ethernet레이어로 전달한다.

Ethernet레이어는 ARP(Address Resolution Protocol)을 사용해서 next hop IP를 가지고 있는 장치의 MAC주소를 알아낸다. 그리고 Ethernet 헤더를 패킷에 추가한다. Ethernet헤더까지 추가된 패킷이 호스트 패킷의 완전체다. Ethernet에서 찾은 MAC주소는 바로 NIC의 MAC주소다. Ethernet은 찾은 주소(NIC)로 완성된 패킷을 전송한다.

NIC는 Ethernet으로부터 패킷을 전달받아서 자신의 메모리로 복사한다. 그리고 Ethernet표준에 맞춰 IFG, preamble(패킷의 시작점을 체크, framing), CRC(데이터 보호를 위한 일종의 checksum)를 패킷에 추가한다. 그리고 Ethernet flow control에 따라서 데이터를 전송할 수 있는 상황에 네트워크 선을 통해 패킷을 전송한다. 그런데 놀랍게도 우리는 아주 멀리 떨어진 어플리케이션단에서 얼마나 패킷이 전송되었는지를 확인할 수 있다. 이 모든것이 운영체제님의 신묘한 조화다. 운영체제는 NIC에게 전송시점에 Interrupt를 발생시켜달라고 미리 언질을 해두었다. 인터럽트가 발생하면, 운영체제는 이 인터럽트에 대응할 수 있는 핸들러를 호출하고, 핸들러는 전송된 패킷을 운영체제에게 반환한다.

데이터 수신

이제 네트워크로 데이터를 수신했을 때의 상황을 알아보자. 송신과는 달리 거꾸로 패킷이 거슬러 오르는 것을 볼 수 있다. 우선 NIC로 패킷이 수신된다. NIC는 일단 패킷을 메모리에 저장한뒤 CRC 검사하여 올바른 데이터인지 체크하고, 올바른 데이터라면 운영체제와 미리 약속한(드라이버 설치할 때) 수신용 메모리 버퍼로 패킷을 전송한다. 그리고 전송때처럼 운영체제님에게 인터럽트를 발생시킨다. NIC의 드라이버는 수신용 메모리에 도착한 패킷이 통신규약에 맞는 규격을 가지고 있는지 체크한뒤, 올바른 패킷이라면 운영체제님이 사용하는 패킷의 형태로 패킹한다. 실제로 Linux, BSD, Windows의 패킷 구조체 형태가 서로 다르다. 이렇게 포장된 패킷을 상위 레이어로 전송한다.

Ethernet 레이어에서 패킷의 안정성을 검사한뒤 패킷의 상위 프로토콜(네트워크 프로토콜)을 탐색한다. Ethernet 헤더의 etherType값이 어떤 프로토콜로 패킷이 사용됬는지를 표현한다. 결과는 아마도 IP일 것이고, IP 레이어로 패킷을 전달한다.
IP 레이어에서도 checksum을 통해 안정성을 검사한다. 그리고 IP헤더값을 가지고 라우팅을 하여 어느 장비로 패킷을 전달해야하는지를 파악한다.(다른 장비로 데이터를 넘겨야 할수도 있다.) 로컬 장비(나)가 처리하는 패킷이면, proto값을 보고 상위 프로토콜(TCP)을 탐색한다. 결과는 아마도 TCP일 것이고, TCP레이어로 패킷을 전달한다.
TCP도 checksum하여 안정성을 검사한 뒤, 이 패킷에 관여하는 TCB(TCP Control Block)을 찾는다.(src IP:PORT/ dst IP:PORT로 검색) 확인된 TCB의 정보에 따라 프로토콜을 수행하여 패킷을 처리한다. 새로운 데이터를 받은 현재의 경우 해당 TCB에 속한 소켓이 할당받은 recieve socket buffer에 데이터(페이로드)를 추가한다.(push_back)

이제 어플리케이션이 read 시스템 콜을 호출하면, 커널 모드로 전환되어 recieve buffer에 있는 데이터를 유저가 지정한 메모리로 복사해준다. 복사가 끝나면 buffer에서 사용한 데이터를 제거하고 TCP를 호출한다. TCP는 socket buffer 공간이 늘어난 것을 확인하여 빈 공간만큼 recieve window를 증가시킨다. 그리고 프로토콜 상태에 따라서 패킷을 전송한다.


'컴퓨터 > Server' 카테고리의 다른 글

Pooling  (0) 2015.04.17
Windows Low Fragmentation Heap  (0) 2015.04.17
PAGE_LOCKING  (1) 2015.04.12
Reactor / Proactor 패턴  (0) 2015.04.12
windows IOCP 기초  (3) 2015.04.06