#keywords minzkn,hwport,programming,home,development,linux,software,skbuff,sk_buff,skb,skb_shared_info,frags,kernel,network,inline,macro,head,tail,mac_header,network_header,header,offset #title Linux Kernel의 skbuff(Socket buffer descriptors)에 대하여 [wiki:Home 대문] / [wiki:CategoryProgramming 프로그래밍] / [wiki:skbuff Linux Kernel의 skbuff에 대하여] ---- == [wiki:skbuff Linux Kernel의 skbuff(Socket buffer descriptors)에 대하여] == * 작성자 조재혁([mailto:minzkn@minzkn.com]) * 고친과정 2020년 12월 07일 : 처음씀 [[TableOfContents]] === 개요 === |Linux packet journey,napi, hardware queue,skb| 참고 영상 || || [[Play(https://youtu.be/6Fl1rsxk4JQ)]] || Linux kernel 에서 sk_buff (skb) 자료형은 network packet 을 처리하는데 중요한 부분입니다. 이 문서는 이를 설명하기 위해서 작성되었습니다. 이 문서는 저 혼자 모든 것을 이해하고 작성한 것이 절대로 아니며 수 많은 검색과 분석을 통하여 먼저 선두에서 정보를 공유해주신 수많은 이름 모를 선배님들의 발자취에 의해서 작성한 것입니다. 개인 적인 관점에서의 해석이 틀릴 수 있으며 이러한 부분을 알려주시면 내용을 갱신하겠습니다. sk_buff (skb) 는 대략 다음과 같은 흐름내에서 데이터를 다루는게 목적입니다. 여기서 패킷의 인입(Input)/포워딩(Forward)/출력(Output) 이 어디서 일어나는지를 중심으로 파악되고 있어야 할 필요가 있습니다. (아래 그림은 IPSec packet 관점에서 그린것이므로 IPSec 용어만 빼고 일반 네트워크 패킷으로 가정하여 보시면 됩니다.) [attachment:VirtualPrivateNetwork/An_IPsec_Packet_flow_2017.07.18_v0.4_(Linux_kernel_v3.8_vanilla_source_기준).png] * 인입(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 기능에는 어떤 것이 있고 어떻게 처리되는지 이해 필요) * 참고: [wiki:rfc1071checksum Computing the Internet Checksum (RFC1071)] * skb를 통하여 packet data를 접근할 때 포인팅관점을 정확히 이해해야 하며 shared data 구조도 고려해야 합니다. * Scatter-Gather 또한 이해하면 좋습니다. * skbuff 의 설계 관점이 어디에 있는 가를 이해하면 왜 이런 구조가 나와야 할까에 대한 의문을 해소하는데 도움이 됩니다. * skb 하나가 인입된 후에 조작 / 추가 / 합치는 등이 있어도 skb 포인터의 변화를 최소화 하는 관점 * 복사 및 할당에 대한 overhead를 줄이는 방법을 제공하는 관점 * headroom / tailroom / shared / scatter&gather / page * Layer초점이 어디에 있는 가를 포인팅하는 관점 * 성능과 메모리 사용 효율의 균형 관점 * skb mempool / queue * driver에서의 split 구현 (인입된 크기와 관계 없이 더 작게 쪼갤수록 queue 사용률에 대한 효율은 증가) === skbuff 의 주요 개괄적 특성 및 주의 사항 요점 정리 === [[attachment:sk_buff-structure-0-20220812.png]] * '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 함수로 이러한 공유 상태를 확인 할 수 있습니다. === skbuff (skb) 를 지정한 NIC로 Tx 하는 방법 === * Direct xmit : 직접 전송하는 방법 {{{#!enscript c 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등...) 을 경유하여 전송하는 방법 {{{#!enscript c 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(); }}} === "include/linux/skbuff.h" === ==== CHECKSUM_XXX ==== {{{#!enscript c #define CHECKSUM_NONE 0 #define CHECKSUM_UNNECESSARY 1 #define CHECKSUM_COMPLETE 2 #define CHECKSUM_PARTIAL 3 #define SKB_MAX_CSUM_LEVEL 3 }}} [wiki:rfc1071checksum Computing the Internet Checksum (RFC1071)] 참고 ==== "struct sk_buff" 자료형 ==== {{{#!folding 펼쳐보기 (일부 최신 커널 버젼과 다를 수 있음. 일부 생략) {{{#!enscript c struct sk_buff_head { /* These two members must be first. */ struct sk_buff *next; struct sk_buff *prev; __u32 qlen; spinlock_t lock; }; #if 0 // 예전 버젼 typedef struct skb_frag_struct skb_frag_t; struct skb_frag_struct { struct { struct page *p; } page; #if (BITS_PER_LONG > 32) || (PAGE_SIZE >= 65536) __u32 page_offset; __u32 size; #else __u16 page_offset; __u16 size; #endif }; #else // 최근 버젼 typedef struct bio_vec skb_frag_t; struct bio_vec { struct page *bv_page; unsigned int bv_len; unsigned int bv_offset; }; #endif struct skb_shared_info { __u8 __unused; __u8 meta_len; __u8 nr_frags; __u8 tx_flags; unsigned short gso_size; /* Warning: this field is not always filled in (UFO)! */ unsigned short gso_segs; struct sk_buff *frag_list; struct skb_shared_hwtstamps hwtstamps; unsigned int gso_type; u32 tskey; /* * Warning : all fields before dataref are cleared in __alloc_skb() */ atomic_t dataref; /* Intermediate layers must ensure that destructor_arg * remains valid until skb destructor */ void * destructor_arg; /* must be last field, see pskb_expand_head() */ skb_frag_t frags[MAX_SKB_FRAGS]; }; #ifdef NET_SKBUFF_DATA_USES_OFFSET typedef unsigned int sk_buff_data_t; #else typedef unsigned char *sk_buff_data_t; #endif struct sk_buff { union { struct { /* These two members must be first. */ struct sk_buff *next; struct sk_buff *prev; union { struct net_device *dev; /* Some protocols might use this space to store information, * while device pointer would be NULL. * UDP receive path is one user. */ unsigned long dev_scratch; }; }; struct rb_node rbnode; /* used in netem, ip4 defrag, and tcp stack */ struct list_head list; }; union { struct sock *sk; int ip_defrag_offset; }; union { ktime_t tstamp; u64 skb_mstamp_ns; /* earliest departure time */ }; /* * This is the control buffer. It is free to use for every * layer. Please put your private variables there. If you * want to keep them across layers you have to do a skb_clone() * first. This is owned by whoever has the skb queued ATM. */ char cb[48] __aligned(8); union { struct { unsigned long _skb_refdst; void (*destructor)(struct sk_buff *skb); }; struct list_head tcp_tsorted_anchor; }; #if defined(CONFIG_NF_CONNTRACK) || defined(CONFIG_NF_CONNTRACK_MODULE) unsigned long _nfct; #endif unsigned int len, data_len; __u16 mac_len, hdr_len; /* Following fields are _not_ copied in __copy_skb_header() * Note that queue_mapping is here mostly to fill a hole. */ __u16 queue_mapping; /* if you move cloned around you also must adapt those constants */ #ifdef __BIG_ENDIAN_BITFIELD #define CLONED_MASK (1 << 7) #else #define CLONED_MASK 1 #endif #define CLONED_OFFSET() offsetof(struct sk_buff, __cloned_offset) /* private: */ __u8 __cloned_offset[0]; /* public: */ __u8 cloned:1, nohdr:1, fclone:2, peeked:1, head_frag:1, pfmemalloc:1; #ifdef CONFIG_SKB_EXTENSIONS __u8 active_extensions; #endif /* fields enclosed in headers_start/headers_end are copied * using a single memcpy() in __copy_skb_header() */ /* private: */ __u32 headers_start[0]; /* public: */ /* if you move pkt_type around you also must adapt those constants */ #ifdef __BIG_ENDIAN_BITFIELD #define PKT_TYPE_MAX (7 << 5) #else #define PKT_TYPE_MAX 7 #endif #define PKT_TYPE_OFFSET() offsetof(struct sk_buff, __pkt_type_offset) /* private: */ __u8 __pkt_type_offset[0]; /* public: */ __u8 pkt_type:3; __u8 ignore_df:1; __u8 nf_trace:1; __u8 ip_summed:2; __u8 ooo_okay:1; __u8 l4_hash:1; __u8 sw_hash:1; __u8 wifi_acked_valid:1; __u8 wifi_acked:1; __u8 no_fcs:1; /* Indicates the inner headers are valid in the skbuff. */ __u8 encapsulation:1; __u8 encap_hdr_csum:1; __u8 csum_valid:1; #ifdef __BIG_ENDIAN_BITFIELD #define PKT_VLAN_PRESENT_BIT 7 #else #define PKT_VLAN_PRESENT_BIT 0 #endif #define PKT_VLAN_PRESENT_OFFSET() offsetof(struct sk_buff, __pkt_vlan_present_offset) /* private: */ __u8 __pkt_vlan_present_offset[0]; /* public: */ __u8 vlan_present:1; __u8 csum_complete_sw:1; __u8 csum_level:2; __u8 csum_not_inet:1; __u8 dst_pending_confirm:1; #ifdef CONFIG_IPV6_NDISC_NODETYPE __u8 ndisc_nodetype:2; #endif __u8 ipvs_property:1; __u8 inner_protocol_type:1; __u8 remcsum_offload:1; #ifdef CONFIG_NET_SWITCHDEV __u8 offload_fwd_mark:1; __u8 offload_l3_fwd_mark:1; #endif #ifdef CONFIG_NET_CLS_ACT __u8 tc_skip_classify:1; __u8 tc_at_ingress:1; #endif #ifdef CONFIG_NET_REDIRECT __u8 redirected:1; __u8 from_ingress:1; #endif #ifdef CONFIG_TLS_DEVICE __u8 decrypted:1; #endif #ifdef CONFIG_NET_SCHED __u16 tc_index; /* traffic control index */ #endif union { __wsum csum; struct { __u16 csum_start; __u16 csum_offset; }; }; __u32 priority; int skb_iif; __u32 hash; __be16 vlan_proto; __u16 vlan_tci; #if defined(CONFIG_NET_RX_BUSY_POLL) || defined(CONFIG_XPS) union { unsigned int napi_id; unsigned int sender_cpu; }; #endif #ifdef CONFIG_NETWORK_SECMARK __u32 secmark; #endif union { __u32 mark; __u32 reserved_tailroom; }; union { __be16 inner_protocol; __u8 inner_ipproto; }; __u16 inner_transport_header; __u16 inner_network_header; __u16 inner_mac_header; __be16 protocol; __u16 transport_header; __u16 network_header; __u16 mac_header; /* private: */ __u32 headers_end[0]; /* public: */ /* These elements must be at the end, see alloc_skb() for details. */ sk_buff_data_t tail; sk_buff_data_t end; unsigned char *head, *data; unsigned int truesize; refcount_t users; #ifdef CONFIG_SKB_EXTENSIONS /* only useable after checking ->active_extensions != 0 */ struct skb_ext *extensions; #endif }; }}} }}} ==== SKB_DATA_ALIGN(X) ==== {{{#!enscript c #define SKB_DATA_ALIGN(X) ALIGN(X, SMP_CACHE_BYTES) }}} ==== SKB_WITH_OVERHEAD(X) ==== {{{#!enscript c #define SKB_WITH_OVERHEAD(X) \ ((X) - SKB_DATA_ALIGN(sizeof(struct skb_shared_info))) }}} ==== SKB_MAX_ORDER(X, ORDER) ==== {{{#!enscript c #define SKB_MAX_ORDER(X, ORDER) \ SKB_WITH_OVERHEAD((PAGE_SIZE << (ORDER)) - (X)) }}} ==== SKB_MAX_HEAD(X) ==== {{{#!enscript c #define SKB_MAX_HEAD(X) (SKB_MAX_ORDER((X), 0)) }}} ==== SKB_MAX_ALLOC ==== {{{#!enscript c #define SKB_MAX_ALLOC (SKB_MAX_ORDER(0, 2)) }}} ==== SKB_TRUESIZE(X) ==== {{{#!enscript c /* 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 포인터의 실제 할당 크기) ==== MAX_SKB_FRAGS ==== {{{#!enscript c #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개) === sk_buff 주요 함수들 === ==== skb_frag_size(frag) ==== {{{#!enscript c static inline unsigned int skb_frag_size(const skb_frag_t *frag); }}} * 주어진 skb_frag_t 에서 크기 정보를 반환합니다. "frag->bv_len"을 의미합니다. (예전 커널에서는 "frag->size"를 의미) ==== skb_frag_size_set(frag, size) ==== {{{#!enscript c static inline void skb_frag_size_set(skb_frag_t *frag, unsigned int size) }}} * 주어진 skb_frag_t 에서 크기를 설정합니다. === 참고자료 === * [wiki:OSI_7LayerModel OSI 7 계층모델] * [wiki:Ethernet 이더넷 (Ethernet)] * [wiki:ICMP ICMP(Internet Control Message Protocol)] * [wiki:IPv4 IPv4] * [wiki:IPv6 IPv6] * [wiki:NAT NAT(Network Address Translation)] * [wiki:rfc1071checksum Computing the Internet Checksum (RFC1071)] : IP, UDP, TCP protocol 에서 사용하는 checksum 알고리즘 * [wiki:AboutLinuxKernel 리눅스 커널에 대하여] * [wiki:SlabAllocator 슬랩할당자 (Slab Allocator)] * [wiki:XDP XDP(eXpress Data Path)] * [wiki:HowToiptables iptables 사용법] * [^https://www.kernel.org/ The Linux Kernel Archives] * [^https://www.kernel.org/doc/htmldocs/networking/ch01s02.html] (Socket Buffer Functions) * [^http://www.skbuff.net/ sk_buff{} documents and resources] * [^https://people.cs.clemson.edu/~westall/853/notes/skbuff.pdf Management of sk_buffs] * [^http://www.chiark.greenend.org.uk/doc/linux-doc-2.6.32/html/networking/index.html Linux Networking and Network Devices APIs] or [^http://einon.net/DocBook/networking/index.html] or [^http://docs.huihoo.com/linux/kernel/2.6.26/networking/index.html] * [^http://www.chiark.greenend.org.uk/doc/linux-doc-2.6.32/html/networking/ch01s02.html Socket Buffer Functions] * [^https://pr0gr4m.github.io/linux/kernel/sk_buff/ Linux Kernel SKB] * [^http://amsekharkernel.blogspot.com/2014/08/what-is-skb-in-linux-kernel-what-are.html What is SKB in Linux kernel? What are SKB operations? Memory Representation of SKB? How to send packet out using skb operations?] * [^https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=sysganda&logNo=30169551168] * [^http://www.embeddedlinux.org.cn/linux_net/0596002556/understandlni-CHP-21-SECT-1.html] * [^https://stackoverflow.com/questions/3869028/how-to-use-cryptoapi-in-the-linux-kernel-2-6]