| 검색 | ?

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 목적의 패킷 (위 그림 상에서는 좌측 하단 부)
      • 즉, 목적지 MAC 주소가 나의 MAC 주소인 PACKET_HOST 상황 및 목적지 IP 주소가 나의 IP 주소인 상태를 의미
    • 다른 장비에게 넘겨주어야 하는 Forward 목적의 패킷으로 나뉘어집니다. (위 그림상에서는 가운데 상단부)
  • 출력(Output)에도 크게 두 가지로 나뉩니다. (위 그림상에서는 우측 하단 구름)
    • 다른 장비에서 인입(Input)되어 포워드(Forward)를 거쳐서 넘어온 패킷을 출력(Output)하는 Forward 목적의 패킷 (위 그림에서는 좌측 상단에서 우측 하단으로 흐르는 패킷)
    • 내 장비에서 발생(생성)하여 출력(Output)하려는 Local-Out 목적의 패킷 (위 그림에서는 우측 상단에서 우측 하단으로 흐르는 패킷)
  • IPSec/AH/IPCOMP/... 등의 변환(XFRM, Transform) 흐름도 있습니다. 이 경우 Local-In 과정에서 다시 회기하거나 Forward 과정에서 경로가 바뀌는 등의 요소들이 존재합니다.
    • Local-In 흐름으로 암호문이 인입되어 복호화하고 복호화된 평문은 다시 회기하는 흐름
    • Forward 되어 dst_output 을 거쳐서 Local-Out(POSTROUTING) 흐름에서 평문을 암호화하고 암호화된 패킷의 라우팅을 다시 확인 후 dst_output 을 거쳐서 Local-Out 으로 암호문이 전송되는 흐름
  • 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 의 주요 개괄적 특성 및 주의 사항 요점 정리

sk_buff-structure-0-20220812.png
[PNG image (177.51 KB)]
  • 'struct sk_buff'는 크게 그 자신의 구조체 sk_buff 와 linear-data buffer 부분 (위 그림에서 'data size' 에 해당하는 부분. alloc_skb 함수에 주어지는 size 인자는 이 것을 의미합니다.) 그리고 frag/paged data부분 ('struct skb_shared_info')를 가지고 있습니다
    • 보통 'struct sk_buff' 와 data buffer 를 각각 할당하고 data buffer 는 실제 data를 저장하는 부분과 단편화(frag/paged) 된 다른 data를 가르키는 'struct skb_shared_info'를 갖습니다. (이것은 할당의 효율과 최소한의 복사구조를 갖기 위한 구조라고 생각하고 보시면 이해가 용이합니다.)
  • packet flow 상에서 항상 head <= data <= tail <= end 조건을 유지하도록 구현합니다. (일단, 이것을 위반하면 동작이 폭주할 수 있습니다.)
    • 또한 특정 layer 처리 시점에서 포인팅이 무효화될 수 있습니다.
      • head [<= headroom] <= data( [mac_header <=] network_header [<= transport_header] ) <= tail [<= tailroom] <= end (end - head <= truesize - sizeof(struct sk_buff) - sizeof(struct skb_shared_info)) 조건정도로 정리할 수 있어보입니다.
      • 이 때 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의 저장 공간의 첫 시작을 의미합니다.
    • truesize 는 skb 에 할당된 실제 크기로 'struct sk_buffer' 및 'struct skb_shared_info' 를 포함한 총 할당 크기를 의미합니다.
    • 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의 저장공간의 맨 마지막을 의미합니다.
      • end 위치 바로 뒤에 'struct skb_shared_info' 가 위치합니다.
    • 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 분할등) 하는게 맞습니다.
      • skb_mac_header_rebuild 함수가 L2 header와 L3 header 간의 hole 을 제거하기 위한 가장 좋은 예가 되는 함수입니다.
  • 여기서 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 처리부에서 사용합니다.)
    • IP defragment 하는 과정에서 현재 패킷을 잠시 모아두게 되는데 이 때도 skb->cb[] 에 defragment 과정에서만 사용할 정보를 담는데 이용합니다.
    • 이 작은 공간이 유용할 때가 대부분은 skb 가 STOLEN 되어 잠시 작업을 보류할 때나 패킷을 암/복호화를 수행시 비동기 H/W 로 수행될 때 그 정보를 잠깐 저장할 목적으로 많이 사용합니다.
    • 다소 공간이 작기 때문에 좀더 큰 임시 공간을 필요로 하는 경우는 tailroom 에서 일부를 사용하기도 합니다.
  • sk_buffer 가 shared 상태(skb->users 가 1보다 큰 경우) 이면 sk_buffer 의 포인팅 및 data는 읽기만 할 수 있고 쓰기 동작을 해서는 안됩니다.
    • 즉, 패킷의 변화를 만들려면 shared 상태가 아니어야 하며 clone 등을 통해서 새로운 skb를 가지고 포인팅 변화를 진행할 수 있으며 data 쓰기도 가능하려면 unclone 해야 합니다.
    • skb_shared 및 skb_cloned 함수로 이러한 공유 상태를 확인 할 수 있습니다.

1.3. skbuff (skb) 를 지정한 NIC로 Tx 하는 방법

  • Direct xmit : 직접 전송하는 방법
    struct sk_buff *skb; /* 주어진 skb 로 가정 */
    struct net_device *odev; /* Tx 할 NIC의 net_device (또는 이 부분을 skb->dst 가 있는 경우 여기서 가르키는 net_device일수도 있음) */
    __u16 queue_map; /* Tx 할 NIC가 Multi-queue 를 지원하는 경우 이에 대한 몇번째 txq를 선택할지 결정할 번호 (이 값의 범위는 0 부터 odev->real_num_tx_queues 사이에서 유효함) */
    struct netdev_queue *txq; /* dev 의 queue_mapping 에 의해서 선택된 txq (여기에는 qdisc도 포함되어 있음) */
    int ret; /* Tx 결과 상태 NETDEV_TX_XXX , NET_XMIT_XXX 등이 OR 형태로 설정 */
    
    /* 이것으로 skb->queue_mapping 이 설정됨. (비슷하지만 관점이 조금 다른 함수로 skb_record_rx_queue 함수가 있음) */
    skb_set_queue_mapping(skb, queue_map /* 이 값이 적절히 분배하는 알고리즘에 의해서 설정되어야 함. 특별한 알고리즘이 없다면 smp_processor_id() 로 대체 가능 */);
    
    ...
    
    /* link down 상태이면 전송할 수 없으므로 DROP */
    if(!netif_running(odev) || !netif_carrier_ok(odev)) {
        return(NET_XMIT_DROP);
    }
    
    /* skb->queue_mapping 에 따른 odev 의 txq를 선택 */
    #if 1L
    queue_map = skb_get_queue_mapping(skb);
    txq = netdev_get_tx_queue(odev, queue_map);
    #else
    txq = skb_get_tx_queue(odev, skb);
    #endif
    
    /* Tx lock 을 활성화 (단, LLTX 장치는 lock을 활성화 하지 않을수 있음) */
    local_bh_disable();
    HARD_TX_LOCK(odev, txq, smp_processor_id() /* owner cpu */);
    
    #if 1L
    if(netif_tx_queue_stopped(txq)) { /* txq->state 가 __QUEUE_STATE_DRV_XOFF bit 상태를 갖는 경우를 의미하며 전송이 가능하여도 상대방 수신측이 받기 어려운 상태등을 의미 */
        /* NETDEV_TX_BUSY 로 간주 */
        ret = NETDEV_TX_BUSY;
        goto unlock;
    }
    #elif 1L /* txq->state 가 __QUEUE_STATE_DRV_XOFF 또는 __QUEUE_STATE_FROZEN bit 상태를 갖는 경우를 의미하며 __QUEUE_STATE_FROZEN 상태조건은 옛날 커널에서는 BQL이 활성화되는 경우 다소 문제가 있음 */
    if(netif_xmit_frozen_or_drv_stopped(txq)) {
        /* NETDEV_TX_BUSY 로 간주 */
        ret = NETDEV_TX_BUSY;
        goto unlock;
    }
    #endif
    
    /* NIC Driver 의 ndo_start_xmit 을 통하여 전송개시 (실제 구현에서는 이렇게 netdev_ops 를 직접 접근하기 보다는 netdev_start_xmit 같은 함수를 사용하는게 바람직) */
    ret = odev->netdev_ops->ndo_start_xmit(skb, odev);
    if(ret == NETDEV_TX_OK) { /* ret 값이 NETDEV_TX_OK{0} 인 경우는 성공이며 나머지는 전송하지 못한 경우를 의미 */
        txq_trans_update(txq);
    }
    else {
        ...
    }
    
    /* Tx lock 을 해제 */
    unlock:
    HARD_TX_UNLOCK(odev, txq);
    local_bh_enable();
    
  • Queued xmit : queue (qdisc등...) 을 경유하여 전송하는 방법
    struct sk_buff *skb; /* 주어진 skb 로 가정 */
    struct net_device *odev; /* Tx 할 NIC의 net_device (또는 이 부분을 skb->dst 가 있는 경우 여기서 가르키는 net_device일수도 있음) */
    __u16 queue_map; /* Tx 할 NIC가 Multi-queue 를 지원하는 경우 이에 대한 몇번째 txq를 선택할지 결정할 번호 (이 값의 범위는 0 부터 odev->real_num_tx_queues 사이에서 유효함) */
    struct netdev_queue *txq; /* dev 의 queue_mapping 에 의해서 선택된 txq (여기에는 qdisc도 포함되어 있음) */
    int ret; /* Tx 결과 상태 NETDEV_TX_XXX , NET_XMIT_XXX 등이 OR 형태로 설정 */
    
    /* 이것으로 skb->queue_mapping 이 설정됨. (비슷하지만 관점이 조금 다른 함수로 skb_set_queue_mapping 함수가 있음) */
    skb_record_rx_queue(skb, queue_map /* 이 값이 적절히 분배하는 알고리즘에 의해서 설정되어야 함. 특별한 알고리즘이 없다면 smp_processor_id() 로 대체 가능 */);
    
    ...
    
    /* link down 상태이면 전송할 수 없으므로 DROP */
    if(!netif_running(odev) || !netif_carrier_ok(odev)) {
        return(NET_XMIT_DROP);
    }
    
    skb->dev = odev;
    
    local_bh_disable();
    
    ret = dev_queue_xmit(skb);
    if(ret == NETDEV_TX_OK) { /* ret 값이 NETDEV_TX_OK{0} 인 경우는 성공이며 나머지는 전송하지 못한 경우를 의미 */
        ...
    }
    else {
        ...
    }
    
    local_bh_enable();
    

1.4. "include/linux/skbuff.h"

1.4.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.4.2. "struct sk_buff" 자료형

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

1.4.3. SKB_DATA_ALIGN(X)

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

1.4.4. SKB_WITH_OVERHEAD(X)

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

1.4.5. SKB_MAX_ORDER(X, ORDER)

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

1.4.6. SKB_MAX_HEAD(X)

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

1.4.7. SKB_MAX_ALLOC

#define SKB_MAX_ALLOC           (SKB_MAX_ORDER(0, 2))

1.4.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.4.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.5. NAPI driver 요건

요즘 작성되는 대부분의 NIC driver는 NAPI구조에 맞춰 작성됩니다. 이 절은 이러한 NAPI driver가 충족해야 하는 조건들을 정리합니다.
  • NIC driver에서 Kernel stack으로 넘어가는 기점
    • napi_gro_receive(napi /* napi context */, skb /* Kernel stack으로 넘기는 sk_buff *) 를 통해서 NAPI driver는 Kernel stack으로 패킷을 넘겨줍니다.
  • napi_gro_receive 호출 직전 sk_buff의 포인팅 요건
    • eth_type_trans 호출 (Ethernet NIC인 경우는 eth_type_trans 함수가 이러한 조정을 합니다) 직전에 skb->data는 L2 header위치를 가르키고 있어야 하며 skb->len은 L2 frame size (FCS제외) 으로 설정되어 있어야 합니다.
    • eth_type_trans 호출하면서 다음과 같이 포인팅이 설정됩니다.
      • skb->mac_header는 L2 header 를 가르키고 있어야 합니다.
      • skb->protocol은 L2 header 의 EtherType값으로 설정되어야 합니다.
      • skb->data는 L2 header 다음의 위치를 가르키고 있어야 합니다.
      • skb->len은 L2를 제외한 frame size이어야 합니다. (이것은 L3 packet size를 의미하지 않습니다. skb->len은 L3 packet size보다 같거가 클 수 있습니다. Kernel stack 초입부에서 이것을 trim 하는 부분이 있을 수 있습니다.)
  • Kernel stack 초입부에서는...
    • skb->data가 L2 header를 건너뛴 부분에 위치하므로 skb->data 위치를 skb->network_header로 설정합니다.
    • skb->mac_header와 skb->network_header의 간격을 skb->mac_len으로 설정합니다. (즉, skb->mac_len은 L2 Header 크기로 설정됩니다.)
    • 이후 적절한 gro_receive handler로 분기하며 포인팅이 조정됩니다.

1.6. sk_buff 주요 함수들

1.6.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.6.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.