라우팅 (Routing Subsystem)
Linux 커널 라우팅 서브시스템: FIB, Policy Routing, ECMP, VRF, SRv6 심화 분석.
라우팅 서브시스템 개요
Linux 라우팅 서브시스템은 패킷의 목적지 주소를 기반으로 다음 홉(nexthop)을 결정하는 핵심 네트워크 계층입니다. FIB(Forwarding Information Base), 라우팅 규칙(rules), nexthop 객체가 유기적으로 연동되어 패킷의 경로를 결정합니다.
패킷 흐름에서 라우팅의 위치
라우팅 결정은 네트워크 스택에서 두 지점에서 발생합니다:
| 경로 | 라우팅 함수 | 시점 | 설명 |
|---|---|---|---|
| 수신 경로 (RX) | ip_route_input_slow() | PREROUTING 후 | 로컬 배달 vs 포워딩 결정 |
| 송신 경로 (TX) | ip_route_output_flow() | 소켓에서 패킷 생성 시 | 출력 인터페이스, nexthop 결정 |
| DNAT 후 재조회 | ip_route_input() | PREROUTING DNAT 후 | 목적지 변경 후 라우팅 재결정 |
FIB 내부 구조
FIB(Forwarding Information Base)는 라우팅 테이블의 커널 내부 표현입니다. Linux는 LC-trie(Level-Compressed trie) 자료구조를 사용하여 최적의 Longest Prefix Match(LPM) 성능을 달성합니다.
핵심 자료구조
/* net/ipv4/fib_semantics.c, net/ipv4/fib_trie.c */
/* fib_table: 하나의 라우팅 테이블 (예: main=254, local=255) */
struct fib_table {
struct hlist_node tb_hlist; /* 테이블 해시 리스트 */
u32 tb_id; /* 테이블 ID (254=main, 255=local) */
int tb_num_default; /* default 경로 수 */
struct rcu_head rcu;
unsigned long __data[]; /* trie 루트 (struct trie) */
};
/* fib_info: 경로(route)의 메타데이터 (여러 fib_alias가 공유 가능) */
struct fib_info {
struct hlist_node fib_hash;
int fib_treeref; /* 참조 카운트 */
u32 fib_flags;
unsigned char fib_scope; /* RT_SCOPE_UNIVERSE, LINK, HOST */
unsigned char fib_type; /* RTN_UNICAST, LOCAL, BROADCAST, ... */
u32 fib_priority; /* 경로 메트릭 (낮을수록 우선) */
struct nexthop *nh; /* nexthop 객체 (5.x+) */
int fib_nhs; /* nexthop 수 (multipath) */
struct fib_nh fib_nh[]; /* nexthop 배열 (레거시) */
};
/* fib_nh: 개별 nexthop (게이트웨이 + 출력 디바이스) */
struct fib_nh {
struct fib_nh_common nh_common;
struct net_device *fib_nh_dev; /* 출력 디바이스 */
__be32 fib_nh_gw4; /* IPv4 게이트웨이 */
int fib_nh_weight; /* ECMP 가중치 */
u32 fib_nh_oif; /* 출력 인터페이스 인덱스 */
u8 fib_nh_scope;
};
LC-trie 자료구조
LC-trie는 path-compressed trie에 level compression을 추가한 구조로, 메모리 효율과 조회 속도를 모두 최적화합니다:
/* net/ipv4/fib_trie.c */
/* trie 노드: internal node와 leaf가 같은 구조체 */
struct key_vector {
t_key key; /* 접두사 키 */
unsigned char pos; /* 이 노드에서 검사 시작 비트 위치 */
unsigned char bits; /* 이 노드에서 검사할 비트 수 */
unsigned char slen; /* suffix length (최적화) */
union {
struct hlist_head leaf; /* leaf: fib_alias 리스트 */
struct key_vector *tnode[]; /* internal: 자식 배열 */
};
};
/* LPM (Longest Prefix Match) 알고리즘:
* 1. root에서 시작, 목적지 IP의 비트를 순차적으로 검사
* 2. 각 internal 노드에서 bits만큼의 비트로 자식 인덱스 결정
* 3. leaf 도달 시 접두사 일치 확인
* 4. 일치하지 않으면 부모로 backtrack하여 더 짧은 접두사 탐색
* 5. 가장 긴 일치 접두사 반환
*
* 시간 복잡도: O(W) where W = 주소 길이(32비트)
* 실제: level compression 덕분에 대부분 O(log n) 이하
*/
# FIB trie 내부 구조 확인
cat /proc/net/fib_trie
# Main:
# +-- 0.0.0.0/0 3 0 0
# +-- 0.0.0.0/4 2 0 0
# +-- 10.0.0.0/24 2 0 0
# |-- 10.0.0.0
# /24 host LOCAL
# +-- 10.0.1.0/24 2 0 0
# |-- 10.0.1.0
# /24 link UNICAST
# FIB 통계
cat /proc/net/fib_triestat
# Basic info: size of leaf/tnode, Max depth, Prefixes, ...
라우팅 테이블 관리
ip route 명령
# 현재 라우팅 테이블 조회
ip route show # main 테이블
ip route show table local # local 테이블
ip route show table all # 모든 테이블
# 경로 추가
ip route add 10.0.0.0/24 via 192.168.1.1 # 게이트웨이 경유
ip route add 10.0.0.0/24 dev eth0 # 직접 연결
ip route add 10.0.0.0/24 via 192.168.1.1 metric 100 # 메트릭 지정
# scope/type 이해
ip route add 10.0.0.0/24 via 192.168.1.1 scope global # 기본: 전역
ip route add 10.0.0.0/24 dev eth0 scope link # 직접 연결 네트워크
# scope: global > site > link > host > nowhere
# 라우팅 타입
ip route add unreachable 10.0.99.0/24 # ICMP unreachable 반환
ip route add blackhole 10.0.99.0/24 # 조용히 드롭
ip route add prohibit 10.0.99.0/24 # ICMP prohibited 반환
ip route add throw 10.0.99.0/24 # 다음 rule로 넘김
# 특정 소스 주소 지정
ip route add 10.0.0.0/24 via 192.168.1.1 src 192.168.1.100
# 경로 삭제/변경
ip route del 10.0.0.0/24
ip route change 10.0.0.0/24 via 192.168.1.2
라우팅 테이블 종류
| 테이블 ID | 이름 | 용도 | 우선순위 |
|---|---|---|---|
| 255 | local | 로컬 주소, 브로드캐스트 주소 (커널 자동 관리) | rule 0 (최우선) |
| 254 | main | 일반 라우팅 (ip route 기본 대상) | rule 32766 |
| 253 | default | 기본 경로 (거의 사용 안 함) | rule 32767 |
| 1~252 | 사용자 정의 | Policy Routing용 커스텀 테이블 | ip rule로 지정 |
커널 FIB 조회 API
/* 커널 모듈에서 라우팅 테이블 조회 */
#include <net/ip_fib.h>
struct fib_result res;
struct flowi4 fl4 = {
.daddr = htonl(0x0A000001), /* 10.0.0.1 */
.flowi4_oif = 0,
.flowi4_scope = RT_SCOPE_UNIVERSE,
};
int err = fib_table_lookup(table, &fl4, &res, FIB_LOOKUP_NOREF);
if (!err) {
/* res.fi → fib_info (nexthop 정보) */
/* res.type → RTN_UNICAST, RTN_LOCAL 등 */
/* res.prefixlen → 일치한 접두사 길이 */
}
/* 또는 전체 라우팅 조회 (rules + table + nexthop 해석) */
struct rtable *rt = ip_route_output_flow(net, &fl4, sk);
if (!IS_ERR(rt)) {
/* rt->dst.dev → 출력 디바이스 */
/* rt->rt_gw4 → 게이트웨이 주소 */
ip_rt_put(rt);
}
Policy Routing
Policy Routing은 목적지 주소뿐 아니라 소스 주소, fwmark, 입력 인터페이스 등 다양한 조건에 따라 다른 라우팅 테이블을 선택합니다.
ip rule 규칙
# 현재 규칙 조회
ip rule show
# 0: from all lookup local
# 32766: from all lookup main
# 32767: from all lookup default
# 규칙 추가: 소스 주소 기반
ip rule add from 10.0.0.0/24 table 100 priority 1000
# fwmark 기반 (netfilter와 연동)
iptables -t mangle -A OUTPUT -p tcp --dport 80 -j MARK --set-mark 1
ip rule add fwmark 1 table 200 priority 2000
# 입력 인터페이스 기반
ip rule add iif eth1 table 300 priority 3000
# 목적지 주소 기반
ip rule add to 203.0.113.0/24 table 400
# uidrange 기반 (특정 사용자의 트래픽)
ip rule add uidrange 1000-1000 table 500
# 복합 조건
ip rule add from 10.0.0.0/24 to 172.16.0.0/12 fwmark 0x10/0xff table 600
# 규칙 삭제
ip rule del priority 1000
커널 내부: fib_rules_ops
/* net/core/fib_rules.c */
/* fib_rules_ops: 프로토콜별 라우팅 규칙 구현 */
struct fib_rules_ops {
int family; /* AF_INET, AF_INET6, AF_DECnet */
int (*action)(struct fib_rule *, struct flowi *, int,
struct fib_lookup_arg *);
int (*match)(struct fib_rule *, struct flowi *, int);
/* ... */
};
/* 라우팅 조회 과정:
* 1. fib_rules_lookup() → 규칙 리스트를 우선순위 순으로 순회
* 2. 각 규칙에 대해 match() 호출 (from/to/mark/iif 검사)
* 3. 매칭 시 action() 호출 → 해당 테이블에서 fib_table_lookup()
* 4. 결과 없으면 (throw) 다음 규칙으로 계속
*/
Policy Routing 활용 예:
- 멀티홈(dual ISP): 소스 주소에 따라 다른 ISP 게이트웨이 사용
- VPN split tunneling: fwmark로 VPN/직접 경로 분리
- QoS 기반 라우팅: TOS/DSCP 값에 따라 경로 분리
- 컨테이너 네트워킹: veth 입력에 따라 별도 라우팅 테이블
IPv6 라우팅
IPv6 라우팅은 IPv4와 유사한 FIB 구조를 사용하지만, 128비트 주소와 RA(Router Advertisement)/NDP(Neighbor Discovery Protocol) 기반 자동 구성이 핵심적인 차이입니다.
FIB6 구조
/* net/ipv6/ip6_fib.c */
/* IPv6 FIB는 radix tree (binary trie) 사용 */
/* IPv4의 LC-trie와 다른 구조 */
struct fib6_info {
struct fib6_table *fib6_table;
struct fib6_info *fib6_nsiblings; /* ECMP siblings */
struct fib6_nh *fib6_nh; /* nexthop */
struct rt6_key fib6_dst; /* 목적지 접두사 */
struct rt6_key fib6_src; /* 소스 접두사 (optional) */
u32 fib6_metric; /* 경로 메트릭 */
u32 fib6_flags; /* RTF_GATEWAY, RTF_REJECT, ... */
unsigned long expires; /* RA 기반 경로 만료 시간 */
};
IPv6 라우팅 특성
# IPv6 라우팅 테이블
ip -6 route show
# ::1 dev lo proto kernel metric 256
# 2001:db8:1::/64 dev eth0 proto kernel metric 256 expires 86400sec
# fe80::/64 dev eth0 proto kernel metric 256
# default via fe80::1 dev eth0 proto ra metric 1024 expires 1800sec
# RA(Router Advertisement)에 의한 자동 경로
# proto ra = 라우터 광고로 설치된 경로 (expires 있음)
# proto kernel = 인터페이스 설정 시 자동 생성
# IPv6 경로 추가
ip -6 route add 2001:db8:2::/48 via 2001:db8:1::1
ip -6 route add 2001:db8:2::/48 dev eth0 # on-link
# link-local 게이트웨이 (일반적 구성)
ip -6 route add default via fe80::1 dev eth0
# IPv6 NDP (Neighbor Discovery) — ARP 대체
ip -6 neigh show
# fe80::1 dev eth0 lladdr 00:11:22:33:44:55 router REACHABLE
# RA 수신 제어
sysctl net.ipv6.conf.eth0.accept_ra=2 # 포워딩 활성 시에도 RA 수신
sysctl net.ipv6.conf.eth0.autoconf=1 # SLAAC 주소 자동 구성
IPv6 vs IPv4 라우팅 차이점:
- NDP vs ARP: IPv6는 ICMPv6 기반 NDP 사용 (더 효율적, 보안 확장 가능)
- RA 기반 자동 구성: IPv6는 라우터가 접두사와 게이트웨이를 광고
- 소스 주소 선택: IPv6 인터페이스에 여러 주소 존재 — 소스 주소 선택 알고리즘(RFC 6724) 중요
- FIB 구조: IPv6은 radix tree, IPv4는 LC-trie (다른 자료구조)
- 경로 만료: RA 경로는 expires 타이머로 자동 만료/갱신
Multipath / ECMP
ECMP(Equal-Cost Multi-Path)는 동일 비용의 여러 경로로 트래픽을 분산합니다. Linux는 flow 해시 기반 부하 분산을 사용하여 동일 플로우의 패킷이 같은 경로로 전송되도록 보장합니다.
ECMP 설정
# 레거시 multipath 경로
ip route add 10.0.0.0/24 \
nexthop via 192.168.1.1 dev eth0 weight 1 \
nexthop via 192.168.2.1 dev eth1 weight 1
# nexthop 객체 사용 (5.x+, 권장)
ip nexthop add id 1 via 192.168.1.1 dev eth0
ip nexthop add id 2 via 192.168.2.1 dev eth1
ip nexthop add id 10 group 1/2 # nexthop 그룹
ip route add 10.0.0.0/24 nhid 10
# 가중치 기반 분산 (비균등 분배)
ip nexthop add id 10 group 1,3/2,1 # nh1:weight3, nh2:weight1 → 3:1 분배
# ECMP 해시 알고리즘 선택
sysctl net.ipv4.fib_multipath_hash_policy=0 # L3 only (src/dst IP)
sysctl net.ipv4.fib_multipath_hash_policy=1 # L4 (src/dst IP + port)
sysctl net.ipv4.fib_multipath_hash_policy=2 # L3+inner (터널용)
sysctl net.ipv4.fib_multipath_hash_policy=3 # custom (BPF 필터 가능, 6.0+)
Resilient Hashing (5.12+)
기존 ECMP는 nexthop 변경 시 모든 플로우의 경로가 재분배되어 대규모 환경에서 문제가 됩니다. Resilient hashing은 변경 영향을 최소화합니다:
# resilient nexthop 그룹 (consistent hashing)
ip nexthop add id 10 group 1/2 type resilient buckets 128 idle_timer 120
# buckets: 해시 버킷 수 (많을수록 정밀한 가중치 분배)
# idle_timer: 유휴 버킷 재할당 대기 시간(초)
# nexthop 삭제/추가 시:
# 기존 ECMP: 모든 플로우 재분배 (기존 연결 끊김 가능)
# resilient: 삭제된 nexthop의 버킷만 재할당 (영향 최소화)
# 버킷 상태 확인
ip nexthop bucket show id 10
ECMP 주의사항:
- 비대칭 라우팅: ECMP에서 요청/응답이 다른 경로를 사용할 수 있음.
rp_filter=2(loose) 설정 필요 - conntrack 상호작용: DNAT + ECMP에서 conntrack이 경로를 고정하므로 의도한 분배가 안 될 수 있음
- 해시 편향: 특정 플로우 패턴에서 해시 충돌로 불균등 분배 발생. L4 해시 정책 사용 권장
- nexthop 장애 감지: 커널은 기본적으로 nexthop 상태를 확인하지 않음. BFD나 keepalived 등 외부 모니터링 필요
VRF (Virtual Routing and Forwarding)
VRF는 단일 호스트에서 여러 개의 독립된 라우팅 도메인(L3 격리)을 제공합니다. 네트워크 네임스페이스보다 가벼우며, 동일 물리 인터페이스를 여러 라우팅 도메인에서 사용할 수 있습니다.
# VRF 디바이스 생성
ip link add vrf-red type vrf table 100
ip link set vrf-red up
# 인터페이스를 VRF에 할당
ip link set eth1 master vrf-red
ip link set eth2 master vrf-red
# VRF별 라우팅 테이블 (자동으로 table 100 사용)
ip route add 10.0.0.0/24 via 192.168.1.1 vrf vrf-red
ip route show vrf vrf-red
# VRF 컨텍스트에서 명령 실행
ip vrf exec vrf-red ping 10.0.0.1
ip vrf exec vrf-red ss -tlnp
# VRF에 바인딩된 소켓 (SO_BINDTODEVICE)
# 또는 sysctl: net.ipv4.tcp_l3mdev_accept=1 (전역 listen 소켓이 VRF 패킷 수신)
# VRF 목록 확인
ip vrf show
# Name Table
# vrf-red 100
# vrf-blue 200
VRF 활용 시나리오
| 시나리오 | 구성 | 장점 |
|---|---|---|
| 멀티테넌트 라우터 | 테넌트별 VRF + 라우팅 테이블 | 테넌트 간 IP 충돌 허용, 격리 |
| 관리 네트워크 분리 | 관리 인터페이스를 별도 VRF에 | 데이터 플레인과 관리 트래픽 격리 |
| BGP/MPLS VPN PE | VRF + FRR(BGP) | L3VPN PE 라우터 구현 |
| 컨테이너 네트워킹 | Pod별 VRF (netns 대신) | netns보다 가벼운 L3 격리 |
VRF vs Network Namespace: VRF는 L3 라우팅만 격리하며 같은 네트워크 스택을 공유합니다. 완전한 네트워크 격리(소켓, netfilter, iptables)가 필요하면 network namespace를 사용하세요. VRF는 오버헤드가 적고 인터페이스를 유연하게 할당/해제할 수 있는 것이 장점입니다.
Routing Cache와 최적화
Route Cache 제거 역사 (3.6+)
커널 3.6 이전에는 라우팅 조회 결과를 해시 테이블에 캐싱했습니다. 그러나 DoS 공격(랜덤 IP로 캐시 오염)과 메모리 문제로 제거되었습니다:
/* 커널 3.6 이전: route cache (제거됨)
* - 해시 테이블에 (src, dst, tos, iif) → rtable 캐싱
* - 문제: 랜덤 목적지 트래픽으로 캐시 크기 폭발 (DoS)
* - 문제: 캐시 GC(Garbage Collection) 비용이 높음
* - commit 89aef8921b ("ipv4: Remove rt cache")
*/
/* 커널 3.6+: FIB nexthop exception cache
* - PMTU, 리다이렉트 등 예외만 캐싱
* - 일반 조회는 매번 FIB trie를 직접 조회 (충분히 빠름)
*/
struct fib_nh_exception {
struct fib_nh_exception *fnhe_next;
int fnhe_genid;
__be32 fnhe_daddr; /* 목적지 */
u32 fnhe_pmtu; /* Path MTU */
bool fnhe_mtu_locked;
__be32 fnhe_gw; /* redirect 게이트웨이 */
unsigned long fnhe_expires; /* 만료 시간 */
struct rtable *fnhe_rth_input;
struct rtable *fnhe_rth_output;
unsigned long fnhe_stamp;
struct rcu_head rcu;
};
FIB 조회 최적화
| 최적화 기법 | 커널 버전 | 효과 |
|---|---|---|
| LC-trie (Level-Compressed Trie) | 2.6.13+ | 메모리 효율적 LPM, 조회 O(W) |
| RCU 기반 조회 | 초기~ | 읽기 측 lock-free, 높은 동시성 |
| per-CPU dst_entry 캐시 | 4.2+ | CPU별 rtable 캐싱으로 캐시 라인 경합 감소 |
| nexthop 객체 | 5.3+ | nexthop을 독립 객체로 분리, 공유 가능 |
| FIB notification chain | 4.13+ | HW 오프로드 드라이버에 FIB 변경 알림 |
라우팅과 Netfilter 상호작용
Netfilter의 NAT 처리는 라우팅 결정에 직접적인 영향을 미칩니다. 특히 DNAT는 목적지 주소를 변경하므로 라우팅 재조회가 필요합니다.
DNAT와 라우팅 재조회
/* PREROUTING에서 DNAT 적용 시 라우팅 재조회 과정:
*
* 1. NIC → ip_rcv() → ip_route_input() [첫 번째 라우팅 결정]
* → 원래 목적지 기준으로 LOCAL/FORWARD 결정
*
* 2. NF_INET_PRE_ROUTING → DNAT 적용
* → skb->_skb_refdst 무효화
*
* 3. ip_rcv_finish() → ip_route_input() [두 번째 라우팅 결정]
* → 변경된 목적지 기준으로 재결정
* → 로컬 주소가 됐다면 LOCAL_IN으로, 아니면 FORWARD로
*/
/* conntrack과 라우팅의 상호작용 */
/* conntrack은 PREROUTING/OUTPUT에서 패킷의 연결을 추적 */
/* NAT도 conntrack을 기반으로 동작 */
/*
* 주의: conntrack이 없으면 DNAT/SNAT 불가
* 주의: conntrack bypass (NOTRACK)된 패킷은 NAT 대상이 아님
* 주의: 첫 패킷만 NAT rule 조회, 이후 conntrack 기반으로 자동 변환
*/
Netfilter 훅과 라우팅 시점
# 패킷 흐름에서 라우팅과 Netfilter의 순서:
#
# 수신 경로:
# NIC → [PREROUTING] → 라우팅 결정 → [INPUT] → 로컬 프로세스
# ↓ (forward)
# [FORWARD] → [POSTROUTING] → NIC
#
# 송신 경로:
# 로컬 프로세스 → 라우팅 결정 → [OUTPUT] → [POSTROUTING] → NIC
#
# DNAT는 PREROUTING에서 적용 → 라우팅 재조회
# SNAT는 POSTROUTING에서 적용 → 라우팅 이후
# REDIRECT는 PREROUTING에서 DNAT의 특수 케이스 (로컬 주소로 변환)
# DNAT + 라우팅 재조회 예시
iptables -t nat -A PREROUTING -d 1.2.3.4 -p tcp --dport 80 \
-j DNAT --to-destination 192.168.1.100:80
# → 1.2.3.4:80 목적지가 192.168.1.100:80으로 변경
# → 라우팅 재조회: 192.168.1.100이 로컬이면 INPUT, 아니면 FORWARD
라우팅과 네임스페이스
각 네트워크 네임스페이스는 독립된 라우팅 테이블, FIB rules, nexthop을 가집니다. 컨테이너 네트워킹의 기반입니다.
네트워크 네임스페이스별 라우팅 격리
# 네임스페이스 생성 및 veth 쌍 연결
ip netns add ns1
ip link add veth0 type veth peer name veth1
ip link set veth1 netns ns1
# 호스트 측
ip addr add 10.0.0.1/24 dev veth0
ip link set veth0 up
# 네임스페이스 측
ip netns exec ns1 ip addr add 10.0.0.2/24 dev veth1
ip netns exec ns1 ip link set veth1 up
ip netns exec ns1 ip route add default via 10.0.0.1
# 네임스페이스의 라우팅 테이블은 완전히 독립
ip netns exec ns1 ip route show
# 10.0.0.0/24 dev veth1 proto kernel scope link src 10.0.0.2
# default via 10.0.0.1 dev veth1
# 호스트에서 네임스페이스로의 포워딩 (호스트에서 설정)
sysctl net.ipv4.ip_forward=1
iptables -t nat -A POSTROUTING -s 10.0.0.0/24 -o eth0 -j MASQUERADE
Bridge + Routing
# 브리지는 L2, 라우팅은 L3 — 상호작용 주의
# 브리지 인터페이스에 IP 할당 → L3 라우팅 가능
ip link add br0 type bridge
ip link set eth1 master br0
ip link set eth2 master br0
ip addr add 10.0.0.1/24 dev br0
ip link set br0 up
# 브리지 내 패킷의 netfilter 통과 제어
sysctl net.bridge.bridge-nf-call-iptables=1 # 브리지 패킷이 iptables 통과
sysctl net.bridge.bridge-nf-call-iptables=0 # 성능 우선: iptables 바이패스
# 주의: bridge-nf-call-iptables=1이면
# L2 포워딩 패킷도 iptables FORWARD 체인 통과
# → 예상치 못한 드롭 발생 가능
# → Docker/Kubernetes 환경에서 자주 문제가 됨
성능 튜닝과 주의사항
ARP/Neighbor 테이블 크기
# 대규모 L2 네트워크에서 ARP 테이블 오버플로 방지
sysctl net.ipv4.neigh.default.gc_thresh1=1024 # GC 시작 임계값
sysctl net.ipv4.neigh.default.gc_thresh2=2048 # soft limit (5초 후 GC)
sysctl net.ipv4.neigh.default.gc_thresh3=4096 # hard limit (즉시 GC)
# IPv6 neighbor 테이블
sysctl net.ipv6.neigh.default.gc_thresh3=4096
# 증상: "Neighbour table overflow" 커널 메시지
# → gc_thresh3 증가 필요
# ARP 캐시 타임아웃
sysctl net.ipv4.neigh.default.gc_stale_time=120 # stale 엔트리 GC 주기(초)
sysctl net.ipv4.neigh.default.base_reachable_time_ms=30000 # REACHABLE 유지 시간
rp_filter (Reverse Path Filtering)
# rp_filter: 소스 주소 기반 패킷 검증 (스푸핑 방지)
sysctl net.ipv4.conf.all.rp_filter=1 # strict mode
# → 소스 주소로의 역경로가 수신 인터페이스와 동일해야 통과
# → 비대칭 라우팅 환경에서 정상 패킷 드롭!
sysctl net.ipv4.conf.all.rp_filter=2 # loose mode
# → 소스 주소로의 역경로가 어떤 인터페이스든 존재하면 통과
# → ECMP, VPN, 멀티홈 환경에서 권장
sysctl net.ipv4.conf.all.rp_filter=0 # disabled
# → 소스 주소 검증 없음 (보안 위험)
# 주의: 인터페이스별 설정과 all의 관계
# 실제 적용값 = max(conf.all.rp_filter, conf.IFNAME.rp_filter)
# → all=1이면 인터페이스별로 0으로 해도 strict 적용됨
ip_forward와 관련 설정
# IP 포워딩 활성화
sysctl net.ipv4.ip_forward=1
sysctl net.ipv6.conf.all.forwarding=1
# 주의: IPv6 forwarding=1 시 RA 수신이 비활성화됨
# 해결: accept_ra=2 설정
sysctl net.ipv6.conf.eth0.accept_ra=2
# 포워딩 관련 성능 파라미터
sysctl net.ipv4.ip_forward_use_pmtu=0 # 0: 인터페이스 MTU 사용 (권장)
sysctl net.ipv4.ip_forward_update_priority=1 # TOS → priority 변환
sysctl net.ipv4.fib_multipath_use_neigh=1 # nexthop 상태 기반 분배
대규모 라우팅 테이블
대규모 라우팅 테이블(BGP full table ~100만 경로) 주의사항:
- 메모리: full BGP table은 ~500MB~1GB 메모리 사용.
fib_info와fib_alias구조체가 대부분 - 수렴 시간: 대량 경로 추가/삭제 시 LC-trie 리밸런싱 비용 증가
- 조회 성능: LC-trie 깊이 증가로 조회 시간 약간 증가 (여전히 O(W=32) 보장)
- gc_thresh: ARP/neigh 테이블도 충분히 크게 설정
- 모니터링:
/proc/net/fib_triestat으로 trie 깊이, 노드 수, 메모리 사용량 확인
디버깅
ip route get — 경로 조회 시뮬레이션
# 특정 목적지로의 경로 확인 (실제 커널 FIB 조회 수행)
ip route get 8.8.8.8
# 8.8.8.8 via 192.168.1.1 dev eth0 src 192.168.1.100 uid 0
# cache
# 소스 주소 지정
ip route get 8.8.8.8 from 10.0.0.1
# mark 지정 (policy routing 테스트)
ip route get 8.8.8.8 mark 0x1
# 입력 인터페이스 지정
ip route get 8.8.8.8 iif eth1
# IPv6
ip -6 route get 2001:4860:4860::8888
# fibmatch: FIB 엔트리 직접 조회 (경로 정보 상세)
ip route get fibmatch 10.0.0.1
# 10.0.0.0/24 dev eth0 proto kernel scope link src 10.0.0.100
FIB 관련 /proc 파일
# FIB trie 구조 (IPv4)
cat /proc/net/fib_trie
# FIB 통계
cat /proc/net/fib_triestat
# Basic info: size of leaf: 56 bytes, size of tnode: 40 bytes.
# Main:
# Aver depth: 2.35
# Max depth: 5
# Leaves: 15
# Prefixes: 18
# Internal nodes: 6
# 1: 3 2: 2 3: 1
# Pointers: 24
# Null ptrs: 12
# Total size: 2 kB
# 기존 형식 라우팅 테이블
cat /proc/net/route
# Iface Destination Gateway Flags RefCnt Use Metric Mask ...
# IPv6 라우팅
cat /proc/net/ipv6_route
FIB 조회 성능 추적
# ftrace로 FIB 조회 함수 추적
echo fib_table_lookup > /sys/kernel/tracing/set_ftrace_filter
echo function > /sys/kernel/tracing/current_tracer
echo 1 > /sys/kernel/tracing/tracing_on
# ... 트래픽 발생 ...
cat /sys/kernel/tracing/trace
# perf로 FIB 조회 비용 측정
perf record -e 'fib:*' -a sleep 10
perf script
# dropwatch로 라우팅 드롭 추적
dropwatch -l kas
# → ip_error, ip_forward 등에서 드롭 위치 확인
# BPF 기반 FIB 조회 추적
bpftrace -e 'kretprobe:fib_table_lookup /retval != 0/ {
@fail[retval] = count();
}'
# skb mark 확인 (policy routing 디버깅)
bpftrace -e 'kprobe:ip_route_input_slow {
printf("mark=%x daddr=%x\n",
((struct sk_buff *)arg0)->mark,
((struct iphdr *)(((struct sk_buff *)arg0)->head +
((struct sk_buff *)arg0)->network_header))->daddr);
}'
Segment Routing (SRv6)
SRv6(Segment Routing over IPv6)는 IPv6 확장 헤더(SRH: Segment Routing Header)를 사용하여 패킷의 경로를 소스에서 지정하는 기술입니다. MPLS의 대안으로 데이터센터와 WAN에서 채택이 증가하고 있습니다.
SRv6 개념
# SRv6 기본 개념:
# - Segment: 네트워크 노드의 IPv6 주소 (또는 함수)
# - Segment List: 패킷이 통과할 노드의 순서 리스트
# - SRH (Segment Routing Header): IPv6 확장 헤더에 세그먼트 리스트 포함
# - SID (Segment Identifier): 128비트 IPv6 주소 형태
# 형식: [Locator (네트워크 접두사)] + [Function (동작)]
# 패킷 형태:
# [IPv6 Header (DA=현재 세그먼트)] [SRH: 세그먼트 리스트] [원본 패킷]
# 각 노드에서: Segments Left-- → DA를 다음 세그먼트로 변경 → 포워딩
Linux SRv6 설정
# SRv6 캡슐화 (Encapsulation)
ip route add 10.0.0.0/24 encap seg6 mode encap \
segs fc00:1::1,fc00:2::1 dev eth0
# → 10.0.0.0/24 향 패킷을 SRv6로 캡슐화
# → 세그먼트: fc00:1::1 → fc00:2::1 순서로 통과
# SRv6 인라인 모드 (원본이 이미 IPv6인 경우)
ip route add 2001:db8:2::/48 encap seg6 mode inline \
segs fc00:1::1,fc00:2::1 dev eth0
# SRv6 로컬 SID 액션 (수신 측)
ip -6 route add fc00:1::100 encap seg6local action End dev eth0
# End: 세그먼트 처리 후 다음 세그먼트로 포워딩
ip -6 route add fc00:1::200 encap seg6local action End.DT4 vrftable 100
# End.DT4: SRH 제거 후 IPv4 패킷을 VRF table 100에서 라우팅
ip -6 route add fc00:1::300 encap seg6local action End.DT6 vrftable 200
# End.DT6: SRH 제거 후 IPv6 패킷을 VRF table 200에서 라우팅
ip -6 route add fc00:1::400 encap seg6local action End.DX4 nh4 10.0.0.1 dev eth1
# End.DX4: SRH 제거 후 특정 IPv4 nexthop으로 전달
주요 SRv6 액션
| 액션 | 설명 | 사용 시나리오 |
|---|---|---|
End | 세그먼트 처리, 다음 SID로 포워딩 | 중간 경유 노드 (transit) |
End.X | End + L3 cross-connect (특정 nexthop으로) | 특정 이웃으로 직접 전달 |
End.DT4 | 캡슐화 해제 → IPv4 라우팅 테이블 조회 | VPN PE에서 IPv4 VRF lookup |
End.DT6 | 캡슐화 해제 → IPv6 라우팅 테이블 조회 | VPN PE에서 IPv6 VRF lookup |
End.DX4 | 캡슐화 해제 → 특정 IPv4 nexthop | 1:1 VPN 터널 종단 |
End.DX6 | 캡슐화 해제 → 특정 IPv6 nexthop | 1:1 VPN 터널 종단 |
End.B6.Encaps | SRv6 재캡슐화 (SRH 추가/수정) | 중간 노드에서 경로 변경 |
SRv6 주의사항:
- MTU 오버헤드: SRH 헤더 추가로 패킷 크기 증가. 세그먼트 1개당 16바이트(IPv6 주소). 4-세그먼트 → +64바이트+SRH 고정 8바이트
- CONFIG_IPV6_SEG6: 커널 설정에서 SRv6 지원 활성화 필요
- 보안: SRH를 통한 경로 조작 가능. 경계 라우터에서 외부 SRH 패킷 필터링 권장
- 성능: 소프트웨어 SRv6 처리는 CPU 집약적. SmartNIC offload 또는 DPDK/VPP 활용 고려
# SRv6 상태 확인
ip -6 route show | grep seg6
ip -6 route show type seg6local
# SRv6 카운터 (커널 6.1+)
cat /proc/net/seg6_hmac # HMAC 인증 키
# tcpdump로 SRH 확인
tcpdump -vvv -i eth0 ip6 and 'ip6[40] == 43'
# Routing Header Type 4 (SRH)
# Segments Left: 2
# [0] fc00:2::1
# [1] fc00:1::1
Linux Bridge (가상 스위치)
Linux Bridge는 커널 내 소프트웨어 L2 스위치입니다. 여러 네트워크 인터페이스를 하나의 브리지에 연결하여 동일 L2 도메인으로 묶고, MAC 학습, STP, VLAN 필터링 등 물리 스위치의 기능을 제공합니다. KVM/QEMU 가상화, Docker, LXC 등 컨테이너 환경의 기본 네트워크 백엔드로 널리 사용됩니다.
브리지 내부 아키텍처
/* net/bridge/br_private.h */
struct net_bridge {
struct net_device *dev; /* 브리지 자체 netdev (br0) */
struct list_head port_list; /* 브리지 포트(멤버) 리스트 */
struct rhashtable fdb_hash_tbl; /* FDB (MAC 주소 테이블) */
u16 group_fwd_mask; /* 포워딩할 L2 그룹 마스크 */
unsigned long ageing_time; /* FDB 엔트리 만료 시간 */
u32 vlan_proto; /* VLAN 프로토콜 (0x8100 등) */
struct net_bridge_vlan_group *vlgrp; /* VLAN 필터링 그룹 */
bool vlan_filtering; /* VLAN 필터링 활성화 */
/* STP 관련 */
bridge_id designated_root;
bridge_id bridge_id;
unsigned char stp_enabled; /* STP_DISABLED, KERNEL_STP, USER_STP */
};
struct net_bridge_port {
struct net_bridge *br; /* 소속 브리지 */
struct net_device *dev; /* 실제 인터페이스 (eth0 등) */
port_id port_id;
u8 state; /* BR_STATE_DISABLED/BLOCKING/FORWARDING */
u16 port_no;
struct net_bridge_vlan_group *vlgrp; /* 포트별 VLAN 설정 */
unsigned long flags; /* BR_HAIRPIN_MODE, BR_LEARNING, ... */
};
/* FDB (Forwarding DataBase) 엔트리 */
struct net_bridge_fdb_entry {
struct rhash_head rhnode;
struct net_bridge_port *dst; /* 학습된 포트 */
struct net_bridge_fdb_key key; /* MAC 주소 + VLAN ID */
unsigned long updated; /* 마지막 갱신 시각 */
unsigned long used; /* 마지막 사용 시각 */
unsigned long flags; /* STATIC, LOCAL, EXTERN_LEARN */
};
브리지 생성 및 설정
# 브리지 생성 (iproute2)
ip link add br0 type bridge
ip link set br0 up
# 포트 추가
ip link set eth0 master br0
ip link set eth1 master br0
ip link set eth0 up
ip link set eth1 up
# 브리지에 IP 할당 → L3 게이트웨이 역할
ip addr add 10.0.0.1/24 dev br0
# FDB (MAC 주소 테이블) 확인
bridge fdb show br br0
# aa:bb:cc:dd:ee:ff dev eth0 master br0 ← 동적 학습
# 00:11:22:33:44:55 dev eth1 master br0 permanent ← 정적
# FDB 에이징 타임 (기본 300초)
ip link set br0 type bridge ageing_time 30000 # centiseconds (300초)
# 정적 FDB 엔트리 추가
bridge fdb add aa:bb:cc:dd:ee:ff dev eth0 master static
# 브리지 정보 확인
ip -d link show br0
bridge link show
STP (Spanning Tree Protocol)
# STP 활성화/비활성화
ip link set br0 type bridge stp_state 1 # 1=on, 0=off
# 브리지 우선순위 (낮을수록 루트 브리지 선출 우선)
ip link set br0 type bridge priority 4096 # 기본 32768, 0~65535 (4096 단위)
# 포트 우선순위 및 cost
ip link set eth0 type bridge_slave priority 32 # 0~63
ip link set eth0 type bridge_slave cost 100 # 경로 비용
# 포트 상태 확인
bridge link show
# 2: eth0 state forwarding ...
# 3: eth1 state blocking ...
# STP 타이머 설정
ip link set br0 type bridge hello_time 200 # centiseconds (2초)
ip link set br0 type bridge max_age 2000 # centiseconds (20초)
ip link set br0 type bridge forward_delay 1500 # centiseconds (15초)
# RSTP (Rapid STP) — 커널 기본 지원, mstpd 데몬으로 MSTP 가능
# 커널 STP는 802.1D-2004 (RSTP 통합 버전) 기반
브리지 VLAN 필터링
# VLAN 필터링 활성화 (브리지 레벨)
ip link set br0 type bridge vlan_filtering 1
# 포트에 VLAN 할당
bridge vlan add vid 100 dev eth0 # tagged (트렁크)
bridge vlan add vid 100 dev eth1 pvid untagged # untagged 액세스 포트
# pvid: 태그 없는 수신 패킷에 이 VLAN ID 부여
# untagged: 이 VLAN의 송신 패킷에서 태그 제거
# 브리지 자체(br0)에도 VLAN 설정 필요 (L3 접근용)
bridge vlan add vid 100 dev br0 self
# VLAN 조회
bridge vlan show
# port vlan-id
# eth0 100
# eth1 100 PVID Egress Untagged
# br0 100
# VLAN 삭제
bridge vlan del vid 100 dev eth0
# VLAN 프로토콜 변경 (802.1ad QinQ 등)
ip link set br0 type bridge vlan_protocol 802.1ad
브리지 고급 설정
# Hairpin 모드 (같은 포트로 되돌아가는 트래픽 허용)
# VM/컨테이너가 같은 브리지 포트 뒤에서 서로 통신할 때 필요
ip link set eth0 type bridge_slave hairpin on
# Multicast snooping (불필요한 멀티캐스트 플러딩 방지)
ip link set br0 type bridge mcast_snooping 1
ip link set br0 type bridge mcast_querier 1 # 브리지가 IGMP 쿼리어 역할
# netfilter 통과 제어
sysctl net.bridge.bridge-nf-call-iptables=0 # 성능 우선: L2 패킷이 iptables 미통과
sysctl net.bridge.bridge-nf-call-ip6tables=0
sysctl net.bridge.bridge-nf-call-arptables=0
# 주의: Docker/K8s는 bridge-nf-call-iptables=1 필요 (서비스 NAT 동작)
# 브리지 포트 학습 비활성화 (정적 FDB만 사용)
ip link set eth0 type bridge_slave learning off
# 브리지 포트 flood 제어 (unknown unicast/multicast 억제)
ip link set eth0 type bridge_slave flood off
ip link set eth0 type bridge_slave mcast_flood off
브리지 패킷 흐름 (커널 내부):
netif_receive_skb()→rx_handler(br_handle_frame()으로 등록됨)br_handle_frame()→ STP BPDU 처리 또는br_handle_frame_finish()- FDB 학습 (
br_fdb_update()) → 소스 MAC + 수신 포트 기록 - 목적지 MAC으로 FDB 조회 → hit: 해당 포트로 유니캐스트, miss: 모든 포트로 flood
- VLAN 필터링 활성 시
br_allowed_ingress()/br_allowed_egress()검사 - 출력 시
br_forward()→br_dev_queue_push_xmit()→dev_queue_xmit()
Bonding (Link Aggregation)
Bonding(팀밍)은 여러 물리 인터페이스를 하나의 논리 인터페이스로 결합하여 대역폭 증가, 장애 대응(failover), 부하 분산을 제공합니다. IEEE 802.3ad(LACP)를 포함한 7가지 모드를 지원합니다.
Bonding 모드
| Mode | 이름 | 설명 | 스위치 요구사항 |
|---|---|---|---|
| 0 | balance-rr | Round-robin: 패킷을 순서대로 각 슬레이브에 분배 | 없음 (비권장: 순서 역전) |
| 1 | active-backup | Active 슬레이브만 사용, 장애 시 백업으로 전환 | 없음 |
| 2 | balance-xor | XOR 해시 기반 분배 (src/dst MAC 등) | 없음 (정적 LAG 권장) |
| 3 | broadcast | 모든 슬레이브에 동일 패킷 전송 | 없음 |
| 4 | 802.3ad | IEEE 802.3ad LACP 동적 집합 | LACP 필수 |
| 5 | balance-tlb | 송신 부하 분산 (수신은 단일 슬레이브) | 없음 |
| 6 | balance-alb | 송수신 모두 부하 분산 (ARP 조작) | 없음 |
Bonding 설정
# Bond 인터페이스 생성 (iproute2)
ip link add bond0 type bond mode 802.3ad
# LACP 관련 파라미터
ip link set bond0 type bond lacp_rate fast # slow(30초) / fast(1초) LACPDU
ip link set bond0 type bond xmit_hash_policy layer3+4 # L3+L4 해시 (권장)
ip link set bond0 type bond ad_select bandwidth # aggregator 선택 정책
# 슬레이브 추가
ip link set eth0 down
ip link set eth1 down
ip link set eth0 master bond0
ip link set eth1 master bond0
ip link set bond0 up
ip link set eth0 up
ip link set eth1 up
# IP 주소 할당
ip addr add 10.0.0.1/24 dev bond0
# 상태 확인
cat /proc/net/bonding/bond0
# Bonding Mode: IEEE 802.3ad Dynamic link aggregation
# Transmit Hash Policy: layer3+4
# MII Status: up
# Slave Interface: eth0 MII Status: up Aggregator ID: 1
# Slave Interface: eth1 MII Status: up Aggregator ID: 1
주요 Bonding 파라미터
# xmit_hash_policy: 부하 분산 해시 알고리즘
ip link set bond0 type bond xmit_hash_policy layer2 # src/dst MAC
ip link set bond0 type bond xmit_hash_policy layer2+3 # MAC + IP
ip link set bond0 type bond xmit_hash_policy layer3+4 # IP + port (권장)
ip link set bond0 type bond xmit_hash_policy encap2+3 # 터널 내부 헤더 사용
ip link set bond0 type bond xmit_hash_policy encap3+4 # 터널 내부 L3+L4
# MII 모니터링 (링크 감지)
ip link set bond0 type bond miimon 100 # MII 폴링 간격(ms)
ip link set bond0 type bond downdelay 200 # 링크 다운 감지 지연(ms)
ip link set bond0 type bond updelay 200 # 링크 업 감지 지연(ms)
# ARP 모니터링 (MII 대안)
ip link set bond0 type bond arp_interval 1000 # ARP 폴링 간격(ms)
ip link set bond0 type bond arp_ip_target 10.0.0.254 # ARP 대상 IP
# active-backup 모드 전용
ip link set bond0 type bond primary eth0 # 우선 슬레이브
ip link set bond0 type bond primary_reselect always # primary 복구 시 즉시 전환
ip link set bond0 type bond fail_over_mac active # failover 시 MAC 전환
커널 내부: Bonding 드라이버
/* drivers/net/bonding/bond_main.c */
struct bonding {
struct net_device *dev; /* bond0 netdev */
struct bond_opt_value params; /* 모드, 해시 정책 등 */
struct list_head slave_list; /* 슬레이브 리스트 */
struct slave *curr_active_slave; /* 현재 active 슬레이브 */
struct slave *primary_slave; /* primary 슬레이브 */
struct bond_up_slave *usable_slaves; /* RCU 보호 사용가능 슬레이브 배열 */
};
struct slave {
struct net_device *dev; /* 실제 NIC */
struct bonding *bond;
u8 link; /* BOND_LINK_UP/DOWN/FAIL/BACK */
u8 state; /* BOND_STATE_ACTIVE/BACKUP */
u16 queue_id; /* TX 큐 매핑 */
u32 speed; /* 링크 속도 (Mbps) */
u32 link_failure_count;
};
/* 802.3ad LACP 송신 해시:
* bond_xmit_hash() → xmit_hash_policy에 따라 해시 계산
* → hash % slave_count → 해당 슬레이브로 전송
*
* 주의: 양단 모두 같은 해시 정책을 사용해야 트래픽이 균등 분배됨
* 스위치: src-dst-ip-port 해시, 리눅스: layer3+4 → 대칭 해시 권장
*/
Bonding 주의사항:
- 802.3ad + 스위치: 반드시 스위치 측에도 LACP (Port Channel/LAG) 설정 필요. 미설정 시 패킷 루프/드롭 발생
- 해시 편향: 소수의 플로우만 있으면 모든 트래픽이 한 슬레이브에 집중될 수 있음. L3+L4 해시 사용 및 플로우 다양성 확보
- balance-rr: 패킷 단위 분배로 TCP 재정렬 발생. 대역폭 테스트 외 비권장
- MTU: 모든 슬레이브의 MTU가 동일해야 함. bond MTU 변경 시 슬레이브도 자동 변경
- VLAN over Bond: bond 위에 VLAN 생성 가능. 역순(VLAN 위에 bond)은 비권장
VLAN (802.1Q)
VLAN(Virtual LAN)은 하나의 물리 네트워크를 여러 논리 브로드캐스트 도메인으로 분할합니다. Linux는 802.1Q 태깅을 완벽 지원하며, 물리/가상 인터페이스 위에 VLAN 서브인터페이스를 생성합니다.
VLAN 설정
# VLAN 서브인터페이스 생성
ip link add link eth0 name eth0.100 type vlan id 100
ip link set eth0.100 up
ip addr add 10.100.0.1/24 dev eth0.100
# 여러 VLAN 생성 (동일 물리 인터페이스)
ip link add link eth0 name eth0.200 type vlan id 200
ip link add link eth0 name eth0.300 type vlan id 300
# VLAN 정보 확인
ip -d link show eth0.100
# vlan protocol 802.1Q id 100 <REORDER_HDR>
# VLAN 삭제
ip link del eth0.100
# Bond 위에 VLAN (일반적 구성: bond + VLAN trunk)
ip link add link bond0 name bond0.100 type vlan id 100
ip addr add 10.100.0.1/24 dev bond0.100
커널 VLAN 처리
/* net/8021q/vlan_dev.c, include/linux/if_vlan.h */
/* 802.1Q 태그 구조 (4바이트) */
/* [TPID: 0x8100 (2B)] [PCP: 3bit] [DEI: 1bit] [VID: 12bit (0~4095)] */
struct vlan_dev_priv {
unsigned int vlan_id; /* VLAN ID (1~4094) */
u16 vlan_proto; /* ETH_P_8021Q (0x8100) */
struct net_device *real_dev; /* 부모 디바이스 (eth0) */
unsigned char real_dev_addr[ETH_ALEN];
struct vlan_pcpu_stats *vlan_pcpu_stats; /* per-CPU 통계 */
unsigned int nr_ingress_mappings; /* PCP→priority 매핑 */
unsigned int nr_egress_mappings;
};
/* 수신 경로:
* 1. NIC 또는 드라이버가 VLAN 태그를 skb->vlan_tci에 추출 (HW offload)
* 또는 소프트웨어에서 __vlan_get_tag()로 추출
* 2. __netif_receive_skb() → vlan_do_receive()
* 3. VLAN ID로 서브인터페이스 찾기 → skb->dev를 VLAN dev로 변경
* 4. 이후 상위 프로토콜(IP 등)에서 VLAN 인터페이스의 패킷으로 처리
*/
/* 송신 경로:
* 1. VLAN dev의 xmit 함수: vlan_dev_hard_start_xmit()
* 2. 802.1Q 태그 삽입: __vlan_hwaccel_put_tag() 또는 vlan_insert_tag()
* 3. real_dev->netdev_ops->ndo_start_xmit()으로 실제 NIC에 전달
*/
QinQ (802.1ad, Double Tagging)
# QinQ: 외부 VLAN (S-VLAN) + 내부 VLAN (C-VLAN)
# ISP/통신사에서 고객 VLAN을 서비스 VLAN으로 캡슐화
# 외부 VLAN (802.1ad, TPID=0x88a8)
ip link add link eth0 name eth0.1000 type vlan proto 802.1ad id 1000
# 내부 VLAN (802.1Q, 외부 VLAN 위에 생성)
ip link add link eth0.1000 name eth0.1000.100 type vlan proto 802.1Q id 100
ip link set eth0.1000 up
ip link set eth0.1000.100 up
ip addr add 192.168.100.1/24 dev eth0.1000.100
# 패킷 형태:
# [Ethernet] [S-VLAN: 0x88a8, VID=1000] [C-VLAN: 0x8100, VID=100] [IP] [Payload]
# 브리지 VLAN 필터링으로 QinQ (대안 방법)
ip link set br0 type bridge vlan_protocol 802.1ad
bridge vlan add vid 1000 dev eth0 pvid untagged
bridge vlan add vid 1000 dev eth1
VLAN QoS (PCP 매핑)
# PCP(Priority Code Point): 802.1Q 태그의 3비트 우선순위 (0~7)
# ingress: PCP → skb->priority 매핑
ip link set eth0.100 type vlan ingress-qos-map 0:0 1:1 2:2 3:3 4:4 5:5 6:6 7:7
# egress: skb->priority → PCP 매핑
ip link set eth0.100 type vlan egress-qos-map 0:0 1:1 2:2 3:3 4:4 5:5 6:6 7:7
# tc(traffic control)와 연동하여 QoS 정책 구현
tc qdisc add dev eth0.100 root handle 1: prio bands 8 priomap 0 1 2 3 4 5 6 7
VXLAN (Virtual Extensible LAN)
VXLAN은 L2 프레임을 UDP로 캡슐화하여 L3 네트워크 위에 가상 L2 오버레이를 구성합니다. 4094개 제한인 VLAN ID 대신 24비트 VNI(16M+)를 사용하며, 데이터센터 멀티테넌트와 Kubernetes/Docker 오버레이 네트워크의 핵심 기술입니다.
VXLAN 패킷 형태
/* VXLAN 캡슐화 구조:
*
* [Outer Ethernet] [Outer IP] [Outer UDP (dst=4789)] [VXLAN Header] [Inner Ethernet] [Inner IP] [Payload]
*
* VXLAN Header (8 bytes):
* Flags (1B): 0x08 (VNI flag set)
* Reserved (3B)
* VNI (3B): 24-bit VXLAN Network Identifier (0 ~ 16,777,215)
* Reserved (1B)
*
* 오버헤드: 50 bytes (14 outer Eth + 20 IP + 8 UDP + 8 VXLAN)
* → 표준 1500 MTU에서 내부 페이로드 1450 bytes
* → 물리 네트워크 MTU를 1550+ (권장 9000 Jumbo)으로 설정
*/
/* include/net/vxlan.h */
struct vxlanhdr {
__be32 vx_flags; /* VXLAN_HF_VNI (0x08000000) */
__be32 vx_vni; /* VNI << 8 (상위 24비트) */
};
VXLAN 설정
# 유니캐스트 VXLAN (point-to-point)
ip link add vxlan100 type vxlan id 100 \
local 10.0.0.1 remote 10.0.0.2 \
dstport 4789 dev eth0
ip link set vxlan100 up
ip addr add 192.168.100.1/24 dev vxlan100
# 멀티캐스트 기반 VXLAN (BUM 트래픽 처리)
ip link add vxlan200 type vxlan id 200 \
local 10.0.0.1 group 239.1.1.1 \
dstport 4789 dev eth0 ttl 10
ip link set vxlan200 up
# Head-End Replication (멀티캐스트 대신 유니캐스트 복제)
ip link add vxlan300 type vxlan id 300 \
local 10.0.0.1 nolearning \
dstport 4789 dev eth0
ip link set vxlan300 up
# FDB에 수동으로 원격 VTEP 추가
bridge fdb append 00:00:00:00:00:00 dev vxlan300 dst 10.0.0.2
bridge fdb append 00:00:00:00:00:00 dev vxlan300 dst 10.0.0.3
# → unknown unicast/broadcast가 10.0.0.2, 10.0.0.3 양쪽에 복제 전송
# VXLAN + Bridge 조합 (일반적 구성)
ip link add br100 type bridge
ip link add vxlan100 type vxlan id 100 \
local 10.0.0.1 nolearning dstport 4789 dev eth0
ip link set vxlan100 master br100
ip link set eth1 master br100 # 로컬 인터페이스도 브리지에 참여
ip link set br100 up
ip link set vxlan100 up
VXLAN 고급 기능
# EVPN (BGP EVPN과 연동 — FRR/BIRD 등)
# EVPN은 BGP를 통해 MAC/IP 정보를 자동 교환
# → FDB 수동 관리 불필요, ARP suppression 가능
ip link add vxlan100 type vxlan id 100 \
local 10.0.0.1 nolearning dstport 4789 dev eth0
ip link set vxlan100 master br100
# FRR에서 BGP EVPN 피어 설정 → MAC/ARP 자동 동기화
# VXLAN 학습 모드
ip link set vxlan100 type vxlan learning # 내부 MAC 자동 학습 (기본)
ip link set vxlan100 type vxlan nolearning # EVPN/컨트롤러 환경 (권장)
# VXLAN FDB 확인
bridge fdb show dev vxlan100
# aa:bb:cc:dd:ee:ff dst 10.0.0.2 self permanent ← 원격 MAC→VTEP 매핑
# 00:00:00:00:00:00 dst 10.0.0.2 self permanent ← BUM 트래픽 목적지
# VXLAN + 체크섬
ip link add vxlan100 type vxlan id 100 \
local 10.0.0.1 dstport 4789 dev eth0 \
udpcsum noudp6zerocsumtx noudp6zerocsumrx
# VXLAN GBP (Group Based Policy) — 마이크로 세그멘테이션
ip link add vxlan100 type vxlan id 100 gbp \
local 10.0.0.1 dstport 4789 dev eth0
# GBP 태그로 보안 그룹 기반 정책 적용 (Cilium 등에서 사용)
VXLAN MTU 계산:
- 물리 MTU 1500 → VXLAN 내부 MTU = 1500 - 50 = 1450
- 물리 MTU 9000 (Jumbo) → VXLAN 내부 MTU = 9000 - 50 = 8950
- 데이터센터에서는 Jumbo Frame 사용 권장 (오버레이 오버헤드 최소화)
- VXLAN over IPv6: 오버헤드 70 bytes (outer IPv6=40B), 내부 MTU = 물리MTU - 70
macvlan / ipvlan
macvlan과 ipvlan은 하나의 물리 인터페이스에서 여러 가상 인터페이스를 생성하되, 브리지 없이 직접 동작합니다. 컨테이너 네트워킹에서 브리지 대비 낮은 오버헤드를 제공합니다.
macvlan
macvlan은 각 가상 인터페이스에 고유 MAC 주소를 부여합니다. 외부에서 보면 여러 NIC가 있는 것처럼 보입니다.
# macvlan 모드
# bridge: macvlan 인터페이스 간 직접 통신 가능 (기본)
# vepa: 모든 트래픽이 외부 스위치를 경유 (IEEE 802.1Qbg)
# private: macvlan 인터페이스 간 완전 격리
# passthru: 단일 macvlan만 허용, NIC 기능 직접 노출
# source: 특정 소스 MAC만 허용 (화이트리스트)
# macvlan bridge 모드 (가장 일반적)
ip link add macvlan0 link eth0 type macvlan mode bridge
ip link set macvlan0 up
ip addr add 10.0.0.10/24 dev macvlan0
# 컨테이너 네임스페이스에 macvlan 할당
ip link add macvlan1 link eth0 type macvlan mode bridge
ip link set macvlan1 netns container1
ip netns exec container1 ip addr add 10.0.0.11/24 dev macvlan1
ip netns exec container1 ip link set macvlan1 up
# 주의: 부모 인터페이스(eth0)와 macvlan 간 직접 통신 불가
# → 해결: 부모에도 macvlan 생성하거나 별도 라우팅 설정
ipvlan
ipvlan은 모든 가상 인터페이스가 부모의 MAC 주소를 공유합니다. MAC 주소 수 제한이 있는 환경(클라우드, 일부 스위치)에서 유용합니다.
# ipvlan L2 모드 (macvlan과 유사하지만 같은 MAC)
ip link add ipvlan0 link eth0 type ipvlan mode l2
ip link set ipvlan0 up
ip addr add 10.0.0.20/24 dev ipvlan0
# ipvlan L3 모드 (라우팅 기반, ARP/브로드캐스트 없음)
ip link add ipvlan1 link eth0 type ipvlan mode l3
ip link set ipvlan1 up
ip addr add 10.1.0.1/24 dev ipvlan1
# → L3 모드에서는 각 ipvlan이 별도 서브넷 가능
# → ARP를 하지 않으므로 호스트에서 proxy ARP 또는 라우팅 필요
# ipvlan L3S 모드 (L3 + conntrack/netfilter 통합)
ip link add ipvlan2 link eth0 type ipvlan mode l3s
# → iptables/nftables 규칙이 ipvlan 트래픽에 적용됨
# → Docker/K8s에서 서비스 IP 기반 필터링 시 필요
macvlan vs ipvlan 비교
| 특성 | macvlan | ipvlan |
|---|---|---|
| MAC 주소 | 각 인터페이스마다 고유 MAC | 부모와 동일 MAC 공유 |
| L2 브로드캐스트 | 정상 동작 | L2 모드만 지원 |
| DHCP | MAC 기반 DHCP 정상 동작 | ClientID 기반 필요 (같은 MAC) |
| 클라우드 환경 | MAC 제한 시 문제 | 적합 (MAC 1개) |
| 802.1X | MAC 인증 실패 가능 | 적합 (원래 MAC 유지) |
| 부모와 통신 | 불가 (직접) | L3/L3S에서 가능 |
| netfilter 통합 | 일반적 | L3S 모드에서 conntrack 가능 |
| 성능 | 브리지보다 높음 | macvlan과 동등 또는 약간 높음 |
가상 네트워크 디바이스 종합 비교
| 디바이스 | 계층 | 주요 용도 | 오버헤드 | 스케일 |
|---|---|---|---|---|
bridge | L2 | VM/컨테이너 스위칭, STP | 낮음 (FDB 조회) | 포트 수백개 |
bond | L1/L2 | 링크 집합, 이중화 | 최소 (해시) | 슬레이브 수십개 |
vlan | L2 | 브로드캐스트 도메인 분리 | 4B 태그 | 4094 VID |
vxlan | L2oL3 | 데이터센터 오버레이 | 50B (IPv4) | 16M VNI |
macvlan | L2 | 컨테이너 (고유 MAC) | 최소 | MAC 수 제한 |
ipvlan | L2/L3 | 컨테이너 (공유 MAC) | 최소 | IP 수 제한 |
veth | L2 | 네임스페이스 연결 | 낮음 | 쌍 단위 |
vrf | L3 | 라우팅 도메인 격리 | 최소 | 테이블 수 |
vxlan+bridge | L2oL3 | EVPN/오버레이 스위칭 | 50B+FDB | 대규모 DC |
실전 토폴로지 예시
# ===== 예시 1: KVM 호스트 (Bond + Bridge + VLAN) =====
# 물리 NIC 2장을 LACP bond → 그 위에 VLAN trunk → 브리지로 VM 연결
# Bond 생성
ip link add bond0 type bond mode 802.3ad
ip link set bond0 type bond xmit_hash_policy layer3+4
ip link set bond0 type bond miimon 100
ip link set eth0 master bond0
ip link set eth1 master bond0
ip link set bond0 up
# 관리 VLAN (untagged는 bond0에 직접)
ip addr add 10.0.0.1/24 dev bond0
# VM 네트워크용 VLAN + Bridge
ip link add link bond0 name bond0.100 type vlan id 100
ip link add br-vm100 type bridge
ip link set bond0.100 master br-vm100
ip link set bond0.100 up
ip link set br-vm100 up
# → VM의 tap 인터페이스를 br-vm100에 연결
# ===== 예시 2: Kubernetes 노드 (VXLAN 오버레이) =====
# 각 노드에 VXLAN 터널 + Bridge로 Pod 네트워크 구성 (Flannel 방식)
# 노드 A (10.0.0.1)
ip link add flannel.1 type vxlan id 1 \
local 10.0.0.1 nolearning dstport 8472 dev eth0
ip link add cni0 type bridge
ip link set flannel.1 master cni0
ip addr add 10.244.0.1/24 dev cni0
ip link set flannel.1 up
ip link set cni0 up
# 원격 노드 B (10.0.0.2)의 Pod 서브넷 경로
ip route add 10.244.1.0/24 via 10.244.1.0 dev flannel.1 onlink
bridge fdb append 00:00:00:00:00:00 dev flannel.1 dst 10.0.0.2
# ===== 예시 3: 멀티테넌트 라우터 (VRF + VLAN) =====
# 테넌트별 VLAN → VRF로 라우팅 격리
# 테넌트 A: VLAN 100 + VRF red
ip link add vrf-red type vrf table 100
ip link set vrf-red up
ip link add link eth0 name eth0.100 type vlan id 100
ip link set eth0.100 master vrf-red
ip link set eth0.100 up
ip addr add 10.100.0.1/24 dev eth0.100
# 테넌트 B: VLAN 200 + VRF blue
ip link add vrf-blue type vrf table 200
ip link set vrf-blue up
ip link add link eth0 name eth0.200 type vlan id 200
ip link set eth0.200 master vrf-blue
ip link set eth0.200 up
ip addr add 10.100.0.1/24 dev eth0.200 # IP 충돌 OK (VRF 격리)
디바이스 조합 규칙:
- Bond + VLAN: bond 위에 VLAN 생성 (역순 비권장). 스위치 측 trunk 설정 필요
- VLAN + Bridge: VLAN 서브인터페이스를 브리지 포트로 추가. 또는 브리지의 VLAN 필터링 사용 (더 효율적)
- Bridge + VRF: 브리지 인터페이스를 VRF에 할당하여 L3 격리
- VXLAN + Bridge: VXLAN 인터페이스를 브리지 포트로 추가하여 오버레이 L2 도메인 구성
- Bond + Bridge: bond 인터페이스를 브리지 포트로 추가 (bond가 아래, bridge가 위)