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->head 는 skb 시작 주소,
skb->end 는 skb 끝 주소,
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 컨트롤 버퍼
- TCP: struct tcp_skb_cb 저장
- IP: fragmentation 정보 저장
- 48바이트 정렬 구조체 주입 가능
_skb_refdst 라우팅 캐시
- 하위 2비트: 참조 상태 플래그
- 상위 비트: struct dst_entry 주소
cloned 플래그
- skb_clone() 시 1로 설정
- 데이터 버퍼 공유 시 사용
이 설명은 Linux 커널 5.15 기준으로, 모든 필드를 누락 없이 기술했습니다. 실제 구현은 아키텍처와 커널 버전에 따라 다를 수 있음
.
'IT > python' 카테고리의 다른 글
Valkey vs Redis: 2024 오픈소스 인메모리 DB 선택 가이드 및 기능 비교 (0) | 2025.03.27 |
---|---|
[python3][one-liner] 섹션 헤더 없는 설정 파일 값 바꾸기 (2) | 2024.12.19 |
윈도우 디펜더에서 바이러스라며 pyinstaller 로 생성한 exe 막는 문제 (2) | 2024.05.27 |
[python3] windows10 에서 pyenv + poetry 환경 구축하기 (0) | 2024.05.03 |
[python3] 예외 이름 출력하기 (0) | 2024.02.15 |