본 글은 The Linux Kernel 을 정리한 것이며, 출처를 밝히지 않은 모든 이미지는 원글에 속한 것입니다.


TCP/IP 네트워크 개요

이더넷 프레임

  • 월드 와이드 웹(이하 WWW)은 거대한 IP 네트워크로, 연결된 기계들은 할당된 고유한 IP 주소(32비트 숫자)로 식별됨
    • IP 주소는 네트워크 주소와 호스트 주소로 나누어 구분할 수 있음
    • 호스트 주소는 서브넷(subnetwork, subnet)과 호스트 주소로 더 자세히 나눌 수 있으며, 네트워크를 사용하는 기관은 자신의 네트워크를 몇 구획으로 나눌 수 있음
    • 네트워크 관리자는 IP 주소를 할당할 때, IP 서브넷을 사용하여 네트워크 관리 부담을 분산시킴
    • IP 서브넷 관리자는 자신의 IP 서브넷 내에서 자유롭게 IP 주소를 할당

https://www.slideshare.net/hugolu/the-linux-networking-architecture

  • IP 주소는 숫자가 많아 외우기 어려우며, 문자열로 된 네트워크 이름을 부여해 기계들은 통신 할 수 있음
  • 네트워크 이름을 IP 주소로 변환해주는 작업은, /etc/hosts 파일에 정적으로 명시하거나 분산 네임 서버(Distributed Name Server, DNS)에 변환 요청을 보내서 처리
    • 로컬 호스트는 하나 이상의 DNS 서버의 IP 주소를 알고 있어야 하며, 이 주소들을 /etc/resolv.conf 에 기록
    • 웹 페이지를 읽을 때처럼 다른 호스트(서버)에 접속할 때, 데이터를 교환하기 위해 접속할 대상의 IP 주소를 사용
  • 데이터들은 IP 패킷(packet)에 담겨 전달되며, 각 패킷마다 출발지 호스트와 목적지 호스트의 IP 주소, 체크섬(checksum) 및 IP 헤더가 추가됨
    • 체크섬은 IP 패킷에 있는 데이터를 가지고 계산한 것으로, IP 패킷 수신자(목적지 호스트)는 전화선의 잡음 등에 의해 전달 중에 패킷이 손상되었는지 판단
    • 데이터는 다루기 쉽게 작은 패킷들로 쪼개져서 보내질 수 있고, 목적지 호스트는 패킷들을 다시 조합하여 프로세스에게 전달

https://www.slideshare.net/hugolu/the-linux-networking-architecture

  • 같은 IP 서브넷에 있는 호스트끼리는 IP 패킷을 직접 보낼 수 있지만, 그렇지 않으면 게이트웨이(gateway) 또는 라우터(router)라고 하는 특별한 호스트에 IP 패킷을 전송 (게이트웨이나 라우터는 한 IP 서브넷에서 다른 IP 서브넷으로 패킷을 전달하는 역할)
  • 각 호스트들은 정확한 목적지로 IP 패킷을 전달하기 위해 라우팅 테이블(routing table)을 작성해서 다음 도착지로 패킷을 전달
    • 라우팅 테이블에는 모든 IP 목적지에 대해 다음 도착지를 결정하는데 필요한 정보를 포함
    • 이 테이블은 동적으로 변경되는데, 네트워크를 사용하거나 네트워크 구성도가 변경되면 시간이 지나면서 바뀜
  • TCP 프로토콜은 신뢰할 수 있는 일대일 프로토콜로, 데이터를 주고 받기 위해 IP 프로토콜을 사용하며, IP 프로토콜은 TCP 외에 다른 프로토콜이 데이터를 보낼 때 사용하는 전송 계층
    • IP 패킷에 헤더가 붙는 것처럼 TCP 패킷에도 헤더가 추가됨
    • TCP 프로토콜로 통신하는 두 프로세스는 통신 과정에서 많은 서브넷, 게이트웨이, 라우터가 있더라도 하나의 가상 접속으로 연결됨 (연결 지향 프로토콜)
    • TCP 프로토콜은 데이터의 손실이나 중복이 없다는 것을 보장 (신뢰할 수 있는 프로토콜)
    • TCP 프로토콜이 IP 프로토콜을 통해 TCP 패킷을 전송할 때, TCP 패킷에 헤더까지 포함된 것이 IP 패킷의 데이터
    • 서로 통신하고 있는 호스트의 IP 계층은 IP 패킷을 주고 받는 역할 (받을 때는 헤더를 제거한 데이터를 TCP 계층으로 보냄)
    • UDP 프로토콜은 TCP 프로토콜과는 달리 신뢰할 수 없는 프로토콜로, IP 계층을 사용하여 데이터그램(datagram) 서비스를 제공하는데 패킷의 순서와 도착을 보장하지 않음
    • IP 프로토콜은 IP 패킷에 담긴 데이터를 전달할 상위 프로토콜을 결정하기 위해 모든 IP 패킷 헤더에 프로토콜 식별자를 지정하는 바이트가 존재 (e.g., TCP라면 IP 패킷 헤더에 데이터가 TCP 패킷인 것을 기록)
  • 프로그램이 TCP/IP 계층으로 통신할 때, 프로세스는 상대 IP 주소 외에 포트(port)도 명시해야 함
    • 포트 번호는 프로세스마다 유일한 것으로, 표준 네트워크 프로세스는 표준 포트 번호를 사용 (e.g., 웹서버의 경우 80번)
    • 등록된 포트 번호는 /etc/services 파일에서 확인 가능
  • 프로토콜의 계층 구조는 TCP/UDP 및 IP로 구분하지 않고, IP 프로토콜 자체도 패킷을 전달하는데 여러 장치를 사용하기 때문에 각 장치에서 각자의 프로토콜 헤더를 추가하기도 함
    • 예를 들어, 이더넷 네트워크에서 많은 호스트가 실제로 하나의 케이블에 동시에 접속할 수 있으므로, 전송되는 모든 이더넷 프레임은 연결된 모든 호스트에게 보여짐 (단, 모든 이더넷 장치는 고유한 주소를 가짐)
    • 호스트는 자기 주소로 전달되는 모든 이더넷 프레임을 받아들이지만, 같은 네트워크에 연결된 다른 호스트들은 이들을 무시
    • 이더넷 주소는 6바이트 길이로 (흔히 MAC 주소로 알려진), "08-00-2B-00-49-A4"와 같은 형식
    • 어떤 이더넷 주소는 멀티캐스트(multicast) 목적으로 예약되어 있어, 이 주소로 보내지는 이더넷 프레임들은 같은 네트워크 안에 있는 모든 호스트가 수신함
    • 이더넷 프레임은 데이터로 수많은 프로토콜들을 전송할 수 있기 때문에, 헤더에 프로토콜 식별자가 존재하며, 이더넷 계층은 정확하게 IP 패킷을 IP 계층에 전달 가능
  • 이더넷 같은 다중 접속 프로토콜을 통해 IP 패킷을 보내려면, IP 계층은 IP 호스트의 이더넷 주소를 탐색 (IP 주소는 개념적 주소이며, 이더넷 장치는 고유한 물리적 주소를 가짐)
    • IP 주소는 네트워크 관리자에 의해 지정되고 변경될 수 있으나, 네트워크 하드웨어는 각자의 물리적 주소 또는 멀티캐스트 주소에만 반응함 (즉, 이더넷 주소는 변경할 수 없음)
  • IP 주소를 이더넷 주소 같은 실제 하드웨어 주소로 변환하는 작업은, 주소 변환 프로토콜(ARP)을 사용해서 처리
    • 변환하고자 하는 IP 주소가 담긴 ARP 요청 패킷을 멀티캐스트 주소로 보내 모든 연결된 호스트에 전달
    • 그 IP 주소를 가지고 있는 호스트는 자신의 하드웨어 주소를 ARP 응답 패킷에 담아서 응답
    • ARP 요청이 불가능한 장치들은 별도로 표시하여 ARP를 시도하지 않음
  • 하드웨어 주소를 IP 주소로 변환하는 작업은, RARP를 사용해서 처리. 보통 이 기능은 게이트웨이가 사용하며, 원격 네트워크에 있는 IP 주소를 대신해서 게이트웨이가 ARP 요청에 응답

https://slidetodoc.com/chapter-8-arp-and-rarp-objectives-upon-completion/

리눅스의 TCP/IP 네트워크 계층

리눅스 네트워크 계층

  • 리눅스는 인터넷 프로토콜 주소 패밀리를 일련의 연관된 소프트웨어 계층으로 구현
  • BSD Sockets 계층 일반적인 소켓 관리 소프트웨어가 BSD 소켓만 처리
  • INET Sockets 계층 소켓 관리 소프트웨어를 지원하는데, IP 기반 프로토콜인 TCP/UDP의 통신 종점을 관리
  • TCP(Transmission Control Protocol) 연결 지향의 신뢰할 수 있는 일대일 프로토콜 (TCP 패킷들에 번호를 매겨 종점 호스트는 데이터를 수신할 때 패킷 순서 및 손실을 확인)

https://www.slideshare.net/hugolu/the-linux-networking-architecture

  • UDP(User Datagram Protocol) 비연결지향 방식의 프로토콜 (패킷이 전송 시 제대로 도착했는지 확인 불가)
  • IP 계층 인터넷 프로토콜을 구현한 계층으로, 전송하는 데이터 앞에 IP 헤더를 붙이고, 들어오는 IP 패킷의 헤더를 제거하여 TCP나 UDP로 전달
  • IP 계층 아래의 PPP 또는 이더넷 같은 네트워크 장치들이 리눅스의 모든 네트워킹을 지원하며, 네트워크 장치는 항상 물리 장치를 가리키는 것이 아니라 루프백 장치 같은 몇몇 순수 소프트웨어로 작성된 것도 존재
    • 네트워크 장치는 다른 장치들과 달리 관련 소프트웨어가 장치를 찾아 초기화해야 장치 파일로 보여짐
    • 해당 이더넷 디바이스 드라이버를 추가하여 커널을 빌드해야만 /dev/eth0 를 볼 수 있음

https://www.educative.io/edpresso/tcp-vs-udp

BSD 소켓 인터페이스

  • 다양한 형태의 네트워킹 외에 프로세스간 통신도 지원하는 일반적인 인터페이스
  • 통신하고 있는 두 프로세스는 연결 시, 데이터를 주고 받기 위한 소켓을 가지며, 파이프와 달리 소켓은 저장가능한 데이터 용량이 제한되지 않음
  • 주소 패밀리(Address Family) 소켓의 클래스, 각 클래스별로 통신에 사용하는 주소 표현법을 가짐
    • UNIX 유닉스 도메인 소켓
    • INET TCP/IP 프로토콜을 이용한 통신을 지원하는 인터넷 주소 패밀리
    • AX25 아마추어 라디오 X.25
    • IPX 노벨의 IPX 프로토콜
    • APPLETALK 애플사의 Appletalk DDP 프로토콜
    • X25 X.25 프로토콜
  • 소켓에는 접속을 지원하는 서비스의 종류를 나타내는 타입이 존재 (모든 주소 패밀리가 모든 형태의 서비스를 지원하지는 않음)
    • Stream 이 소켓은 데이터가 전송 중 분실, 오염, 또는 중복되지 않는다는 것을 보장하는 신뢰성 있는 양방향 순차 데이터 스트림
    • Datagram 이 소켓은 양방향 데이터 전송을 제공하나, 메시지 도착 유무, 순서 보장, 중복 제거, 오염 유무 등을 보장하지 않음
    • Raw 프로세스가 하부 프로토콜에 직접 접근 가능. 이더넷 장치에 이 소켓을 열어 가공되지 않은 IP 데이터 흐름을 볼 수 있음
    • Reliable Delivered Messages 데이터그램 소켓과 유사하지만, 데이터가 목적지에 도착한다는 것을 보장
    • Sequenced Packets 스트림 소켓과 유사하며, 데이터 패킷 크기가 고정됨
    • Packet 표준 BSD 소켓 타입이 아니며, 장치 수준에서 프로세스가 직접 패킷에 접근할 수 있는 확장 패킷 유형
  • 소켓을 사용하여 통신하는 프로세스는 클라이언트 서버 모델을 따르며, 서버는 서비스를 제공하고, 클라이언트는 이 서비스를 이용
    • 서버는 먼저 소켓을 생성하여 이름(소켓 주소 패밀리에 따라 다르며 대개 로컬 주소)을 bind한 후, 소켓의 이름 또는 주소를 sockaddr 구조체에 명시 (INET 소켓은 그것에 바인드된 IP 포트 번호를 가짐)
    • 서버는 바인드된 주소를 가리키는 연결 요청이 들어오는 listen
    • 클라이언트는 소켓을 생성하여 서버의 주소를 명시하여 서버측 소켓에 대해 연결 요청 (INET 소켓의 경우 포트 번호를 포함)
    • 연결 요청은 다양한 프로토콜 계층을 통해 전달되어 서버의 소켓에 도달하고, 서버는 요청을 받고 수락(accept) 또는 거절(reject) 할 수 있음
    • 요청을 받아들이면, 새로운 소켓을 생성 (listen 소켓은 연결 요청을 받아들이는데만 사용 가능)
    • 연결이 성립되면, 서버와 클라이언트는 자유롭게 데이터를 주고 받음
    • 연결이 필요없는 경우, 소켓을 종료(shutdown)하며, 이때 전송 중인 데이터 처리에 유의해야 함

https://rastating.github.io/using-socket-reuse-to-exploit-vulnserver/

  • 커널 초기화 과정에서 커널에 구현된 주소 패밀리는 BSD 소켓 인터페이스와 함께 커널에 자신을 등록
  • 프로세스가 BSD 소켓을 만들어 사용할 때, BSD 소켓과 지원하는 주소 패밀리 사이의 연관관계는 connection 자료구조와 주소 패밀리 고유의 지원 루틴 테이블을 통해 생성 (프로그램이 새 소켓을 생성할 때, 주소 패밀리 고유의 소켓 생성 루틴이 존재)
  • 커널을 설정할 때 주소 패밀리와 프로토콜을 protocols 배열에 추가. 이 배열에는 각 주소 패밀리 또는 프로토콜의 이름(e.g., INET)과 초기화 루틴이 포함되어 있음
  • 부팅 시 소켓 인터페이스를 초기화할 때, 각 프로토콜의 초기화 루틴이 호출되고, 소켓 주소 패밀리마다 일련의 프로토콜 연산 루틴을 등록 (이것은 루틴들의 집합이며, 각 루틴은 해당 주소 패밀리의 고유한 특정 연산을 수행)
  • proto_ops 구조체는 주소 패밀리 타입과 특정 주소 패밀리에 고유한 소켓 연산 루틴에 대한 포인터들의 집합으로 구성됨
  • pops 배열은 인터넷 주소 패밀리 같은 (AF_INET은 2) 주소 패밀리 식별자로 인덱싱

https://slidetodoc.com/socket-layer-coms-w-6998-spring-2010-erich/

INET 소켓 계층

  • TCP/IP 프로토콜을 포함하는 인터넷 주소 패밀리를 지원, 한 프로토콜이 다른 프로토콜의 서비스를 이용하며, 리눅스 TCP/IP 코드와 자료구조는 프로토콜들의 계층 구조를 반영
  • BSD 소켓 계층과의 인터페이스는 네트워크 초기화 중에 BSD 소켓 계층에 등록한 인터넷 주소 패밀리 소켓 함수들의 집합
    • BSD 소켓 계층에서 pops 배열에 등록된 다른 주소 패밀리와 함께 보관됨
    • BSD 소켓 계층은 등록된 INET proto_ops 구조체로부터 INET 계층의 소켓 지원 루틴을 호출하여 작업
    • 주소 패밀리에 INET을 주고 BSD 소켓 생성을 요구하면, 이는 하위 INET 소켓 생성 함수를 호출
  • BSD 소켓 계층은 각 함수를 호출할 때마다 INET 계층에 있는 BSD 소켓을 나타내는 socket 구조체를 전달하고, socket 구조체에 있는 data 포인터는 sock 구조체를 참조하는데, 이 구조체는 INET 소켓 계층에서 사용됨
  • sock 구조체의 프로토콜 함수 포인터는 생성 시 설정되는 것으로, 요구한 프로토콜에 따라 다름. 만약 TCP를 요구했다면, 프로토콜 함수 포인터는 TCP 연결을 위해 필요한 TCP 프로토콜 함수 집합을 참조

  • BSD 소켓 생성
    • 새 소켓을 만드는 시스템 콜에는 주소 패밀리 식별자, 소켓 타입, 프로토콜을 인자로 넘겨야 하며, 요구한 주소 패밀리를 사용하여 pops 배열에서 일치하는 주소 패밀리를 탐색
      • 어떤 주소 패밀리는 커널 모듈에 의해 생성되었기 때문에, kerneld 데몬이 이 모듈을 이용해서 주소 패밀리를 탐색
    • BSD 소켓을 나타내기 위해 새 socket 구조체를 할당. 이 구조체는 VFS inode 구조체의 일부이며 실제로 VFS inode를 할당하는 것과 같음 (이는 소켓이 일반 파일과 동일하게 작동하게 하며, 파일 함수들을 이용해 소켓을 열고, 닫고, 읽고, 쓸 수 있음)
    • socket 구조체는 주소 패밀리에 따라 특수한 소켓 루틴들에 대한 포인터를 포함하며, pops 배열에서 얻을 수 있는 proto_ops 구조체에 이 포인터들이 설정됨 (타입은 요구한 소켓 타입으로 설정: SOCK_STREAM 또는 SOCK_DGRAM 등)
    • proto_ops 구조체에 있는 주소를 호출하면, 주소 패밀리에 따라 다른 생성 루틴이 실행
    • 현재 프로세스의 fd 배열에 텅빈 파일 기술자가 할당되고 이는 초기화된 file 구조체를 참조하며 이 구조체의 파일 함수 포인터가 BSD 소켓 파일 함수들을 가리키도록 설정
    • 이후 소켓 파일 연산들은 BSD 소켓 인터페이스로 전달되며, 인터페이스는 차례로 주소 패밀리의 함수들을 호출함으로써 각 주소 패밀리로 작업을 전달
  •  bind: 주소와 INET BSD 소켓 바인딩
    • 각 서버는 INET BSD 소켓(접속 요청을 받는 listen 용도)을 만들어 서버의 주소와 바인드
      • 주소와 바인드된 소켓은 다른 통신을 위해서는 사용할 수 없음 (socket 상태는 TCP_CLOSE)
    • 바인드 작업은 대부분 INET 소켓 계층이 아래 계층인 TCP/UDP 프로토콜 계층으로부터 어느 정도 지원을 받아 처리
    • INET 주소 패밀리를 지원하며, 네트워크 장치에 할당된 IP 주소가 바인드된 주소
      • IP 주소는 모두 1 또는 0인 IP 브로드캐스트(broadcast) 주소일 수 있으며, 이는 모든 호스트에게 보내라는 의미
      • 단, 슈퍼유저 권한의 프로세스만이 아무 IP 주소에나 바인드 할 수 있음
      • 바인드된 IP 주소는 recv_addr 구조체에 있는 sock 구조체와  saddr 항목에 저장됨
    • 포트 번호는 옵션이며, 지정하지 않으면 이를 지원하는 네트워크에게 빈 포트 번호를 요청
      • 1024보다 작은 포트 번호는 슈퍼유저 권한이 없는 프로세스는 사용할 수 없음 (well-known ports)
    • 네트워크 장치는 패킷을 받으면, 이를 올바른 INET 과 BSD 소켓으로 전달하여 처리
    • TCP/UDP는 들어온 IP 메시지에 있는 주소를 조회하여 올바른 socket/sock 쌍으로 전달하기 위한 해시 테이블을 관리
      • TCP는 연결 지향 프로토콜로, UDP 패킷을 처리할 때보다 TCP 패킷을 처리할 때 더 많은 정보가 사용됨
      • UDP는 할당된 UDP 포트의 해시 테이블인 udp_hash 자료구조를 관리
      • 이는 sock 구조체의 포인터 배열로, 포트 번호에 기반하여 해시 값을 계산
    • TCP는 바인드 작업 동안에 바인드하는 sock 구조체를 해시 테이블에 추가하지 않고, 단지 요구한 포트 번호가 현재 사용중인지만 검사 (sock 구조체는 listen 작업 중에 TCP 해시 테이블에 추가됨)
  • connect: INET BSD 소켓으로 연결
    • 연결 요청을 받는 용도(listen)로 사용되지 않으면, 연결 요청을 하는 용도(connect)로 사용 가능
    • UDP는 비연결지향이므로 이런 작업이 필요 없으며, TCP만 두 프로세스 사이에 가상 연결을 생성할 때 필요함
      • UDP도 접속 BSD 소켓 함수를 지원하지만, UDP INET BSD 소켓에서의 접속 작업은 단순히 원격지의 IP 주소 및 포트 번호를 설정하는 것
      • 추가적으로 라우팅 테이블 엔트리에 대한 캐시를 설정하여, 이 BSD 소켓으로 보낸 UDP 패킷이 다시 라우팅 DB를 검사하지 않도록 함 (경로가 틀리지 않은 경우)
      • 캐시된 라우팅 정보는 INET sock 구조체에서 ip_route_cache 에 의해 참조됨
      • UDP는 sock 상태를 TCP_ESTABLISHED 로 변경
    • TCP는 접속 정보를 가진 메시지를 하나 생성해 목적지로 전달하며, 이 메시지는 시작 메시지 순서 번호와 시작하는 호스트에서 처리할 수 있는 메시지 최대 크기, 송수신 윈도우 크기(아직 보내지 않은 메시지들 중 보관 가능한 메시지의 수) 등을 포함
      • 최대 메시지 크기(MTU)는 요청을 시작한 쪽에서 사용하고 있는 네트워크 장치에 따라 바뀜
      • 받는 쪽의 네트워크 장치가 이보다 작은 최대 메시지 크기를 지원하면 접속중 최소 MTU 값을 사용
    • TCP에서는 모든 메시지에 번호가 붙으며, 초기 순서 번호는 첫번째 메시지 번호와 같고, 악의적인 프로토콜 공격을 피하기 위해 허용 범위 내에서 임의값으로 번호를 지정
    • TCP에서 메시지를 수신하는 호스트는 모든 메시지에 대해 성공적으로 도착하였다는 응답을 송신지로 전달. 만약 이 응답이 없으면 송신측에서 같은 메시지를 재전송
    • TCP 접속 요청을 원하는 프로세스는 요청이 accept/reject 중 하나라는 응답을 받을 때까지 블락되며, 타이머를 걸어 타임아웃시 프로세스가 실행되도록 할 수 있음
    • TCP는 메시지가 들어올 때까지 대기하며, tcp_listening_hash 를 추가하여 들어온 메시지가 sock 구조체로 전달되게 함
    • 클라이언트가 연결을 요청하고, 서버가 요청을 수락하는 과정에서 TCP는 총 3개의 패킷을 주고 받음
      • 이를 3-Way Handshaking 이라고 함
    • 클라이언트가 연결 종료를 요청하고, 서버가 요청을 받고 소켓을 종료하는 과정에서 TCP는 총 4개의 패킷을 주고 받음
      • 이를 4-Way Handshaking 이라고 함

3-way handshaking
https://asfirstalways.tistory.com/356

  • listen: INET BSD 소켓에서 접속 요청 대기
    • 소켓에 주소를 바인딩 했으면, 바인드한 주소를 지정하여 들어오는 접속 요청을 대기하는데, listen 함수는 소켓의 상태를 TCP_LISTEN 으로 바꾸고 들어오는 접속을 허가하기 위해 필요한 특수 작업을 처리
    • 먼저 주소를 바인드하지 않고 접속을 기다릴 수 있으며, 여기서 INET 소켓 계층은 사용하지 않는 포트 번호를 찾아 소켓에 지정
    • UDP는 소켓 상태만 변경하는 반면, TCP는 sock 구조체를 두 개의 해시 테이블(tcp_bound_hash, tcp_listening_hash)에 추가 (두 해시 테이블 모두 IP 포트 번호에 기반한 해시 함수를 통해 인덱싱)
    • listen 소켓은 TCP 접속 요청을 받으면 이를 나타내는 sock 구조체를 생성하고, 접속 요청을 포함한 sk_buff 구조체를 복사하여 sock 구조체의 receive_queue 뒤에 이를 추가
    • 복사한 sk_buff 는 새로 만든 sock 구조체에 대한 포인터를 저장

https://myaut.github.io/dtrace-stap-book/kernel/net.html

  • accept: 접속 요청 허가
    • INET 소켓 접속을 허락하는 것은 TCP 프로토콜에만 적용 (UDP는 해당 없음)
    • listen 소켓 외에 accept/reject 을 위한 소켓에서 socket 구조체를 복사하여 새 socket 구조체를 생성
    • 지원하는 프로토콜 계층(TCP)에 허가하라는 명령을 전달
    • 블럭킹 모드가 아닌 경우, 들어오는 접속이 없으면, 위 작업은 실패하며 새로 만들어진 socket 구조체는 제거됨
    • 블럭킹 모드이면, 접속을 허가하는 프로세스는 대기 큐에 추가되고, 요청을 받을 때까지 중단됨
    • 클라이언트는 서버로부터 ACK 받으면 다시 ACK(접속을 허용 해달라는 메시지)를 보내는데, 이때 sk_buff 구조체는 무시되고, sock 구조체는 이전에 새로 만든 socket 구조체와 연결된 INET 소켓 계층으로 반환됨
    • 새로 생성된 소켓의 파일 기술자(fd)를 프로세스에게 돌려주고, 프로세스는 새 BSD 소켓을 가지고 작업할 때마다 fd를 사용

TCP 연결 과정
https://myaut.github.io/dtrace-stap-book/kernel/net.html

IP 계층

https://www.slideshare.net/hugolu/the-linux-networking-architecture

  • 소켓 버퍼(Socket Buffer)
    • 여러 네트워크 프로토콜 계층을 가지면, 각 계층에서 다른 계층의 서비스를 사용하게 되는데, 전송할 때는 데이터에 헤더와 테일을 붙이고, 받을 때는 데이터에서 헤더와 테일을 제거해야 하는 오버헤드가 존재
    • 패킷에서 프로토콜 헤더와 테일을 탐색해야 하므로, 프로토콜 사이에 데이터 버퍼를 전달하는 것을 어렵게 함 (단순히 버퍼를 복사하는 것은 매우 비효율적)
    • 리눅스는 프로토콜 계층 사이에서 네트워크 디바이스 드라이버 간에 데이터를 주고 받기 위해, sk_buff 구조체의 리스트 포인터를 이용하는 소켓 버퍼를 사용
    • 소켓 버퍼는 포인터를 이용해 각 프로토콜 계층이 표준 함수로 프로그램 데이터를 다룰 수 있게 함
    • 각 sk_buff 구조체는 각자의 데이터 블럭을 가지며, 데이터를 다루고 관리하기 위해 4개의 데이터 포인터를 포함
      • head 메모리에서 데이터의 시작 위치를 참조 (블럭 할당 시 고정)
      • data 현재 프로토콜 데이터의 시작 위치를 참조 (현재 sk_buff 구조체를 소유하고 있는 프로토콜 계층에 따라 바뀜)
      • tail 현재 프로토콜 데이터의 끝 위치를 참조 (현재 sk_buff 구조체를 소유하고 있는 프로토콜 계층에 따라 바뀜)
      • end 메모리에서 데이터의 끝 위치를 참조 (블럭 할당 시 고정)
    • sk_buff 구조체를 다루는 코드는 데이터에 헤더와 테일을 붙이고 제거하는 표준 함수를 제공
      • len 현재 프로토콜 패킷의 길이 (data ~ tail)
      • truesize 데이터 버퍼의 전체 크기 (head ~ end)
      • push() data 포인터를 데이터 영역의 시작 쪽으로 이동시키고, len 값을 증가시킴. 전송할 패킷의 시작 부분에 데이터나 프로토콜 헤더를 추가하는데 사용
      • pull() data 포인터를 데이터 영역의 끝 쪽으로 이동시키고, len 값을 감소시킴. 수신한 패킷의 시작 부분에서 데이터나 프로토콜 헤더를 제거하는데 사용
      • put() tail 포인터를 데이터 영역의 끝 쪽으로 이동시키고, len 값을 증가시킴. 전송할 패킷의 끝에 데이터나 프로토콜 정보를 추가하는데 사용
      • trim() tail 포인터를 데이터 영역의 시작 쪽으로 이동시키고, len 값을 감소시킴. 수신한 패킷의 끝 부분에서 데이터나 프로토콜 정보를 제거하는데 사용

https://www.slideshare.net/hugolu/the-linux-networking-architecture

  • 디바이스 드라이버 초기화의 결과는 dev_base 리스트에서 서로 연결되어 있는 일련의 device 구조체들로, 각 구조체는 장치를 기술하고 네트워크 프로토콜 계층에서 드라이버가 작업할 때 호출하는 콜백 함수 집합을 제공
    • 이들 함수들은 대부분 데이터 전송 및 네트워크 장치 주소와 관련된 것

https://www.slideshare.net/hugolu/the-linux-networking-architecture

  • IP 패킷 수신
    • 네트워크 장치가 패킷을 수신하면 이들은 sk_buff 구조체로 변환되어, backlog 큐에 sk_buff 구조체가 추가됨
      • 만약 backlog 큐가 너무 커지면, 패킷을 무시
    • 이후 실행 준비가 되었음을 네트워크 하반부(bottom half)에 표시하고, 스케줄러는 하반부 핸들러를 실행하는데, 이 핸들러는 backlog 큐를 처리하기 전에 수신한 패킷을 전달할 프로토콜 계층을 결정하며, 전송 대기 중인 패킷들을 처리
    • 네트워크 계층은 초기화될 때, 각 프로토콜은 packet_type 구조체를 ptype_all 리스트나 ptype_base 해시 테이블에 추가하여 자신을 커널에 등록
      • packet_type 구조체는 프로토콜 타입, 네트워크 장치에 대한 포인터, 프로토콜의 수신 데이터 처리 루틴 및 리스트나 해시 테이블에 있는 다음 packet_type 구조체에 대한 포인터를 포함
      • ptype_base 해시 테이블은 프로토콜 식별자로 인덱싱되어, 수신한 패킷을 받을 프로토콜을 결정하기 위해 사용됨
    • 네트워크 하반부 핸들러는 들어오는 sk_buff 구조체의 프로토콜 타입과 ptype_base 해시 테이블에 있는 하나 이상의 packet_type 엔트리와 비교 (반드시 프로토콜은 하나 이상의 엔트리와 매칭됨)

https://www.slideshare.net/hugolu/the-linux-networking-architecture

  • IP 패킷 송신
    • 프로그램이 전송할 데이터를 생성하면, 데이터를 포함한 sk_buff 구조체가 만들어지고, 각 프로토콜 계층을 위에서 아래로 통과하면서 계층별 헤더가 추가됨
    • sk_buff 구조체는 전송할 네트워크 장치로 전달되며, IP 같은 프로토콜은 사용할 장치를 결정
    • 패킷은 루프백 장치를 통해 PPP 모뎀 연결의 끝에 있는 게이트웨이 또는 로컬 호스트 둘 중 하나로 전달됨
    • IP 패킷을 전송할 때는 도달할 IP 주소로 가는 루트(route)를 결정하기 위해 라우팅 테이블(routing table)이 사용되며, 각 IP 목적지는 자신의 라우팅 테이블로부터 다음 도착지를 알아내 루트를 기술하는 rtable 구조체를 반환
      • 이 구조체는 사용할 출발지 IP 주소, 네트워크 device 구조체의 주소, 간혹 미리 만들어진 하드웨어 헤더를 포함
      • 하드웨어 헤더는 네트워크 장치마다 다르며, 출발지와 도착지의 하드웨어 주소와 매개체 별로 다른 정보를 포함
      • 예를 들어, 이더넷 장치이면 출발지/도착지 주소는 물리적 주소(이던세 주소)
    • 하드웨어 헤더는 루트와 함께 캐시되는데, 이 헤더가 해당 루트를 통하여 전송하는 모든 IP 패킷에 추가되는 과정을 줄임
    • 하드웨어 헤더는 ARP 프로토콜로 해결(resolve)되어야 하는 물리 주소가 필요할 수 있는데, 패킷은 주소가 해결될 때까지 블락됨 (한 번 ARP를 요청하면 캐싱하므로 재요청이 필요 없음)
  • 데이터 조각내기(Data Fragmentation)
    • 모든 네트워크 장치는 최대 패킷 크기(MTU)를 가지기 때문에 이보다 큰 크기의 데이터를 보내거나 받기 위해 IP 프로토콜은 데이터를 처리 가능한 크기로 나눔
    • IP 패킷을 보내기 위해, IP 라우팅 테이블에서 네트워크 장치를 찾고, 패킷 크기가 MTU 보다 크면 크기에 맞춰 데이터를 조각내서 각 sk_buff 구조체를 전달 (패킷을 쪼개는 도중에 IP가 sk_buff 구조체를 할당받지 못한다면 전송은 실패)
    • IP 패킷 헤더는 플래그와 이 조각의 오프셋을 가리키는 항목을 포함하며, 마지막 패킷은 마지막 IP 조각이라고 표시
    •  IP 패킷을 받기 위해, IP 패킷 조각이 임의의 순서로 도착하는 점을 고려하여 모두 받은 뒤 재조립
      • 수신할 때마다 패킷 조각인지 검사하고, 처음 받은 경우 새 ipq 구조체를 생성
      • ipq 구조체는 재조립을 기다리는 IP 패킷 조각 리스트인 ipqueue 에 추가됨
      • IP 패킷 조각을 받을 때마다, 맞는 ipq 구조체를 찾기 위해 이 조각을 나타낼 ipfrag 구조체를 생성함 (ipq 구조체에서 fragments 변수에 저장됨)
      • 각 ipq 구조체는 조각난 IP 수신 프레임을 출발지와 도착지 IP 주소와 함께 유일하게 기술하며, 위 프로토콜 계층의 식별자와 해당 IP 프레임의 식별자를 가짐
      • 모든 조각이 도착하면, 데이터들은 하나의 ipq 구조체에서 리스트로 보관되므로, 이들을 sk_buff 구조체 하나로 병합
      • 병합된 sk_buff 구조체를 다음 프로토콜 계층으로 전달
    • 각 ipq 구조체는 조각이 도착할 때마다 타이머를 시작하는데, 타임아웃되면 ipq 구조체와 이것의 ipfrag 구조체들은 메모리에서 소멸되어 메시지는 전송 중 사라진 것으로 간주 (이들의 재전송은 상위 프로토콜의 책임)

데이터 조각화
https://www.slideshare.net/hugolu/the-linux-networking-architecture

주소 결정 프로토콜(Address Resolution Protocol, ARP)

  • IP 주소에서 이더넷 주소 같은 하드웨어 주소로의 변환을 제공하는 프로토콜
  • IP는 디바이스 드라이버에게 데이터를 sk_buff 구조체로 전달하기 전에 ARP를 호출해서 하드웨어 주소를 설정
  • 이 장치가 하드웨어 헤더를 필요로 한다면, 패킷용으로 하드웨어 헤더를 다시 만들어야 하는지 알기 위해 여러 검사를 수행
  • 리눅스는 하드웨어 헤더를 자주 만들지 않도록 캐시를 사용하며, 다시 만들어야 한다면 장치 고유의 하드웨어 헤더 재생성 루틴을 호출
  • 모든 이더넷 장치는 동일한 헤더 재생성 루틴을 호출하며, 이 루틴은 목적지 IP 주소를 물리 주소로 바꾸기 위한 ARP 서비스를 사용
  • ARP 요청과 응답 두 가지 메시지 형태가 존재: 요청은 변환할 IP 주소를 가지며, 응답은 하드웨어 주소(변환된 IP 주소)를 가짐
  • ARP 요청은 네트워크에 연결된 모든 호스트로 전달(브로드캐스트)되므로, 이더넷에 연결된 모든 호스트들이 ARP 요청을 수신하게 되는데 해당 IP 주소의 호스트만 응답

https://slidetodoc.com/chapter-8-arp-and-rarp-objectives-upon-completion/

  • ARP 계층은 각 IP 주소에서 하드웨어 주소로의 변환을 나타내는 arp_table 자료구조를 가지며, 각 엔트리는 IP 주소가 변환될 필요가 있을 때 만들어지고 시간이 지나면서 제거됨
    • last used arp_table 엔트리가 마지막으로 사용된 시간
    • last updated arp_table 엔트리가 마지막으로 수정된 시간
    • flags arp_table 엔트리가 완료되었는지 같은 상태를 나타내는 플래그
    • ip address 엔트리가 나타내는 IP 주소
    • hardware address 변환된 하드웨어 주소
    • hardware header 캐시된 하드웨어 헤더에 대한 포인터
    • timer 응답하지 않는 요구를 타임아웃시키기 위한 timer_list 리스트의 엔트리
    • retries ARP 요청을 재시도한 횟수
    • sk_buff queue 이 IP 주소를 해결(resolve)하길 기다리는 sk_buff 구조체들의 리스트
  • arp_tables 배열은 arp_table의 엔트리들에 대한 캐시로, 각 엔트리는 IP 주소의 끝 두 바이트를 가져와 인덱싱되며, 원하는 엔트리를 찾기 위해 해시 테이블에서 배열을 인덱싱하여 얻은 리스트를 순회
  • 미리 만들어진 하드웨어 헤더의 경우 hh_cache 구조체로 arp_table 엔트리에 캐시
  • 일치하는 arp_table 엔트리가 없는 경우
    • ARP 요청 메시지를 브로드캐스트로 전달하고 타이머를 실행
    • 새 arp_table 엔트리를 생성한 후, 주소 변환을 필요로 하는 패킷들을 엔트리의 리스트(sk_buff queue)에 추가
    • ARP 응답이 없다면, 여러번 요청을 재시도 (여전히 응답이 없으면 arp_table에서 엔트리는 제거되고 큐된 sk_buff 구조체는 실패로 처리 --> TCP는 성립된 TCP 연결을 통해 패킷을 재전송하려고 시도)
    • ARP 응답이 있다면, arp_table 엔트리는 완료된 것으로 표시되고 큐에서 sk_buff 구조체들을 제거
    • 하드웨어 주소는 각 sk_buff 구조체의 하드웨어 헤더에 기록됨
  • ARP 계층은 자신의 프로토콜 타입(ETH_P_ARP)을 커널 네트워크 자료구조에 등록하고, packet_type 구조체를 생성
  • ARP 계층은 네트워크 장치가 수신한 모든 ARP 패킷을 전달받으며, ARP 요청에는 자신의 IP 주소가 지정되어 있으면 반드시 응답
  • 수신한 장치의 device 구조체에 저장되어 있는 하드웨어 주소로 ARP 응답을 생성
  • 네트워크 구성은 시간에 따라 바뀔 수 있으며, IP 주소는 다른 하드웨어 주소로 재할당 될 수 있음. 예를 들어, 전화접속 서비스는 연결될 때마다 각각 다른 IP 주소를 배정 받음
  • arp_tables 캐시가 항상 가장 최근의 엔트리를 가질 수 있도록, 정기적인 타이머를 실행해 모든 엔트리들이 타임아웃되지 않았는지 검사. 만약 하나 이상의 캐시된 하드웨어 헤더를 가지는 엔트리들(다른 자료구조들과 의존 관계)은 제거되지 않도록 주의
  • arp_table 엔트리 중 몇몇은 영구적이며, 이들은 할당이 해제되지 않도록 별도로 표시됨
  • 각 엔트리는 커널 메모리에 저장되기 때문에 너쿠 커지지 않도록 크기가 최대값에 도달할 때마다 가장 오래된 엔트리들을 제거

https://slidetodoc.com/chapter-8-arp-and-rarp-objectives-upon-completion/

IP 라우팅

  • IP 라우팅 함수는 특정 IP 주소를 목적지로 하는 IP 패킷의 다음 도착지를 결정
  • 목적지에 도착 가능 유무, 전송하는데 사용할 네트워크 장치, 장치의 성능 등에 관한 정보를 관리하는 IP 라우팅 DB가 2개 존재
  • 전달 정보 데이터베이스(Forwarding Information Database) IP 주소와 알려진 루트(route)의 목록을 저장
    • 각 IP 서브넷은 fib_zone 구조체로 표현되며, 이들 모두는 fib_zones 해시 테이블에서 참조
    • 해시 값은 IP 서브넷 마스크로 생성되며, 동일한 서브넷으로의 모든 루트들은 fib_node 쌍으로 기술됨
    • fib_info 구조체는 각 fib_zone 구조체의 fz_list 큐에 추가됨
    • 만약 이 서브넷에 있는 루트 개수가 많아지면, fib_node 구조체를 쉽게 찾도록 해시 테이블이 생성됨
    • 서브넷으로 가는 루트가 여러 개 있다면, 각 루트는 다른 게이트웨이를 사용해야 함
    • 한 루트의 거리는 도달해야 하는 서브넷까지 거쳐야 하는 IP 서브넷의 개수를 의미 (이 값이 클수록 좋지 않은 루트)
    • 루트는 BSD 소켓 인터페이스로 IOCTL 요청을 보내서 추가되거나 제거 될 수 있음
      • 네트워크 구성이 시간이 지남에 따라 바뀌면 루트도 동적으로 변할 수 있음
    • 슈퍼유저 권한을 프로세스만이 INET 프로토콜 계층에서 IP 루트를 추가 및 제거 할 수 있음
    • 대부분 시스템은 라우터가 아닌 단말 시스템의 경우, 고정된 루트를 사용
    • 라우팅 프로토콜은 GATED 같은 데몬으로 구현되어 있으며, IOCTL 요청을 보내서 루트를 추가하거나 삭제

  • 목적지로 가는 루트를 빨리 찾기 위해, 더 작고 더 빠른 DB인 루트 캐시(route cache)를 사용
    • 루트 캐시는 자주 접근하는 루트에 대한 것들만 저장하며, ip_rt_hash_table 자료구조로 표현
    • 이 해시 테이블은 rtable 구조체의 리스트에 대한 포인터를 가진 배열로, 해시 값은 IP 주소 하위 두 바이트로 계산됨
    • 두 바이트는 목적지마다 다르기 때문에 해시 값을 가장 잘 분산시켜줌
    • 각 rtable 엔트리는 루트에 대한 정보를 포함: 목적지 IP 주소, 이 주소에 도달하는데 사용할 네트워크 장치, 메시지의 최대 크기, 참조 횟수, 사용 횟수, 타임스탬프 등
    • 참조 횟수는 이 루트가 사용될 때마다 증가하는 값으로, 이 루트를 사용하는 네트워크 연결의 개수를 의미. 프로세스가 이 루트로 데이터를 주고받지 않게 되면 감소
    • 사용 횟수는 이 루트를 발견할 때마다 증가하며, rtable 리스트에서 엔트리의 순서를 결정하는데 사용
    • 타임스탬프는 마지막으로 사용한 시간 값으로, 엔트리가 너무 오래되지 않았는지 검사하는데 사용
  • IP 루트를 조회하면 일치하는 루트를 찾기 위해 루트 캐시를 먼저 검사
  • 루트 캐시에 일치하는 루트가 없다면, 전달 정보 데이터베이스에서 루트를 탐색
  • 어떤 루트도 찾을 수 없다면, IP 패킷은 전송 실패로 처리되어 프로세스에게 전달됨
  • 루트가 전달 정보 데이터베이스에 있고 루트 캐시에 없다면, 이 루트에 해당하는 새 엔트리를 만들어 루트 캐시에 추가
  • 루트 캐시는 LRU 방식으로 동작: 최근에 많이 사용되지 않은 엔트리는 루트 캐시에서 제거하고, 만약 찾는 루트가 캐시에 있다면 리스트 맨 앞에 오도록 배치 (가장 최근에 많이 접한 엔트리가 항상 앞에 오도록 함)

 

+ Recent posts