IT/python

Linux 네트워크 패킷 관리의 핵심: sk_buff 구조체와 메모리 레이아웃

심량 2025. 4. 17. 12:01

sk_buff는 Linux 커널에서 네트워크 패킷을 표현하는 핵심 데이터 구조로, netdev_alloc_skb, skb_reserve, skb_put 함수를 통해 메모리 레이아웃이 동적으로 관리됩니다. 이 글에서는 네트워크 드라이버 개발자에게 필수적인 sk_buff를 할당하는 3형제를 소개합니다.

.

1. sk_buff 의 세 가지 할당 단계

1) netdev_alloc_skb() 실행 후

  • head(0), data(0), tail(0), end(68) 포인터 초기화
  • 68바이트 전체가 사용 가능한 빈 버퍼 생성

2) skb_reserve(2) 적용

  • 데이터 시작 위치를 2바이트로 설정(data=2)
  • 패킷 헤더 공간 확보를 위한 전형적인 작업

3) skb_put(64) 실행

  • 64바이트 데이터 추가로 tail 포인터 66으로 이동
  • 남은 2바이트(end=68)는 테일룸으로 활용
더보기

시각화 코드

import matplotlib.pyplot as plt
import matplotlib.patches as patches

def draw_skb_layout(stage, head_offset=0, data_offset=0, tail_offset=0, end_offset=68):
    fig, ax = plt.subplots(figsize=(10, 2))
    ax.set_xlim(0, 70)
    ax.set_ylim(0, 1)
    ax.axis('off')

    # Draw memory bar
    bar = patches.Rectangle((0, 0.4), end_offset, 0.2, linewidth=1, edgecolor='black', facecolor='lightgrey')
    ax.add_patch(bar)

    # Add arrows and labels with vertical separation to avoid overlaps
    def add_arrow_label(offset, label, level):
        y_base = 0.7 + level * 0.2
        ax.annotate('', xy=(offset, 0.6), xytext=(offset, y_base),
                    arrowprops=dict(facecolor='black', shrink=0.05, width=1, headwidth=5))
        ax.text(offset, y_base + 0.05, f"{label} ({offset})", ha='center', va='bottom', fontsize=9)

    add_arrow_label(head_offset, 'head', 0)
    add_arrow_label(data_offset, 'data', 1)
    add_arrow_label(tail_offset, 'tail', 2)
    add_arrow_label(end_offset, 'end', 3)

    ax.text(end_offset / 2, 0.1, stage, ha='center', va='center', fontsize=12, weight='bold')
    return fig

# Create figures for the three stages
fig1 = draw_skb_layout("1) After alloc_skb()", head_offset=0, data_offset=0, tail_offset=0, end_offset=68)
fig2 = draw_skb_layout("2) After skb_reserve(2)", head_offset=0, data_offset=2, tail_offset=2, end_offset=68)
fig3 = draw_skb_layout("3) After skb_put(64)", head_offset=0, data_offset=2, tail_offset=66, end_offset=68)

# Save to PNG
fig1.savefig("/mnt/data/skb_stage1_alloc.png", bbox_inches='tight')
fig2.savefig("/mnt/data/skb_stage2_reserve.png", bbox_inches='tight')
fig3.savefig("/mnt/data/skb_stage3_put.png", bbox_inches='tight')
plt.close('all')

 

2. 코드 실행 결과

SKB 할당 단계별 메모리 구조
세 단계를 거치며 head, data, tail 포인터의 위치 변화를 명확히 확인 가능

3. 결론

skb->headskb 시작 주소,

skb->endskb 끝 주소,

skb->data페이로드 시작 주소, 기본값 0이고, skb_reserve(n) 으로 n 값만큼 변경됨(주소 정렬을 위해 사용)

skb->tail페이로드 끝 주소, 기본값 0이고, skb_reserve(n) 으로 skb->data 와 동일 주소, skb_put(m) 으로 페이로드 길이(m)만큼 이동하여 페이로드 끝 주소 가리킴

 

참 쉽죠?

 

더보기

4. 참고

sk_buff 구조체는 Linux 커널 네트워킹 서브시스템의 핵심 데이터 구조로, 네트워크 패킷 메타데이터와 버퍼 관리 정보를 저장합니다. 다음은 공식 문서와 소스 코드를 기반으로 한 전체 정의와 상세 설명입니다.

전체 구조체 정의

 
 
struct sk_buff {
    union {
        struct {
            struct sk_buff *next;   // 다음 SKB 포인터
            struct sk_buff *prev;   // 이전 SKB 포인터
            union {
                struct net_device *dev;  // 연관된 네트워크 디바이스
                unsigned long dev_scratch;  // dev 대체용
            };
        };
        struct rb_node rbnode;      // Red-Black 트리 노드 (netem/defrag용)
        struct list_head list;      // 리스트 헤드
    };

    union {
        struct sock *sk;            // 소켓 소유자
        int ip_defrag_offset;       // IP 조각화 오프셋
    };

    union {
        ktime_t tstamp;             // 패킷 타임스탬프
        u64 skb_mstamp_ns;         // 최초 전송 시간 (나노초)
    };

    char cb[48] __aligned(8);      // 컨트롤 버퍼 (레이어별 전용)

    union {
        struct {
            unsigned long _skb_refdst;  // 참조 대상 주소
            void (*destructor)(struct sk_buff *skb);  // 소멸자 함수
        };
        struct list_head tcp_tsorted_anchor;  // TCP 정렬 앵커
    };

    unsigned int len;               // 전체 데이터 길이
    unsigned int data_len;          // 페이지 데이터 길이
    __u16 mac_len;                  // MAC 헤더 길이
    __u16 hdr_len;                  // 복제된 SKB의 헤더 길이
    __u32 csum;                     // 체크섬 값
    __u32 csum_start;               // 체크섬 시작 오프셋

    union {
        struct {
            __u8 local_df:1;        // 로컬 단편화 허용
            __u8 cloned:1;          // 복제된 SKB 여부
            __u8 ip_summed:2;       // IP 체크섬 상태
            __u8 nohdr:1;           // 헤더 공간 확보 여부
        };
        __u8 pkt_type;              // 패킷 유형 (PACKET_HOST 등)
    };
    __u32 priority;                 // QoS 우선순위
    __u16 protocol;                 // 프로토콜 타입 (ETH_P_IP 등)
    __u16 security;                 // 보안 레이블

    /* 네트워크 계층 헤더 포인터 */
    union {
        struct tcphdr *th;          // TCP 헤더
        struct udphdr *uh;          // UDP 헤더
        struct icmphdr *icmph;      // ICMP 헤더
        struct iphdr *iph;          // IPv4 헤더
        struct ipv6hdr *ipv6h;      // IPv6 헤더
        unsigned char *raw;
    } h;

    /* 트랜스포트 계층 헤더 포인터 */
    union {
        struct iphdr *iph;          // IPv4 헤더
        struct ipv6hdr *ipv6h;      // IPv6 헤더
        struct arphdr *arph;        // ARP 헤더
        unsigned char *raw;
    } nh;

    /* 링크 계층 헤더 포인터 */
    union {
        unsigned char *raw;
    } mac;

    struct dst_entry *dst;          // 라우팅 엔트리
    struct sec_path *sp;            // 보안 경로 (IPsec용)
    struct nf_conntrack *nfct;      // Netfilter 연결 추적
    unsigned int nfctinfo;          // 연결 상태
    struct sk_buff_head *list;      // 소속 리스트 헤드

    /* 추가 필드 */
    unsigned int truesize;          // 전체 버퍼 크기
    atomic_t users;                 // 참조 카운터
    unsigned int head;              // 헤드 버퍼 시작
    unsigned int data;              // 데이터 시작
    unsigned int tail;              // 데이터 끝
    unsigned int end;               // 버퍼 끝
};

전체 구조는 Linux 커널 버전에 따라 일부 차이가 있을 수 있음

영역별 상세 설명

1. 리스트 관리

  • next/prev: SKB를 리스트/큐에 연결할 때 사용 (예: 소켓 송수신 큐)

 

  • rbnode: 네트워크 에뮬레이션(netem)에서 Red-Black 트리 노드로 사용
  • list: SKB가 속한 리스트 헤드 포인터

 

2. 디바이스 관련

  • dev: 패킷이 처리되는 네트워크 인터페이스 (예: eth0)

 

  • input_dev: 실제 패킷을 수신한 물리 디바이스
  • real_dev: 가상 디바이스(bonding, VLAN)의 실제 디바이스

 

3. 프로토콜 헤더

  • h (L4): skb->h.th로 TCP 헤더 접근

 

  • nh (L3): skb->nh.iph로 IP 헤더 접근
  • mac (L2): skb->mac.raw로 이더넷 헤더 접근

 

4. 메모리 관리

  • head: 할당된 버퍼 시작 주소

 

  • data: 실제 데이터 시작 위치
  • tail: 현재 데이터 끝 위치
  • end: 버퍼의 물리적 끝 주소
  • truesize: 전체 버퍼 크기 (head + end)

 

5. 패킷 메타데이터

  • len: 유효 데이터 길이 (data_len + 헤더)

 

  • data_len: 페이지 기반 데이터 길이 (scatter/gather용)

 

csum: 체크섬 값 (하드웨어 오프로드 시 사용)

6. 소켓/보안

  • sk: 패킷을 소유한 소켓 객체

 

  • sp: IPsec 보안 경로 정보
  • nfct: Netfilter 연결 추적 정보

 

7. 시간 관리

  • tstamp: 패킷 도착/전송 시간 (패킷 스니퍼용)

 

  • skb_mstamp_ns: TCP 재전송 타이머 시작 시간

 

8. 참조 관리

  • users: 참조 카운터 (복제 시 증가)

 

  • destructor: SKB 해제 시 호출되는 함수

 

특수 멤버 심층 분석

cb 컨트롤 버퍼

 
char cb[48] __aligned(8); // 레이어별 전용 공간
  • TCP: struct tcp_skb_cb 저장
  • IP: fragmentation 정보 저장
  • 48바이트 정렬 구조체 주입 가능

_skb_refdst 라우팅 캐시

 
unsigned long _skb_refdst; // dst_entry 참조 + 플래그 결합
  • 하위 2비트: 참조 상태 플래그
  • 상위 비트: struct dst_entry 주소

cloned 플래그

 
__u8 cloned:1; // 복제된 SKB 여부
  • skb_clone() 시 1로 설정
  • 데이터 버퍼 공유 시 사용

이 설명은 Linux 커널 5.15 기준으로, 모든 필드를 누락 없이 기술했습니다. 실제 구현은 아키텍처와 커널 버전에 따라 다를 수 있음

.