네트워크 공격 방어
리눅스 커널 관점에서 DoS/DDoS를 방어하는 핵심은 대역폭보다 먼저 상태(state) 예산과 CPU 예산을 보호하는 것입니다. 이 문서는 SYN Flood, UDP/ICMP 증폭, conntrack 고갈, Slowloris 계열, IP 스푸핑과 L2 위변조를 커널 수신 경로에 맞춰 정리하고, XDP, nftables/SYNPROXY, TCP backlog, conntrack, 관측 지표와 사고 대응 절차를 실무 관점에서 묶어 설명합니다. VPN 암호화는 IPSec & xfrm, WireGuard 문서를 따로 보세요.
핵심 요약
- PPS — 초당 패킷 수입니다. 대역폭이 낮아도 PPS가 높으면 CPU가 먼저 무너질 수 있습니다.
- backlog — 패킷 또는 연결이 잠시 대기하는 큐입니다. RX backlog, SYN backlog, accept queue를 구분해야 합니다.
- conntrack — 상태 기반 방화벽/NAT를 위한 흐름 테이블입니다. 공격 시 가장 흔한 전역 병목입니다.
- syncookie — SYN backlog overflow 시 서버가 반개방 연결 상태를 저장하지 않고 ACK를 검증하는 비상 모드입니다.
- synproxy — 방화벽이 백엔드 대신 TCP 3-way handshake를 먼저 완료해 주는 보호막입니다.
단계별 이해
- 공격 분류
대역폭형인지, 상태 고갈형인지, 애플리케이션 느린 요청형인지 먼저 구분합니다. - 병목 지점 확인
NIC RX 링, NAPI poll, softnet backlog, conntrack, listener queue, worker 중 어디가 먼저 차는지 계측합니다. - 차단 위치 상향
가능하면 XDP, 어렵다면 raw/prerouting, 그다음 input/socket으로 차단 위치를 앞당깁니다. - 상태 예산 보호
syncookies, SYNPROXY, notrack, timeout 단축으로 메모리 점유 시간을 줄입니다. - 검증과 롤백
카운터 증가 방향, 정상 트래픽 손실, 재현 테스트를 보고 단계적으로 고정합니다.
- IPSec & xfrm — 암호화 터널과 SA/SP, 오프로드
- WireGuard — Noise 기반 현대 VPN
- eBPF 기반 보안 정책 — identity 기반 정책과 BPF 보안 제어
- Netlink 심화 — 정책 배포 제어 평면
위협 모델 먼저 잡기
같은 DDoS처럼 보여도 커널 입장에서 고갈되는 자원은 다릅니다. 대충 "패킷이 많다"로 묶으면 잘못된 계층에 방어를 걸게 됩니다. 커널 방어는 어떤 상태가 만들어지기 전에 끊을 수 있는가를 기준으로 설계해야 합니다.
| 공격 유형 | 주요 고갈 자원 | 대표 증상 | 가장 먼저 볼 차단 위치 |
|---|---|---|---|
| 볼류메트릭 L3/L4 Flood | NIC RX 예산, softirq CPU, cache miss | pps 급증, rx_dropped, softnet_stat 증가 |
XDP, NIC 하드웨어 필터, ingress ACL |
| SYN Flood | SYN backlog, request_sock, accept queue | SyncookiesSent, TCPReqQFull*, ListenOverflows |
nftables SYNPROXY, syncookies, listener 튜닝 |
| UDP/ICMP 반사·증폭 | IRQ/NAPI 예산, conntrack, ICMP 응답 생성 비용 | 작은 대역폭 대비 높은 PPS, 닫힌 포트 ICMP 폭증 | XDP, raw/prerouting, stateless rate limit |
| conntrack 고갈 | 전역 흐름 테이블 메모리와 조회 시간 | nf_conntrack_count가 nf_conntrack_max 근접 |
notrack, timeout 단축, set/meter, flow 분리 |
| Slowloris / Slow Read | 소켓, 워커, 파일 디스크립터, 애플리케이션 큐 | 연결 수는 많지만 PPS는 낮음, 워커 고갈 | 애플리케이션 timeout, TCP_DEFER_ACCEPT, reverse proxy |
| 스푸핑 / L2 위변조 | 정책 우회, 조사 난이도 상승 | 비정상 source, ARP cache 오염, 비대칭 경로 이슈 | rp_filter, arp_filter, 스위치 DAI/port security |
수신 경로 병목을 알아야 방어가 맞습니다
패킷은 NIC RX 링에서 시작해 NAPI poll, softnet backlog, Netfilter/conntrack, TCP/UDP 스택, 소켓 큐, 애플리케이션 worker까지 올라갑니다. 공격이 어느 단계에서 CPU와 메모리를 태우는지 보지 않으면, 늦은 계층에서 비싼 검사를 하다가 이미 무너진 뒤에야 드롭하게 됩니다.
| 병목 지점 | 전형적 징후 | 주요 지표 | 실전 대응 |
|---|---|---|---|
| NIC RX 링 / IRQ | 드롭은 많지만 conntrack은 아직 여유 | ethtool -S, IRQ 분포, RSS 큐 편중 |
RSS, IRQ affinity, XDP, L2 ACL |
| NAPI / softnet backlog | 한 코어만 바쁘고 다른 코어는 놀음 | /proc/net/softnet_stat, netdev_max_backlog |
RPS/RFS, netdev_budget, Flow Limit |
| Netfilter / conntrack | 새 연결이 불규칙하게 실패 | nf_conntrack_count, conntrack -S, nft counter |
notrack, set/meter, timeout 조정, ct 분리 |
| SYN backlog / accept queue | 접속 지연, ListenOverflows, syncookie 발동 |
nstat, ss -lnt, somaxconn |
SYNPROXY, syncookies, backlog/accept 튜닝 |
| 애플리케이션 worker | PPS는 낮은데 응답이 멈춤 | accept 대기, worker 점유율, 요청 timeout | header/body timeout, TCP_DEFER_ACCEPT, reverse proxy |
RSS, RPS, RFS, Flow Limit으로 고 PPS를 분산합니다
고 PPS 공격은 평균 CPU 사용률보다 어느 큐와 어느 CPU가 과열되는지가 더 중요합니다. 리눅스 커널 문서는 RSS를 하드웨어 큐 분산, RPS를 소프트웨어 기반 RSS, RFS를 애플리케이션 CPU locality 강화, Flow Limit를 backlog가 절반을 넘었을 때 큰 flow를 먼저 억제하는 장치로 설명합니다. 이 네 가지는 서로 대체하는 기능이 아니라 계층이 다른 도구입니다.
| 기법 | 동작 계층 | 주목적 | 언제 쓰는가 | 주의점 |
|---|---|---|---|---|
| RSS | NIC 하드웨어 | RX 큐와 IRQ 분산 | 멀티큐 NIC, 하드웨어 queue 사용 가능 | irqbalance나 잘못된 affinity가 분산 효과를 망칠 수 있음 |
| RPS | softirq / backlog | 프로토콜 처리 CPU 재분배 | NIC 큐 수가 CPU 수보다 적을 때 | IPI 비용이 생기므로 RSS가 충분하면 오히려 불필요할 수 있음 |
| RFS | softirq → socket | 애플리케이션 CPU locality 향상 | worker pinning, SO_INCOMING_NAPI_ID 기반 최적화 |
rps_sock_flow_entries, 큐별 rps_flow_cnt 설정 필요 |
| Flow Limit | per-CPU input backlog | 포화 시 큰 flow 억제 | 한 소스/flow가 queue를 독점하는 DoS | 기본 비활성화. backlog가 절반을 넘을 때만 본격 동작 |
# 1. 하드웨어 RX 큐와 IRQ 분포 확인
ethtool -x eth0
grep eth0 /proc/interrupts
# 2. NIC 큐 수가 CPU 수보다 적을 때 RPS 활성화 예시
echo f > /sys/class/net/eth0/queues/rx-0/rps_cpus
echo f > /sys/class/net/eth0/queues/rx-1/rps_cpus
# 3. RFS 활성화: 전역 엔트리 + 큐별 엔트리
sysctl -w net.core.rps_sock_flow_entries=32768
echo 16384 > /sys/class/net/eth0/queues/rx-0/rps_flow_cnt
echo 16384 > /sys/class/net/eth0/queues/rx-1/rps_flow_cnt
# 4. Flow Limit 활성화: backlog가 절반을 넘을 때 큰 flow 억제
sysctl -w net.core.flow_limit_cpu_bitmap=f
sysctl -w net.core.netdev_max_backlog=4096
# 5. 특정 포트를 별도 RSS context로 분리하는 ethtool 예시
ethtool -X eth0 hfunc toeplitz context new
ethtool -x eth0 context 1
ethtool -X eth0 equal 2 context 1
ethtool -N eth0 flow-type tcp6 dst-port 22 context 1
irqbalance가 다시 바꿀 수 있으니 같이 확인하세요.
SYN Flood: backlog가 어디서 차는지 구분해야 합니다
SYN Flood는 TCP가 3-way handshake를 완료하기 전까지 잠깐 저장하는 상태를 겨냥합니다. 여기서 자주 헷갈리는 것이 listen backlog와 SYN backlog의 차이입니다.
listen(backlog)와somaxconn— 애플리케이션이accept()하기 전 완료된 연결이 대기하는 accept queue 상한입니다. 현재 커널 문서는somaxconn기본값을 4096으로 설명합니다.tcp_max_syn_backlog— 아직 ACK를 받지 못한SYN_RECV요청을 기억하는 per-listener 한도입니다.tcp_synack_retries— 커널 문서 기준 기본값 5이며, 현재 RTO 정책에서 마지막 재전송까지 약 31초, 최종 타임아웃까지 약 63초가 걸릴 수 있습니다.tcp_syncookies— SYN backlog overflow 시 request_sock를 만들지 않고 cookie 기반으로 ACK를 검증합니다. 커널 문서도 이것을 "정상 과부하 튜닝용"이 아니라 공격 대응용 비상 모드로 취급하라고 강하게 경고합니다.tcp_abort_on_overflow— accept가 너무 느릴 때 연결을 reset으로 끊습니다. 문서도 "정말 확신할 때만" 켜라고 합니다.
/* net/ipv4/tcp_ipv4.c + tcp_input.c 경로를 읽기 쉽게 줄인 의사 코드 */
int tcp_v4_rcv(struct sk_buff *skb)
{
/* 1. LISTEN 소켓이면 새 연결 요청 처리 */
if (sk_state(skb) == TCP_LISTEN)
return tcp_conn_request(...);
}
int tcp_conn_request(..., struct sock *sk, struct sk_buff *skb)
{
bool want_cookie = false;
/* 2. SYN backlog가 꽉 찼는지 확인 */
if (inet_csk_reqsk_queue_is_full(sk)) {
if (!READ_ONCE(net->ipv4.sysctl_tcp_syncookies))
goto drop;
want_cookie = true;
}
/* 3. 정상 경로면 request_sock를 만들고 SYN+ACK 전송 */
if (!want_cookie)
inet_csk_reqsk_queue_hash_add(sk, req, timeout);
/* 4. cookie 경로면 상태를 저장하지 않고 ISN에 쿠키 부호화 */
if (want_cookie)
isn = cookie_v4_init_sequence(sk, skb, ...);
/* 5. 최종 ACK가 오면 cookie 검증 후 child socket 생성 */
return tcp_v4_send_synack(sk, skb, ...);
drop:
return 0;
}
코드 설명
- 1-5행LISTEN 소켓으로 들어온 패킷은 일반 데이터 경로가 아니라 연결 생성 경로로 빠집니다. 방어 포인트가 일반 TCP 세션 처리와 다릅니다.
- 9-13행
inet_csk_reqsk_queue_is_full()가 반개방 연결 큐 포화를 판단합니다. 이때가 syncookie 발동 분기점입니다. - 16-17행정상 경로에서는
request_sock가 메모리에 생성됩니다. 공격자는 바로 이 상태 보유 비용을 노립니다. - 20-21행syncookie 경로는 상태를 저장하지 않고 초기 시퀀스 번호에 검증 정보를 부호화합니다. 메모리 사용량은 줄지만 항상 이상적인 경로는 아닙니다.
- 23행최종 ACK가 돌아왔을 때만 실제 child socket을 만들기 때문에 backlog overflow 상황에서 생존성이 올라갑니다.
| 항목 | syncookie | SYNPROXY |
|---|---|---|
| 배치 위치 | 서버 커널 내부 | 방화벽/로드밸런서/프런트 호스트 |
| 보호 대상 | 해당 서버의 SYN backlog | 백엔드 전체의 SYN backlog와 request_sock |
| 상태 절감 | overflow 시 반개방 상태 미저장 | 백엔드가 handshake 자체를 거의 안 보게 함 |
| 주의점 | 커널 문서가 TCP 확장과 서비스 저하 가능성을 경고 | MSS, window scale, timestamp, SACK 옵션을 백엔드와 맞춰야 함 |
| 적합한 상황 | 개별 호스트 보호, 즉시 대응 | 공격이 프런트에서 집중되는 서비스, 다수 백엔드 보호 |
nf_conntrack_tcp_loose=0으로 느슨한 TCP 추적을 끄고, 흐름에 맞지 않는 ACK를 INVALID로 분류한 뒤 SYNPROXY에 넘깁니다.
실제 운영에서 이 값을 빼먹으면 규칙은 맞아 보여도 기대한 대로 보호가 안 걸릴 수 있습니다.
# nftables SYNPROXY 예시
# 공식 nftables manpage 예시 구조를 Linux 서버 환경에 맞게 정리한 것
sysctl -w net.netfilter.nf_conntrack_tcp_loose=0
nft add table ip raw
nft 'add chain ip raw prerouting { type filter hook prerouting priority raw; policy accept; }'
nft add rule ip raw prerouting tcp flags syn notrack
nft add table ip filter
nft 'add chain ip filter input { type filter hook input priority filter; policy accept; }'
nft add rule ip filter input ct state invalid,untracked synproxy mss 1460 wscale 9 timestamp sack-perm
nft add rule ip filter input ct state invalid drop
# 서버 자체 보호용 최소 확인
sysctl -w net.ipv4.tcp_syncookies=1
sysctl -w net.ipv4.tcp_max_syn_backlog=8192
sysctl -w net.ipv4.tcp_synack_retries=3
sysctl -w net.core.somaxconn=8192
syncookies=1만 켠다고 SYN Flood 방어가 끝나는 것이 아닙니다.
accept queue가 꽉 차는 문제는 somaxconn, 애플리케이션의 accept 속도, reverse proxy 앞단 구조, ListenOverflows/ListenDrops 카운터를 같이 봐야 해결됩니다.
UDP/ICMP Flood와 반사·증폭 공격
UDP Flood는 TCP처럼 handshake 상태를 만들지 않기 때문에 "가볍다"고 오해하기 쉽지만, 실제 운영에서는 높은 PPS, 닫힌 포트 ICMP 응답 생성, conntrack 엔트리 남발, 애플리케이션 파서 비용 때문에 빠르게 CPU를 태웁니다. 특히 DNS, NTP, SSDP 같은 반사·증폭 계열은 상대적으로 작은 송신량으로 큰 응답을 유도할 수 있습니다.
| sysctl | 기본값 | 의미 | 언제 만지는가 |
|---|---|---|---|
net.ipv4.icmp_ratelimit |
1000 | 대상별 ICMP 응답 사이 최소 간격(ms) | 닫힌 포트 UDP flood, 오류 응답 폭증 |
net.ipv4.icmp_msgs_per_sec |
1000 | 호스트 전체 ICMP 초당 최대 메시지 수 | 전역 ICMP 송신 예산 보호 |
net.ipv4.icmp_msgs_burst |
50 | ICMP burst 허용량 | 짧은 스파이크 허용치 조정 |
net.ipv4.icmp_echo_ignore_broadcasts |
1 | broadcast/multicast echo 요청 무시 | Smurf 류 반사 방지 |
# ICMP 응답 예산 보호
sysctl -w net.ipv4.icmp_ratelimit=1000
sysctl -w net.ipv4.icmp_msgs_per_sec=1000
sysctl -w net.ipv4.icmp_msgs_burst=50
sysctl -w net.ipv4.icmp_echo_ignore_broadcasts=1
# UDP / ICMP 속도 제한 예시
nft add table inet ddos
nft 'add chain inet ddos input { type filter hook input priority filter; policy accept; }'
nft add rule inet ddos input udp dport { 53, 123, 1900, 11211 } meter amp4 '{ ip saddr timeout 10s limit rate over 200/second }' drop
nft add rule inet ddos input ip protocol icmp icmp type echo-request meter ping4 '{ ip saddr timeout 10s limit rate over 20/second }' drop
nft add rule inet ddos input ct state invalid drop
# 서비스가 conntrack/NAT를 정말 필요로 하지 않는 앞단이라면 raw 훅에서 notrack 검토
# 단, notrack는 stateful firewall/NAT 관측을 우회하므로 적용 범위를 매우 좁게 잡아야 함
conntrack 고갈은 전체 서비스에 전염됩니다
nf_conntrack는 특정 서비스 하나의 큐가 아니라 호스트 또는 네임스페이스 전체에서 공유되는 상태 자원입니다. 그래서 한 서비스로 들어오는 flood가 전혀 관계없는 다른 서비스의 새 연결까지 실패하게 만들 수 있습니다. 특히 UDP spray, spoofed SYN, fragment 폭탄은 짧은 시간에 많은 엔트리를 만들고, timeout이 길면 메모리 점유가 길어집니다.
| 항목 | 기본값/성질 | 보안 관점 의미 |
|---|---|---|
nf_conntrack_count |
읽기 전용 현재 엔트리 수 | 실시간 수용량 확인의 기준점 |
nf_conntrack_max |
기본적으로 nf_conntrack_buckets와 동일 |
전역 수용량 상한. 가득 차면 새 흐름이 실패 |
nf_conntrack_buckets |
메모리 기반 자동 계산 | 해시 분산 품질과 lookup 충돌에 영향 |
nf_conntrack_tcp_timeout_syn_recv |
60초 | 반개방 TCP 엔트리 잔류 시간 |
nf_conntrack_udp_timeout |
30초 | 일반 UDP 엔트리 잔류 시간 |
nf_conntrack_udp_timeout_stream |
120초 | 양방향 UDP stream 판정 후 잔류 시간 |
nf_conntrack_log_invalid |
0 | INVALID 패킷 로그를 프로토콜별로 선택 출력 |
nf_conntrack_acct |
0 | 흐름별 패킷/바이트 카운터 부여. 관측에는 좋지만 약간의 추가 비용 |
nf_conntrack_checksum |
1 | 잘못된 체크섬 패킷을 INVALID로 취급 |
nf_conntrack_frag6_high_thresh |
262144 bytes | IPv6 fragment reassembly 메모리 상한 |
# 현재 conntrack 상태 점검
cat /proc/sys/net/netfilter/nf_conntrack_count
cat /proc/sys/net/netfilter/nf_conntrack_max
cat /proc/sys/net/netfilter/nf_conntrack_buckets
conntrack -S
# 공격 시 자주 보는 보수적 조정 예시
sysctl -w net.netfilter.nf_conntrack_tcp_timeout_syn_recv=30
sysctl -w net.netfilter.nf_conntrack_udp_timeout=15
sysctl -w net.netfilter.nf_conntrack_udp_timeout_stream=60
sysctl -w net.netfilter.nf_conntrack_log_invalid=6
sysctl -w net.netfilter.nf_conntrack_acct=1
# state가 불필요한 아주 제한된 트래픽만 raw에서 notrack
# nft add table inet raw
# nft 'add chain inet raw prerouting { type filter hook prerouting priority raw; policy accept; }'
# nft add rule inet raw prerouting ip protocol icmp notrack
# 위 예시는 "가능한 문법" 예시일 뿐이며, 실제 운영에선 NAT/정책 우회 영향을 반드시 검토해야 합니다.
ct timeout 객체 또는 서비스 분리로 가는 편이 안전합니다.
조각화와 재조립 큐도 별도 자원 예산입니다
fragment flood는 대역폭이 아주 크지 않아도 미완성 재조립 큐와 긴 timeout을 이용해 메모리를 오래 점유할 수 있습니다. 조각이 충분히 모이지 않으면 상위 계층까지 가지 못한 채 재조립 버퍼만 남습니다. 서비스가 실제로 큰 UDP, 터널, 특수 장비 트래픽을 얼마나 쓰는지 모른 채 threshold만 올리면 공격 표면도 같이 커집니다.
| 항목 | 대상 | 의미 | 보안 관점 메모 |
|---|---|---|---|
net.ipv4.ipfrag_high_thresh |
IPv4 재조립 | fragment queue가 사용할 수 있는 메모리 상한 | 상한이 너무 높으면 메모리 체류 비용이 커지고, 너무 낮으면 정상 fragment도 자주 탈락 |
net.ipv4.ipfrag_time |
IPv4 재조립 | 미완성 fragment queue 보관 시간 | 짧게 줄이면 공격 체류 시간은 감소하지만 느린 경로나 재전송 많은 환경에 영향 |
net.ipv4.ipfrag_max_dist |
IPv4 재조립 | 같은 source 기준으로 queue 사이를 얼마나 건너뛸 수 있는지 판단 | 문서가 기본값 64를 설명하며, 너무 작으면 재정렬된 정상 조각 드롭, 너무 크면 잘못된 조각 결합 위험 |
net.ipv6.ip6frag_high_thresh |
IPv6 재조립 | IPv6 fragment 재조립 메모리 상한 | IPv6 조각화는 경로 특성 영향이 커서 실제 트래픽 패턴 확인이 선행돼야 함 |
net.ipv6.ip6frag_time |
IPv6 재조립 | IPv6 fragment 보관 시간 | 터널과 대형 UDP 응용이 있다면 지나치게 줄이기 어려움 |
net.netfilter.nf_conntrack_frag6_high_thresh |
IPv6 + conntrack | conntrack의 IPv6 fragment 재조립 메모리 상한 | 문서 기본값은 262144 bytes이며, high를 넘으면 low 아래로 내려갈 때까지 큐 회수 |
net.netfilter.nf_conntrack_frag6_timeout |
IPv6 + conntrack | conntrack 조각 보관 시간 | stateful 방화벽 앞단이면 이 timeout이 공격 체류 시간에 직접 연결 |
# 현재 fragment / reassembly 관련 값 확인
sysctl net.ipv4.ipfrag_high_thresh
sysctl net.ipv4.ipfrag_time
sysctl net.ipv4.ipfrag_max_dist
sysctl net.ipv6.ip6frag_high_thresh
sysctl net.ipv6.ip6frag_time
sysctl net.netfilter.nf_conntrack_frag6_high_thresh
sysctl net.netfilter.nf_conntrack_frag6_low_thresh
sysctl net.netfilter.nf_conntrack_frag6_timeout
# 공격 시 자주 시도하는 보수적 조정 예시
sysctl -w net.ipv4.ipfrag_time=15
sysctl -w net.ipv4.ipfrag_max_dist=64
sysctl -w net.ipv6.ip6frag_time=15
sysctl -w net.netfilter.nf_conntrack_frag6_timeout=30
# 서비스가 조각화를 거의 쓰지 않는다면 fragment 비율 자체를 관측 경보로 올리는 편이 안전
nstat -az IpReasmReqds IpReasmOKs IpReasmFails
ipfrag_max_dist는 "낮출수록 안전"한 값이 아닙니다.
커널 문서도 값이 너무 작으면 정상 재정렬을 드롭하고, 너무 크면 잘못된 fragment queue에 붙을 위험이 커질 수 있다고 설명합니다.
XDP, nftables, conntrack을 계층적으로 써야 합니다
방어를 잘하는 시스템은 "모든 검사를 한 군데서 많이" 하지 않습니다. 보통은 다음 순서가 좋습니다.
- XDP — 명백히 버릴 패킷을 가장 먼저 드롭합니다. spoofed source, 알려진 반사 포트, 비정상 헤더, 매우 단순한 rate limiting이 여기 적합합니다.
- nftables raw/prerouting — notrack 여부를 결정하고, 값싼 매치와 set/meter로 더 줄입니다.
- nftables filter/input + conntrack — 살아남은 패킷에만 stateful 정책을 적용합니다.
- TCP/애플리케이션 — syncookie, SYNPROXY,
TCP_DEFER_ACCEPT, reverse proxy timeout으로 마지막 상태 예산을 보호합니다.
커널 XDP 문서는 redirect 경로를 세 단계로 설명합니다. bpf_redirect()/bpf_redirect_map()이 대상 정보를 per-CPU 구조체에 넣고, 드라이버가 xdp_do_redirect()로 enqueue한 뒤, NAPI poll 종료 전 xdp_do_flush()를 호출해 실제 전송을 완료합니다. 이 구조는 flood 방어에서 드롭만 아니라 격리에도 중요합니다. 예를 들어 공격 의심 트래픽을 AF_XDP나 cpumap으로 분리해 분석할 수 있습니다.
/* XDP에서 source별 간단한 토큰 버킷을 적용하는 의사 코드 */
struct rate_key {
__u32 saddr;
__u16 dport;
__u8 proto;
};
struct rate_val {
__u64 window_ns;
__u32 packets;
};
SEC("xdp")
int ddos_guard(struct xdp_md *ctx)
{
if (!parse_ipv4_udp(ctx, &iph, &uh))
return XDP_PASS;
if (uh->dest == bpf_htons(53) || uh->dest == bpf_htons(123)) {
key.saddr = iph->saddr;
key.dport = uh->dest;
key.proto = iph->protocol;
val = bpf_map_lookup_elem(&rate_map, &key);
if (over_limit(val, bpf_ktime_get_ns()))
return XDP_DROP;
}
return XDP_PASS;
}
sk_buff 생성과 Netfilter 훅 순회 비용이 아깝습니다.
XDP는 패킷이 일반 네트워크 스택에 들어오기 전에 드롭할 수 있다는 점이 핵심입니다.
Slowloris 계열은 패킷보다 연결 점유 시간이 문제입니다
Slowloris, Slow POST, Slow Read 류 공격은 "패킷이 매우 많다"기보다 오래 붙잡고 놓지 않는 연결을 이용합니다. 그래서 XDP나 단순 PPS 제한은 거의 안 듣고, 소켓 수, accept loop, 워커 스레드, 애플리케이션 요청 timeout이 핵심이 됩니다. 커널이 할 수 있는 일과 애플리케이션이 해야 하는 일을 분리해서 봐야 합니다.
| 대책 | 계층 | 효과 | 주의점 |
|---|---|---|---|
TCP_DEFER_ACCEPT |
커널 소켓 | 데이터가 오기 전에는 listener를 덜 깨움 | 프로토콜 특성과 timeout 값에 민감 |
somaxconn / 애플리케이션 backlog |
커널 + 앱 | 짧은 스파이크 흡수 | worker가 느리면 문제를 뒤로 미루기만 함 |
tcp_abort_on_overflow |
커널 | accept queue overflow 시 즉시 reset | 문서가 신중 사용을 권고 |
| header/body/read timeout | 애플리케이션 | Slowloris 대응의 핵심 | 정상 느린 클라이언트에 영향 |
| reverse proxy + buffer | L7 | 백엔드 worker 보호 | 프런트단 메모리 정책 필요 |
# 커널 쪽에서 먼저 보는 값
sysctl net.core.somaxconn
sysctl net.ipv4.tcp_abort_on_overflow
# listener queue 관찰
ss -lnt
nstat -az TcpExtListenOverflows TcpExtListenDrops TcpExtTCPBacklogDrop
# 애플리케이션은 별도로 header/read timeout, keepalive timeout을 짧게 가져가야 함
# Slowloris는 커널 한 군데만 만져서 해결되지 않습니다.
스푸핑과 L2 위변조는 ingress filtering이 기본입니다
IP 스푸핑이 허용되면 UDP 증폭과 SYN Flood의 추적과 정책 적용이 모두 어려워집니다. 커널의 rp_filter는 RFC 3704가 설명하는 strict/loose reverse path filtering 개념과 대응됩니다. 커널 문서는 strict mode(1)가 DDoS 방어를 위해 권장되지만, 비대칭 라우팅이나 복잡한 멀티홈 환경이면 loose mode(2)가 더 적절할 수 있다고 설명합니다.
| 항목 | 값 | 의미 | 주의점 |
|---|---|---|---|
rp_filter |
0 | source 검증 안 함 | 기본값. 배포판이 부팅 스크립트에서 바꿀 수 있음 |
rp_filter |
1 | strict reverse path | 비대칭 경로면 정상 트래픽 오탐 가능 |
rp_filter |
2 | loose reverse path | strict보다 느슨하지만 멀티홈 환경에 현실적 |
arp_filter |
0/1 | 라우팅 결과에 맞는 인터페이스만 ARP 응답 | 같은 서브넷에 여러 NIC가 있을 때 특히 중요 |
# 스푸핑 방어 시작점
sysctl -w net.ipv4.conf.all.rp_filter=1
sysctl -w net.ipv4.conf.default.rp_filter=1
# 비대칭 경로 / 멀티홈이면 loose mode 고려
sysctl -w net.ipv4.conf.eth0.rp_filter=2
# 같은 서브넷 다중 NIC 환경
sysctl -w net.ipv4.conf.all.arp_filter=1
# 매우 중요한 피어는 정적 neighbor 사용도 검토
ip neigh add 10.0.0.1 lladdr aa:bb:cc:dd:ee:ff nud permanent dev eth0
관측 지표: 어디가 먼저 무너지는지 바로 보이게 만들기
방어는 규칙보다 계측이 먼저입니다. 카운터가 없으면 "좋아진 것 같다" 수준에서 멈춥니다. 커널에서 바로 볼 수 있는 지표를 병목별로 묶으면 다음과 같습니다.
| 영역 | 확인 명령 | 의미 |
|---|---|---|
| NIC / 드라이버 | ethtool -S eth0 |
rx_packets, rx_dropped, ring overflow, queue 편중 여부 확인 |
| softnet | cat /proc/net/softnet_stat |
softirq backlog가 밀리는지 확인 |
| TCP backlog | nstat -az TcpExtSyncookiesSent TcpExtListenOverflows TcpExtTCPReqQFullDoCookies TcpExtTCPReqQFullDrop |
syncookie 발동, accept queue overflow, SYN request queue 포화 확인 |
| listener 상태 | ss -lnt |
Recv-Q/Send-Q로 accept queue 압력을 간접 확인 |
| conntrack | conntrack -S, cat /proc/sys/net/netfilter/nf_conntrack_count |
전역 흐름 테이블 사용량과 drop 통계 |
| Netfilter | nft list ruleset -a |
rule counter와 set hit 수 확인 |
| XDP | bpftool prog show, perf record -a -e xdp:xdp_redirect_err -e xdp:xdp_exception |
XDP 예외, redirect 실패, 프로그램 부착 상태 확인 |
# 1. SYN Flood / listener 관측
nstat -az TcpExtSyncookiesSent TcpExtSyncookiesRecv TcpExtSyncookiesFailed \
TcpExtListenOverflows TcpExtListenDrops \
TcpExtTCPReqQFullDoCookies TcpExtTCPReqQFullDrop
# 2. conntrack 압력
cat /proc/sys/net/netfilter/nf_conntrack_count
cat /proc/sys/net/netfilter/nf_conntrack_max
conntrack -S
# 3. 소프트웨어 수신 큐 병목
watch -n 1 'cat /proc/net/softnet_stat'
# 4. XDP redirect / 예외 추적
perf record -a -e xdp:xdp_redirect_err -e xdp:xdp_redirect_map_err -e xdp:xdp_exception -e xdp:xdp_devmap_xmit
# 5. 드라이버 카운터
watch -n 1 'ethtool -S eth0 | grep -E "rx_packets|rx_dropped|rx_errors|rx_missed"'
SyncookiesSent만 오르고ListenOverflows는 잠잠하면 우선 SYN request queue 압력부터 의심합니다.ListenOverflows와ListenDrops가 같이 오르면 애플리케이션의 accept 속도나 backlog가 더 큰 문제일 수 있습니다.nf_conntrack_count가 빠르게 치솟는데 NIC 드롭은 없으면 stateful firewall/NAT가 공격의 실제 병목일 가능성이 큽니다.
사고 대응 절차
- 공격 형태를 분리합니다. bps보다 pps, TCP SYN 비율, UDP 대상 포트, ICMP 타입, 소스 분산 정도를 먼저 봅니다.
- 병목 카운터를 고정합니다.
softnet_stat,nstat,nf_conntrack_count,ethtool -S중 어디가 가장 먼저 증가하는지 찍습니다. - 차단 계층을 앞당깁니다. input 훅에서 버티지 말고 XDP, raw, prerouting으로 옮길 수 있으면 옮깁니다.
- 상태를 줄입니다. SYNPROXY, syncookies, notrack, timeout 단축으로 메모리 체류 시간을 줄입니다.
- 정상 트래픽 손실을 검증합니다. health check, 실제 사용자 경로, TLS handshake, DNS 질의 등 핵심 정상 트래픽을 동시에 확인합니다.
- 임시 규칙을 영속 정책으로 정리합니다. 공격 중 급히 넣은 drop rule은 사후에 범위, 만료 시간, 모니터링과 함께 다시 설계합니다.
권장 하드닝 체크리스트
- SYN 서비스는
somaxconn, 애플리케이션 backlog,tcp_max_syn_backlog,tcp_syncookies를 함께 점검합니다. - 대량 PPS 방어는 가능하면 XDP 또는 그에 준하는 더 앞단 장비로 옮깁니다.
- NIC queue가 CPU보다 적거나 한 queue만 과열되면 RSS만 보지 말고 RPS, RFS, Flow Limit을 함께 검토합니다.
- nftables에서는 set/meter/counter를 적극 사용하고, state가 필요 없는 흐름만 제한적으로 notrack 합니다.
nf_conntrack_count와nf_conntrack_max는 반드시 메트릭으로 수집합니다.- fragment를 실제로 쓰는 서비스가 적다면
ipfrag_*,ip6frag_*,nf_conntrack_frag6_*값과IpReasm*카운터를 같이 관찰합니다. - 비대칭 경로가 아니라면
rp_filter=1을 기본으로 검토하고, 멀티홈이면2로 완화합니다. - Slowloris 대응은 커널보다 애플리케이션 timeout과 reverse proxy 정책이 주력임을 문서화합니다.
nstat의SyncookiesSent,ListenOverflows,TCPReqQFullDoCookies를 대시보드에 올립니다.- XDP를 쓰면
xdp_exception,xdp_redirect_errtracepoint까지 수집해 조용한 실패를 막습니다.
SYN Flood 커널 처리 경로와 syncookie 메커니즘
SYN Flood 공격이 커널 내부에서 어떤 함수 경로를 타는지 정확히 아는 것이 방어의 시작입니다. TCP 리스너로 SYN 패킷이 도착하면 tcp_v4_rcv() → tcp_v4_do_rcv() → tcp_rcv_state_process() → tcp_conn_request() 경로를 거칩니다. 이 경로에서 tcp_syn_flood_action()이 syncookie 발동 여부를 결정하는 핵심 게이트입니다.
/* net/ipv4/tcp_input.c — tcp_syn_flood_action() 핵심 로직 */
static bool tcp_syn_flood_action(const struct sock *sk,
const char *proto)
{
const char *msg = "Sending cookies";
bool want_cookie = false;
struct listen_sock *lopt;
/* sysctl tcp_syncookies 값 확인 */
if (READ_ONCE(net->ipv4.sysctl_tcp_syncookies) != 2) {
/* 2가 아니면(=1) overflow 시에만 syncookie 발동 */
lopt = inet_csk(sk)->icsk_accept_queue.listen_opt;
if (!lopt->qlen_young)
return false;
}
/* syncookies=2 이면 항상 cookie 모드 */
want_cookie = true;
NET_INC_STATS(net, LINUX_MIB_TCPREQQFULLDOCOOKIES);
/* rate-limited 경고 로그 출력 */
if (!net->ipv4.sysctl_tcp_syncookies)
msg = "Dropping request";
net_info_ratelimited("%s: Possible SYN flooding on port %d. %s.\n",
proto, sk->sk_num, msg);
return want_cookie;
}
코드 설명
- 9-13행
sysctl_tcp_syncookies가 1이면 overflow 시에만, 2면 항상 syncookie를 발동합니다. 0이면 완전 비활성화되어 drop만 합니다. - 17-18행
LINUX_MIB_TCPREQQFULLDOCOOKIES카운터가 증가합니다.nstat의TcpExtTCPReqQFullDoCookies로 관측됩니다. - 21-24행syncookies가 비활성화된 상태에서 SYN backlog가 차면 "Dropping request" 메시지가 dmesg에 rate-limited로 출력됩니다.
tcp_syncookies=2는 항상 cookie 모드를 강제하므로 TCP 옵션(window scale, SACK, timestamp) 협상이 제한됩니다. 커널 문서도 이 모드는 테스트 또는 극단적 상황에서만 사용하라고 경고합니다.
| sysctl 값 | 동작 | TCPReqQFullDoCookies | TCPReqQFullDrop |
|---|---|---|---|
tcp_syncookies=0 |
syncookie 비활성화, SYN backlog 초과 시 drop | 증가 안 함 | 증가 |
tcp_syncookies=1 |
SYN backlog overflow 시에만 cookie 발동 (권장) | overflow 시 증가 | 증가 안 함 (cookie로 대체) |
tcp_syncookies=2 |
항상 cookie 모드 강제 | 모든 SYN에서 증가 | 증가 안 함 |
/* net/ipv4/syncookies.c — cookie_v4_init_sequence() 핵심 */
__u32 cookie_v4_init_sequence(const struct sk_buff *skb,
__u16 *mssp)
{
const struct iphdr *iph = ip_hdr(skb);
const struct tcphdr *th = tcp_hdr(skb);
int mssind;
/* MSS를 4개 슬롯 중 하나로 양자화 */
const __u16 msstab[] = { 536, 1300, 1440, 1460 };
mssind = cookie_mss_index(*mssp, msstab);
/* ISN = HMAC(saddr, daddr, sport, dport, count) | mssind */
return secure_tcp_syn_cookie(iph->saddr, iph->daddr,
th->source, th->dest,
ntohl(th->seq),
mssind);
}
SYN Flood 관측과 대응 순서
SYN Flood 공격이 의심될 때 커널 카운터를 읽는 순서가 중요합니다. 먼저 SyncookiesSent가 증가하는지 확인하고, 다음으로 ListenOverflows와 ListenDrops를 비교합니다. 두 값이 함께 오르면 accept queue 문제이고, SyncookiesSent만 오르면 SYN backlog 문제입니다.
# SYN Flood 단계별 진단 순서
# 1단계: syncookie 발동 여부 확인
nstat -az TcpExtSyncookiesSent TcpExtSyncookiesRecv TcpExtSyncookiesFailed
# SyncookiesSent > 0 → SYN backlog overflow 발생 중
# SyncookiesFailed > 0 → 위조된 ACK나 만료된 cookie
# 2단계: accept queue overflow 확인
nstat -az TcpExtListenOverflows TcpExtListenDrops
# ListenOverflows > 0 → accept queue가 가득 참
# 애플리케이션의 accept() 속도가 느린 것
# 3단계: 어느 리스너가 압력을 받는지 확인
ss -lnt | awk 'NR>1 && $2>0 {print}'
# Recv-Q > 0 인 리스너가 accept queue 압력을 받는 것
# 4단계: SYN 소스 분포 확인
conntrack -L -p tcp --state SYN_RECV 2>/dev/null | \
awk '{for(i=1;i<=NF;i++) if($i~/^src=/) print $i}' | \
sort | uniq -c | sort -rn | head -20
# 5단계: 대응 — backlog 확대 + syncookie 활성 확인
sysctl -w net.ipv4.tcp_max_syn_backlog=16384
sysctl -w net.core.somaxconn=16384
sysctl -w net.ipv4.tcp_syncookies=1
| 관측 패턴 | 의미 | 대응 |
|---|---|---|
SyncookiesSent 증가, ListenOverflows 0 |
SYN backlog만 포화, accept은 여유 | tcp_max_syn_backlog 확대, tcp_synack_retries 축소 |
SyncookiesSent 증가, ListenOverflows 증가 |
SYN backlog와 accept queue 둘 다 포화 | 앱 accept 속도 개선, somaxconn 확대, SYNPROXY 검토 |
SyncookiesSent 0, ListenOverflows 증가 |
정상 연결이 많아 accept queue만 밀림 | 앱 worker 수 증가, reverse proxy 도입 |
SyncookiesFailed 높음 |
위조 ACK 또는 cookie 만료 | 공격 소스 차단, tcp_synack_retries 축소 |
request_sock 메모리 구조
SYN Flood가 노리는 request_sock는 per-listener 해시 테이블에 저장됩니다. 각 request_sock는 약 256바이트를 차지하며, tcp_max_syn_backlog 개수만큼 할당될 수 있습니다. 공격 시 이 메모리와 해시 조회 비용이 동시에 증가합니다.
/* include/net/request_sock.h — request_sock 핵심 필드 */
struct request_sock {
struct sock_common __req_common;
struct request_sock *dl_next;
u16 mss;
u8 num_retrans;
u8 syncookie:1; /* cookie 경로 여부 */
u8 num_timeout:7;
u32 ts_recent;
struct timer_list rsk_timer; /* SYN+ACK 재전송 타이머 */
const struct request_sock_ops *rsk_ops;
struct sock *sk; /* 부모 listen socket */
struct saved_syn *saved_syn;
};
/* 공격 시 request_sock 해시 체인이 길어지면:
* 1. 조회 시간 O(n) 증가
* 2. SYN+ACK 재전송 타이머가 CPU를 소모
* 3. 메모리 사용량 = max_syn_backlog × sizeof(request_sock) */
SYNPROXY 아키텍처와 conntrack 연동
SYNPROXY는 방화벽이 백엔드 대신 TCP 3-way handshake를 먼저 완료하는 방어 기법입니다. 커널의 nf_synproxy_core 모듈이 핵심이며, Netfilter의 UNTRACKED 상태와 conntrack을 정교하게 연동합니다. 백엔드 서버는 검증된 연결만 받으므로 SYN backlog와 request_sock가 완전히 보호됩니다.
# SYNPROXY 전체 설정 예시 (nftables)
# 1단계: 전제 조건 — tcp_loose 비활성화
sysctl -w net.netfilter.nf_conntrack_tcp_loose=0
# 2단계: raw 테이블에서 SYN을 NOTRACK 처리
nft add table ip raw
nft 'add chain ip raw prerouting { type filter hook prerouting priority raw; policy accept; }'
nft add rule ip raw prerouting tcp dport 80 tcp flags syn notrack
# 3단계: filter 테이블에서 SYNPROXY 적용
nft add table ip filter
nft 'add chain ip filter input { type filter hook input priority filter; policy accept; }'
nft add rule ip filter input ct state invalid,untracked \
synproxy mss 1460 wscale 9 timestamp sack-perm
nft add rule ip filter input ct state invalid drop
# 4단계: 검증 확인
conntrack -L -p tcp --state SYN_SENT # 백엔드 방향 SYN만 보여야 정상
nft list ruleset -a # counter 확인
| SYNPROXY 옵션 | 의미 | 설정 시 주의 |
|---|---|---|
mss |
클라이언트에게 알려줄 MSS 값 | 백엔드 서버의 실제 MSS와 일치시켜야 합니다 |
wscale |
window scale 팩터 | 백엔드의 tcp_window_scaling 설정과 맞춰야 합니다 |
timestamp |
TCP timestamp 옵션 사용 | 백엔드가 timestamp를 사용하지 않으면 빼야 합니다 |
sack-perm |
SACK 허용 옵션 | 백엔드가 SACK을 지원해야 합니다 |
nf_conntrack_max를 충분히 높이고, SYNPROXY가 보호하는 포트 범위를 좁게 유지해야 합니다. 모든 포트에 SYNPROXY를 거는 것은 비효율적입니다.
rp_filter: Reverse Path Filtering으로 스푸핑 차단
rp_filter(Reverse Path Filter)는 수신 패킷의 소스 주소가 해당 인터페이스로 라우팅 가능한지 검증하여 IP 스푸핑을 차단합니다. RFC 3704의 BCP 38을 커널에서 구현한 것으로, strict 모드와 loose 모드의 동작 차이를 정확히 이해해야 비대칭 라우팅 환경에서 오탐을 피할 수 있습니다.
/* net/ipv4/fib_frontend.c — __fib_validate_source() 핵심 로직 */
static int __fib_validate_source(struct sk_buff *skb,
__be32 src, __be32 dst,
u8 tos, int oif,
struct net_device *dev,
int rpf, struct in_device *idev)
{
struct fib_result res;
int ret;
/* src 주소로 FIB lookup 수행 */
ret = fib_lookup(net, &fl4, &res, 0);
if (ret)
goto last_resort; /* 경로 없음 → drop */
/* strict mode: 수신 dev와 FIB 결과 dev 비교 */
if (rpf == 1) {
if (res.fi->fib_dev != dev)
ret = -EXDEV; /* 인터페이스 불일치 → drop */
}
/* loose mode: 경로 존재 여부만 확인 (dev 비교 안 함) */
return ret;
}
rp_filter=2를 사용하고, 스위치/라우터 레벨의 uRPF(Unicast RPF)와 조합하는 것이 현실적입니다.
| rp_filter 값 | 검증 기준 | 적합한 환경 | 주의점 |
|---|---|---|---|
0 (비활성화) |
검증 안 함 | 스푸핑 방어 불필요한 내부망 | 외부 인터페이스에서는 권장하지 않음 |
1 (strict) |
수신 인터페이스 = FIB best 경로 | 단일 업링크, 대칭 라우팅 | 비대칭 경로 시 오탐, VRF 사용 시 주의 |
2 (loose) |
FIB에 소스 경로 존재 여부만 | 멀티홈, BGP, 비대칭 라우팅 | 같은 AS 내 스푸핑은 못 잡음 |
rp_filter는 conf.all과 인터페이스별 값 중 큰 값이 적용됩니다. 즉 conf.all.rp_filter=1이면 개별 인터페이스에서 0으로 설정해도 strict가 적용됩니다. 이 동작을 모르면 비대칭 라우팅 문제 해결이 안 됩니다.
rp_filter 실전 설정 예시
# 시나리오 1: 단일 업링크 서버 — strict 권장
sysctl -w net.ipv4.conf.all.rp_filter=1
sysctl -w net.ipv4.conf.default.rp_filter=1
# 시나리오 2: BGP 멀티홈 — loose 사용
sysctl -w net.ipv4.conf.all.rp_filter=0 # all을 0으로 해야 개별 인터페이스 설정 가능
sysctl -w net.ipv4.conf.eth0.rp_filter=2 # 외부 인터페이스는 loose
sysctl -w net.ipv4.conf.eth1.rp_filter=2 # 두 번째 업링크도 loose
sysctl -w net.ipv4.conf.eth2.rp_filter=1 # 내부 인터페이스는 strict
# 시나리오 3: VRF 환경 — VRF master에서 설정
sysctl -w net.ipv4.conf.vrf-mgmt.rp_filter=1
sysctl -w net.ipv4.conf.vrf-prod.rp_filter=2
# 드롭 카운터 확인 (rp_filter에 의한 드롭)
nstat -az IpInAddrErrors # rp_filter 드롭 시 증가
# 진단: 특정 소스가 rp_filter에 걸리는지 확인
ip route get 10.0.1.5 from 0.0.0.0 iif eth0 # FIB 경로 확인
| 관련 sysctl | 기본값 | 보안 효과 |
|---|---|---|
accept_source_route |
0 | IP 소스 라우팅 옵션 거부 — 스푸핑 경로 조작 방지 |
accept_redirects |
1 | ICMP redirect 수신 허용. 서버에서는 0 권장 — 라우팅 조작 방지 |
send_redirects |
1 | ICMP redirect 송신. 라우터가 아닌 호스트에서는 0 권장 |
log_martians |
0 | 비정상 소스 주소 패킷 로그. 진단 시 1 활성화 |
secure_redirects |
1 | 게이트웨이 목록에 있는 곳에서 온 redirect만 수용 |
rp_filter가 기본적으로 지원되지 않습니다. 대신 accept_ra=0(서버), accept_redirects=0, nftables에서 소스 주소 검증 규칙을 명시적으로 추가해야 합니다. IPv6 NDP spoofing은 RA Guard, SEND(Secure Neighbor Discovery)로 대응합니다.
TCP 보안 파라미터 하드닝
TCP 스택의 보안 관련 sysctl 파라미터는 개별적으로 보면 간단하지만, 조합에 따라 보안과 성능에 큰 차이가 납니다. 여기서는 tcp_syncookies, tcp_timestamps, tcp_rfc1337, tcp_tw_reuse 등의 상호작용을 정리합니다.
| 파라미터 | 기본값 | 보안 권장 | 상세 설명 |
|---|---|---|---|
tcp_timestamps |
1 | 1 (활성화 유지) | PAWS(Protection Against Wrapped Sequences)에 필수입니다. 끄면 고속 연결에서 시퀀스 번호 충돌 위험이 커집니다. syncookie도 timestamp 비트를 활용합니다. |
tcp_rfc1337 |
0 | 1 (활성화) | RFC 1337의 TIME-WAIT 암살 문제를 방지합니다. TIME_WAIT 상태의 소켓이 RST를 받아도 즉시 닫지 않고 2MSL을 유지합니다. |
tcp_tw_reuse |
2 | 상황에 따라 | 0=비활성, 1=outgoing 연결에서 TIME_WAIT 재사용, 2=loopback만 재사용. timestamp가 활성화되어야 안전하게 동작합니다. |
tcp_challenge_ack_limit |
1000 | 충분히 높게 | CVE-2016-5696 공격 대응. 초당 challenge ACK 수를 제한하여 off-path 공격자가 연결 상태를 추측하는 것을 어렵게 합니다. |
tcp_synack_retries |
5 | 2-3 | SYN+ACK 재전송 횟수를 줄이면 SYN Flood 시 반개방 연결의 메모리 체류 시간이 단축됩니다. |
tcp_fin_timeout |
60 | 30 | FIN_WAIT_2 상태의 최대 대기 시간. 짧게 줄이면 소켓 자원 회수가 빨라지지만 느린 클라이언트에 영향을 줄 수 있습니다. |
# TCP 보안 하드닝 권장 설정
sysctl -w net.ipv4.tcp_syncookies=1
sysctl -w net.ipv4.tcp_timestamps=1
sysctl -w net.ipv4.tcp_rfc1337=1
sysctl -w net.ipv4.tcp_synack_retries=3
sysctl -w net.ipv4.tcp_max_syn_backlog=8192
sysctl -w net.core.somaxconn=8192
sysctl -w net.ipv4.tcp_fin_timeout=30
sysctl -w net.ipv4.tcp_max_tw_buckets=200000
# challenge ACK limit — CVE-2016-5696 대응
sysctl -w net.ipv4.tcp_challenge_ack_limit=999999999
# tcp_tw_reuse — timestamp와 함께 사용해야 안전
sysctl -w net.ipv4.tcp_tw_reuse=2 # loopback만 재사용 (기본값)
# 확인
sysctl -a | grep -E 'tcp_(syncookies|timestamps|rfc1337|synack_retries|max_syn|tw_reuse|challenge_ack|fin_timeout)'
Netfilter 보안 기능과 conntrack 상태 추적
Netfilter는 단순한 패킷 필터가 아니라 conntrack 상태 머신, ct helper(ALG), set/meter, flowtable 등 다양한 보안 기능을 제공합니다. 이 기능들을 보안 관점에서 올바르게 사용하려면 각 기능의 공격 표면과 성능 영향을 이해해야 합니다.
| conntrack 상태 | 의미 | 보안 정책 |
|---|---|---|
NEW |
새 연결의 첫 패킷 | 서비스 포트별로 선택적 허용 |
ESTABLISHED |
양방향 패킷이 관찰된 연결 | 기본 허용 (fast path) |
RELATED |
기존 연결과 관련된 새 연결 (ICMP 오류, ct helper) | 허용하되 helper 범위 제한 필요 |
INVALID |
어떤 알려진 연결에도 속하지 않는 패킷 | 반드시 drop — 스캔, 잘못된 상태 전이, 조작된 패킷 |
UNTRACKED |
raw 테이블에서 notrack 처리된 패킷 | SYNPROXY 흐름 또는 stateless 서비스에 제한적 사용 |
# ct helper 보안: 자동 할당 비활성화 (명시적 할당만 허용)
sysctl -w net.netfilter.nf_conntrack_helper=0
# 명시적 ct helper 할당 (nftables)
nft add ct helper inet filter ftp-helper '{ type "ftp" protocol tcp; }'
nft add rule inet filter input tcp dport 21 ct helper set ftp-helper
# INVALID 패킷 반드시 drop
nft add rule inet filter input ct state invalid drop
nft add rule inet filter forward ct state invalid drop
# 서비스별 ct timeout 분리
nft add ct timeout inet filter aggressive-timeout '{ protocol udp; l3proto ip; policy = { unreplied : 10, replied : 30 }; }'
nft add rule inet filter prerouting udp dport 53 ct timeout set aggressive-timeout
# INVALID 로그 활성화 (디버깅용)
sysctl -w net.netfilter.nf_conntrack_log_invalid=6 # TCP만
nf_conntrack_helper=1(자동 할당)이면 FTP, SIP, IRC 등의 helper가 패킷 내용을 파싱하여 동적으로 포트를 열어줍니다. 이는 공격자가 조작된 패킷으로 의도하지 않은 포트를 열 수 있는 공격 표면이 됩니다. 반드시 nf_conntrack_helper=0으로 설정하고 필요한 helper만 명시적으로 할당하세요.
DDoS 완화 커널 경로: rate limiting, early drop, XDP 방어
DDoS 공격을 커널에서 완화하는 전략은 가능한 한 앞단에서, 가능한 한 적은 비용으로 드롭하는 것입니다. XDP → TC ingress → raw/prerouting → filter/input 순서로 방어 계층을 구성하면, 각 계층에서 걸러지지 않은 트래픽만 다음 계층으로 넘어갑니다.
/* XDP에서 SYN cookie를 직접 처리하는 패턴 (의사 코드) */
SEC("xdp")
int xdp_syn_cookie(struct xdp_md *ctx)
{
struct ethhdr *eth;
struct iphdr *iph;
struct tcphdr *th;
if (!parse_headers(ctx, ð, &iph, &th))
return XDP_PASS;
/* SYN만 처리 (SYN+ACK, ACK는 통과) */
if (!(th->syn && !th->ack))
return XDP_PASS;
/* per-source SYN rate 확인 */
struct rate_info *ri = bpf_map_lookup_elem(&syn_rate, &iph->saddr);
if (ri && ri->count > SYN_RATE_LIMIT)
return XDP_DROP;
/* XDP에서 SYN+ACK 생성 (bpf_xdp_adjust_head 사용) */
/* cookie ISN 부호화: hash(saddr,daddr,sport,dport,secret) */
generate_syn_ack_cookie(ctx, eth, iph, th);
return XDP_TX; /* SYN+ACK를 같은 인터페이스로 전송 */
}
nf_conntrack_max에 근접하면 커널은 자동으로 비보증(unconfirmed) 엔트리부터 제거합니다. 이 메커니즘은 nf_conntrack_max를 올바르게 설정했을 때만 유효하며, 공격 시 정상 연결이 함께 제거될 수 있으므로 서비스별 ct timeout 분리가 중요합니다.
XDP SYN cookie vs 커널 syncookie 비교
최근 커널(5.19+)에서는 XDP 레벨에서 직접 SYN cookie를 처리하는 기능이 추가되었습니다. 이는 기존 TCP 스택의 syncookie와 비교하여 sk_buff 생성 자체를 회피하므로 훨씬 높은 PPS를 처리할 수 있습니다.
| 비교 항목 | 커널 syncookie | XDP SYN cookie |
|---|---|---|
| 처리 위치 | TCP 스택 (tcp_conn_request) | XDP 드라이버 수준 |
| sk_buff 생성 | 필요 (생성 후 처리) | 불필요 (xdp_md에서 직접 처리) |
| 처리 성능 | 수백만 pps 수준 | 수천만 pps 수준 |
| TCP 옵션 지원 | MSS (4단계), timestamp (제한적) | 구현에 따라 다름 (보통 더 제한적) |
| conntrack 영향 | conntrack 이후에 동작 | conntrack 이전에 동작 (conntrack 부하 감소) |
| 설정 복잡도 | sysctl 하나 (tcp_syncookies=1) | BPF 프로그램 작성/배포 필요 |
| 적합한 상황 | 일반 서버, 즉시 대응 | 대규모 DDoS, 전용 방어 호스트 |
DDoS 방어 계층별 비용 분석
각 방어 계층에서 패킷 하나를 처리하는 데 소요되는 CPU 사이클과 메모리 접근 횟수는 크게 다릅니다. 이 차이를 이해하면 방어 규칙의 최적 배치를 결정할 수 있습니다.
| 계층 | per-packet 시간(추정) | 메모리 접근 | 가능한 작업 | 제약 |
|---|---|---|---|---|
| NIC 하드웨어 필터 | 0 (회선 속도) | 없음 | 5-tuple 매칭, VLAN, MAC 필터 | 규칙 수 매우 제한적 |
| XDP (native) | ~10-50ns | 최소 (L2/L3 헤더) | 해시, map lookup, 헤더 파싱, TX | sk_buff 없음, conntrack 없음 |
| TC ingress | ~50-100ns | sk_buff 생성 | BPF classifier, redirect, mark | XDP보다 약간 늦지만 유연 |
| nftables raw | ~100-300ns | nftables VM 실행 | set, meter, notrack, stateless 매칭 | conntrack 이전, stateful 불가 |
| nftables filter + ct | ~300-1000ns | conntrack 해시 + ct 생성 | ct state, SYNPROXY, ct helper | conntrack 메모리/CPU 비용 |
| Socket/App | ~1-10us+ | 소켓 할당, 앱 파싱 | L7 검사, 앱 로직 | 가장 비쌈, 최후 방어선 |
# nftables 동적 차단 리스트 (meter + set)
nft add table inet ddos
nft 'add chain inet ddos input { type filter hook input priority filter; policy accept; }'
# per-source SYN rate limit: 초당 100개 초과 시 drop
nft add rule inet ddos input tcp flags syn \
meter syn_flood '{ ip saddr timeout 30s limit rate over 100/second }' drop
# 동적 차단 set: 과도한 소스를 자동으로 차단 리스트에 추가
nft add set inet ddos blocklist '{ type ipv4_addr; timeout 5m; }'
nft add rule inet ddos input ip saddr @blocklist drop
nft add rule inet ddos input tcp flags syn \
meter syn_detect '{ ip saddr timeout 10s limit rate over 500/second }' \
add @blocklist '{ ip saddr timeout 5m }'
# 확인
nft list set inet ddos blocklist
nft list meter inet ddos syn_flood
네트워크 네임스페이스와 컨테이너 네트워크 격리
네트워크 네임스페이스는 Linux 커널의 핵심 격리 메커니즘으로, 각 네임스페이스가 독립적인 네트워크 스택(인터페이스, 라우팅 테이블, iptables/nftables 규칙, conntrack 테이블, 소켓)을 가집니다. 컨테이너 환경에서는 이 격리가 보안의 기본이지만, veth pair를 통한 통신과 bridge 구성에서 보안 허점이 생길 수 있습니다.
| 격리 요소 | 네임스페이스 별 독립 | 보안 주의점 |
|---|---|---|
| 네트워크 인터페이스 | 예 (각 NS에 자체 인터페이스) | veth pair 반대쪽이 호스트 bridge에 연결되면 L2 통신 가능 |
| 라우팅 테이블 | 예 | 호스트의 ip_forward 설정이 NS 간 라우팅에 영향 |
| nftables/iptables | 예 | 호스트 FORWARD 체인이 NS 간 트래픽을 제어 |
| conntrack | 예 (커널 4.7+, net.netfilter.nf_conntrack_max per-NS) | 한 NS의 conntrack 고갈이 다른 NS에 영향 안 줌 |
| 소켓 | 예 | CAP_NET_RAW 없으면 raw 소켓 사용 불가 |
| sysctl (net.*) | 부분적 (네트워크 관련 sysctl은 NS별 독립) | 일부 전역 sysctl은 init NS에서만 변경 가능 |
# 컨테이너 네임스페이스 보안 설정 예시
# 1. 호스트에서 컨테이너 간 bridge 트래픽 제어
sysctl -w net.bridge.bridge-nf-call-iptables=1
sysctl -w net.bridge.bridge-nf-call-ip6tables=1
# 2. 컨테이너 NS 내부 하드닝
ip netns exec container1 sysctl -w net.ipv4.conf.all.rp_filter=1
ip netns exec container1 sysctl -w net.ipv4.conf.all.accept_redirects=0
ip netns exec container1 sysctl -w net.ipv4.conf.all.send_redirects=0
ip netns exec container1 sysctl -w net.ipv4.icmp_echo_ignore_broadcasts=1
# 3. veth에 BPF 정책 적용 (TC ingress)
tc qdisc add dev veth-host-1 clsact
tc filter add dev veth-host-1 ingress bpf da obj container_policy.o sec classifier
# 4. 컨테이너별 conntrack 제한
ip netns exec container1 sysctl -w net.netfilter.nf_conntrack_max=65536
# 5. 네임스페이스 확인
ip netns list
ip netns exec container1 ip link show
ip netns exec container1 ss -lnt
컨테이너 탈출 방지와 네트워크 격리 강화
컨테이너 네트워크 격리는 네임스페이스만으로 완전하지 않습니다. capability 제한, seccomp 필터, AppArmor/SELinux 정책이 함께 적용되어야 네트워크 관련 syscall 남용을 막을 수 있습니다.
| 위협 | 공격 벡터 | 방어 수단 |
|---|---|---|
| raw 소켓 스니핑 | CAP_NET_RAW로 같은 NS 내 트래픽 캡처 | CAP_NET_RAW 제거 (Docker: --cap-drop NET_RAW) |
| 네트워크 규칙 변경 | CAP_NET_ADMIN으로 nftables/라우팅 조작 | CAP_NET_ADMIN 제거 또는 seccomp 필터 |
| NS 간 통신 우회 | bridge 경유 L2 직접 통신 | bridge-nf-call-iptables + FORWARD 정책 |
| 호스트 네트워크 접근 | --network=host 사용 |
프로덕션에서 host 네트워크 모드 금지 |
| conntrack 고갈 공격 | 한 컨테이너가 대량 연결 생성 | NS별 nf_conntrack_max 제한 |
# Docker 컨테이너 네트워크 보안 실행 예시
docker run --cap-drop ALL --cap-add NET_BIND_SERVICE \
--security-opt no-new-privileges \
--sysctl net.ipv4.conf.all.rp_filter=1 \
--sysctl net.ipv4.conf.all.accept_redirects=0 \
--sysctl net.ipv4.icmp_echo_ignore_broadcasts=1 \
--network bridge \
my-app:latest
# Kubernetes NetworkPolicy 예시 (YAML)
# apiVersion: networking.k8s.io/v1
# kind: NetworkPolicy
# spec:
# podSelector: {matchLabels: {app: web}}
# policyTypes: [Ingress, Egress]
# ingress:
# - from: [{podSelector: {matchLabels: {app: frontend}}}]
# ports: [{protocol: TCP, port: 8080}]
소켓 보안: SO_BINDTODEVICE, SO_MARK, cgroup BPF
소켓 수준의 보안 옵션은 네트워크 정책을 프로세스와 연결 단위로 적용할 수 있게 합니다. SO_BINDTODEVICE로 특정 인터페이스에만 바인딩하고, SO_MARK로 패킷에 fwmark를 설정하여 nftables/라우팅 정책과 연동하며, cgroup BPF로 프로세스 그룹별 네트워크 접근을 제어합니다.
/* SO_BINDTODEVICE 사용 예시 */
int bind_to_device(int sockfd, const char *ifname)
{
/* CAP_NET_RAW 권한 필요 */
return setsockopt(sockfd, SOL_SOCKET, SO_BINDTODEVICE,
ifname, strlen(ifname) + 1);
}
/* SO_MARK 사용 예시 */
int set_fwmark(int sockfd, uint32_t mark)
{
/* CAP_NET_ADMIN 권한 필요 */
return setsockopt(sockfd, SOL_SOCKET, SO_MARK,
&mark, sizeof(mark));
}
/* cgroup BPF: 소켓 연결 제어 (의사 코드) */
SEC("cgroup/connect4")
int cg_connect4(struct bpf_sock_addr *ctx)
{
/* 특정 IP 대역으로의 연결만 허용 */
if ((ctx->user_ip4 & bpf_htonl(0xFFFF0000)) !=
bpf_htonl(0x0A000000)) /* 10.0.0.0/16만 허용 */
return 0; /* 차단 */
return 1; /* 허용 */
}
| 소켓 옵션 | 권한 | 용도 | 보안 효과 |
|---|---|---|---|
SO_BINDTODEVICE |
CAP_NET_RAW | 특정 인터페이스에만 소켓 바인딩 | 관리 네트워크 격리, VRF 바인딩 |
SO_MARK |
CAP_NET_ADMIN | 패킷에 fwmark 설정 | 정책 라우팅, nftables 매칭, QoS 분류 |
SO_INCOMING_CPU |
없음 (읽기 전용) | 패킷 수신 CPU 확인 | RFS/NAPI 최적화 모니터링 |
SO_PRIORITY |
CAP_NET_ADMIN (6 이상) | 패킷 우선순위 설정 | TC qdisc 분류, VLAN PCP 매핑 |
TCP_DEFER_ACCEPT |
없음 | 데이터 수신 전까지 accept 지연 | Slowloris 류 공격 완화 |
SO_MARK로 설정한 fwmark는 ip rule의 fwmark 매칭과 연동됩니다. 예를 들어 관리 트래픽에 mark 100을 설정하고, ip rule add fwmark 100 table mgmt로 별도 라우팅 테이블을 사용하면 관리 네트워크를 서비스 네트워크로부터 완전히 분리할 수 있습니다.
cgroup BPF 네트워크 제어 상세
cgroup BPF는 프로세스 그룹 단위로 네트워크 접근을 제어하는 강력한 메커니즘입니다. BPF_CGROUP_INET_INGRESS, BPF_CGROUP_INET_EGRESS, BPF_CGROUP_INET4_CONNECT, BPF_CGROUP_INET4_BIND 등 다양한 attach point에서 BPF 프로그램을 실행할 수 있습니다.
| attach type | 동작 시점 | 제어 가능 항목 | 사용 예시 |
|---|---|---|---|
BPF_CGROUP_INET_INGRESS |
패킷 수신 시 | 수신 패킷 drop/accept | 특정 cgroup의 인바운드 트래픽 제한 |
BPF_CGROUP_INET_EGRESS |
패킷 송신 시 | 송신 패킷 drop/accept, mark 설정 | 아웃바운드 대역폭 제한, 정책 라우팅 |
BPF_CGROUP_INET4_CONNECT |
connect() 호출 시 | 대상 IP/포트 변경, 차단 | 서비스 메시 투명 프록시, 접근 제어 |
BPF_CGROUP_INET4_BIND |
bind() 호출 시 | 바인드 주소/포트 제어 | 컨테이너의 포트 바인딩 제한 |
BPF_CGROUP_UDP4_SENDMSG |
UDP sendmsg() 시 | 대상 주소 변경, 차단 | DNS 쿼리 리다이렉션 |
BPF_CGROUP_SOCK_OPS |
TCP 이벤트 시 | TCP 파라미터 튜닝 | 서비스별 TCP congestion 알고리즘 변경 |
# cgroup BPF 프로그램 attach 예시
# 1. cgroup 생성
mkdir -p /sys/fs/cgroup/net_restricted
# 2. BPF 프로그램 컴파일
clang -O2 -target bpf -c cgroup_net_policy.c -o cgroup_net_policy.o
# 3. BPF 프로그램 attach
bpftool cgroup attach /sys/fs/cgroup/net_restricted connect4 \
pinned /sys/fs/bpf/cgroup_connect4
# 4. 프로세스를 해당 cgroup에 배치
echo $PID > /sys/fs/cgroup/net_restricted/cgroup.procs
# 5. 현재 attach된 BPF 프로그램 확인
bpftool cgroup show /sys/fs/cgroup/net_restricted
ftrace/bpftrace로 네트워크 보안 모니터링
네트워크 보안 이벤트를 실시간으로 추적하려면 커널의 tracing 인프라를 활용해야 합니다. ftrace의 kprobe/tracepoint와 bpftrace를 사용하면 conntrack 엔트리 생성/삭제, SYN backlog 상태, XDP 드롭, Netfilter 규칙 매칭 등을 프로덕션 환경에서도 안전하게 관찰할 수 있습니다.
# 1. ftrace: tcp_syn_flood_action 호출 추적
echo 1 > /sys/kernel/debug/tracing/events/tcp/tcp_bad_csum/enable
echo 'p:syn_flood tcp_syn_flood_action' > /sys/kernel/debug/tracing/kprobe_events
echo 1 > /sys/kernel/debug/tracing/events/kprobes/syn_flood/enable
cat /sys/kernel/debug/tracing/trace_pipe
# 2. bpftrace: SYN 수신 rate 관측
bpftrace -e '
tracepoint:tcp:tcp_receive_reset {
@rst[args->saddr] = count();
}
interval:s:5 { print(@rst); clear(@rst); }
'
# 3. bpftrace: conntrack 엔트리 생성 추적
bpftrace -e '
kprobe:__nf_conntrack_alloc {
@ct_alloc = count();
}
kprobe:nf_conntrack_free {
@ct_free = count();
}
interval:s:10 {
printf("alloc: %d, free: %d\n", @ct_alloc, @ct_free);
clear(@ct_alloc); clear(@ct_free);
}
'
# 4. bpftrace: XDP 드롭 카운터 추적
bpftrace -e '
tracepoint:xdp:xdp_bulk_tx {
@xdp_act[args->action] = count();
}
interval:s:5 { print(@xdp_act); clear(@xdp_act); }
'
# 5. conntrack 이벤트 실시간 모니터링
conntrack -E -e NEW,DESTROY -p tcp --dport 80
# 6. bpftrace: nftables drop 원인 분석
bpftrace -e '
kprobe:nf_hook_slow {
@hook[arg1] = count();
}
interval:s:10 { print(@hook); clear(@hook); }
'
# 7. perf: Netfilter/conntrack CPU 프로파일링
perf record -g -a -e cycles -- sleep 30
perf report --sort=dso,comm
| 추적 대상 | 도구 | probe/tracepoint | 관찰 지표 |
|---|---|---|---|
| SYN flood 발동 | bpftrace | kprobe:tcp_syn_flood_action |
발동 빈도, 소스 분포 |
| syncookie 발급 | nstat | TcpExtSyncookiesSent |
초당 cookie 발급 수 |
| conntrack 생성/삭제 | conntrack -E | ctnetlink 이벤트 | 엔트리 증감 속도 |
| XDP 액션 | bpftrace | tracepoint:xdp:* |
DROP/PASS/TX/REDIRECT 비율 |
| Netfilter 처리 시간 | perf | CPU 프로파일링 | nf_hook_slow, conntrack lookup 비용 |
| TCP RST 수신 | bpftrace | tracepoint:tcp:tcp_receive_reset |
RST 폭주 감지 |
interval 블록에서 주기적으로 clear()하는 것이 중요합니다.
네트워크 보안 종합 체크리스트
지금까지 다룬 커널 네트워크 보안 설정을 운영 환경에 적용할 때 빠짐없이 확인해야 할 체크리스트입니다. 서버 역할(웹 서버, 라우터/방화벽, 컨테이너 호스트)에 따라 강조되는 항목이 다릅니다.
| 분류 | sysctl / 설정 | 권장값 | 확인 명령 |
|---|---|---|---|
| TCP 하드닝 | net.ipv4.tcp_syncookies |
1 | sysctl net.ipv4.tcp_syncookies |
net.ipv4.tcp_timestamps |
1 | sysctl net.ipv4.tcp_timestamps |
|
net.ipv4.tcp_rfc1337 |
1 | sysctl net.ipv4.tcp_rfc1337 |
|
net.ipv4.tcp_synack_retries |
2-3 | sysctl net.ipv4.tcp_synack_retries |
|
net.ipv4.tcp_fin_timeout |
30 | sysctl net.ipv4.tcp_fin_timeout |
|
| IP 스푸핑 방지 | net.ipv4.conf.all.rp_filter |
1 (비대칭 시 2) | sysctl net.ipv4.conf.all.rp_filter |
net.ipv4.conf.all.accept_redirects |
0 | sysctl net.ipv4.conf.all.accept_redirects |
|
net.ipv4.conf.all.send_redirects |
0 (라우터가 아닌 경우) | sysctl net.ipv4.conf.all.send_redirects |
|
| conntrack 보호 | net.netfilter.nf_conntrack_max |
서비스 규모에 맞게 | sysctl net.netfilter.nf_conntrack_max |
net.netfilter.nf_conntrack_helper |
0 | sysctl net.netfilter.nf_conntrack_helper |
|
net.netfilter.nf_conntrack_tcp_loose |
0 (SYNPROXY 사용 시) | sysctl net.netfilter.nf_conntrack_tcp_loose |
|
| ICMP 보호 | net.ipv4.icmp_echo_ignore_broadcasts |
1 | sysctl net.ipv4.icmp_echo_ignore_broadcasts |
net.ipv4.icmp_ratelimit |
1000 | sysctl net.ipv4.icmp_ratelimit |
|
net.ipv4.icmp_ignore_bogus_error_responses |
1 | sysctl net.ipv4.icmp_ignore_bogus_error_responses |
|
| IPv6 보호 | net.ipv6.conf.all.accept_ra |
0 (서버) / 1 (호스트) | sysctl net.ipv6.conf.all.accept_ra |
net.ipv6.conf.all.accept_redirects |
0 | sysctl net.ipv6.conf.all.accept_redirects |
# 네트워크 보안 종합 검증 스크립트
#!/bin/bash
echo "=== TCP 하드닝 ==="
sysctl net.ipv4.tcp_syncookies net.ipv4.tcp_timestamps \
net.ipv4.tcp_rfc1337 net.ipv4.tcp_synack_retries \
net.ipv4.tcp_fin_timeout net.ipv4.tcp_max_syn_backlog \
net.core.somaxconn
echo "=== IP 스푸핑 방지 ==="
sysctl net.ipv4.conf.all.rp_filter \
net.ipv4.conf.all.accept_redirects \
net.ipv4.conf.all.send_redirects
echo "=== conntrack 상태 ==="
sysctl net.netfilter.nf_conntrack_max \
net.netfilter.nf_conntrack_helper \
net.netfilter.nf_conntrack_tcp_loose
cat /proc/sys/net/netfilter/nf_conntrack_count
echo "=== ICMP 보호 ==="
sysctl net.ipv4.icmp_echo_ignore_broadcasts \
net.ipv4.icmp_ratelimit \
net.ipv4.icmp_ignore_bogus_error_responses
echo "=== TCP backlog 관측 ==="
nstat -az TcpExtSyncookiesSent TcpExtListenOverflows \
TcpExtListenDrops TcpExtTCPReqQFullDoCookies
echo "=== NIC 드롭 ==="
for iface in $(ls /sys/class/net/ | grep -v lo); do
echo "--- $iface ---"
ethtool -S $iface 2>/dev/null | grep -E 'rx_dropped|rx_errors|rx_missed'
done
echo "=== listener 상태 ==="
ss -lnt
/etc/sysctl.d/99-network-security.conf에 영속화하고, 배포 도구(Ansible, Puppet 등)로 관리하는 것을 권장합니다. 수동 sysctl -w는 재부팅 시 초기화됩니다.
sysctl 영속화 템플릿
# /etc/sysctl.d/99-network-security.conf
# 네트워크 보안 하드닝 — 서버 프로파일
# === TCP 하드닝 ===
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_timestamps = 1
net.ipv4.tcp_rfc1337 = 1
net.ipv4.tcp_synack_retries = 3
net.ipv4.tcp_max_syn_backlog = 8192
net.core.somaxconn = 8192
net.ipv4.tcp_fin_timeout = 30
net.ipv4.tcp_max_tw_buckets = 200000
net.ipv4.tcp_challenge_ack_limit = 999999999
# === IP 스푸핑 방지 ===
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1
net.ipv4.conf.all.accept_source_route = 0
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.all.log_martians = 1
# === ICMP 보호 ===
net.ipv4.icmp_echo_ignore_broadcasts = 1
net.ipv4.icmp_ignore_bogus_error_responses = 1
net.ipv4.icmp_ratelimit = 1000
# === conntrack ===
net.netfilter.nf_conntrack_helper = 0
net.netfilter.nf_conntrack_tcp_loose = 0
# === IPv6 ===
net.ipv6.conf.all.accept_redirects = 0
net.ipv6.conf.all.accept_ra = 0
nstat, ethtool -S, conntrack -S)를 비교하고, 정상 트래픽 손실이 없는지 확인한 뒤 다음 항목으로 넘어가세요. 특히 rp_filter와 ct timeout 변경은 비대칭 경로와 장시간 세션에 영향을 줄 수 있습니다.
- TLS handshake가 정상적으로 완료되는지 (
openssl s_client로 테스트) - DNS 질의가 정상 응답하는지 (conntrack timeout 영향 확인)
- 비대칭 경로 서비스가 있다면 rp_filter 설정 후 접근 가능 여부
- 컨테이너 간 통신이 정상인지 (bridge-nf-call 설정 확인)
- health check 프로브가 rate limit에 걸리지 않는지
관련 문서
참고자료
- Linux Kernel Documentation: IP Sysctl —
tcp_syncookies,tcp_max_syn_backlog,somaxconn,icmp_*,rp_filter,arp_filter - Linux Kernel Documentation: Netfilter Conntrack Sysctl —
nf_conntrack_max,nf_conntrack_count, timeout, fragment 메모리 제한 - Linux Kernel Documentation: Scaling in the Linux Networking Stack — RSS, RPS, RFS, XPS, Flow Limit
- Linux Kernel Documentation: XDP Redirect —
XDP_REDIRECT,xdp_do_redirect(),xdp_do_flush(), tracepoint - nftables Manpage —
synproxy,notrack,limit,meter문법 - RFC 4987 — TCP SYN flooding attack와 countermeasure 분석
- RFC 3704 — ingress filtering과 strict/loose reverse path forwarding
- Linux man-pages: tcp(7) —
TCP_DEFER_ACCEPT, TCP socket 옵션