네트워킹 스택
왜 계층으로 나뉘는가
데이터가 네트워크를 오가는 과정은 복잡하다. 애플리케이션은 "data.json 파일을 서버에 보내라"고 생각하지만, 실제 하드웨어는 전기 신호나 광 펄스로 이루어진 비트 스트림을 전송할 뿐이다. 이 두 세계를 연결하려면 각 계층이 바로 아래 계층의 세부사항을 감추고 위 계층에게 깔끔한 인터페이스를 제공해야 한다.
Linux 커널은 OSI 7계층을 실용적으로 압축해 4계층 구조로 구현한다.
send / recv / select
흐름 제어·혼잡 제어
Netfilter 훅
DMA·링 버퍼·인터럽트
sk_buff: 패킷의 여권
struct sk_buff(소켓 버퍼, 약어 skb)는 하나의 네트워크 패킷을 표현하는 핵심 자료구조다. NIC 드라이버가 패킷을 수신하는 순간 생성되어, L2 → L3 → L4 → 소켓 수신 큐에 이르기까지 동일한 skb가 계층을 관통한다. 각 계층은 데이터를 복사하지 않고 포인터만 이동시켜 헤더를 드러내거나 감춘다.
struct sk_buff {
/* 연결 리스트 */
struct sk_buff *next;
struct sk_buff *prev;
/* 데이터 영역 포인터 */
unsigned char *head; // 할당된 버퍼 시작 (헤더 여분 포함)
unsigned char *data; // 현재 페이로드 시작
unsigned char *tail; // 현재 페이로드 끝
unsigned char *end; // 할당된 버퍼 끝
/* 메타데이터 */
__u32 len; // 총 데이터 길이
__be16 protocol;// ETH_P_IP, ETH_P_IPV6, ...
__u32 priority;
struct net_device *dev; // 수신/발신 네트워크 인터페이스
/* L3 / L4 헤더 포인터 (파싱 시 설정) */
struct iphdr *nh_iph; // IP 헤더 위치
union { struct tcphdr *th; struct udphdr *uh; } h; // L4 헤더
/* 페이지 기반 비선형 데이터 (Zero-copy 수신) */
struct skb_shared_info *shinfo; // frags[] 배열 (paged data)
};헤더 추가와 제거: push / pull
헤더를 추가(캡슐화)할 때는 skb_push() — data 포인터를 앞으로 이동해 head와의 공간에 헤더를 씀. 헤더를 제거(역캡슐화)할 때는 skb_pull() — data 포인터를 뒤로 이동해 헤더 영역을 숨김.
// L4 → L3 캡슐화 (TCP → IP)
skb_push(skb, sizeof(struct iphdr)); // data를 앞으로 당겨 IP 헤더 공간 확보
ip_hdr(skb)->version = 4; // data가 가리키는 위치에 IP 헤더 작성이 포인터 이동만으로 헤더 조작이 이루어지기 때문에, 전체 스택에서 데이터 복사가 최소화된다.
패킷 수신 경로: NIC → 소켓
패킷 하나가 NIC로 들어와 recv() 시스템 콜로 애플리케이션에 전달되기까지의 전 여정이다.
(이더넷 프레임) → NIC DMA
→ Ring Buffer → HardIRQ 발생
(인터럽트) → NAPI 스케줄
(SoftIRQ NET_RX) → skb 할당
netif_receive_skb()
PREROUTING → 라우팅 결정
ip_route_input() → Netfilter
LOCAL_IN → ip_local_deliver()
단편 재조립
(SYN/ACK/DATA) → 수신 큐
sk_receive_queue → 애플리케이션
recv() 반환
NAPI: 인터럽트 폭주 해결
고속 NIC에서 패킷이 밀려올 때 매 패킷마다 인터럽트를 발생시키면 CPU가 인터럽트 처리에만 매달린다. NAPI(New API)는 첫 패킷에서만 인터럽트를 발생시키고, 이후 SoftIRQ 컨텍스트에서 폴링(polling)으로 전환해 budget개(기본 300개) 패킷을 한 번에 처리한다. 처리할 패킷이 없으면 다시 인터럽트 모드로 복귀한다.
# NAPI 통계: SoftIRQ 패킷 처리 수
cat /proc/net/softnet_stat
# 열: total processed, dropped(budget 초과), time_squeeze(budget 소진), ...
# 수신 큐 깊이 및 드롭 통계
ethtool -S eth0 | grep -E 'rx_dropped|rx_missed'패킷 송신 경로: 소켓 → NIC
소켓 송신 버퍼 → tcp_write_xmit()
세그먼트 생성 → ip_queue_xmit()
IP 헤더 추가 → Netfilter
LOCAL_OUT
POSTROUTING → QDisc
(트래픽 쉐이핑) → dev_queue_xmit()
→ NIC 드라이버 → DMA → 물리
전송
QDisc(Queueing Discipline)는 패킷 스케줄러다. 기본은 pfifo_fast(3단계 우선순위 FIFO)이며, tc 명령으로 HTB·fq·cake 등의 정교한 스케줄러로 교체할 수 있다.
Netfilter와 Conntrack
Netfilter는 패킷 경로 5곳에 훅(hook) 포인트를 삽입한다. 커널 모듈이 이 훅에 콜백을 등록해 방화벽·NAT·패킷 수정 등을 구현한다.
PREROUTING → 라우팅 → ②
LOCAL_IN → 로컬 소켓
FORWARD → ⑤
POSTROUTING → NIC OUT
LOCAL_OUT → 라우팅 → ⑤
POSTROUTING → NIC OUT
Connection Tracking(conntrack)은 상태 기반 방화벽의 핵심이다. 패킷이 PREROUTING 훅을 통과할 때 (src_ip, dst_ip, src_port, dst_port, protocol) 5-tuple로 연결 상태 테이블을 조회·갱신한다.
# 현재 conntrack 테이블
conntrack -L | head -20
# tcp 6 299 ESTABLISHED src=10.0.0.5 dst=10.0.0.1 sport=54321 dport=443 ...
# conntrack 테이블 용량 (기본 65536)
cat /proc/sys/net/netfilter/nf_conntrack_max
# 커넥션이 꽉 찰 경우 (컨테이너 환경에서 흔한 이슈)
dmesg | grep "nf_conntrack: table full"
# → /etc/sysctl.conf 에서 nf_conntrack_max 늘리기TCP 소켓 내부: send buffer와 receive buffer
소켓은 두 개의 큐를 갖는다:
- sk_rcvbuf (수신 버퍼): NIC → 커널이 채우고
recv()호출 시 애플리케이션이 비운다. 가득 차면 패킷을 드롭한다. - sk_sndbuf (송신 버퍼):
send()호출 시 애플리케이션이 채우고 커널이 비운다. 가득 차면send()가 블로킹된다.
# 수신/송신 버퍼 크기 확인 (단위: bytes)
ss -tmi | grep -A1 "ESTABLISHED"
# rcvbuf:87380 sndbuf:2626560
# 전역 TCP 버퍼 튜닝 (고성능 서버 기본 설정)
sysctl -w net.core.rmem_max=134217728 # 128 MB
sysctl -w net.core.wmem_max=134217728
sysctl -w net.ipv4.tcp_rmem="4096 87380 134217728"
sysctl -w net.ipv4.tcp_wmem="4096 65536 134217728"
sysctl -w net.ipv4.tcp_mem="94500000 915000000 927000000"TCP_NODELAY: Nagle 알고리즘을 비활성화한다. 작은 메시지를 여러 번 보내는 프로토콜(Redis, gRPC)에서 레이턴시를 낮추려면 반드시 설정해야 한다.
int one = 1;
setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one));XDP: 커널 바이패스 없는 고속 패킷 처리
XDP(eXpress Data Path)는 eBPF 프로그램을 NIC 드라이버 직후에 실행해 sk_buff 할당 전에 패킷을 처리한다. 커널을 완전히 우회하는 DPDK보다 안전하면서도 수십 Mpps 처리가 가능하다.
| 방식 | 위치 | 장점 | 단점 |
|---|---|---|---|
XDP_DROP | 드라이버 | DDoS 패킷 즉시 폐기, 최고속 | 커널 네트워킹 기능 없음 |
XDP_PASS | 드라이버 | 선택적 패킷을 커널 스택으로 올림 | 일반 처리 경로 |
XDP_TX | 드라이버 | 패킷 수정 후 동일 인터페이스로 재전송 | L3 라우팅 불가 |
XDP_REDIRECT | 드라이버 | 다른 인터페이스로 포워딩, AF_XDP 소켓으로 유저스페이스 전달 | 프로그래밍 복잡도 |
# XDP 프로그램 로드 (ip 명령 활용)
ip link set dev eth0 xdp obj xdp_drop.o sec xdp
# 현재 XDP 상태 확인
ip link show eth0 | grep xdp네트워킹 성능 진단
# 수신 큐 드롭 통계 (NIC Ring Buffer 오버플로)
ethtool -S eth0 | grep -i drop
# SS로 소켓 상태 상세 확인
ss -tinp sport = :443
# State Recv-Q Send-Q rtt:1.23/0.45 retrans:0/0 cwnd:10 ...
# TCP 재전송 카운터 (지속적으로 증가하면 네트워크 문제)
netstat -s | grep -i retransmit
# 또는: ss -s
# NAPI 폴링과 SoftIRQ 부하 확인
watch -n1 'cat /proc/softirqs | grep NET_RX'
# 패킷 캡처 (커널 레벨)
tcpdump -i eth0 -nn port 8080 -w /tmp/trace.pcapReferences
- Networking Stack — Linux Kernel (mintlify.wiki)
- sk_buff: The Network Buffer — Linux Kernel Internals
- Linux Networking Part 1: Kernel Net Stack — DEV Community
- Kernel Subsystems: The Linux Networking Stack — CircuitLabs
- Linux Kernel Network Packet Processing Explained — ThinkPalm
- Nftables — Packet flow and Netfilter hooks in detail
- Network Stack Architecture and Packet Flow — DeepWiki
- Networking — The Linux Kernel documentation