Socket (cont'd)

John·2025년 6월 13일

system programming

목록 보기
3/11

Socket이란?

소켓은 네트워크 프로그래밍에 있어, 유저와 커널 사이를 연결하는 인터페이스
소켓이 있기 때문에, 굳이 커널 내부 동작까지 직접 접근하지 않아도 operation을 수행할 수 있다.(추상화)

이전에도 서술했듯이, 소켓은 fd값을 return하기 때문에 파일처럼 관리할 수 있다. (Everything is a file). 따라서, 소켓을 통한 네트워크 관련 동작은 리눅스에서 파일 시스템으로 파일을 관리하는 방식과 유사하게 작동한다고 볼 수 있다.

<작동 방식>
1. 유저 프로세스에서 통신을 위해 소켓을 생성한다
2. 소켓을 생성하면 fd값이 주어진다.
3. fd값을 파라미터로 이용해 다양한 operation(read, write등)을 호출한다.
4. operation은 VFS을 거쳐 알맞은 파일시스템의 함수를 찾아간다.(ex. btrfs, ext4 등. socket은 sockfs)
*vfs로 operation의 추상화가 가능해져, 직접 특정 파일 시스템을 굳이 찾아가지 않고 socket에 관련된 syscall만 때려도 구현 가능.

Structure of Socket

앞서 살펴봤듯, 소켓은 일종의 창구로서 어떤 종류의 네트워크 스택이나 프로토콜을 사용할지 지정하는 역할을 한다.

소켓의 구조는 다음과 같다.
int socket (int family, int type, int protocol)

family : protocol family(PF)

ex)
PF_INET : IPv4 *INET = internet

PF_INET6 : IPv6

PF_UNIX(=PF_LOCAL) : 내PC 내부에서 전송. 패킷이 PC 밖으로 안나감 e.g. IPC(Inter Process Communication)

PF_PACKET : L3이나 L4 거치치 않고 L2 protocol 만 처리. L3, L4를 위해 처리되지 않은 패킷 그자체를 봐야할 경우
ex)연구, 디버깅 필요할 때. pcap 라이브러리가 주로 이 프로토콜 사용. wireshark 프로그램 또한 PF_PACKET을 사용한 pcap 라이브러리를 사용해 패킷을 직접 들여다 봄

fyi) Google의 QUIC 프로토콜

type : a semantics

type은 전송 방식 중 요구되는 동작을 정의한 것이다. 참고로 cs에서 semantics란, 기대되는 행동 방식을 뜻한다.
즉, type은 어떤 PF인지에 따라 기대되는 행동 방식을 정의한 것이다.

ex)
PF_INET 의 대표적인 type

  • SOCK_STREAM : 순차적, 신뢰성 보장(패킷이 정확히 도착해야 함), 연결 지향형 -> TCP 프로토콜을 실현하는데 사용
  • SOCK_DGRAM : 비연결 지향형, 신뢰성 보장X -> UDP 프로토콜에 사용
  • SOCK_RAW : raw network을 위한 type.

cf)
raw socket : "un-headered" packet access

일반적인 socket의 작동 방식 :
컴퓨터 A 에서 컴퓨터 B로 데이터를 전달할 때, 여러 메타데이터를 붙여서 보낸다. (택배 보낼 때 송장 붙이는 것처럼.) 계층 별로 필요한 메타데이터가 데이터 옆에 붙어있는 것을 header라고 한다. 각각 layer을 지나갈 때 header을 읽은 후 다음 layer로 넘긴다.

raw socket : 계층 별 메타데이터를 안뜯어보고 바로 상위 계층으로 옮기는 방법. 이전의 계층을 그냥 일종의 데이터처럼 처리해서 본다고 생각하면 될 듯.

레이어를 거치면 오버헤드가 발생하는데, 굳이 이전 레이어에서 처리가 안필요하다면 raw socket을 통해서 오버헤드 줄일 수 있음. ex) ICMP, IGMP 프로토콜은 유저 레벨에서 주로 구현이 됨.
유저 단계에서 헤더를 보고 수정할 수 있기 때문에, 새 프로토콜을 짤 때도 유용.

*그림 참조 : https://www.opensourceforu.com/2015/03/a-guide-to-using-raw-sockets/

왼쪽이 일반적인 프로세스, 오른쪽 화살표가 raw socket 프로세스.

동작 예시(참고)
PF_INET + SOCK_RAW -> L3, L4 헤더 관찰 가능
PF_PACKET + SOCK_RAW -> L2, L3, L4 헤더 관찰 가능(full raw frame access. i.e. sniffing)
PF_PACKET + SOCK_DGRAM -> L3, L4 헤더 관찰 가능

protocol : usually set as 0

protocol : 어떤 프로토콜을 사용할지 설정. 일반적으로는 0을 사용한다. 이유? 어차피 PF랑 type을 결정하면 대부분의 자주 사용하는 프로토콜이 이미 특정되기 때문.
ex)
PF_INET + SOCK_STREAM --> TCP + IP
PF_INET + SOCK_DGRAM --> UDP + IP

(교수님 왈: 처음 설계할 때 나중에는 많은 프로토콜이나 타입을 예상하고 설계한 듯하다. 사실 지금은 생각보다 쓰이는 프로토콜이 한정적이라 굳이 안쓰게 된 듯.)

Kernel Data Structures for Sockets

파일 시스템에서, dentry, file 등등 파일 시스템에 쓰이는 다양한 구조체가 있듯이, 네트워킹 시스템에서도 커널 내부에 여러 종류의 구조체가 존재한다.

대표적으로 BSD Socket(struct socket), protocol-specific socket(struct sock), skb(sk_buff) 3가지를 알아보겠다.

BSD socket

* BSD : Berkeley Software Distribution (=Berkeley Unix)

struct socket

유저와 커널 사이의 가교 역할을 하는 커널 내 구조체. 유저의 명령에 따라 다양한 operation을 수행한 뒤, 데이터를 전송하는 역할을 하는 친구다.

  • Identifier : addresses(5-tuples)
    source IP/port + destination IP/port + protocol
    총 5개의 주소로 구별 가능하다. --> 각 연결마다 unique

  • sock 이라고 하는 variable들이 있다면, 이 구조체를 참고한다고 볼 수 있다.

protocol-specific socket

struct sock

socket의 PF(e.g. PF_INET)과 관련된 정보와 연결 상태를 담기 위한 프로토콜 특화 구조체.

  • sk 라는 이름을 가진 variable들은 이 구조체를 바탕으로 한다.

How It Works

유저가 socket()을 통해 얻은 fd로 소통하는 것이 핵심이다.

fd 값을 통해 fd에 대응되는 커널의 struct file 을 찾고, VFS를 통해 socket file system 의 struct socket으로 찾아 들어간다. struct socket에 정의되어 있는 주요 파라미터로는 file(참조하고 있는 파일을 가리킴), sock(protocol-specific socket), ops(프로토콜에 따른 operation)가 있다.

*강의 ppt슬라이드 재구성

<예시 상황 - socket operation>

유저가 connect(),ioctl(), sendto등과 같은 syscall ->

struct socket 내부의 ops에 정의된 operation에 mapping ->

해당 operation이 또 다시 ops와 연결된 inet_stream_ops(for TCP), unix_dgram_ops(for UDP)등에 mapping되어 동작 수행

-> fs과 매우 유사하게 동작! (fs 에서도 최종 내부동작은 vfs가 아니라 ext4, btrfs 등에서 수행됨)

socket Buffer

What is a Socket Buffer(skb)?

skb(socket buffer) : 커널에 패킷을 저장하는 구조체.
송수신 과정에서 커널에서 생성되고 free된다.

데이터 송신:

  • allocation :
    커널이 skb를 생성한 이후, 보낼 데이터를 복사해 담는다. 이후 여러 layer을 거치며 header을 push해서 write queue에 위치시킨다(TCP의 경우. UDP는 connectionless이므로 X).
  • free :
    NIC를 통해 내 디바이스에서 나가면 free된다.

데이터 수신:

  • allocation :
    NIC가 패킷을 받으면 커널이 skb를 생성해 패킷을 담는다.
  • free :
    skb가 네트워크 스택을 돌며 계층 별 프로토콜에 의해 파싱된 뒤, receive queue에 담긴다. 유저가 recv() 하면 데이터가 유저공간으로 cp되고, queue에서 빠져나가 free 된다.

* 송수신 과정 모두에서 네트워크 스택을 traverse하는 동안 데이터 cp는 일어나지 않는다.
* 데이터 cp가 일어나는 경우 :
1. user - kernel 간
2. kernel - NIC 간
* queue는 doubly linked ring structure.


cf) 왜 skb가 따로 필요한가?

  • 패킷과 프로토콜을 따로 구분할 필요가 있다.
    프로토콜은 sock 과 관련된 구조체에 의해 구체화 된다. 데이터(패킷)은 skb을 통해 분리되어 있으므로, 프로토콜을 비교적 쉽게 추가할 수 있게 된다. (프로토콜에 대한 정보는 sock에서만 관리되기 때문에, 패킷을 굳이 고려하지 않아도 됨)
  • NIC을 통해 패킷이 커널에서 처리될 때, 네트워크 계층에 따라 처리가 되어야 하니, 당연히 커널 안에 공간이 있어야 한다.

Use of skb

skb를 통해 layer을 돌며 헤더를 붙이거나 파싱(읽어들이기) 할 수 있다.
skb는 사용에 있어 효율적이어야 한다.

  • layer간 이동 시 불필요한 데이터 cp를 막아야함.
  • 새 프로토콜 헤더를 붙여야 할 때(송신 시)
  • 프로토콜 헤더를 뗄 때(수신 시)
  • 데이터 자체를 붙이거나(하나의 데이터가 너무 작으면 붙일 수 있음), 분리할 때(하나의 데이터가 너무 클 때)

Structure of skb (sk_buff)

skb는 sk_buff 의 구조체로 구현된다.

sk_buff는 크게 두 파트로 구성된다.

  1. packet data
    sk_buff->head에 저장돼있음.

  2. management data(metadata)
    대표적인 metadata :
    header pointers (transport_header, network_header,mac_header) : packetdata 내부를 참조해 굳이 모든 데이터를 traverse하지 않아도 되게 구현. 각 네트워크 레이어를 돌 때 마다 레이어 별 메타데이터가 업데이트 됨.
    next, prev : queue를 위해 skb끼리 연결.
    sk : struct sock 포인팅. 어느 sock에 위치해 있는지에 대한 정보.
    dev : NIC 장치 포인팅.

*참고 사진 : https://amsekharkernel.blogspot.com/2014/08/what-is-skb-in-linux-kernel-what-are.html

Socket Initialization

소켓을 생성하는 syscall은
int socket(int family, int type, int protocol) 이었다.
만약 TCP, IP 소켓을 하나 생성한다고 하면,
int socket(PF_INET, SOCK_STREAM, 0) (syscall) -> socket_create() -> __sock_create() -> inet_create() -> sk_alloc, sock_init_data(), tcp_v4_init_sock() 이 수행되어 struct tcp_sock이 만들어지게 된다.

Conclusion

socket은 네트워크 시스템에서 유저-커널간 데이터를 주고받는 창구이다.
socket은 abstraction을 통해 쉬운 데이터 송수신을 돕는다.
socket은 'Everything is a file'정책에 따라 file처럼 관리된다.

socket과 관련해서는 크게 3가지 자료구조가 있다.
1. struct socket
2. struct sock
3. struct sk_buff

주소에 따라 unique한 struct socket은 그 프로토콜의 특징이 정의된 struct sock을 포함하고 있고, struct sock안에는 실제 패킷이 저장된 struct sk_buff가 존재한다.

-->
fd는 창구 번호, socket은 창구, sock은 창구의 정보, skb는 처리해야 할 고객 이라고 생각하면 될 듯..?

* 참고 자료 : https://www.minzkn.com/moniwiki/wiki.php/skbuff

profile
¡Salid y Disfrutad!

0개의 댓글