1.1. 개요


Linux kernel 에서 sk_buff (skb) 자료형은 network packet 을 처리하는데 중요한 부분입니다. 이 문서는 이에 대한 여러가지 요소를 다룹니다.

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


  • packet flow 상에서 항상 head <= data <= tail <= end 조건을 유지하도록 구현합니다. (일단, 이것을 위반하면 동작이 폭주할 수 있습니다.)
    • 또한 특정 layer 처리시점에서 포인팅이 무효화될 수 있습니다. head <= [mac_header <] network_header [< transport_header] <= tail 조건도 있습니다.
      • 이 때 mac_header는 이 조건을 지킬 수 없으면 (Local-Out 또는 RX 시점 이후에 발생할 수 있음) 무효상태로 표시됩니다. 무효상태가 되면 skb_mac_header_was_set(skb) 호출로 확인해볼 때 0을 반환하게 됩니다.
    • head 는 실제 skb의 저장공간의 첫 시작을 의미합니다.
    • data 는 현재 Layer 가 다루어야 하는 위치를 의미합니다.
      • data는 head 보다 앞설수 없습니다. (즉, 항상 head <= data <= tail <= end 조건을 유지)
      • skb_push(skb), skb_pull(skb) 함수가 이러한 Layer 전환기점에 호출됩니다.
      • 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로 환원하는 식
    • tail 은 현재 skb에 유효저장된 마지막 위치를 의미합니다.
      • skb가 할당된 직후시점에는 data 와 위치가 같은 상태에서 시작합니다. 이후 L2, L3, L4가 채워지면서 이 부분이 뒤로 늘어납니다.
      • tail은 결코 end를 넘을 수 없습니다. (즉, 항상 head <= data <= tail <= end 조건을 유지)
      • tail을 조정하는 것은 skb_put이 대표적입니다.
      • skb_reserve 함수도 이를 조정합니다. (skb의 data 와 함께 조정함)
      • skb의 headroom 은 data 와 head 간의 격차를 headroom 으로 정의함.
        • headroom을 확보하려면 pskb_expand_head 또는 skb_copy_expand 또는 skb_cow 함수를 사용할 수 있습니다. (최하위 기본 함수는 pskb_expand_head 입니다.)
    • 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. struct sk_buff 자료형


  • "include/linux/skbuff.h"
    struct sk_buff_head {
        /* These two members must be first. */
        struct sk_buff  *next;
        struct sk_buff  *prev;
    
        __u32       qlen;
        spinlock_t  lock;
    };
    
    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
    };
    





/*
[ FrontPage | PrintView | RawView | RSS ]

Copyright ⓒ MINZKN.COM
All Rights Reserved.

MINZKN
*/