MINZKN.COM |  |  | Services | About

1.1. 개요

Linux packet journey,napi, hardware queue,skb
참고 영상

Linux kernel 에서 sk_buff (skb) 자료형은 network packet 을 처리하는데 중요한 부분입니다. 이 문서는 이를 설명하기 위해서 작성되었습니다.

어디까지나 이 문서는 저 혼자 모든 것을 이해하고 작성한 것이 절대로 아니며 수 많은 검색과 분석을 통하여 먼저 선두에서 정보를 공유해주신 수많은 이름 모를 선배님들의 발자취에 의해서 작성한 것입니다. 개인 적인 관점에서의 해석이 틀릴 수 있으며 이러한 부분을 알려주시면 내용을 갱신하겠습니다.

sk_buff (skb) 는 대략 다음과 같은 흐름내에서 데이터를 다루는게 목적입니다. 여기서 패킷의 인입(Input)/포워딩(Forward)/출력(Output) 이 어디서 일어나는지를 중심으로 파악되고 있어야 할 필요가 있습니다. (아래 그림은 IPSec packet 관점에서 그린것이므로 IPSec 용어만 빼고 일반 네트워크 패킷으로 가정하여 보시면 됩니다.)
An_IPsec_Packet_flow_2017.07.18_v0.4_(Linux_kernel_v3.8_vanilla_source_기준).png
[PNG image (557.04 KB)]
  • 인입(Input)에는 크게 두 가지로 나뉩니다. (위 그림상에서는 좌측 상단 구름)
    • 내 장비가 처리해야 하는 Local-In 목적의 패킷 (위 그림상에서는 좌측 하단부)
    • 다른 장비에게 넘겨주어야 하는 Forward 목적의 패킷으로 나뉘어집니다. (위 그림상에서는 가운에 상단부)
  • 출력(Output)에도 크게 두 가지로 나뉩니다. (위 그림상에서는 우측 하단 구름)
    • 다른 장비에서 인입(Input)되어 포워드(Forward)를 거쳐서 넘어온 패킷을 출력(Output)하는 Forward 목적의 패킷 (위 그림에서는 좌측 상단에서 우측 하단으로 흐르는 패킷)
    • 내 장비에서 발생(생성)하여 출력(Output)하려는 Local-Out 목적의 패킷 (위 그림에서는 우측 상단에서 우측 하단으로 흐르는 패킷)
  • IPSec/AH/IPCOMP/... 등의 변환(XFRM, Transform) 흐름도 있습니다. 이 경우 Local-In 과정에서 다시 회기하거나 Forward 과정에서 경로가 바뀌는 등의 요소들이 존재합니다.
  • Filter/Mangle/Nat/... 등의 흐름도 있습니다. (위 그림에서 충분히 이것들은 모두 담아서 그리지는 못했으니 참고)
    • Filter의 경우 PREROUTING/FORWARD/POSTROUTING/LOCALOUT 등 어느 위치에서 어떤 상태의 패킷 처리 하는지 이해 필요
    • NAT의 경우 SNAT, DNAT, FNAT, XNAT 등이 어느 위치에서 처리 되는지 이해 필요
    • Mangle의 경우 MSS조정, TProxy, ... 등이 어느 위치에서 처리 되는지 이해 필요
    • IPSec VPN의 경우 XFRM 흐름을 이해 필요
    • TTL 감소는 어느 위치에서 처리 되는지 이해 필요
  • 라우팅(Routing) 은 크게 PREROUTING과 POSTROUTING 시점으로 구분하기도 하고 dst_input() / dst_output() 을 호출하는 시점전에 skb->dst를 채우는 위치를 통해서 구분할 수도 있습니다. 라우팅은 각기 흐름 요소 중간중간 패킷의 변화가 있을 때 갱신될 수 있습니다.
    • Forward 를 결정짓는 것은 Routing 또는 Policy (XFRM policy) 라고 할 수 있겠죠.
  • 위 그림은 L3 Layer 흐름 기준이라고 볼 수 있으나 bridge등의 L2 layer 흐름, L4 layer 등의 관점에서 skb->data와 skb->len이 어디를 얼만큼 지정해야 하는지 이해할 수 있는지가 가장 중요한 부분이라고 생각됩니다.
  • 각 Layer에서 checksum 은 어떻게 계산되고 완성되는지 이해 필요 (skb->ip_summed 이해 필요, Checksum Offload 기능에는 어떤 것이 있고 어떻게 처리되는지 이해 필요)
  • skb를 통하여 packet data를 접근할 때 포인팅관점을 정확히 이해해야 하며 shared data 구조도 고려해야 합니다.
    • Scatter-Gather 또한 이해하면 좋습니다.
  • skbuff 의 설계 관점이 어디에 있는 가를 이해하면 왜 이런 구조가 나와야 할까에 대한 의문을 해소하는데 도움이 됩니다.
    • skb 하나가 인입된 후에 조작 / 추가 / 합치는 등이 있어도 skb 포인터의 변화를 최소화 하는 관점
    • 복사 및 할당에 대한 overhead를 줄이는 방법을 제공하는 관점
      • headroom / tailroom / shared / scatter&gather / page
    • Layer초점이 어디에 있는 가를 포인팅하는 관점
    • 성능과 메모리 사용 효율의 균형 관점
      • skb mempool / queue
      • driver에서의 split 구현 (인입된 크기와 관계 없이 더 작게 쪼갤수록 queue 사용률에 대한 효율은 증가)

1.2. skbuff 의 주요 개괄적 특성 및 주의 사항 요점 정리

  • packet flow 상에서 항상 head <= data <= tail <= end 조건을 유지하도록 구현합니다. (일단, 이것을 위반하면 동작이 폭주할 수 있습니다.)
    • 또한 특정 layer 처리 시점에서 포인팅이 무효화될 수 있습니다. head [<= headroom] <= data( [mac_header <=] network_header [<= transport_header] ) <= tail [<= tailroom] <= end (end - head <= truesize) 조건정도로 정리할 수 있어보입니다.
      • 이 때 mac_header는 이 조건을 지킬 수 없으면 (Local-Out 또는 RX 시점 이후에 발생할 수 있음) 무효상태로 표시됩니다. 무효상태가 되면 skb_mac_header_was_set(skb) 호출로 확인해볼 때 0을 반환하게 됩니다.
      • data와 len은 head에서 tail 사이를 Layer처리 단계에 따라서 포인팅을 이동하면서 처리합니다.
        • data + len은 tail을 초과할 수 있습니다. 하지만 data + len - data_len은 tail을 넘지 않아야 합니다. (skb_shared_info 에 기인함.)
        • 즉, tail은 data + len - data_len 이 되어야 할 겁니다. 하지만 tail 은 data + len이 되는 경우는 data_len은 0이라는 조건이 필요합니다.
          • skb->data 로 직접 접근할 수 있는 크기는 skb->len - skb->data_len 이며 skb->data_len이 0보다 큰 경우는 Non-Linear model 입니다. 여기서 skb->len - skb->data_len은 skb_headlen(skb) 로 확인할 수 있습니다.
      • Device driver에 따라서 할당된 skb의 headroom공간은 다를 수 있습니다. (L2의 종류에 따라서 skb_reserve 로 예약되는 크기가 다르게 할 수 있다는 관점)
    • head 는 실제 skb의 저장 공간의 첫 시작을 의미합니다.
      • skb 에 할당된 실제 크기 truesize
    • data 는 현재 Layer 가 다루어야 하는 위치를 의미합니다.
      • data는 head 보다 앞 설수 없습니다. (즉, 항상 head <= data <= tail <= end 조건을 유지)
      • skb_push(skb), skb_pull(skb) 함수가 이러한 Layer 전환 기점에 호출됩니다.
        • skb_push는 data를 앞쪽 (head방향)으로 이동하고 len을 그만큼 증가 시킵니다. (즉, 현재의 data 위치를 Layer가 낮은 쪽으로 이동)
          • 즉, data 가 L3 layer (network_header) 를 가르키고 있다가 L2 layer (mac_header) 로 이동하는 경우등에 사용합니다.
        • skb_pull는 data를 뒤쪽 (tail방향)으로 이동하고 len을 그만큼 감소 시킵니다. (즉, 현재의 data 위치를 Layer가 높은 쪽으로 이동)
          • 즉, data 가 L2 layer (mac_header) 를 가르키고 있다가 L3 layer (network_header) 로 이동하는 경우등에 사용합니다.
        • skb_put은 지정한 길이만큼 tail을 end쪽으로 이동 시키고 len도 그만큼 증가시킵니다. 반환값은 tail을 이동하기 이전의 포인터 위치를 반환합니다.
          • 즉, 현재 Layer에 추가 data를 덧붙이기 위한 tail 쪽 공간 확보를 할 때 사용합니다.
      • skb->len : 현재 Layer 단계에서의 skb->data를 기점으로 길이 (즉, skb->data가 L4 layer 를 가르킨다면 skb->len 은 L4 크기를 의미함)
        • 즉, L2 layer 시점에서는 L2의 길이를 의미하지만 L3 layer handler로 올라가면 더이상 L2 길이가 아닌 L3 길이를 나타냅니다.
        • 보통은 Kernel flow 에서 L4 layer 가지 올라가지 않지만 전혀 없는 것은 아니며 이 경우도 skb->len은 L4 길이를 나타내어야 합니다. L4 layer 조작을 마치면 다시 L3 layer로 환원하는 식
        • skb->data_len 은 skb->data 가 아닌 skb skb_shared_info가 가지는 크기를 의미합니다.
          • skb->data + x 를 통해서 접근하는 구현을 하려고 한다면 x는 skb->len - skb->data_len을 초과하게 되면 잘못된 구현이라는 것.
          • skb_shared_info가 사용되는 구조를 Non-Linear model 이라고들 합니다. 이 경우 skb->data 포인팅에서 직접적으로 접근을 보장하는 것은 L3 Header 까지만입니다. 그 이상은 skb_copy_bits 를 통해서 접근하거나 직접 skb_shared_info 로 접근해서 frags접근을 해야 합니다.
            • skb->data_len이 0이면 Linear model 이며 0보다 큰 값을 가진다면 Non-linear model 입니다.
            • Non-linear model 에서 shared 가 있는 경우 패킷을 조작하면 의도하지 않은 동작이 있을 수 있습니다.
              • shared된 것을 독립적으로 패킷 조작하려면 skb_share_check 함수 사용을 검토해야 할 수 있습니다.
                • skb를 읽기만 하는 경우는 shared 를 유지해도 되지만 변경/조작/갱신을 하려는 목적의 접근시 사전에 shared 를 해제하는 것을 고려해야 할 수 있습니다.
    • tail 은 현재 skb에 유효 저장된 마지막 위치를 의미합니다.
      • skb가 할당된 직후 시점에는 data 와 위치가 같은 상태에서 시작합니다. 이후 L2, L3, L4가 채워지면서 이 부분이 뒤로 늘어납니다.
      • tail은 결코 end를 넘을 수 없습니다. (즉, 항상 head <= data <= tail <= end 조건을 유지)
      • tail을 조정하는 것은 skb_put이 대표적입니다. (skb_put 함수는 tail과 len을 지정한 길이만큼 함께 조정합니다.)
      • skb_reserve 함수도 이를 조정합니다. (skb의 data 와 함께 조정함)
      • skb의 headroom 은 data 와 head 간의 격차를 headroom 으로 정의함.
        • headroom과 tailroom을 확보(추가 할당)하려면 pskb_expand_head 또는 skb_copy_expand 또는 skb_cow 함수를 사용할 수 있습니다. (최하위 기본 함수는 pskb_expand_head 입니다.)
          • 보통 xfrm 등에서 IPSec Encryption 할 때 UDP,ESP 등의 헤더를 추가적으로 붙이게 되는데 이 때 이러한 할당으로 확보하는 방식이 호출될 수 있습니다.
        • alloc_skb 직후 일정량의 headroom 을 미리 건너뛰는 것으로도 headroom을 확보(추가 할당이 아닌 skb->data를 밀어서 확보)하는 방식의 skb_reserve 함수도 있습니다. (보통 driver에서 L2 이하 헤더를 위해서 이를 통하여 미리 예약을 지정합니다.)
          • skb_reserve 함수는 보통 alloc_skb 직후의 비어있는 상태에서만 사용하는게 의미가 있습니다. (headroom을 확보하고 tailroom은 감소하는 형태의 기능)
    • end 는 skb의 저장공간의 맨 마지막을 의미합니다.
    • mac_header 는 L2 layer 의 entry 의 위치를 의미합니다.
      • skb_mac_header_was_set(skb) 함수로 체크하여 0인 경우는 L2 header 를 다루지 않는 상태의 skb임을 확인해야 함. Rx ~ forward 구간은 거의 1이며 Local-Out 등은 0인 경우가 존재
      • skb->mac_len : L2 header 의 크기를 의미
      • 여기서 중요한 것은 mac_header 와 network_header 사이에는 L2 header 만 꽉 채워진 것이 아니고 의미없는 공간 (Hole) 이 존재할 수 있다는 점입니다. TX시점에서 mac_header 가 network_header 바로 앞에 붙여지는 동작을 하게 됩니다.
        • 이러한 Hole 을 강제로 없애기 위해서 skb_mac_header_rebuild 함수를 사용할 수 있기도 합니다. (mac header를 network header 앞으로 memmove 로 이동하는 구현)
    • network_header 는 L3 layer 의 entry 의 위치를 의미합니다.
      • IPSec 에서는
        • Decrypt 직전에는 New IP header를 가르키지만 Decrypt 이후에는 암호문의 IV를 건너뛴 Original IP Header 를 가르킵니다.
        • Decrypt 후에는 network_header 가 이동하지만 mac_header는 그대로이므로 mac_header + mac_len 부터 network_header 까지 사이에 New IP 및 ESP header 그리고 IV까지영역이 skb 유효공간의 의미로는 의미없는 Hole 상태가 됩니다.
          • 즉, Decrypt 후에도 암호문내에 있는 L3+L4 평문의 위치는 변화하지 않으며 network_header 포인팅만 바뀝니다. (이 사항은 실제와 약간은 다른 설명이므로 유의!)
    • transport_header 는 L4 layer 의 entry 의 위치를 의미합니다.
    • headroom 은 data의 앞부분을 일컫는 용어이며 skb alloc 시 일정량 약간의 여유공간을 의미하기도 합니다. (실제로 이 양은 driver [net_device] 에 따라 다를 수 있음)
    • tailroom 은 tail 뒷부분에 skb alloc 시 일정량 약간의 여유공간을 의미 (실제로 이 양은 driver에 따라 다를 수 있음)
  • Layer 가 올라가면 (처리 handler의 계층이 올라가면) 하위 Layer 의 저장된 값은 항상이라고 볼 수 없으나 더이상 볼일이 없어야 원칙적으로 맞습니다. (IPIP/IPSec/GRE/L2TP/... 등 흐름에서 위치이동이 아닌 Hole을 두어 성능을 유지하는 것을 위해서... 어찌 보면 skb 자료구조의 포인팅 개념의 목적 중 하나)
    • 그리고 Forward를 거쳐서 Layer 가 내려가면 각 하위 Layer 는 새롭게 채워지거나 shift (Layer간에 생긴 빈 공간을 정리 또는 skb 분할등) 하는게 맞습니다.
  • 여기서 head, data, tail, end 등이 주요 기점이라는 것입니다. mac_header, network_header, transport_header는 L2, L3, L4 layer 처리포인터를 의미합니다.
  • mac_header와 network_header 와는 실제 RX에서 Forward 까지의 경우 network header 가 뒤로 후퇴하는 경우가 존재합니다. (Decrypt, Decapsulation 등 IPsec/GRE/Tun/IPIP/L2TP 의 Decap 동작에 의해서) 그리고 Forward 에서 TX 사이의 경우 mac_header 는 마지막에 확정되며 확정되기 전까지 mac_header는 유효하지 않거나 network_header 가 침범할 수 있습니다. (Encrypt/Encap 등의 동작에 의해서. network_header 가 mac_header를 앞서는 경우 mac_header는 invalid 상태로 처리할 수 있어야 합니다.)
  • head는 저장공간 기준 첫 시작. tail은 저장유효공간의 끝을 가르키므로 tail - head 가 실제 저장된 유효공간이라고 볼 수 있습니다.
  • skb->len 은 skb->data 와 관련있습니다. 이것은 RX 에서 TX 사이에 Layer handler 에 따라서 계속하여 해당 Layer 에 맞는 위치와 길이를 갖도록 MUST 구현되어야 합니다.
  • skb의 위치(포인팅 의미를 갖는)를 의미하는 모든 멤버변수는 직접 접근하는 것을 하면 호환(32bits와 64bits 아키텍쳐를 기준으로 다른 기준으로 구현되기 때문) 에 문제가 생기므로 가급적 관련 inline macro를 찾아서 사용해야 합니다.
  • IPSec VPN 의 경의 skb 할당시 New IP + ESP/AH + Trailer 를 고려한 skb의 headroom 및 tailroom 이 확보되는게 좋습니다.
  • skb->cb[] : 각 Layer 처리부에서 임시로 처리할 데이터를 저장하는 용도로 사용됩니다. 이 임시공간은 정확히 skb flow를 완전히 이해해야 그 생명주기를 파악할 수 있습니다. (보통은 마지막 Layer 처리부에서 사용합니다.)

1.3. "include/linux/skbuff.h"

1.3.1. CHECKSUM_XXX

#define CHECKSUM_NONE           0
#define CHECKSUM_UNNECESSARY    1
#define CHECKSUM_COMPLETE       2
#define CHECKSUM_PARTIAL        3

#define SKB_MAX_CSUM_LEVEL      3
Computing the Internet Checksum (RFC1071) 참고

1.3.2. "struct sk_buff" 자료형

펼쳐보기 (일부 최신 커널 버젼과 다를 수 있음. 일부 생략)

1.3.3. SKB_DATA_ALIGN(X)

#define SKB_DATA_ALIGN(X)       ALIGN(X, SMP_CACHE_BYTES)

1.3.4. SKB_WITH_OVERHEAD(X)

#define SKB_WITH_OVERHEAD(X)    \
        ((X) - SKB_DATA_ALIGN(sizeof(struct skb_shared_info)))

1.3.5. SKB_MAX_ORDER(X, ORDER)

#define SKB_MAX_ORDER(X, ORDER) \
        SKB_WITH_OVERHEAD((PAGE_SIZE << (ORDER)) - (X))

1.3.6. SKB_MAX_HEAD(X)

#define SKB_MAX_HEAD(X)         (SKB_MAX_ORDER((X), 0))

1.3.7. SKB_MAX_ALLOC

#define SKB_MAX_ALLOC           (SKB_MAX_ORDER(0, 2))

1.3.8. SKB_TRUESIZE(X)

/* return minimum truesize of one skb containing X bytes of data */
#define SKB_TRUESIZE(X) ((X) +                                          \
                         SKB_DATA_ALIGN(sizeof(struct sk_buff)) +       \
                         SKB_DATA_ALIGN(sizeof(struct skb_shared_info)))
  • sk_buff 할당할 크기를 주어진 data크기와 함께 합산. (skb 포인터의 실제 할당 크기)

1.3.9. MAX_SKB_FRAGS

#if (65536/PAGE_SIZE + 1) < 16
#define MAX_SKB_FRAGS 16UL
#else
#define MAX_SKB_FRAGS (65536/PAGE_SIZE + 1)
#endif
  • skb->frags 의 최대 배열요소 개수를 이 정의로 제한합니다. (이 이상의 frags 배열로 쪼개지 않는다는 것. 4K 이상의 page 기준 16개)

1.4. sk_buff 주요 함수들

1.4.1. skb_frag_size(frag)

static inline unsigned int skb_frag_size(const skb_frag_t *frag);
  • 주어진 skb_frag_t 에서 크기 정보를 반환합니다. "frag->bv_len"을 의미합니다. (예전 커널에서는 "frag->size"를 의미)

1.4.2. skb_frag_size_set(frag, size)

static inline void skb_frag_size_set(skb_frag_t *frag, unsigned int size)
  • 주어진 skb_frag_t 에서 크기를 설정합니다.


Copyright ⓒ MINZKN.COM
All Rights Reserved.