Netfilter 프레임워크 심화
Netfilter 훅 시스템, nftables/iptables 아키텍처, conntrack 심화, NFQUEUE, NFLOG, ipset, ebtables, TPROXY, DDoS 방어, eBPF Netfilter, 방화벽 자동화 종합 가이드.
Netfilter 개요
Netfilter는 리눅스 커널의 패킷 처리 프레임워크로서, 네트워크 스택의 전략적 지점에 훅(hook)을 삽입하여 패킷을 검사, 수정, 폐기, 큐잉할 수 있습니다. 방화벽(iptables/nftables), NAT, 연결 추적(conntrack), 패킷 맹글링, 로깅 등 커널의 모든 패킷 필터링 기능은 Netfilter 위에 구축됩니다.
Netfilter의 핵심 컴포넌트: 훅 시스템(패킷 가로채기), conntrack(연결 상태 추적), NAT(주소/포트 변환 — NAT 심화 참조), 패킷 맹글링(헤더 수정), 로깅(NFLOG/LOG).
/* net/netfilter/core.c — Netfilter 핵심 자료구조 */
struct nf_hook_entries {
u16 num_hook_entries;
struct nf_hook_entry hooks[]; /* 우선순위 정렬된 훅 배열 */
/* 뒤에 struct nf_hook_ops *orig_ops[] 가 이어짐 */
};
/* 훅 실행 경로: NF_HOOK() 매크로 → nf_hook() → nf_hook_slow() */
static inline int NF_HOOK(
uint8_t pf, unsigned int hook,
struct net *net, struct sock *sk,
struct sk_buff *skb,
struct net_device *in, struct net_device *out,
int (*okfn)(struct net *, struct sock *, struct sk_buff *));
훅 시스템 (Hook System)
Netfilter는 IPv4/IPv6/ARP/Bridge 프로토콜 패밀리에 대해 5개의 훅 포인트를 제공합니다. 각 훅 포인트에는 우선순위에 따라 정렬된 콜백 함수들이 등록되며, 패킷이 해당 지점을 통과할 때 순서대로 호출됩니다.
struct nf_hook_ops
/* include/linux/netfilter.h */
struct nf_hook_ops {
nf_hookfn *hook; /* 훅 콜백 함수 */
struct net_device *dev; /* 특정 디바이스에만 적용 (NULL=전체) */
void *priv; /* 콜백에 전달할 private 데이터 */
u8 pf; /* 프로토콜 패밀리 (NFPROTO_IPV4 등) */
enum nf_hook_ops_type hook_ops_type;
unsigned int hooknum; /* 훅 번호 (NF_INET_PRE_ROUTING 등) */
int priority; /* 우선순위 (낮을수록 먼저 실행) */
};
/* 훅 콜백 함수 시그니처 */
typedef unsigned int nf_hookfn(
void *priv,
struct sk_buff *skb,
const struct nf_hook_state *state);
훅 우선순위
훅 우선순위는 같은 훅 포인트에 등록된 여러 콜백의 실행 순서를 결정합니다. 값이 작을수록 먼저 실행됩니다.
| 상수 | 값 | 용도 |
|---|---|---|
NF_IP_PRI_FIRST | INT_MIN | 최우선 실행 |
NF_IP_PRI_RAW_BEFORE_DEFRAG | -450 | raw 테이블 (defrag 이전) |
NF_IP_PRI_CONNTRACK_DEFRAG | -400 | conntrack 조각 재조립 |
NF_IP_PRI_RAW | -300 | raw 테이블 |
NF_IP_PRI_SELINUX_FIRST | -225 | SELinux 첫 번째 |
NF_IP_PRI_CONNTRACK | -200 | conntrack 연결 추적 |
NF_IP_PRI_MANGLE | -150 | mangle 테이블 |
NF_IP_PRI_NAT_DST | -100 | DNAT (목적지 NAT) |
NF_IP_PRI_FILTER | 0 | filter 테이블 (기본 방화벽) |
NF_IP_PRI_SECURITY | 50 | security 테이블 |
NF_IP_PRI_NAT_SRC | 100 | SNAT (소스 NAT) |
NF_IP_PRI_SELINUX_LAST | 225 | SELinux 마지막 |
NF_IP_PRI_CONNTRACK_HELPER | 300 | conntrack helper |
NF_IP_PRI_CONNTRACK_CONFIRM | INT_MAX | conntrack 확정 (최후) |
훅 Verdict
| Verdict | 값 | 설명 |
|---|---|---|
NF_DROP | 0 | 패킷 폐기 (메모리 해제) |
NF_ACCEPT | 1 | 패킷 통과 (다음 훅으로 진행) |
NF_STOLEN | 2 | 콜백이 패킷 소유권 획득 (Netfilter 처리 중단) |
NF_QUEUE | 3 | 유저스페이스 큐(NFQUEUE)로 전달 |
NF_REPEAT | 4 | 현재 훅 재실행 (주의: 무한 루프 위험) |
NF_STOP | 5 | 이후 훅 건너뛰고 즉시 수락 (deprecated) |
훅 등록 API
/* per-namespace 훅 등록 (현대적 API) */
int nf_register_net_hook(struct net *net, const struct nf_hook_ops *ops);
void nf_unregister_net_hook(struct net *net, const struct nf_hook_ops *ops);
/* 다수 훅 일괄 등록 */
int nf_register_net_hooks(struct net *net,
const struct nf_hook_ops *reg,
unsigned int n);
void nf_unregister_net_hooks(struct net *net,
const struct nf_hook_ops *reg,
unsigned int n);
nftables 아키텍처
nftables는 iptables/ip6tables/arptables/ebtables를 통합 대체하는 차세대 패킷 분류 프레임워크입니다. 커널 측에서는 바이트코드 VM(가상 머신)으로 규칙을 실행하며, 사용자 공간에서는 nft 유틸리티를 통해 netlink 기반으로 규칙을 전달합니다.
nftables vs iptables 비교
| 항목 | iptables | nftables |
|---|---|---|
| 커널 구조 | 프로토콜별 별도 테이블 (ip/ip6/arp/ebtables) | 통합 프레임워크 (nf_tables) |
| 규칙 평가 | 선형 매칭 (O(n)) | set/map 기반 최적화 (O(1) 가능) |
| 확장 방식 | match/target 커널 모듈 | expression 기반 (커널+유저 모듈 쌍) |
| 원자적 규칙 교체 | iptables-restore (전체 교체) | 네이티브 트랜잭션 지원 |
| 사용자 도구 | iptables, ip6tables, arptables, ebtables | nft (통합 CLI) |
| 커널-유저 통신 | getsockopt/setsockopt | netlink (nfnetlink) |
| sets/maps | ipset (별도 모듈) | 네이티브 set, map, vmap |
| Flowtable | 미지원 | 하드웨어 오프로드 가능 |
nftables 내부 구조
/* net/netfilter/nf_tables_api.c — nftables 핵심 객체 */
/* 테이블: 체인들의 컨테이너 */
struct nft_table {
struct list_head list;
struct list_head chains; /* 소속 체인 목록 */
struct list_head sets; /* 소속 set 목록 */
struct list_head flowtables; /* flowtable 목록 */
u64 hgenerator;
u32 use; /* 참조 카운트 */
u16 family; /* NFPROTO_* */
u16 flags; /* NFT_TABLE_F_* */
char *name;
};
/* 체인: 규칙들의 순서 리스트 + 훅 정보 */
struct nft_chain {
struct nft_rule_blob *blob_gen_0; /* 규칙 바이트코드 (세대 0) */
struct nft_rule_blob *blob_gen_1; /* 규칙 바이트코드 (세대 1) */
struct list_head rules; /* struct nft_rule 리스트 */
struct nft_table *table;
u64 handle;
u32 use;
char *name;
};
/* 규칙: 표현식(expression)들의 배열 */
struct nft_rule {
struct list_head list;
u64 handle;
u32 dlen; /* 표현식 데이터 총 길이 */
unsigned char data[]; /* nft_expr 배열 */
};
nftables 표현식 (Expression)
nftables 규칙은 표현식의 연속으로 구성됩니다. 각 표현식은 패킷에서 데이터를 추출(payload), 비교(cmp), 조회(lookup), 액션(verdict) 등을 수행합니다.
/* 표현식 타입 등록 */
struct nft_expr_type {
const struct nft_expr_ops *(*select_ops)(...);
const struct nft_expr_ops *ops;
const char *name;
struct module *owner;
u32 flags; /* NFT_EXPR_STATEFUL 등 */
};
/* 표현식 연산 인터페이스 */
struct nft_expr_ops {
void (*eval)(const struct nft_expr *expr,
struct nft_regs *regs,
const struct nft_pktinfo *pkt);
int (*init)(...);
void (*destroy)(...);
int (*dump)(...);
u32 type; /* NFT_EXPR_* */
unsigned int size;
};
int nft_register_expr(struct nft_expr_type *type);
void nft_unregister_expr(struct nft_expr_type *type);
nft 명령어 예제
# 테이블 생성 (inet = IPv4 + IPv6 통합)
nft add table inet my_filter
# base chain 생성 (훅에 직접 연결)
nft add chain inet my_filter input \
'{ type filter hook input priority 0; policy accept; }'
nft add chain inet my_filter forward \
'{ type filter hook forward priority 0; policy drop; }'
# 규칙 추가
nft add rule inet my_filter input tcp dport 22 accept
nft add rule inet my_filter input tcp dport 80 accept
nft add rule inet my_filter input ct state established,related accept
nft add rule inet my_filter input counter drop
# NAT 테이블
nft add table ip my_nat
nft add chain ip my_nat postrouting \
'{ type nat hook postrouting priority 100; }'
nft add rule ip my_nat postrouting oifname "eth0" masquerade
# set 활용 (고속 매칭)
nft add set inet my_filter blocked_ips '{ type ipv4_addr; }'
nft add element inet my_filter blocked_ips '{ 10.0.0.1, 10.0.0.2 }'
nft add rule inet my_filter input ip saddr @blocked_ips drop
# map 활용 (키→액션 매핑)
nft add map inet my_filter port_vmap \
'{ type inet_service : verdict; }'
nft add element inet my_filter port_vmap \
'{ 22 : accept, 80 : accept, 443 : accept }'
nft add rule inet my_filter input tcp dport vmap @port_vmap
# flowtable (커넥션 fast path)
nft add flowtable inet my_filter f \
'{ hook ingress priority 0; devices = { eth0, eth1 }; }'
nft add rule inet my_filter forward ct state established \
flow add @f
# 규칙 확인
nft list ruleset
iptables 레거시 아키텍처
iptables는 Netfilter의 전통적인 사용자 공간 인터페이스로, 커널 내에서 xt_table 구조와 match/target 확장 모듈을 사용합니다. 현재는 nftables가 권장되지만, 여전히 많은 시스템에서 운용 중이며 Docker, Kubernetes 등 컨테이너 생태계가 의존하고 있습니다.
현대 배포판에서 iptables 명령어는 두 가지 백엔드 중 하나로 동작합니다: nf_tables 백엔드(iptables-nft, 권장)는 내부적으로 nftables 커널 모듈을 사용하고, legacy 백엔드(iptables-legacy)는 x_tables 커널 모듈을 직접 사용합니다. iptables -V로 확인할 수 있습니다.
테이블 종류
| 테이블 | 커널 모듈 | 용도 | 기본 체인 (훅 포인트) |
|---|---|---|---|
raw | iptable_raw | conntrack 바이패스 (NOTRACK) | PREROUTING, OUTPUT |
mangle | iptable_mangle | 패킷 헤더 수정 (TOS, TTL, MARK) | PREROUTING, INPUT, FORWARD, OUTPUT, POSTROUTING |
nat | iptable_nat | 주소/포트 변환 | PREROUTING, INPUT, OUTPUT, POSTROUTING |
filter | iptable_filter | 패킷 허용/차단 (기본 방화벽) | INPUT, FORWARD, OUTPUT |
security | iptable_security | SELinux SECMARK 규칙 | INPUT, FORWARD, OUTPUT |
체인 구조와 규칙 평가
각 테이블에는 빌트인 체인(훅 포인트에 직접 연결)과 사용자가 만드는 커스텀 체인이 있습니다. 빌트인 체인에는 기본 정책(ACCEPT/DROP)이 있으며, 어떤 규칙도 매치되지 않으면 기본 정책이 적용됩니다.
패킷 진입
│
▼ 빌트인 체인 (예: INPUT)
├─ 규칙 1: [-p tcp --dport 22 -j ACCEPT] ← 매치 시 ACCEPT, 체인 종료
├─ 규칙 2: [-p tcp --dport 80 -j my_chain] ← 매치 시 커스텀 체인으로 점프
│ │
│ ▼ 커스텀 체인 (my_chain)
│ ├─ 규칙 A: [--src 10.0.0.0/8 -j ACCEPT] ← 매치 시 ACCEPT
│ ├─ 규칙 B: [-j LOG --log-prefix "WEB: "] ← LOG 후 계속 진행
│ └─ (체인 끝) → 호출 지점으로 복귀 (규칙 3으로)
│
├─ 규칙 3: [-m conntrack --ctstate ESTABLISHED -j ACCEPT]
├─ 규칙 4: [-j DROP] ← 무조건 매치, DROP
└─ (체인 끝) → 기본 정책(policy) 적용
규칙 평가 핵심: 규칙은 위에서 아래로 순서대로 평가됩니다(O(n) 선형 탐색). terminating target(ACCEPT, DROP, REJECT)에 매치되면 즉시 체인을 빠져나갑니다. non-terminating target(LOG, ULOG, MARK, CONNMARK, TOS)은 패킷을 계속 진행시킵니다. -j <custom_chain>은 서브루틴 호출처럼 동작합니다.
iptables 내부 구조
/* net/netfilter/x_tables.c — xtables 핵심 */
struct xt_table {
struct list_head list;
unsigned int valid_hooks; /* 유효한 훅 비트마스크 */
struct xt_table_info *private; /* per-CPU 규칙 데이터 */
struct module *me;
u_int8_t af; /* 프로토콜 패밀리 */
int priority;
const char name[XT_TABLE_MAXNAMELEN];
};
/* xt_table_info: per-CPU 규칙 블롭과 카운터 */
struct xt_table_info {
unsigned int size; /* 규칙 데이터 총 크기 */
unsigned int number; /* 엔트리 개수 */
unsigned int initial_entries;
unsigned int hook_entry[NF_INET_NUMHOOKS]; /* 체인 시작 오프셋 */
unsigned int underflow[NF_INET_NUMHOOKS]; /* 기본 정책 오프셋 */
unsigned int stacksize;
void ***jumpstack; /* 커스텀 체인 점프 스택 */
unsigned char entries[]; /* ipt_entry 배열 */
};
/* net/ipv4/netfilter/ip_tables.c — 개별 규칙 엔트리 */
struct ipt_entry {
struct ipt_ip ip; /* src/dst IP, 인터페이스, 프로토콜 */
unsigned int nfcache;
__u16 target_offset; /* match 데이터 끝 = target 시작 */
__u16 next_offset; /* 다음 엔트리까지 거리 */
unsigned int comefrom; /* 체인 추적 (역추적용) */
struct xt_counters counters; /* 패킷/바이트 카운터 */
unsigned char elems[]; /* [match...][target] */
};
/* per-CPU 카운터로 lock-free 패킷/바이트 집계 */
struct xt_counters {
__u64 pcnt; /* 패킷 카운트 */
__u64 bcnt; /* 바이트 카운트 */
};
match / target 확장 모듈
/* match 확장 구조 */
struct xt_match {
const char name[XT_EXTENSION_MAXNAMELEN];
u_int8_t revision; /* 버전 (하위 호환) */
u_int8_t family; /* NFPROTO_* */
/* 패킷 매칭 콜백 — true 반환 시 매치 */
bool (*match)(const struct sk_buff *skb,
struct xt_action_param *par);
/* 규칙 등록 시 유효성 검증 */
int (*checkentry)(const struct xt_mtchk_param *par);
void (*destroy)(const struct xt_mtdtor_param *par);
unsigned int matchsize; /* per-rule 사설 데이터 크기 */
unsigned int hooks; /* 사용 가능 훅 비트마스크 */
struct module *me;
};
/* target 확장 구조 */
struct xt_target {
const char name[XT_EXTENSION_MAXNAMELEN];
u_int8_t revision;
u_int8_t family;
/* 패킷 처리 콜백 — verdict 반환 */
unsigned int (*target)(struct sk_buff *skb,
const struct xt_action_param *par);
int (*checkentry)(const struct xt_tgchk_param *par);
void (*destroy)(const struct xt_tgdtor_param *par);
unsigned int targetsize;
unsigned int hooks;
struct module *me;
};
/* 등록/해제 API */
int xt_register_match(struct xt_match *match);
void xt_unregister_match(struct xt_match *match);
int xt_register_matches(struct xt_match *match, unsigned int n);
int xt_register_target(struct xt_target *target);
void xt_unregister_target(struct xt_target *target);
int xt_register_targets(struct xt_target *target, unsigned int n);
주요 match 모듈
| match 모듈 | 커널 소스 | 용도 | 사용 예 |
|---|---|---|---|
conntrack | xt_conntrack.c | 연결 상태 매칭 | -m conntrack --ctstate ESTABLISHED,RELATED |
multiport | xt_multiport.c | 여러 포트 동시 매칭 | -m multiport --dports 22,80,443 |
iprange | xt_iprange.c | IP 주소 범위 | -m iprange --src-range 10.0.0.1-10.0.0.100 |
string | xt_string.c | 페이로드 문자열 검색 | -m string --string "malware" --algo bm |
limit | xt_limit.c | 속도 제한 (token bucket) | -m limit --limit 5/min --limit-burst 10 |
hashlimit | xt_hashlimit.c | per-IP/per-port 속도 제한 | -m hashlimit --hashlimit-above 10/sec --hashlimit-mode srcip |
recent | xt_recent.c | 최근 접속 IP 추적 | -m recent --name ssh --rcheck --seconds 60 --hitcount 3 |
owner | xt_owner.c | 소켓 소유자 (UID/GID) | -m owner --uid-owner 1000 |
mark | xt_mark.c | 패킷 마크 매칭 | -m mark --mark 0x1 |
connmark | xt_connmark.c | 연결 마크 매칭 | -m connmark --mark 0x2/0xff |
mac | xt_mac.c | MAC 주소 매칭 | -m mac --mac-source AA:BB:CC:DD:EE:FF |
physdev | xt_physdev.c | 브릿지 물리 디바이스 | -m physdev --physdev-in eth0 |
tcp | xt_tcpudp.c | TCP 플래그/옵션 | -p tcp --tcp-flags SYN,ACK SYN |
addrtype | xt_addrtype.c | 주소 타입 (LOCAL, BROADCAST 등) | -m addrtype --dst-type LOCAL |
comment | xt_comment.c | 규칙에 주석 추가 | -m comment --comment "Allow SSH" |
주요 target 모듈
| target | 유형 | 커널 소스 | 설명 |
|---|---|---|---|
ACCEPT | terminating | 빌트인 | 패킷 허용 |
DROP | terminating | 빌트인 | 패킷 무시 (무응답) |
REJECT | terminating | ipt_REJECT.c | 패킷 거부 + ICMP/TCP RST 응답 |
RETURN | terminating | 빌트인 | 현재 체인에서 호출자로 복귀 |
LOG | non-term | xt_LOG.c | 커널 로그에 패킷 정보 기록 |
NFLOG | non-term | xt_NFLOG.c | 유저스페이스 로깅 (ulogd2) |
NFQUEUE | terminating | xt_NFQUEUE.c | 유저스페이스 큐로 전달 |
MARK | non-term | xt_MARK.c | 패킷 마크(skb->mark) 설정 |
CONNMARK | non-term | xt_CONNMARK.c | 연결 마크(ct->mark) 설정/복사 |
DNAT | terminating | nf_nat | 목적지 주소/포트 변환 |
SNAT | terminating | nf_nat | 소스 주소/포트 변환 |
MASQUERADE | terminating | nf_nat | 동적 IP SNAT (인터페이스 IP 사용) |
REDIRECT | terminating | nf_nat | 로컬 포트로 리다이렉트 (투명 프록시) |
TPROXY | terminating | xt_TPROXY.c | 투명 프록시 (NAT 없이) |
TOS | non-term | xt_DSCP.c | IP TOS/DSCP 필드 수정 |
TTL | non-term | ipt_TTL.c | IP TTL 값 수정 |
TCPMSS | non-term | xt_TCPMSS.c | TCP MSS 클램핑 (MTU 문제 해결) |
CT | non-term | xt_CT.c | conntrack helper/zone/timeout 지정 |
NOTRACK | non-term | xt_CT.c | conntrack 추적 비활성화 |
SECMARK | non-term | xt_SECMARK.c | SELinux 보안 컨텍스트 설정 |
AUDIT | non-term | xt_AUDIT.c | 감사 로그 기록 |
iptables 명령어 구문
# 기본 구문
# iptables [-t 테이블] 명령 체인 [매치조건...] -j 타겟 [타겟옵션...]
# ── 체인 관리 ──
iptables -N MY_CHAIN # 커스텀 체인 생성
iptables -X MY_CHAIN # 커스텀 체인 삭제 (비어있어야 함)
iptables -P INPUT DROP # 빌트인 체인 기본 정책 설정
iptables -E OLD_CHAIN NEW_CHAIN # 체인 이름 변경
iptables -F INPUT # 체인의 모든 규칙 삭제 (flush)
iptables -Z INPUT # 카운터 초기화 (zero)
# ── 규칙 추가/삽입/삭제 ──
iptables -A INPUT ... # 체인 끝에 규칙 추가 (append)
iptables -I INPUT 1 ... # 체인 N번째에 삽입 (insert)
iptables -R INPUT 3 ... # 체인 3번 규칙 교체 (replace)
iptables -D INPUT 3 # 체인 3번 규칙 삭제 (by number)
iptables -D INPUT -p tcp --dport 22 -j ACCEPT # 규칙 명세로 삭제
# ── 조회 ──
iptables -L -n -v # 전체 규칙 조회 (숫자 표시, 카운터 포함)
iptables -L INPUT -n --line-numbers # 규칙 번호와 함께 조회
iptables -S # iptables-save 형식으로 출력
실전 규칙 예제
# ━━━ 기본 서버 방화벽 ━━━
# 기본 정책: 인바운드 차단, 아웃바운드 허용
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT
# 루프백 허용
iptables -A INPUT -i lo -j ACCEPT
# established/related 연결 허용 (stateful)
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
# invalid 패킷 차단
iptables -A INPUT -m conntrack --ctstate INVALID -j DROP
# ICMP (ping) 허용 (속도 제한)
iptables -A INPUT -p icmp --icmp-type echo-request \
-m limit --limit 1/s --limit-burst 4 -j ACCEPT
# SSH 허용 (brute force 방어: 60초 내 3회 초과 시 차단)
iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW \
-m recent --name ssh --set
iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW \
-m recent --name ssh --rcheck --seconds 60 --hitcount 4 -j DROP
iptables -A INPUT -p tcp --dport 22 -j ACCEPT
# HTTP/HTTPS 허용
iptables -A INPUT -p tcp -m multiport --dports 80,443 \
-m conntrack --ctstate NEW -j ACCEPT
# ━━━ SYN Flood 방어 ━━━
iptables -A INPUT -p tcp --syn -m hashlimit \
--hashlimit-above 30/sec \
--hashlimit-burst 50 \
--hashlimit-mode srcip \
--hashlimit-name syn_flood \
-j DROP
# ━━━ NAT (게이트웨이 서버) ━━━
# MASQUERADE (동적 IP 환경)
iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
# SNAT (고정 IP 환경, MASQUERADE보다 빠름)
iptables -t nat -A POSTROUTING -o eth0 \
-j SNAT --to-source 203.0.113.1
# DNAT (포트 포워딩: 외부 8080 → 내부 10.0.0.5:80)
iptables -t nat -A PREROUTING -p tcp --dport 8080 \
-j DNAT --to-destination 10.0.0.5:80
iptables -A FORWARD -p tcp -d 10.0.0.5 --dport 80 \
-m conntrack --ctstate NEW -j ACCEPT
# REDIRECT (투명 프록시: 로컬 80 → 로컬 3128)
iptables -t nat -A PREROUTING -p tcp --dport 80 \
-j REDIRECT --to-ports 3128
# ━━━ 패킷 마킹 (정책 라우팅 연동) ━━━
iptables -t mangle -A PREROUTING -p tcp --dport 80 \
-j MARK --set-mark 0x1
# → ip rule add fwmark 0x1 table 100
# ━━━ TCP MSS 클램핑 (VPN/터널 MTU 문제 해결) ━━━
iptables -A FORWARD -p tcp --tcp-flags SYN,RST SYN \
-j TCPMSS --clamp-mss-to-pmtu
# ━━━ CONNMARK: 연결 단위 마킹 ━━━
# 새 연결에 마크 설정 → 연결 마크로 저장
iptables -t mangle -A PREROUTING -m conntrack --ctstate NEW \
-p tcp --dport 443 -j MARK --set-mark 0x2
iptables -t mangle -A PREROUTING -j CONNMARK --save-mark
# 이후 패킷에서 연결 마크 복원
iptables -t mangle -A PREROUTING -j CONNMARK --restore-mark
iptables-save / iptables-restore
규칙의 영속화와 원자적 교체에 사용됩니다. iptables-restore는 iptables 명령어를 반복 실행하는 것보다 훨씬 빠르며, 규칙을 원자적으로 교체하여 교체 중 패킷 누락을 방지합니다.
# 현재 규칙 저장
iptables-save > /etc/iptables/rules.v4
ip6tables-save > /etc/iptables/rules.v6
# 규칙 복원 (원자적 교체)
iptables-restore < /etc/iptables/rules.v4
ip6tables-restore < /etc/iptables/rules.v6
# 카운터 포함 저장 (-c)
iptables-save -c > /etc/iptables/rules.v4
# 특정 테이블만 저장
iptables-save -t filter > /etc/iptables/filter.rules
# iptables-save 출력 형식:
# *filter
# :INPUT DROP [0:0] ← 체인:정책 [패킷:바이트]
# :FORWARD DROP [0:0]
# :OUTPUT ACCEPT [0:0]
# -A INPUT -i lo -j ACCEPT
# -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
# -A INPUT -p tcp -m multiport --dports 22,80,443 -j ACCEPT
# COMMIT ← 원자적 커밋
# 부팅 시 자동 복원 (systemd)
# Debian/Ubuntu: apt install iptables-persistent
# → /etc/iptables/rules.v4, /etc/iptables/rules.v6 자동 로드
# RHEL/CentOS: iptables-services
# systemctl enable iptables
# → /etc/sysconfig/iptables 자동 로드
ip6tables (IPv6)
ip6tables는 IPv6 전용 iptables로, 구문은 iptables와 동일하지만 IPv6 고유 기능을 추가로 지원합니다.
# IPv6 기본 방화벽
ip6tables -P INPUT DROP
ip6tables -P FORWARD DROP
ip6tables -P OUTPUT ACCEPT
ip6tables -A INPUT -i lo -j ACCEPT
ip6tables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
# ICMPv6는 IPv6 동작에 필수 — 전면 차단 금지!
# NDP(Neighbor Discovery), RA, Path MTU Discovery 등이 ICMPv6 사용
ip6tables -A INPUT -p icmpv6 --icmpv6-type router-advertisement -j ACCEPT
ip6tables -A INPUT -p icmpv6 --icmpv6-type router-solicitation -j ACCEPT
ip6tables -A INPUT -p icmpv6 --icmpv6-type neighbour-solicitation -j ACCEPT
ip6tables -A INPUT -p icmpv6 --icmpv6-type neighbour-advertisement -j ACCEPT
ip6tables -A INPUT -p icmpv6 --icmpv6-type echo-request \
-m limit --limit 1/s -j ACCEPT
# 서비스 허용
ip6tables -A INPUT -p tcp -m multiport --dports 22,80,443 -j ACCEPT
IPv6 ICMPv6 주의: IPv4와 달리 IPv6에서는 ICMPv6를 전면 차단하면 NDP(Neighbor Discovery Protocol)가 동작하지 않아 네트워크 통신이 완전히 단절됩니다. router-solicitation, router-advertisement, neighbour-solicitation, neighbour-advertisement 타입은 반드시 허용해야 합니다.
iptables 호환 레이어 아키텍처
현대 배포판에서 iptables 명령어는 두 가지 경로로 커널과 통신합니다:
┌─────────────────────────────────────────────────────────┐
│ 유저스페이스 │
│ │
│ iptables-legacy ──→ getsockopt/setsockopt ──→ x_tables │
│ (구식) (IPT_SO_SET_REPLACE) 커널 모듈 │
│ │
│ iptables-nft ──→ nfnetlink (netlink) ──→ nf_tables │
│ (현대적) (NFNL_SUBSYS_NFTABLES) 커널 모듈 │
│ │
│ nft (네이티브) ──→ nfnetlink (netlink) ──→ nf_tables │
│ 커널 모듈 │
└─────────────────────────────────────────────────────────┘
혼용 금지: iptables-nft와 iptables-legacy를 같은 시스템에서 혼용하면 규칙이 별도 데이터 경로에 저장되어 예상치 못한 동작이 발생합니다. update-alternatives로 하나만 선택하거나, 순수 nft로 통일하세요.
iptables → nftables 마이그레이션
# 개별 규칙 변환
iptables-translate -A INPUT -p tcp --dport 22 -j ACCEPT
# 출력: nft add rule ip filter INPUT tcp dport 22 counter accept
iptables-translate -t nat -A POSTROUTING -o eth0 -j MASQUERADE
# 출력: nft add rule ip nat POSTROUTING oifname "eth0" counter masquerade
# 전체 규칙셋 변환
iptables-save | iptables-restore-translate > nftables-rules.nft
ip6tables-save | ip6tables-restore-translate >> nftables-rules.nft
# 변환된 nftables 규칙 적용
nft -f nftables-rules.nft
# 커널의 iptables 백엔드 확인
iptables -V
# iptables v1.8.x (nf_tables) ← nft 백엔드 (권장)
# iptables v1.8.x (legacy) ← 레거시 x_tables
# 백엔드 전환 (Debian/Ubuntu)
update-alternatives --config iptables
# 1. /usr/sbin/iptables-nft (nf_tables 백엔드)
# 2. /usr/sbin/iptables-legacy (x_tables 백엔드)
Connection Tracking 심화
Connection tracking(conntrack)은 Netfilter의 상태 추적 엔진으로, 모든 네트워크 연결의 상태를 해시 테이블에 기록합니다. 방화벽의 stateful 규칙, NAT, 프로토콜 helper 모두 conntrack 위에 동작합니다.
struct nf_conn
/* include/net/netfilter/nf_conntrack.h */
struct nf_conn {
struct nf_conntrack ct_general; /* 참조 카운트 */
/* 양방향 튜플 (원본 + 응답) */
struct nf_conntrack_tuple_hash tuplehash[IP_CT_DIR_MAX];
unsigned long status; /* IPS_* 플래그 */
u32 mark; /* CONNMARK */
/* 타임아웃 (jiffies 기반) */
struct timer_list timeout;
/* 프로토콜별 상태 (TCP window tracking 등) */
union nf_conntrack_proto proto;
/* 확장 데이터 (helper, nat, selinux 등) */
struct nf_ct_ext *ext;
/* conntrack 존 (멀티테넌트 격리) */
struct nf_conntrack_zone zone;
};
/* 연결 상태 (ct state) */
enum ip_conntrack_info {
IP_CT_ESTABLISHED, /* 양방향 패킷 확인 */
IP_CT_RELATED, /* 기존 연결과 관련 (ICMP error, FTP data 등) */
IP_CT_NEW, /* 새 연결 */
IP_CT_IS_REPLY, /* 응답 방향 */
IP_CT_ESTABLISHED_REPLY = IP_CT_ESTABLISHED + IP_CT_IS_REPLY,
IP_CT_RELATED_REPLY = IP_CT_RELATED + IP_CT_IS_REPLY,
};
conntrack 해시 테이블
conntrack 엔트리는 소스/목적지 IP, 포트, 프로토콜로 구성된 튜플(tuple)의 해시값으로 해시 테이블에 저장됩니다. 기본 해시 크기는 nf_conntrack_buckets 파라미터로 조정합니다.
# conntrack 해시 테이블 크기 확인/조정
sysctl net.netfilter.nf_conntrack_buckets
sysctl -w net.netfilter.nf_conntrack_buckets=65536
# 최대 추적 연결 수
sysctl net.netfilter.nf_conntrack_max
sysctl -w net.netfilter.nf_conntrack_max=262144
# 현재 추적 중인 연결 수
conntrack -C
# 또는
cat /proc/sys/net/netfilter/nf_conntrack_count
conntrack 존 (Zone)
conntrack 존은 동일한 튜플을 가진 연결을 격리합니다. 멀티테넌트 환경이나 컨테이너에서 겹치는 IP 주소 공간을 처리할 때 사용합니다.
# nftables에서 conntrack 존 할당
nft add rule inet raw prerouting iifname "veth-tenant1" ct zone set 1
nft add rule inet raw prerouting iifname "veth-tenant2" ct zone set 2
nft add rule inet raw output oifname "veth-tenant1" ct zone set 1
nft add rule inet raw output oifname "veth-tenant2" ct zone set 2
conntrack Helper 프레임워크
conntrack helper는 FTP, SIP, TFTP 등 데이터 연결을 별도로 여는 프로토콜의 관련(related) 연결을 자동으로 추적합니다.
/* include/net/netfilter/nf_conntrack_helper.h */
struct nf_conntrack_helper {
struct hlist_node hnode;
char name[NF_CT_HELPER_NAME_LEN];
struct module *me;
struct nf_conntrack_expect_policy *expect_policy;
unsigned int expect_class_max;
/* 패킷 검사 콜백 */
int (*help)(struct sk_buff *skb,
unsigned int protoff,
struct nf_conn *ct,
enum ip_conntrack_info conntrackinfo);
struct nf_conntrack_tuple tuple;
};
int nf_conntrack_helper_register(struct nf_conntrack_helper *helper);
void nf_conntrack_helper_unregister(struct nf_conntrack_helper *helper);
conntrack 이벤트와 ctnetlink
# 실시간 conntrack 이벤트 모니터링
conntrack -E
# [NEW] tcp 6 120 SYN_SENT src=10.0.0.1 dst=93.184.216.34 ...
# [UPDATE] tcp 6 60 SYN_RECV ...
# [UPDATE] tcp 6 432000 ESTABLISHED ...
# conntrack 내용 조회
conntrack -L
conntrack -L -p tcp --state ESTABLISHED
# 특정 연결 삭제
conntrack -D -s 10.0.0.1 -d 93.184.216.34
TCP Window Tracking
conntrack은 TCP 연결에 대해 시퀀스 번호, 윈도우 크기, 스케일 팩터를 추적하여 유효하지 않은 패킷을 탐지합니다.
/* net/netfilter/nf_conntrack_proto_tcp.c */
struct ip_ct_tcp {
struct ip_ct_tcp_state seen[2]; /* 양방향 TCP 상태 */
u8 state; /* TCP_CONNTRACK_* */
u8 last_dir;
u8 retrans;
u8 last_index;
u32 last_seq;
u32 last_ack;
u32 last_end;
u16 last_win;
};
struct ip_ct_tcp_state {
u32 td_end; /* 시퀀스 윈도우 끝 */
u32 td_maxend; /* 최대 끝 */
u32 td_maxwin; /* 최대 윈도우 크기 */
u32 td_maxack; /* 최대 ACK */
u8 td_scale; /* 윈도우 스케일 팩터 */
u8 flags;
};
conntrack 타임아웃 정책
# 프로토콜별 기본 타임아웃 확인
sysctl net.netfilter.nf_conntrack_tcp_timeout_established
# 432000 (5일)
sysctl net.netfilter.nf_conntrack_tcp_timeout_time_wait
# 120 (2분)
sysctl net.netfilter.nf_conntrack_udp_timeout
# 30 (30초)
sysctl net.netfilter.nf_conntrack_udp_timeout_stream
# 120 (2분)
sysctl net.netfilter.nf_conntrack_icmp_timeout
# 30 (30초)
# nftables에서 커스텀 타임아웃 정책
nft add ct timeout inet my_filter web_timeout \
'{ protocol tcp; l3proto ip; \
policy = { established: 3600, close_wait: 10, close: 10 }; }'
nft add rule inet my_filter input tcp dport 80 \
ct timeout set "web_timeout"
NFQUEUE (유저스페이스 패킷 처리)
NFQUEUE는 커널에서 패킷을 유저스페이스로 전달하여 사용자 프로그램이 패킷을 검사하고 verdict(수락/폐기/수정)를 내릴 수 있게 합니다. IDS/IPS, DPI(Deep Packet Inspection), 커스텀 방화벽 구현에 사용됩니다.
# nftables에서 NFQUEUE로 패킷 전달
nft add rule inet my_filter input tcp dport 80 queue num 0
# 다수 큐 + CPU 바인딩 (로드 밸런싱)
nft add rule inet my_filter input tcp dport 80 queue num 0-3 fanout
# iptables에서 NFQUEUE
iptables -A INPUT -p tcp --dport 80 -j NFQUEUE --queue-num 0
iptables -A INPUT -p tcp --dport 80 -j NFQUEUE --queue-balance 0:3
/* libnetfilter_queue 기본 사용 패턴 */
#include <libnetfilter_queue/libnetfilter_queue.h>
static int cb(struct nfq_q_handle *qh,
struct nfgenmsg *nfmsg,
struct nfq_data *nfa, void *data)
{
struct nfqnl_msg_packet_hdr *ph = nfq_get_msg_packet_hdr(nfa);
u_int32_t id = ntohl(ph->packet_id);
unsigned char *payload;
int len = nfq_get_payload(nfa, &payload);
/* 패킷 분석 로직 ... */
/* verdict 판정: NF_ACCEPT 또는 NF_DROP */
return nfq_set_verdict(qh, id, NF_ACCEPT, 0, NULL);
}
int main(void)
{
struct nfq_handle *h = nfq_open();
nfq_unbind_pf(h, AF_INET);
nfq_bind_pf(h, AF_INET);
struct nfq_q_handle *qh = nfq_create_queue(h, 0, &cb, NULL);
nfq_set_mode(qh, NFQNL_COPY_PACKET, 0xffff);
int fd = nfq_fd(h);
char buf[4096];
while (1) {
int rv = recv(fd, buf, sizeof(buf), 0);
nfq_handle_packet(h, buf, rv);
}
}
NFLOG (패킷 로깅)
NFLOG는 패킷을 유저스페이스의 로깅 데몬(ulogd2)으로 전달합니다. 커널 내 LOG 타겟과 달리 syslog를 거치지 않으므로 고성능 로깅이 가능합니다.
| 항목 | LOG 타겟 | NFLOG 타겟 |
|---|---|---|
| 출력 경로 | 커널 printk → syslog | netlink → ulogd2 (유저스페이스) |
| 성능 | syslog 병목 | 고성능 (비동기) |
| 유연성 | syslog 형식 고정 | DB, pcap, 파일 등 다양한 출력 |
| 그룹 | 미지원 | 그룹 번호로 분류 |
| 속도 제한 | --limit 필요 | qthreshold, 타이머 기반 |
# nftables에서 NFLOG 사용
nft add rule inet my_filter input tcp dport 22 \
log prefix "SSH: " group 1 queue-threshold 10
nft add rule inet my_filter forward ct state invalid \
log prefix "INVALID: " group 2 drop
# ulogd2 설정 (/etc/ulogd.conf)
# [global]
# logfile="/var/log/ulogd.log"
# stack=log1:NFLOG,base1:BASE,pcap1:PCAP
# [log1]
# group=1
# [pcap1]
# file="/var/log/nflog.pcap"
# sync=1
Netfilter Netlink (nfnetlink)
nfnetlink은 Netfilter 서브시스템의 커널-유저스페이스 통신 채널입니다. netlink 프로토콜 패밀리 NETLINK_NETFILTER를 사용하며, 각 서브시스템이 별도의 메시지 타입을 등록합니다.
| 서브시스템 상수 | 용도 |
|---|---|
NFNL_SUBSYS_CTNETLINK | conntrack 항목 조회/생성/삭제/이벤트 |
NFNL_SUBSYS_CTNETLINK_EXP | conntrack expect (helper 관련) |
NFNL_SUBSYS_QUEUE | NFQUEUE 패킷 전달/verdict |
NFNL_SUBSYS_ULOG | NFLOG 패킷 로그 |
NFNL_SUBSYS_NFTABLES | nftables 규칙 관리 (트랜잭션 지원) |
NFNL_SUBSYS_CTNETLINK_TIMEOUT | conntrack 타임아웃 정책 |
NFNL_SUBSYS_CTTIMEOUT | conntrack 타임아웃 오브젝트 |
NFNL_SUBSYS_IPSET | ipset 관리 |
nftables의 규칙 업데이트는 nfnetlink 트랜잭션으로 처리됩니다. 여러 규칙 변경을 하나의 원자적 배치로 커밋할 수 있어, 규칙 교체 중 패킷이 누락되지 않습니다.
# nft의 원자적 규칙 교체 (-f 파일 로드)
nft -f /etc/nftables.conf
# 전체 ruleset을 원자적으로 교체
nft flush ruleset
nft -f /etc/nftables.conf
테이블/체인 아키텍처 상세
패킷이 각 훅 포인트를 통과할 때, 등록된 테이블들이 우선순위 순서로 처리됩니다. 전체 패킷 통과 순서를 이해하는 것이 방화벽 규칙 설계의 핵심입니다.
PREROUTING 훅의 테이블 처리 순서
패킷 수신
│
▼ (priority -450) raw (NOTRACK 결정)
▼ (priority -400) conntrack defrag (IP 조각 재조립)
▼ (priority -200) conntrack (연결 상태 추적 시작)
▼ (priority -150) mangle (MARK, TOS 수정)
▼ (priority -100) DNAT (목적지 주소 변환)
│
▼ 라우팅 결정
├─→ INPUT 훅 (로컬 배달)
│ ▼ mangle → filter → security → SNAT → conntrack confirm
│
└─→ FORWARD 훅 (포워딩)
▼ mangle → filter → security
│
▼ POSTROUTING 훅
▼ mangle → SNAT/MASQUERADE → conntrack confirm
raw 테이블
raw 테이블은 conntrack 이전에 실행되어, 특정 패킷의 연결 추적을 건너뛸 수 있습니다. 대량 트래픽에서 conntrack 오버헤드를 줄이는 데 유용합니다.
# nftables에서 NOTRACK 설정
nft add table inet raw
nft add chain inet raw prerouting \
'{ type filter hook prerouting priority -300; }'
nft add rule inet raw prerouting tcp dport 8080 notrack
nft add rule inet raw prerouting udp dport 53 notrack
mangle 테이블
# 패킷 마킹 (정책 라우팅, QoS 연동)
nft add rule inet mangle_tbl forward ip saddr 10.0.1.0/24 \
meta mark set 0x1
nft add rule inet mangle_tbl forward ip saddr 10.0.2.0/24 \
meta mark set 0x2
# TTL 수정
nft add rule inet mangle_tbl forward ip ttl set 64
# DSCP/TOS 수정
nft add rule inet mangle_tbl forward tcp dport 22 \
ip dscp set cs4
Netfilter 성능 최적화
규칙 최적화 전략
핵심 원칙: 선형 규칙 리스트(O(n)) 대신 set/map(O(1))을 사용하세요. 1,000개의 IP 차단 규칙보다 하나의 set + lookup이 훨씬 빠릅니다.
# 나쁜 예: 선형 규칙 (O(n))
nft add rule inet filter input ip saddr 10.0.0.1 drop
nft add rule inet filter input ip saddr 10.0.0.2 drop
# ... 수백 개 반복
# 좋은 예: set 활용 (O(1) 해시 조회)
nft add set inet filter blocklist '{ type ipv4_addr; flags interval; }'
nft add element inet filter blocklist '{ 10.0.0.0/24, 192.168.1.0/24 }'
nft add rule inet filter input ip saddr @blocklist drop
# verdict map으로 다중 분기 최적화
nft add map inet filter svc_map '{ type inet_service : verdict; }'
nft add element inet filter svc_map \
'{ 22 : accept, 80 : accept, 443 : accept }'
nft add rule inet filter input tcp dport vmap @svc_map
conntrack 튜닝
# 해시 테이블 크기 (버킷 수, 2의 거듭제곱 권장)
# 메모리: 약 buckets × 16 bytes
sysctl -w net.netfilter.nf_conntrack_buckets=131072
# 최대 추적 연결 수 (buckets × 4 ~ buckets × 8 권장)
sysctl -w net.netfilter.nf_conntrack_max=524288
# TCP established 타임아웃 단축 (서버 환경)
sysctl -w net.netfilter.nf_conntrack_tcp_timeout_established=86400
# 불필요한 트래픽 NOTRACK (conntrack 부하 감소)
nft add rule inet raw prerouting udp dport 53 notrack
nft add rule inet raw output udp sport 53 notrack
Flowtable Fast Path
Flowtable은 이미 확립된(ESTABLISHED) 연결의 패킷을 Netfilter 훅 체인을 우회하여 직접 전달합니다. 포워딩 성능을 크게 향상시킵니다. 상세 내용은 NAT 심화 페이지를 참조하세요.
# flowtable 설정
nft add flowtable inet filter ft \
'{ hook ingress priority 0; devices = { eth0, eth1 }; }'
# established 연결을 flowtable으로 오프로드
nft add rule inet filter forward ct state established \
flow add @ft
nft add rule inet filter forward ct state related accept
nft add rule inet filter forward ct state new accept
XDP vs Netfilter 트레이드오프
| 항목 | Netfilter | XDP |
|---|---|---|
| 실행 위치 | 네트워크 스택 내부 (L3/L4) | 드라이버 수준 (L2 이전) |
| 성능 | ~수 Mpps | ~24+ Mpps (native) |
| 기능 | conntrack, NAT, 완전한 상태 추적 | 상태 없음, BPF map으로 구현 |
| 프로토콜 접근 | 파싱 완료된 sk_buff | raw 패킷 데이터 직접 파싱 |
| 적합 용도 | 일반 방화벽, NAT, stateful 필터링 | DDoS 방어, 고속 패킷 드롭/리다이렉트 |
nftables 하드웨어 오프로드
# flowtable 하드웨어 오프로드 (SmartNIC 필요)
nft add flowtable inet filter ft_hw \
'{ hook ingress priority 0; devices = { eth0 }; flags offload; }'
# 오프로드 상태 확인
nft list flowtable inet filter ft_hw
# table inet filter {
# flowtable ft_hw {
# hook ingress priority filter
# devices = { eth0 }
# flags offload
# }
# }
네트워크 네임스페이스와 Netfilter
각 네트워크 네임스페이스(netns)는 독립적인 Netfilter 규칙셋과 conntrack 테이블을 갖습니다. 컨테이너 환경에서 이는 격리와 동시에 복잡한 패킷 흐름 문제를 야기합니다.
Per-netns conntrack 테이블
# 네임스페이스별 독립적인 conntrack
ip netns exec ns1 conntrack -L # ns1의 conntrack 테이블
ip netns exec ns2 conntrack -L # ns2의 conntrack 테이블
# 각 네임스페이스에서 독립적인 방화벽 규칙
ip netns exec ns1 nft list ruleset
ip netns exec ns2 nft list ruleset
브릿지와 네임스페이스 간 패킷 흐름
컨테이너 환경 주의사항: Docker/Kubernetes는 호스트 netns에 iptables/nftables 규칙을 추가합니다. br_netfilter 모듈이 로드되면 브릿지된 L2 트래픽도 iptables 규칙을 통과하여 예상치 못한 패킷 드롭이 발생할 수 있습니다. sysctl net.bridge.bridge-nf-call-iptables=0으로 비활성화할 수 있으나 컨테이너 네트워킹에 영향을 줄 수 있습니다.
# veth 쌍을 통한 네임스페이스 간 통신에서 Netfilter 경로
# Host netns:
# veth-host → PREROUTING → 라우팅 → FORWARD → POSTROUTING → veth-ns
# Container netns:
# eth0(=veth-ns) → PREROUTING → INPUT → 로컬 프로세스
# br_netfilter 관련 설정
lsmod | grep br_netfilter
sysctl net.bridge.bridge-nf-call-iptables
sysctl net.bridge.bridge-nf-call-ip6tables
sysctl net.bridge.bridge-nf-call-arptables
Netfilter 디버깅
nftables trace
# 특정 패킷에 대한 nftables 규칙 추적
# 1단계: 추적 대상 마킹
nft add rule inet raw prerouting ip saddr 10.0.0.1 meta nftrace set 1
# 2단계: trace 모니터링
nft monitor trace
# trace id abcd1234 inet raw prerouting
# packet: iif "eth0" ip saddr 10.0.0.1 ip daddr 192.168.1.1 ...
# rule: ip saddr 10.0.0.1 meta nftrace set 1 (verdict continue)
# trace id abcd1234 inet filter input
# rule: tcp dport 22 accept (verdict accept)
conntrack 디버깅
# 실시간 conntrack 이벤트 모니터링
conntrack -E
conntrack -E -e NEW,DESTROY
# 특정 연결 상세 조회
conntrack -L -s 10.0.0.1 -d 93.184.216.34 -p tcp
conntrack -L --status ASSURED
# conntrack 통계
conntrack -S
# cpu=0 found=... invalid=... insert=... insert_failed=... drop=...
# ↑ drop/insert_failed 증가 시 nf_conntrack_max 초과 의심
# /proc 인터페이스
cat /proc/net/nf_conntrack # 전체 conntrack 테이블
cat /proc/sys/net/netfilter/nf_conntrack_count # 현재 엔트리 수
cat /proc/sys/net/netfilter/nf_conntrack_max # 최대 엔트리 수
iptables LOG / NFLOG 활용
# nftables log로 패킷 내용 기록
nft add rule inet filter input tcp dport 80 \
log prefix "HTTP_IN: " level info
nft add rule inet filter forward ct state invalid \
log prefix "INVALID_FWD: " group 3 drop
# 로그 확인
dmesg | grep "HTTP_IN:"
journalctl -k | grep "INVALID_FWD:"
BPF 기반 Netfilter 추적
# bpftrace로 nf_hook_slow 추적
bpftrace -e 'kprobe:nf_hook_slow {
printf("hook=%d pf=%d in=%s\n",
arg2, arg1, str(((struct net_device *)arg4)->name));
}'
# nft 규칙 평가 추적
bpftrace -e 'kprobe:nft_do_chain {
printf("chain eval: %s\n", str(((struct nft_chain *)arg1)->name));
}'
# conntrack 새 연결 추적
bpftrace -e 'kprobe:__nf_conntrack_confirm {
printf("conntrack confirm: skb=%p\n", arg0);
}'
주요 /proc, /sys 파일
| 경로 | 설명 |
|---|---|
/proc/net/nf_conntrack | conntrack 엔트리 전체 목록 |
/proc/sys/net/netfilter/nf_conntrack_max | 최대 연결 추적 수 |
/proc/sys/net/netfilter/nf_conntrack_count | 현재 추적 중인 연결 수 |
/proc/sys/net/netfilter/nf_conntrack_buckets | 해시 테이블 버킷 수 |
/proc/sys/net/netfilter/nf_conntrack_tcp_* | TCP 프로토콜 타임아웃 |
/proc/sys/net/netfilter/nf_conntrack_udp_* | UDP 프로토콜 타임아웃 |
/proc/net/stat/nf_conntrack | per-CPU conntrack 통계 |
/proc/sys/net/bridge/bridge-nf-call-iptables | 브릿지 트래픽의 iptables 통과 여부 |
Netfilter 커널 모듈 작성
커스텀 훅 모듈 예제
/* nf_example.c — 커스텀 Netfilter 훅 모듈 */
#include <linux/module.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv4.h>
#include <linux/ip.h>
#include <linux/tcp.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Example");
MODULE_DESCRIPTION("Netfilter hook example");
static unsigned int my_hook_fn(
void *priv,
struct sk_buff *skb,
const struct nf_hook_state *state)
{
struct iphdr *iph;
struct tcphdr *tcph;
if (!skb)
return NF_ACCEPT;
iph = ip_hdr(skb);
if (iph->protocol != IPPROTO_TCP)
return NF_ACCEPT;
tcph = tcp_hdr(skb);
/* 포트 8888로의 SYN 패킷 차단 */
if (ntohs(tcph->dest) == 8888 && tcph->syn && !tcph->ack) {
pr_info("Blocked SYN to port 8888 from %pI4\n",
&iph->saddr);
return NF_DROP;
}
return NF_ACCEPT;
}
static struct nf_hook_ops my_nfho = {
.hook = my_hook_fn,
.pf = NFPROTO_IPV4,
.hooknum = NF_INET_PRE_ROUTING,
.priority = NF_IP_PRI_FIRST,
};
static int __init my_init(void)
{
int ret = nf_register_net_hook(&init_net, &my_nfho);
if (ret < 0) {
pr_err("nf_register_net_hook failed: %d\n", ret);
return ret;
}
pr_info("Netfilter hook registered\n");
return 0;
}
static void __exit my_exit(void)
{
nf_unregister_net_hook(&init_net, &my_nfho);
pr_info("Netfilter hook unregistered\n");
}
module_init(my_init);
module_exit(my_exit);
nftables 커스텀 표현식 모듈
/* nft_example_expr.c — nftables 커스텀 표현식 모듈 (골격) */
#include <linux/module.h>
#include <net/netfilter/nf_tables.h>
struct nft_example_priv {
u32 threshold;
atomic_t counter;
};
static void nft_example_eval(
const struct nft_expr *expr,
struct nft_regs *regs,
const struct nft_pktinfo *pkt)
{
struct nft_example_priv *priv = nft_expr_priv(expr);
if (atomic_inc_return(&priv->counter) > priv->threshold) {
/* 임계값 초과 시 DROP verdict */
regs->verdict.code = NF_DROP;
}
}
static const struct nft_expr_ops nft_example_ops = {
.eval = nft_example_eval,
.size = NFT_EXPR_SIZE(sizeof(struct nft_example_priv)),
/* .init, .destroy, .dump 등 구현 필요 */
};
static struct nft_expr_type nft_example_type = {
.name = "example",
.ops = &nft_example_ops,
.owner = THIS_MODULE,
};
static int __init nft_example_init(void)
{
return nft_register_expr(&nft_example_type);
}
static void __exit nft_example_exit(void)
{
nft_unregister_expr(&nft_example_type);
}
module_init(nft_example_init);
module_exit(nft_example_exit);
MODULE_LICENSE("GPL");
conntrack helper 모듈 작성
/* nf_conntrack_myproto.c — 커스텀 conntrack helper 골격 */
#include <linux/module.h>
#include <net/netfilter/nf_conntrack.h>
#include <net/netfilter/nf_conntrack_helper.h>
#include <net/netfilter/nf_conntrack_expect.h>
#define MYPROTO_PORT 9999
static int myproto_help(
struct sk_buff *skb,
unsigned int protoff,
struct nf_conn *ct,
enum ip_conntrack_info ctinfo)
{
/* 페이로드에서 데이터 포트 추출 */
u16 data_port;
/* ... 프로토콜 파싱 ... */
/* 관련 연결에 대한 expectation 생성 */
struct nf_conntrack_expect *exp;
exp = nf_ct_expect_alloc(ct);
if (!exp)
return NF_ACCEPT;
nf_ct_expect_init(exp, NF_CT_EXPECT_CLASS_DEFAULT,
nf_ct_l3num(ct),
&ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.u3,
&ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.u3,
IPPROTO_TCP, NULL, &data_port);
nf_ct_expect_related(exp);
nf_ct_expect_put(exp);
return NF_ACCEPT;
}
static struct nf_conntrack_expect_policy myproto_exp_policy = {
.max_expected = 1,
.timeout = 5 * 60,
};
static struct nf_conntrack_helper myproto_helper = {
.name = "myproto",
.me = THIS_MODULE,
.help = myproto_help,
.expect_policy = &myproto_exp_policy,
.tuple.src.l3num = NFPROTO_IPV4,
.tuple.src.u.tcp.port = cpu_to_be16(MYPROTO_PORT),
.tuple.dst.protonum = IPPROTO_TCP,
};
static int __init myproto_init(void)
{
return nf_conntrack_helper_register(&myproto_helper);
}
static void __exit myproto_exit(void)
{
nf_conntrack_helper_unregister(&myproto_helper);
}
module_init(myproto_init);
module_exit(myproto_exit);
MODULE_LICENSE("GPL");
Netfilter 주요 보안 취약점 사례
Netfilter/nf_tables는 커널 네트워크 스택의 핵심 구성 요소로, 권한 상승 취약점의 주요 공격 대상이 되어왔습니다. 특히 nf_tables의 복잡한 규칙 관리와 트랜잭션 처리 과정에서 메모리 안전성 문제가 반복적으로 발견되고 있습니다.
nf_tables Use-After-Free (CVE-2024-1086)
nft_verdict_init()에서 NF_DROP verdict에 양수 errno 값을 허용하여, nf_hook_slow()가 Drop 대신 Accept로 처리합니다. 이미 해제 예정인 skb가 Accept 경로로 계속 진행하면서 이중 해제(double-free)가 발생합니다. Linux 3.15~6.8에 영향을 미치며, 비특권 user namespace에서 nftables 규칙 생성 권한만으로 트리거 가능합니다.
/* CVE-2024-1086 공격 흐름 */
/* 1. nft_verdict_init()에서 NF_DROP + 양수 errno 설정 */
/* NF_DROP(0) | (NF_ACCEPT << 16) → nf_hook_slow()에서 NF_ACCEPT로 해석 */
/* net/netfilter/nf_tables_api.c — 취약 코드 */
switch (data->verdict.code) {
case NF_DROP:
/* BUG: NF_VERDICT_MASK 비트에 양수 값(NF_ACCEPT)을 허용
* → nf_hook_slow()가 verdict를 NF_ACCEPT로 오인 */
break;
}
/* net/netfilter/core.c — nf_hook_slow() */
int nf_hook_slow(...) {
verdict = nf_hook_entry_hookfn(&state, e);
switch (verdict & NF_VERDICT_MASK) {
case NF_ACCEPT:
/* skb가 이미 NF_DROP으로 처리(해제 예정)되었지만
* Accept 경로로 진행 → 이후 다시 해제 시도 → UAF/double-free */
break;
}
}
/* 수정: NF_DROP의 errno를 음수 값만 허용 (commit f342de4...) */
case NF_DROP:
if (data->verdict.code & NF_VERDICT_MASK)
return -EINVAL; /* 양수 errno 거부 */
break;
nf_tables Batch Request UAF (CVE-2023-32233)
nf_tables의 batch 요청 처리에서 익명(anonymous) set을 같은 batch 내에서 생성하고 삭제할 때, 트랜잭션 커밋 과정에서 이미 해제된 set에 대한 참조가 남아 Use-After-Free가 발생합니다. Linux 3.18~6.3에 영향을 미칩니다.
/* CVE-2023-32233: 익명 set 생명주기 결함 */
/*
* nf_tables batch 처리에서의 경쟁:
* 1. NFT_MSG_NEWSET (익명 set 생성) — set이 트랜잭션 목록에 추가
* 2. NFT_MSG_DELSET (같은 batch에서 삭제) — set이 삭제 표시
* 3. 커밋 시: 삭제가 먼저 처리되어 set 해제
* 4. 생성 측에서 해제된 set을 규칙에 바인딩 시도 → UAF
*
* 수정: 익명 set의 batch 내 생명주기 추적을 엄격하게 관리
* 같은 batch에서 생성/삭제 시 양쪽 모두 no-op으로 처리
*/
Conntrack 관련 버그 패턴
conntrack은 멀티코어 환경에서 해시 테이블을 동시에 접근하므로, 경쟁 조건에 취약합니다:
RCU 보호 누락: conntrack 엔트리 순회 중 다른 CPU에서 삭제 시 UAF 발생. nf_conntrack_find_get()에서 RCU read lock과 refcount 확인이 원자적이지 않으면 해제된 엔트리에 접근 가능
Helper 등록/해제 경쟁: conntrack helper 모듈 언로드 시 활성 연결의 helper 참조가 무효화. nf_conntrack_helper_unregister()에서 모든 활성 conntrack을 순회하여 helper 참조를 제거해야 함
해시 테이블 리사이즈: nf_conntrack_max 변경 시 해시 테이블 교체 과정에서의 동시 접근 → RCU 기반 교체로 완화
/* conntrack 해시 테이블 경쟁 조건 방어 패턴 */
/* 안전한 conntrack 순회 — RCU read lock 필수 */
rcu_read_lock();
for (i = 0; i < nf_conntrack_htable_size; i++) {
struct nf_conntrack_tuple_hash *h;
struct hlist_nulls_node *n;
hlist_nulls_for_each_entry_rcu(h, n,
&nf_conntrack_hash[i], hnnode) {
struct nf_conn *ct = nf_ct_tuplehash_to_ctrack(h);
/* refcount를 먼저 증가시켜 해제 방지 */
if (!refcount_inc_not_zero(&ct->ct_general.use))
continue; /* 이미 해제 중 — 건너뜀 */
/* 안전하게 ct 사용 */
...
nf_ct_put(ct); /* refcount 감소 */
}
}
rcu_read_unlock();
/* conntrack 최대 항목 수 조정 시 주의사항 */
/* nf_conntrack_max 변경은 해시 테이블 리사이즈를 유발
* RCU grace period 대기 후 이전 테이블 해제 */
sysctl -w net.netfilter.nf_conntrack_max=262144
1. nf_tables 권한 제한: net.core.bpf_jit_harden=2 및 user namespace 제한으로 비특권 사용자의 nf_tables 접근 차단
2. conntrack 모니터링: conntrack -E로 실시간 이벤트 감시, 비정상적 conntrack 생성 패턴 탐지
3. 커널 업데이트: nf_tables 취약점은 2023~2024년에 집중 발견되었으며, 6.8 이후 커널에서 주요 수정이 적용됨
4. seccomp 프로파일: 컨테이너 환경에서 NFT_* 관련 시스템 콜을 차단하여 공격 면적 축소
ipset과 nftables Sets/Maps 심화
대규모 방화벽 규칙셋에서 개별 규칙을 반복 나열하면 O(n) 선형 탐색이 발생합니다. ipset(레거시)과 nftables sets/maps는 해시 또는 트리 기반 자료구조로 O(1)~O(log n) 조회를 제공하여, 수만 개 IP/포트를 효율적으로 관리합니다.
ipset 개요와 내부 구조
ipset은 iptables와 함께 사용하는 커널 모듈(ip_set)로, 다양한 데이터 타입의 집합을 커널 메모리에 유지합니다. iptables 규칙에서 -m set --match-set으로 참조합니다.
/* include/linux/netfilter/ipset/ip_set.h */
struct ip_set {
char name[IPSET_MAXNAMELEN];
spinlock_t lock;
u32 ref; /* 참조 카운트 */
u32 ref_netlink;
struct ip_set_type_variant *variant; /* hash/bitmap/list 구현 */
struct ip_set_type *type;
u8 family; /* NFPROTO_IPV4/IPV6 */
u8 revision;
void *data; /* 타입별 데이터 */
struct net *net;
struct list_head list;
size_t ext_size;
unsigned long timeout; /* 기본 타임아웃 */
};
/* ipset 타입별 연산 인터페이스 */
struct ip_set_type_variant {
int (*kadt)(struct ip_set *set, const struct sk_buff *skb,
const struct xt_action_param *par,
enum ipset_adt adt, struct ip_set_adt_opt *opt);
int (*uadt)(struct ip_set *set, struct nlattr *tb[],
enum ipset_adt adt, u32 *lineno, u32 flags, bool retried);
bool (*test)(struct ip_set *set, void *value,
const struct ip_set_ext *ext, struct ip_set_ext *mext,
u32 flags);
};
| ipset 타입 | 저장 구조 | 용도 | 조회 복잡도 |
|---|---|---|---|
hash:ip | 해시 테이블 | IP 주소 집합 | O(1) |
hash:net | 해시 + CIDR | 서브넷 집합 | O(1) |
hash:ip,port | 해시 테이블 | IP+포트 조합 | O(1) |
hash:ip,port,net | 해시 테이블 | 소스IP+포트+목적지넷 | O(1) |
hash:net,iface | 해시 테이블 | 서브넷+인터페이스 | O(1) |
bitmap:ip | 비트맵 | 연속 IP 범위 (/16 이하) | O(1) |
bitmap:port | 비트맵 | 포트 범위 | O(1) |
list:set | 링크드 리스트 | set의 set (메타 세트) | O(n) |
# ipset 생성 및 사용 예제
# IP 차단 리스트 생성 (자동 만료 포함)
ipset create blacklist hash:ip hashsize 65536 maxelem 1000000 timeout 3600
# 엔트리 추가
ipset add blacklist 192.168.1.100
ipset add blacklist 10.0.0.0/8 timeout 7200 # 개별 타임아웃
# iptables에서 참조
iptables -A INPUT -m set --match-set blacklist src -j DROP
# IP+포트 조합 set
ipset create allowed_services hash:ip,port
ipset add allowed_services 10.0.1.0/24,tcp:80
ipset add allowed_services 10.0.1.0/24,tcp:443
iptables -A INPUT -m set --match-set allowed_services src,dst -j ACCEPT
# 카운터 및 코멘트 확장
ipset create monitored hash:ip counters comment
ipset add monitored 10.0.0.1 comment "web-server"
ipset list monitored # 패킷/바이트 카운터 포함 출력
# set 저장/복원 (재부팅 시 보존)
ipset save > /etc/ipset.conf
ipset restore < /etc/ipset.conf
nftables Sets 심화
nftables는 set을 규칙셋의 일급 객체로 취급합니다. ipset과 달리 별도 도구 없이 nft 명령어로 직접 관리하며, 다양한 데이터 타입과 정책을 지원합니다.
/* net/netfilter/nf_tables_api.c — nft set 내부 구조 */
struct nft_set {
struct list_head list;
struct list_head bindings; /* 이 set을 참조하는 규칙들 */
struct nft_table *table;
const struct nft_set_ops *ops; /* hash/rbtree/bitmap 구현 */
u16 flags; /* NFT_SET_* 플래그 */
u64 timeout; /* 기본 타임아웃 (ns) */
u32 gc_int; /* GC 주기 (ms) */
u32 size; /* 최대 엘리먼트 수 */
char *name;
/* ... */
};
/* set 구현체 선택 — 플래그에 따라 자동 결정 */
/* NFT_SET_INTERVAL → rbtree, 그 외 → hash
* NFT_SET_CONCAT → pipapo (Pile Packet Policies) 알고리즘 */
# === 명명된(named) set ===
# IPv4 주소 set (자동 GC 타임아웃)
nft add set inet filter blacklist \
'{ type ipv4_addr; flags timeout; timeout 1h; gc-interval 5m; size 100000; }'
# 엘리먼트 추가 (개별 타임아웃 가능)
nft add element inet filter blacklist '{ 10.0.0.1 timeout 30m, 10.0.0.2 }'
# 규칙에서 참조
nft add rule inet filter input ip saddr @blacklist counter drop
# === 인터벌 set (CIDR/범위 지원) ===
nft add set inet filter internal_nets \
'{ type ipv4_addr; flags interval; }'
nft add element inet filter internal_nets \
'{ 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 }'
# === 연결(concatenation) set ===
# IP + 포트 조합 매칭 (pipapo 알고리즘 사용)
nft add set inet filter svc_whitelist \
'{ type ipv4_addr . inet_service; flags interval; }'
nft add element inet filter svc_whitelist \
'{ 10.0.0.0/24 . 80, 10.0.0.0/24 . 443, 172.16.0.0/16 . 22 }'
nft add rule inet filter input ip saddr . tcp dport @svc_whitelist accept
# === 익명(anonymous) set — 인라인 ===
nft add rule inet filter input tcp dport { 22, 80, 443, 8080 } accept
# set 통계 확인
nft list set inet filter blacklist
nftables Maps과 Verdict Maps
Map은 키-값 매핑으로, 하나의 규칙에서 다중 분기를 처리합니다. Verdict map(vmap)은 값이 verdict(accept/drop/jump 등)인 특수 map입니다.
# === 일반 map: 데이터 변환 ===
# 포트 번호 → 마크 값 매핑
nft add map inet filter port2mark '{ type inet_service : mark; }'
nft add element inet filter port2mark '{ 80 : 0x1, 443 : 0x2, 22 : 0x3 }'
nft add rule inet filter prerouting tcp dport map @port2mark meta mark set
# === verdict map: 다중 분기 ===
nft add map inet filter port_policy '{ type inet_service : verdict; }'
nft add element inet filter port_policy \
'{ 22 : jump ssh_chain, 80 : accept, 443 : accept, 25 : drop }'
nft add rule inet filter input tcp dport vmap @port_policy
# === 인터페이스별 verdict map ===
nft add map inet filter iface_policy '{ type ifname : verdict; }'
nft add element inet filter iface_policy \
'{ "eth0" : jump wan_filter, "eth1" : jump lan_filter, "lo" : accept }'
nft add rule inet filter input iifname vmap @iface_policy
# === 동적 set (meter) — 실시간 추적 ===
# 소스 IP별 연결 속도 제한
nft add rule inet filter input \
tcp dport 22 \
meter ssh_meter '{ ip saddr limit rate 3/minute burst 5 packets }' \
accept
# meter 내용 확인
nft list meter inet filter ssh_meter
ipset vs nftables set 마이그레이션: nftables set은 ipset의 상위 호환입니다. hash:ip → type ipv4_addr, hash:net → type ipv4_addr; flags interval, hash:ip,port → type ipv4_addr . inet_service로 대응됩니다. nftables set은 별도 커널 모듈 없이 nf_tables 내장 기능으로 동작합니다.
ebtables와 브릿지 방화벽
ebtables는 이더넷 프레임 수준(L2)에서 필터링하는 Netfilter 프레임워크 확장입니다. 브릿지 인터페이스를 통과하는 트래픽을 MAC 주소, VLAN 태그, 이더넷 프로토콜 기반으로 필터링합니다. 현대 시스템에서는 nftables bridge family가 ebtables를 대체합니다.
브릿지 Netfilter 아키텍처
브릿지 환경에서 패킷은 L2 포워딩 경로를 따르지만, br_netfilter 모듈이 활성화되면 L3(iptables/nftables inet) 규칙도 브릿지 트래픽에 적용됩니다.
/* net/bridge/br_netfilter_hooks.c — 브릿지 Netfilter 훅 */
/* 브릿지 훅 포인트 (L2 레벨) */
enum nf_br_hook_priorities {
NF_BR_PRI_FIRST = INT_MIN,
NF_BR_PRI_NAT_DST_BRIDGED = -300,
NF_BR_PRI_FILTER_BRIDGED = -200,
NF_BR_PRI_BRNF = 0,
NF_BR_PRI_NAT_DST_OTHER = 100,
NF_BR_PRI_FILTER_OTHER = 200,
NF_BR_PRI_NAT_SRC = 300,
NF_BR_PRI_LAST = INT_MAX,
};
/* br_netfilter는 브릿지 프레임을 "가짜 IP 패킷"으로 위장하여
* iptables/nftables inet 규칙 체인을 통과시킴 */
static unsigned int br_nf_pre_routing(
void *priv,
struct sk_buff *skb,
const struct nf_hook_state *state)
{
/* L2 브릿지 프레임을 NF_INET_PRE_ROUTING 훅으로 라우팅 */
if (br_validate_ipv4(net, skb))
return NF_DROP;
/* nf_bridge_info에 원본 L2 정보 저장 */
nf_bridge = nf_bridge_alloc(skb);
nf_bridge->physindev = skb->dev;
return NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING,
net, sk, skb, skb->dev, NULL,
br_nf_pre_routing_finish);
}
ebtables 규칙 (레거시)
# ebtables 기본 사용법
# MAC 주소 기반 필터링
ebtables -A FORWARD -s 00:11:22:33:44:55 -j DROP
ebtables -A FORWARD -d ff:ff:ff:ff:ff:ff -j DROP # 브로드캐스트 차단
# VLAN 태그 필터링
ebtables -A FORWARD -p 802_1Q --vlan-id 100 -j ACCEPT
ebtables -A FORWARD -p 802_1Q -j DROP
# ARP 스푸핑 방지
ebtables -A FORWARD -p ARP --arp-ip-src 10.0.0.1 \
--arp-mac-src ! 00:11:22:33:44:55 -j DROP
# 프로토콜 필터링 (IPv4/IPv6만 허용)
ebtables -A FORWARD -p IPv4 -j ACCEPT
ebtables -A FORWARD -p IPv6 -j ACCEPT
ebtables -A FORWARD -p ARP -j ACCEPT
ebtables -A FORWARD -j DROP
# 브릿지 인터페이스별 필터링
ebtables -A FORWARD -i eth0 -o eth1 -j ACCEPT
ebtables -A FORWARD -i eth1 -o eth0 -j ACCEPT
ebtables -A FORWARD -j DROP
nftables bridge family
nftables의 bridge family는 ebtables의 현대적 대체제로, 동일한 nft 문법으로 L2 필터링을 수행합니다.
# nftables bridge family 예제
# 브릿지 테이블 생성
nft add table bridge filter
# 체인 생성
nft add chain bridge filter forward \
'{ type filter hook forward priority filter; policy accept; }'
# MAC 주소 필터링
nft add rule bridge filter forward ether saddr 00:11:22:33:44:55 drop
# VLAN 필터링
nft add rule bridge filter forward vlan id 100 accept
nft add rule bridge filter forward vlan id != 0 drop
# ARP 제한 (ARP storm 방지)
nft add rule bridge filter forward arp operation request \
limit rate 100/second burst 50 packets accept
nft add rule bridge filter forward arp operation request drop
# STP BPDU 보호 (허가되지 않은 포트에서 BPDU 차단)
nft add rule bridge filter forward ether daddr 01:80:c2:00:00:00 \
meta ibrname "br0" meta iifname "eth2" drop
# MAC+IP 바인딩 (IP 스푸핑 방지)
nft add set bridge filter mac_ip_bind \
'{ type ether_addr . ipv4_addr; }'
nft add element bridge filter mac_ip_bind \
'{ 00:11:22:33:44:55 . 10.0.0.1, aa:bb:cc:dd:ee:ff . 10.0.0.2 }'
nft add rule bridge filter forward ether saddr . ip saddr != @mac_ip_bind drop
# 브릿지 규칙셋 확인
nft list table bridge filter
br_netfilter와 nftables bridge 충돌: br_netfilter가 활성화되면 브릿지 트래픽이 inet family 훅도 통과합니다. nftables bridge family와 inet family 규칙이 동시에 적용되어 이중 필터링이 발생할 수 있습니다. 컨테이너 환경이 아니라면 sysctl net.bridge.bridge-nf-call-iptables=0으로 비활성화하세요.
TPROXY (투명 프록시)
TPROXY(Transparent Proxy)는 클라이언트의 원본 목적지 주소를 변경하지 않고 패킷을 로컬 프록시 프로세스로 리다이렉트하는 Netfilter 타겟입니다. 일반 REDIRECT와 달리 원본 목적지 IP/포트가 보존되어, 프록시가 원래 목적지를 알 수 있습니다.
TPROXY vs REDIRECT 비교
| 항목 | REDIRECT | TPROXY |
|---|---|---|
| NAT 테이블 | 사용 (DNAT 수행) | 사용 안 함 (mangle) |
| conntrack | 필요 (NAT 엔트리 생성) | 불필요 (NOTRACK 가능) |
| 원본 목적지 | 소실 (SO_ORIGINAL_DST로 복구) | 보존 (getsockname()으로 확인) |
| 성능 | conntrack 오버헤드 | 경량 (IP_TRANSPARENT 소켓) |
| 포워딩 트래픽 | 로컬 바운드만 | 포워딩 트래픽 가로채기 가능 |
| 지원 프로토콜 | TCP, UDP | TCP, UDP |
TPROXY 커널 내부
/* net/netfilter/xt_TPROXY.c — TPROXY 타겟 구현 */
/* TPROXY는 mangle/PREROUTING 체인에서 동작:
* 1. 패킷의 목적지와 일치하는 리스닝 소켓을 검색
* 2. IP_TRANSPARENT가 설정된 소켓이 발견되면
* 3. skb->sk를 해당 소켓으로 할당
* 4. skb->mark를 설정하여 라우팅 정책과 연동 */
static unsigned int
tproxy_tg4(struct sk_buff *skb,
const struct xt_action_param *par)
{
const struct xt_tproxy_target_info_v1 *tgi = par->targinfo;
struct sock *sk;
/* IP_TRANSPARENT 리스닝 소켓 검색 */
sk = nf_tproxy_get_sock_v4(
dev_net(skb->dev), skb,
iph->protocol,
iph->saddr,
tgi->laddr.ip ? tgi->laddr.ip : iph->daddr,
hp->source,
tgi->lport ? tgi->lport : hp->dest,
par->in, NF_TPROXY_LOOKUP_LISTENER);
if (sk) {
/* 패킷을 프록시 소켓에 할당 */
nf_tproxy_assign_sock(skb, sk);
skb->mark = (skb->mark & ~tgi->mark_mask) ^ tgi->mark_value;
return NF_ACCEPT;
}
return NF_DROP;
}
TPROXY 설정 가이드
# === 1단계: 정책 라우팅 설정 ===
# TPROXY 마크된 패킷을 로컬로 라우팅
ip rule add fwmark 0x1/0x1 lookup 100
ip route add local 0.0.0.0/0 dev lo table 100
# === 2단계: iptables TPROXY 규칙 ===
# HTTP 트래픽을 로컬 프록시 (포트 3128)로 리다이렉트
iptables -t mangle -A PREROUTING -p tcp --dport 80 \
-j TPROXY --tproxy-mark 0x1/0x1 --on-port 3128
# HTTPS 트래픽도 가로채기
iptables -t mangle -A PREROUTING -p tcp --dport 443 \
-j TPROXY --tproxy-mark 0x1/0x1 --on-port 3129
# === nftables 버전 ===
nft add table ip mangle
nft add chain ip mangle prerouting \
'{ type filter hook prerouting priority mangle; }'
nft add rule ip mangle prerouting tcp dport 80 \
tproxy to :3128 meta mark set 0x1
# === 3단계: 프록시 애플리케이션 소켓 설정 ===
# 프록시 프로세스는 IP_TRANSPARENT 소켓 옵션이 필요
# (CAP_NET_ADMIN 또는 CAP_NET_RAW 권한 필요)
/* 프록시 애플리케이션의 IP_TRANSPARENT 소켓 설정 */
int fd = socket(AF_INET, SOCK_STREAM, 0);
/* IP_TRANSPARENT: 로컬에 바인딩되지 않은 주소로도 수신 가능 */
int one = 1;
setsockopt(fd, SOL_IP, IP_TRANSPARENT, &one, sizeof(one));
/* 프록시 주소에 바인딩 */
struct sockaddr_in addr = {
.sin_family = AF_INET,
.sin_port = htons(3128),
.sin_addr = { .s_addr = INADDR_ANY },
};
bind(fd, (struct sockaddr *)&addr, sizeof(addr));
listen(fd, SOMAXCONN);
/* accept() 후 getsockname()으로 원본 목적지 확인 */
int client_fd = accept(fd, ...);
struct sockaddr_in orig_dst;
socklen_t len = sizeof(orig_dst);
getsockname(client_fd, (struct sockaddr *)&orig_dst, &len);
/* orig_dst에 클라이언트가 원래 접속하려던 목적지 IP/포트 */
TPROXY 사용 사례: Squid/HAProxy 투명 프록시, Envoy/Istio 서비스 메시 사이드카, 네트워크 모니터링/IDS/IPS 인라인 배치, SSL/TLS 검사 장비 등에서 널리 사용됩니다. conntrack이 불필요하므로 고성능 프록시 환경에 적합합니다.
DDoS 방어 패턴
Netfilter/nftables를 활용한 DDoS(Distributed Denial of Service) 방어는 커널 레벨에서 악성 트래픽을 조기에 차단하여 애플리케이션 부하를 줄입니다. 대규모 공격은 XDP/BPF 조합이 효과적이지만, 일반적인 규모의 공격은 nftables만으로도 효과적으로 방어할 수 있습니다.
SYN Flood 방어
# === 커널 SYN Cookie 활성화 (필수) ===
sysctl -w net.ipv4.tcp_syncookies=1
sysctl -w net.ipv4.tcp_max_syn_backlog=65536
# === nftables SYN 속도 제한 ===
nft add table inet ddos_defense
nft add chain inet ddos_defense input \
'{ type filter hook input priority filter - 10; policy accept; }'
# 소스 IP별 SYN 속도 제한
nft add rule inet ddos_defense input \
tcp flags syn \
ct state new \
meter syn_meter '{ ip saddr limit rate over 30/second burst 50 packets }' \
drop
# 전체 SYN 속도 제한 (모든 소스 합산)
nft add rule inet ddos_defense input \
tcp flags syn \
ct state new \
limit rate over 10000/second \
drop
# 비정상 TCP 플래그 조합 차단
nft add rule inet ddos_defense input tcp flags '& (fin|syn|rst|psh|ack|urg) == fin|syn' drop
nft add rule inet ddos_defense input tcp flags '& (fin|syn|rst|psh|ack|urg) == syn|rst' drop
nft add rule inet ddos_defense input tcp flags '& (fin|syn|rst|psh|ack|urg) == fin|rst' drop
nft add rule inet ddos_defense input tcp flags '& (fin|syn|rst|psh|ack|urg) == 0x0' drop
연결 수 제한
# 소스 IP당 동시 연결 수 제한
nft add rule inet ddos_defense input \
tcp dport { 80, 443 } \
ct state new \
meter conn_limit '{ ip saddr ct count over 100 }' \
reject with tcp reset
# 서브넷 단위 제한 (/24)
nft add rule inet ddos_defense input \
tcp dport { 80, 443 } \
ct state new \
meter subnet_limit '{ ip saddr & 255.255.255.0 ct count over 500 }' \
drop
UDP Flood / DNS Amplification 방어
# UDP flood 속도 제한
nft add rule inet ddos_defense input \
udp \
meter udp_meter '{ ip saddr limit rate over 100/second burst 200 packets }' \
drop
# DNS amplification 방어 (소스 포트 53 대량 유입 차단)
nft add rule inet ddos_defense input \
udp sport 53 \
limit rate over 1000/second \
drop
# NTP amplification 방어 (monlist 응답)
nft add rule inet ddos_defense input \
udp sport 123 \
udp length > 100 \
limit rate over 500/second \
drop
# 소스 포트 0 차단 (스푸핑 표시)
nft add rule inet ddos_defense input udp sport 0 drop
nft add rule inet ddos_defense input tcp sport 0 drop
ICMP Flood 방어
# ICMP echo-request 속도 제한
nft add rule inet ddos_defense input \
icmp type echo-request \
limit rate 10/second burst 20 packets \
accept
nft add rule inet ddos_defense input \
icmp type echo-request \
drop
# ICMPv6 필수 타입 허용, 나머지 제한
nft add rule inet ddos_defense input \
icmpv6 type { nd-neighbor-solicit, nd-neighbor-advert, \
nd-router-solicit, nd-router-advert } \
accept
nft add rule inet ddos_defense input \
icmpv6 type echo-request \
limit rate 10/second burst 20 packets \
accept
# Smurf 공격 방어 (브로드캐스트 ICMP 차단)
sysctl -w net.ipv4.icmp_echo_ignore_broadcasts=1
hashlimit을 이용한 정밀 제한
# iptables hashlimit — 소스 IP + 목적지 포트 조합별 제한
iptables -A INPUT -p tcp --dport 80 \
-m hashlimit \
--hashlimit-above 50/sec \
--hashlimit-burst 100 \
--hashlimit-mode srcip,dstport \
--hashlimit-name http_limit \
--hashlimit-htable-expire 60000 \
-j DROP
# nftables meter로 동일 기능
nft add rule inet ddos_defense input \
tcp dport 80 \
meter http_limit '{ ip saddr . tcp dport limit rate over 50/second burst 100 packets }' \
drop
동적 차단 (자동 블랙홀)
# nftables set + meter 조합으로 자동 블랙리스팅
# 차단 리스트 set (5분 자동 만료)
nft add set inet ddos_defense auto_blacklist \
'{ type ipv4_addr; flags timeout; timeout 5m; }'
# 블랙리스트에 있으면 즉시 차단
nft add rule inet ddos_defense input \
ip saddr @auto_blacklist drop
# SYN 임계치 초과 시 자동으로 블랙리스트에 추가
nft add rule inet ddos_defense input \
tcp flags syn \
ct state new \
meter syn_detect '{ ip saddr limit rate over 50/second burst 100 packets }' \
add @auto_blacklist '{ ip saddr }' \
drop
# 현재 블랙리스트 확인
nft list set inet ddos_defense auto_blacklist
DDoS 방어 계층화 전략: Netfilter만으로는 대규모 DDoS(수십 Gbps 이상)를 방어하기 어렵습니다. 효과적인 방어를 위해 XDP(드라이버 레벨 드롭, ~24Mpps), nftables(stateful 필터링), 애플리케이션 레벨 WAF, 외부 DDoS 방어 서비스(Cloudflare, AWS Shield 등)를 계층적으로 조합하세요. XDP에 대한 상세 내용은 BPF/XDP 페이지를 참조하세요.
conntrack 고급 기능
Connection Tracking은 단순한 연결 상태 추적을 넘어 트래픽 통계, 라벨링, 관련 연결 예측(expectation) 등 고급 기능을 제공합니다. 이 섹션에서는 성능 모니터링과 정밀한 정책 수립에 활용되는 conntrack 고급 기능을 다룹니다.
conntrack Accounting (통계)
conntrack accounting은 각 연결에 대해 양방향 패킷/바이트 카운터를 유지합니다. 네트워크 모니터링, 과금, 트래픽 분석에 활용됩니다.
# conntrack accounting 활성화
sysctl -w net.netfilter.nf_conntrack_acct=1
# 또는 부팅 시 모듈 파라미터
modprobe nf_conntrack acct=1
# 통계 확인 (패킷/바이트 카운터 포함)
conntrack -L -o extended
# 출력 예:
# tcp 6 431995 ESTABLISHED src=10.0.0.1 dst=10.0.0.2 sport=45678 dport=80
# packets=1523 bytes=125840
# src=10.0.0.2 dst=10.0.0.1 sport=80 dport=45678
# packets=2105 bytes=3145728 [ASSURED] mark=0 use=1
# nftables에서 conntrack 카운터 활용
# 특정 연결의 바이트 수 기반 규칙
nft add rule inet filter forward \
ct bytes > 1073741824 \
log prefix '"heavy-flow: "' \
counter
/* include/net/netfilter/nf_conntrack_acct.h */
struct nf_conn_acct {
struct nf_conn_counter counter[IP_CT_DIR_MAX]; /* 원본/응답 */
};
struct nf_conn_counter {
atomic64_t packets; /* 패킷 수 */
atomic64_t bytes; /* 바이트 수 */
};
/* conntrack 확장으로 등록 — nf_conn->ext에 저장 */
static struct nf_ct_ext_type acct_extend = {
.len = sizeof(struct nf_conn_acct),
.align = __alignof__(struct nf_conn_acct),
.id = NF_CT_EXT_ACCT,
};
/* 패킷 통과 시 카운터 업데이트 (nf_conntrack_core.c) */
static void nf_ct_acct_update(
struct nf_conn *ct,
enum ip_conntrack_info ctinfo,
unsigned int len)
{
struct nf_conn_acct *acct = nf_conn_acct_find(ct);
if (acct) {
struct nf_conn_counter *counter = acct->counter;
unsigned int dir = CTINFO2DIR(ctinfo);
atomic64_inc(&counter[dir].packets);
atomic64_add(len, &counter[dir].bytes);
}
}
conntrack Labels
conntrack label은 각 연결에 비트맵 라벨을 부여하여, 방화벽 규칙에서 특정 라벨이 설정된 연결을 매칭합니다. connmark(32비트 정수)보다 풍부한 메타데이터를 연결에 부여할 수 있습니다.
# connlabel 정의 파일
# /etc/xtables/connlabel.conf (또는 /etc/connlabel.conf)
# 비트 번호와 라벨명 매핑
# 0 allowed
# 1 denied
# 2 inspected
# 3 logged
# 4 rate_limited
# iptables에서 connlabel 사용
# 라벨 설정
iptables -A FORWARD -m conntrack --ctstate NEW \
-s 10.0.0.0/8 -j CONNLABEL --set allowed
# 라벨 매칭
iptables -A FORWARD -m connlabel --label allowed -j ACCEPT
# nftables에서 connlabel 사용
nft add rule inet filter forward ct state new \
ip saddr 10.0.0.0/8 ct label set "allowed"
nft add rule inet filter forward ct label "allowed" accept
# conntrack 이벤트에서 라벨 확인
conntrack -E -o label
conntrack Expectations (관련 연결 예측)
Expectation은 기존 연결(master)에서 파생될 새로운 연결(related)을 미리 예측하여, 방화벽이 동적 포트의 관련 연결을 자동으로 허용하는 메커니즘입니다. FTP, SIP, H.323 등 데이터 채널을 별도 연결로 사용하는 프로토콜에 필수적입니다.
/* include/net/netfilter/nf_conntrack_expect.h */
struct nf_conntrack_expect {
struct hlist_node hnode; /* 해시 리스트 */
struct nf_conntrack_tuple tuple; /* 예상 튜플 */
struct nf_conntrack_tuple mask; /* 튜플 마스크 */
struct nf_conntrack_tuple master; /* 마스터 연결 튜플 */
struct nf_conn *master_ct; /* 마스터 nf_conn */
struct timer_list timeout; /* 만료 타이머 */
refcount_t use;
unsigned int flags; /* NF_CT_EXPECT_* */
unsigned int class; /* helper 정의 클래스 */
void (*expectfn)(struct nf_conn *new,
struct nf_conntrack_expect *this);
struct nf_conntrack_helper *helper;
};
/* FTP helper 예제: PORT 명령 파싱 후 데이터 연결 expectation 생성 */
/* net/netfilter/nf_conntrack_ftp.c */
static int help(
struct sk_buff *skb,
unsigned int protoff,
struct nf_conn *ct,
enum ip_conntrack_info ctinfo)
{
/* 1. FTP 제어 채널에서 PORT/PASV 응답 파싱 */
/* 2. 데이터 연결의 IP:포트 추출 */
/* 3. expectation 생성 */
struct nf_conntrack_expect *exp;
exp = nf_ct_expect_alloc(ct);
nf_ct_expect_init(exp, NF_CT_EXPECT_CLASS_DEFAULT,
nf_ct_l3num(ct),
&ct->tuplehash[!dir].tuple.src.u3,
&cmd.u3,
IPPROTO_TCP, NULL, &cmd.u.tcp.port);
/* expectation 등록 → 새 데이터 연결이 RELATED로 분류됨 */
nf_ct_expect_related(exp);
nf_ct_expect_put(exp);
}
# conntrack expectation 관리
# 현재 expectation 목록
conntrack -L expect
# 출력 예:
# expect: proto=6 src=10.0.0.1 dst=10.0.0.2 sport=0 dport=20
# mask-src=255.255.255.255 mask-dst=255.255.255.255 sport=0 dport=65535
# master-src=10.0.0.1 master-dst=10.0.0.2 sport=45678 dport=21
# class=0 helper=ftp
# helper 모듈 로드 (자동 할당)
modprobe nf_conntrack_ftp
modprobe nf_conntrack_sip
modprobe nf_conntrack_tftp
# nftables에서 helper 명시적 할당
nft add ct helper inet filter ftp-helper \
'{ type "ftp" protocol tcp; }'
nft add rule inet filter input \
tcp dport 21 ct state new \
ct helper set "ftp-helper"
# expectation 최대 수 조정
sysctl -w net.netfilter.nf_conntrack_expect_max=1024
# 특정 helper의 expectation 이벤트 모니터링
conntrack -E expect
ctnetlink (커널-유저스페이스 동기화)
ctnetlink는 Netlink 기반으로 conntrack 테이블을 유저스페이스와 동기화하는 인터페이스입니다. HA(High Availability) 구성에서 방화벽 페일오버 시 conntrack 상태를 동기화하는 데 핵심적입니다.
# conntrackd — conntrack 동기화 데몬
# 기본 구성 (/etc/conntrackd/conntrackd.conf)
# Primary/Backup 모드: VRRP/keepalived와 연동
# 실시간 conntrack 이벤트 스트림
conntrack -E
# [NEW] tcp 6 120 SYN_SENT src=10.0.0.1 dst=10.0.0.2 sport=45678 dport=80
# [UPDATE] tcp 6 60 SYN_RECV src=10.0.0.1 dst=10.0.0.2 sport=45678 dport=80
# [UPDATE] tcp 6 432000 ESTABLISHED src=10.0.0.1 dst=10.0.0.2 ...
# [DESTROY] tcp 6 src=10.0.0.1 dst=10.0.0.2 sport=45678 dport=80
# 특정 프로토콜/상태 이벤트만 필터링
conntrack -E -p tcp --state ESTABLISHED
conntrack -E -p udp --event-mask NEW,DESTROY
# conntrack 엔트리 수동 추가 (페일오버 동기화 등)
conntrack -I --protonum 6 --timeout 120 \
--src 10.0.0.1 --dst 10.0.0.2 \
--sport 45678 --dport 80 \
--state ESTABLISHED
# 특정 연결 삭제
conntrack -D -p tcp --src 10.0.0.1 --dst 10.0.0.2 --dport 80
/* ctnetlink 메시지 구조 (Netlink 기반) */
/* include/uapi/linux/netfilter/nfnetlink_conntrack.h */
enum cntl_msg_types {
IPCTNL_MSG_CT_NEW, /* 새 conntrack 엔트리 */
IPCTNL_MSG_CT_GET, /* conntrack 조회 */
IPCTNL_MSG_CT_DELETE, /* conntrack 삭제 */
IPCTNL_MSG_CT_GET_CTRZERO, /* 카운터 읽고 초기화 */
IPCTNL_MSG_CT_GET_STATS_CPU, /* CPU별 통계 */
IPCTNL_MSG_CT_GET_STATS, /* 전체 통계 */
IPCTNL_MSG_CT_GET_DYING, /* 소멸 중인 엔트리 */
IPCTNL_MSG_CT_GET_UNCONFIRMED, /* 미확인 엔트리 */
};
/* ctnetlink 속성 (nla_policy) */
enum ctattr_type {
CTA_TUPLE_ORIG, /* 원본 방향 튜플 */
CTA_TUPLE_REPLY, /* 응답 방향 튜플 */
CTA_STATUS, /* IPS_* 상태 플래그 */
CTA_PROTOINFO, /* 프로토콜 상태 (TCP window 등) */
CTA_TIMEOUT, /* 남은 타임아웃 */
CTA_MARK, /* connmark 값 */
CTA_COUNTERS_ORIG, /* 원본 방향 카운터 */
CTA_COUNTERS_REPLY, /* 응답 방향 카운터 */
CTA_LABELS, /* connlabel 비트맵 */
/* ... */
};
HA 방화벽 conntrack 동기화: conntrackd는 Primary/Backup 또는 Active/Active 모드를 지원합니다. VRRP(keepalived) 전환 시 conntrackd -c(커밋)으로 대기 노드의 외부 캐시를 커널 conntrack 테이블에 주입하여, 기존 TCP 연결이 끊기지 않고 유지됩니다.
eBPF와 Netfilter 통합
Linux 6.4부터 BPF_PROG_TYPE_NETFILTER 프로그램 타입이 도입되어, eBPF 프로그램을 Netfilter 훅 포인트에 직접 연결할 수 있습니다. 기존 nftables 규칙과 공존하면서, BPF의 유연한 프로그래밍 모델과 Netfilter의 성숙한 인프라(conntrack, NAT 등)를 결합합니다.
BPF_PROG_TYPE_NETFILTER 아키텍처
/* include/uapi/linux/bpf.h (Linux 6.4+) */
/* BPF 프로그램을 Netfilter 훅에 연결하는 link 속성 */
struct { /* bpf_link_create의 netfilter 속성 */
__u32 pf; /* 프로토콜 패밀리 (NFPROTO_IPV4 등) */
__u32 hooknum; /* 훅 포인트 (NF_INET_PRE_ROUTING 등) */
__s32 priority; /* 훅 우선순위 */
__u32 flags; /* 향후 확장용 */
} netfilter;
/* BPF 프로그램의 Netfilter 훅 컨텍스트 */
struct bpf_nf_ctx {
const struct nf_hook_state *state; /* 훅 상태 (in/out dev 등) */
struct sk_buff *skb; /* 패킷 버퍼 */
};
/* BPF Netfilter 프로그램이 반환할 수 있는 verdict */
/* NF_DROP (0), NF_ACCEPT (1) — NF_STOLEN, NF_QUEUE 등은 불허 */
BPF Netfilter 프로그램 예제
/* bpf_netfilter_example.c — BPF Netfilter 훅 프로그램 */
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <linux/ip.h>
#include <linux/tcp.h>
/* 차단 IP 맵 */
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 10000);
__type(key, __u32); /* IPv4 주소 */
__type(value, __u64); /* 차단 카운터 */
} blocklist SEC(".maps");
/* 연결별 통계 맵 */
struct {
__uint(type, BPF_MAP_TYPE_LRU_HASH);
__uint(max_entries, 100000);
__type(key, struct flow_key);
__type(value, struct flow_stats);
} flow_table SEC(".maps");
SEC("netfilter")
int nf_block_and_count(struct bpf_nf_ctx *ctx)
{
struct sk_buff *skb = ctx->skb;
struct iphdr *iph;
__u32 saddr;
iph = bpf_skb_load_bytes(...);
if (!iph)
return NF_ACCEPT;
saddr = iph->saddr;
/* 블랙리스트 조회 */
__u64 *count = bpf_map_lookup_elem(&blocklist, &saddr);
if (count) {
__sync_fetch_and_add(count, 1);
return NF_DROP; /* 차단 */
}
/* 플로우 통계 업데이트 */
struct flow_key key = {
.saddr = iph->saddr,
.daddr = iph->daddr,
.protocol = iph->protocol,
};
struct flow_stats *stats = bpf_map_lookup_elem(&flow_table, &key);
if (stats) {
__sync_fetch_and_add(&stats->packets, 1);
__sync_fetch_and_add(&stats->bytes, skb->len);
} else {
struct flow_stats new_stats = { .packets = 1, .bytes = skb->len };
bpf_map_update_elem(&flow_table, &key, &new_stats, BPF_NOEXIST);
}
return NF_ACCEPT;
}
# BPF Netfilter 프로그램 로드 및 연결
# 컴파일
clang -O2 -target bpf -c bpf_netfilter_example.c -o bpf_nf.o
# bpftool로 로드 및 Netfilter 훅에 연결
bpftool prog load bpf_nf.o /sys/fs/bpf/nf_block
bpftool net attach netfilter \
pinned /sys/fs/bpf/nf_block \
pf 2 \
hooknum 1 \
priority -100
# libbpf로 프로그래밍 방식 연결 (C 코드)
# struct bpf_link *link = bpf_program__attach_netfilter(prog, &opts);
# 연결된 BPF 프로그램 확인
bpftool net list
# 블랙리스트 맵에 IP 추가 (유저스페이스에서)
bpftool map update pinned /sys/fs/bpf/nf_block/blocklist \
key hex 0a 00 00 01 value hex 00 00 00 00 00 00 00 00
| 항목 | nftables | BPF Netfilter | XDP |
|---|---|---|---|
| 훅 위치 | Netfilter 훅 | Netfilter 훅 | 드라이버 수준 |
| conntrack 접근 | 네이티브 | bpf_ct_* 헬퍼 | 불가 |
| 프로그래밍 | 선언적 규칙 | C (BPF 바이트코드) | C (BPF 바이트코드) |
| 동적 업데이트 | set/map 업데이트 | BPF map 업데이트 | BPF map 업데이트 |
| 성능 | 보통 | JIT 컴파일 → 고속 | 최고 (sk_buff 미생성) |
| 상태 관리 | conntrack 의존 | conntrack + BPF map | BPF map만 |
| 적합 용도 | 일반 방화벽 | 커스텀 로직 + stateful | 고속 무상태 필터링 |
BPF conntrack 헬퍼 함수
/* BPF 프로그램에서 conntrack 정보 접근 (Linux 6.4+) */
/* conntrack 조회 */
struct nf_conn *bpf_xdp_ct_lookup(
struct xdp_md *xdp,
struct bpf_sock_tuple *tuple,
u32 tuple_size,
struct bpf_ct_opts *opts,
u32 opts_size);
/* conntrack 삽입 */
struct nf_conn *bpf_skb_ct_alloc(
struct sk_buff *skb,
struct bpf_sock_tuple *tuple,
u32 tuple_size,
struct bpf_ct_opts *opts,
u32 opts_size);
/* conntrack 상태/마크 설정 */
int bpf_ct_set_status(struct nf_conn *ct, u32 status);
int bpf_ct_change_status(struct nf_conn *ct, u32 status);
int bpf_ct_set_nat_info(struct nf_conn *ct,
union nf_inet_addr *addr, int port, enum nf_nat_manip_type manip);
/* 사용 예: BPF에서 conntrack 조회 후 마크 기반 분류 */
SEC("netfilter")
int nf_classify(struct bpf_nf_ctx *ctx)
{
struct bpf_ct_opts opts = {
.netns_id = BPF_F_CURRENT_NETNS,
.l4proto = IPPROTO_TCP,
};
struct bpf_sock_tuple tuple = { ... };
struct nf_conn *ct = bpf_skb_ct_lookup(
ctx->skb, &tuple, sizeof(tuple.ipv4),
&opts, sizeof(opts));
if (ct) {
if (ct->mark == 0x42) {
bpf_ct_release(ct);
return NF_DROP;
}
bpf_ct_release(ct);
}
return NF_ACCEPT;
}
BPF Netfilter vs XDP 선택 기준: conntrack/NAT 상태가 필요하면 BPF_PROG_TYPE_NETFILTER, 순수 패킷 수준 고속 처리가 필요하면 XDP를 사용하세요. 두 타입은 공존 가능하며, XDP에서 1차 필터링 → Netfilter BPF에서 상태 기반 2차 필터링 구성이 효과적입니다.
방화벽 자동화와 관리 도구
프로덕션 환경에서 iptables/nftables 규칙을 직접 관리하는 것은 복잡하고 오류 발생 가능성이 높습니다. firewalld, ufw 등의 관리 도구와 nft JSON API를 통한 프로그래밍 방식의 규칙 관리가 효과적입니다.
firewalld
firewalld는 D-Bus 인터페이스를 제공하는 동적 방화벽 관리 데몬으로, zone 기반 정책 모델을 사용합니다. 백엔드로 nftables(기본) 또는 iptables를 사용합니다.
# === firewalld 기본 사용법 ===
# 상태 확인
firewall-cmd --state
firewall-cmd --list-all
# 존(zone) 관리
firewall-cmd --get-zones
firewall-cmd --get-default-zone
firewall-cmd --get-active-zones
# 서비스 허용 (영구 적용)
firewall-cmd --zone=public --add-service=http --permanent
firewall-cmd --zone=public --add-service=https --permanent
firewall-cmd --reload
# 포트 직접 허용
firewall-cmd --zone=public --add-port=8080/tcp --permanent
# rich rule (세밀한 규칙)
firewall-cmd --zone=public --add-rich-rule='
rule family="ipv4"
source address="10.0.0.0/8"
port protocol="tcp" port="22"
accept' --permanent
# 소스 IP 기반 존 할당
firewall-cmd --zone=trusted --add-source=192.168.1.0/24 --permanent
# 포트 포워딩
firewall-cmd --zone=public \
--add-forward-port=port=80:proto=tcp:toport=8080:toaddr=10.0.0.2 \
--permanent
# 백엔드 확인 (nftables vs iptables)
firewall-cmd --get-backend
# 실제 생성된 nftables 규칙 확인
nft list ruleset | grep firewalld
| firewalld 존 | 기본 정책 | 용도 |
|---|---|---|
drop | 모두 차단 (ICMP 응답 없음) | 최대 보안 |
block | 모두 차단 (ICMP 거부 응답) | 명시적 거부 |
public | 선택적 허용 | 외부 네트워크 (기본) |
external | 선택적 허용 + NAT | 외부 인터페이스 |
dmz | 선택적 허용 | DMZ 서버 |
work | 대부분 허용 | 업무 네트워크 |
home | 대부분 허용 | 가정 네트워크 |
internal | 대부분 허용 | 내부 네트워크 |
trusted | 모두 허용 | 신뢰 네트워크 |
ufw (Uncomplicated Firewall)
# ufw 기본 사용법 (Ubuntu/Debian)
# 활성화/비활성화
ufw enable
ufw disable
ufw status verbose
# 기본 정책 설정
ufw default deny incoming
ufw default allow outgoing
# 서비스/포트 허용
ufw allow ssh
ufw allow 80/tcp
ufw allow 443/tcp
# IP 기반 규칙
ufw allow from 10.0.0.0/8 to any port 22
ufw deny from 192.168.1.100
# 속도 제한 (SSH 브루트포스 방어)
ufw limit ssh/tcp
# 로깅 수준
ufw logging medium
# 규칙 삭제
ufw status numbered
ufw delete 3
# ufw 내부: /etc/ufw/user.rules (iptables 규칙 파일)
nft JSON API
nftables는 JSON 형식으로 규칙셋을 import/export할 수 있어, 프로그래밍 방식의 방화벽 관리에 적합합니다. 자동화 도구(Ansible, Terraform 등)와의 통합에 유용합니다.
# 현재 규칙셋을 JSON으로 내보내기
nft -j list ruleset | python3 -m json.tool
# JSON 규칙셋 예제
cat <<'EOF' > /tmp/firewall.json
{
"nftables": [
{ "flush": { "ruleset": null } },
{ "add": {
"table": {
"family": "inet",
"name": "filter"
}
}},
{ "add": {
"chain": {
"family": "inet",
"table": "filter",
"name": "input",
"type": "filter",
"hook": "input",
"prio": 0,
"policy": "drop"
}
}},
{ "add": {
"rule": {
"family": "inet",
"table": "filter",
"chain": "input",
"expr": [
{ "match": {
"left": { "ct": { "key": "state" } },
"right": ["established", "related"],
"op": "=="
}},
{ "accept": null }
]
}
}},
{ "add": {
"rule": {
"family": "inet",
"table": "filter",
"chain": "input",
"expr": [
{ "match": {
"left": { "meta": { "key": "iifname" } },
"right": "lo",
"op": "=="
}},
{ "accept": null }
]
}
}},
{ "add": {
"rule": {
"family": "inet",
"table": "filter",
"chain": "input",
"expr": [
{ "match": {
"left": { "payload": {
"protocol": "tcp",
"field": "dport"
}},
"right": { "set": [22, 80, 443] },
"op": "=="
}},
{ "accept": null }
]
}
}}
]
}
EOF
# JSON 규칙셋 적용
nft -j -f /tmp/firewall.json
# Python에서 nft JSON API 활용
# import subprocess, json
# result = subprocess.run(['nft', '-j', 'list', 'ruleset'],
# capture_output=True, text=True)
# ruleset = json.loads(result.stdout)
Ansible/자동화 통합
# Ansible nftables 관리 예제 (ansible.posix.nftables 모듈 활용 가능)
# 또는 template 모듈로 nftables.conf 배포
# /etc/nftables.conf — 부팅 시 자동 로드
# systemctl enable nftables
# nftables 규칙 영속화
nft list ruleset > /etc/nftables.conf
# 또는 include 디렉티브로 모듈화
# /etc/nftables.conf:
# #!/usr/sbin/nft -f
# flush ruleset
# include "/etc/nftables.d/*.nft"
# 규칙 검증 (적용 전 문법 확인)
nft -c -f /etc/nftables.conf
# 성공 시 출력 없음, 실패 시 에러 메시지
# 원자적 규칙셋 교체 (중단 없음)
nft -f /etc/nftables.conf
# flush + 새 규칙 로드가 하나의 트랜잭션으로 처리
프로덕션 방화벽 관리 권장사항:
- 버전 관리: nftables 규칙 파일을 Git으로 관리하여 변경 이력 추적
- 원자적 교체:
nft -f는 flush + reload를 단일 트랜잭션으로 처리하여 규칙 공백 방지 - 사전 검증:
nft -c -f로 문법 검증 후 적용 - 롤백 계획:
at now + 5 minutes으로 규칙 적용 전 자동 복원 스케줄링 - 모니터링:
nft monitor로 규칙 변경 실시간 감시