Linux Bridge 심화
Linux 커널 Bridge(소프트웨어 L2 스위치) 심화 분석: net_bridge/net_bridge_port 아키텍처, FDB(Forwarding Database), STP/RSTP 루프 방지, IGMP/MLD Snooping 멀티캐스트 최적화, br_netfilter와 ebtables, TC flower HW 오프로드, XDP redirect, ftrace/bpftrace 진단까지 실무 관점으로 다룹니다.
핵심 요약
- net_bridge 구조체는 브리지의 핵심 자료구조로, port_list(포트 목록), fdb_hash_tbl(FDB 해시), vlgrp(VLAN 그룹), MDB(멀티캐스트 DB), STP 타이머를 모두 포함합니다.
- FDB(Forwarding Database)는 MAC+VLAN을 키로 하는 rhashtable 해시 테이블이며, 학습(learning) → 에이징(aging, 기본 300초) → 삭제 주기로 동작합니다.
- STP/RSTP는 브리지 루프를 방지하며, 포트 상태(Disabled → Listening → Learning → Forwarding / Blocking)를 관리합니다. RSTP는 수렴 시간을 수십 초에서 수 초로 단축합니다.
- VLAN-aware 모드에서는 vlan_filtering=1로 활성화하며, 각 포트에 PVID(기본 태그), tagged(트렁크), untagged(액세스) 속성을 설정합니다.
- IGMP/MLD Snooping은 멀티캐스트 트래픽을 필요한 포트에만 전달하도록 MDB(Multicast Database)를 관리하여 불필요한 flooding을 방지합니다.
- br_netfilter 모듈은 L2 브리지 패킷을 L3 iptables 체인으로 전달하며, Docker/Kubernetes 환경에서 iptables 규칙 적용에 필수적입니다.
- ebtables/nftables bridge는 L2 레벨 패킷 필터링을 수행하며, MAC 기반 접근 제어, ARP 필터링, VLAN 조작 등에 사용됩니다.
- TC flower HW 오프로드는 NIC 하드웨어로 브리지 규칙을 내려보내 CPU 부하 없이 L2 포워딩을 수행합니다(switchdev 드라이버 필요).
- XDP redirect는 드라이버 레벨에서 패킷을 직접 다른 포트로 전달하여 커널 네트워크 스택 전체를 우회하는 초고속 경로를 제공합니다.
- switchdev 프레임워크는 커널 브리지 이벤트(FDB 추가/삭제, VLAN 설정)를 HW 스위치 ASIC에 동기화하여 하드웨어 가속을 실현합니다.
- Hairpin 모드(BR_HAIRPIN_MODE)는 같은 포트로 들어온 패킷을 같은 포트로 다시 내보내는 기능으로, Docker 컨테이너 간 통신에 필수입니다.
- Proxy ARP(BR_PROXYARP)는 브리지가 다른 포트의 호스트를 대신하여 ARP 응답을 생성하며, PVLAN(Private VLAN) 구현에 활용됩니다.
단계별 이해
- 브리지 생성과 포트 연결:
ip link add br0 type bridge로 net_bridge 구조체가 생성되고,ip link set eth0 master br0로 NIC의 rx_handler에br_handle_frame()이 등록됩니다. 이후 해당 NIC로 수신되는 모든 패킷은 브리지 코드로 진입합니다. - 패킷 수신과 MAC 학습: 패킷이 NIC에 도착하면
br_handle_frame()→br_fdb_update()로 소스 MAC 주소가 FDB 해시 테이블에 기록됩니다. 이미 존재하는 엔트리는 타임스탬프(jiffies)만 갱신합니다. - FDB 룩업과 포워딩 결정: 목적지 MAC으로
br_fdb_find_rcu()를 호출합니다. 일치하는 엔트리가 있으면 해당 포트로 unicast 전달, 없으면 모든 포트로 flooding합니다. 브로드캐스트/멀티캐스트도 이 경로를 따릅니다. - VLAN 필터링 적용: vlan_filtering 활성 시
br_allowed_ingress()에서 untagged 패킷에 PVID 태그를 부여하고 허용 VLAN 목록을 검사합니다. 송신 시br_allowed_egress()에서 UNTAGGED 플래그가 있으면 태그를 제거합니다. - STP 루프 방지: STP 활성 시 BPDU 교환으로 루트 브리지를 선출하고 중복 경로를 Blocking 상태로 전환합니다. 토폴로지 변경 감지 시 FDB를 빠르게 에이징하여 수렴합니다.
- 멀티캐스트 최적화: IGMP/MLD Snooping이 활성화되면 Join/Leave 메시지를 감청하여 MDB를 구성합니다. 멀티캐스트 패킷은 MDB에 등록된 포트에만 전달되어 불필요한 대역폭 낭비를 방지합니다.
- Netfilter 연동: br_netfilter 모듈 로드 시 브리지 패킷이 iptables FORWARD 체인을 통과합니다.
net.bridge.bridge-nf-call-iptables=1sysctl로 제어하며, 컨테이너 환경에서 네트워크 정책 적용의 핵심입니다. - 하드웨어 오프로드: switchdev 지원 NIC에서는 FDB/VLAN/MDB 변경이 자동으로 HW ASIC에 반영됩니다. TC flower로 매칭 규칙을 HW에 내려보내거나, XDP redirect로 드라이버 레벨 고속 포워딩을 구현할 수 있습니다.
Linux Bridge
Linux Bridge는 IEEE 802.1D 표준을 구현하는 소프트웨어 L2 스위치입니다. 물리/가상 인터페이스를 포트로 추가하여 동일 브로드캐스트 도메인으로 연결하며, MAC 주소 학습(FDB), STP/RSTP, VLAN 필터링을 지원합니다.
Bridge 아키텍처 (net_bridge)
커널 내부에서 브리지는 struct net_bridge로 표현됩니다. 각 포트는 struct net_bridge_port로 관리되며, FDB(Forwarding Database)는 해시 테이블 기반입니다.
/* net/bridge/br_private.h — 브리지 핵심 구조체 */
struct net_bridge {
spinlock_t lock;
struct list_head port_list; /* 브리지 포트 목록 */
struct net_device *dev; /* 브리지 net_device */
struct pcpu_sw_netstats __percpu *stats;
struct hlist_head fdb_list; /* FDB 엔트리 목록 */
struct rhashtable fdb_hash_tbl; /* FDB 해시 테이블 */
unsigned long ageing_time; /* FDB 에이징 타이머 (기본 300초) */
u16 group_fwd_mask;
u16 default_pvid; /* VLAN-aware 모드: 기본 PVID */
/* 멀티캐스트 관련 필드 */
struct bridge_mcast_own_query ip4_own_query;
struct bridge_mcast_own_query ip6_own_query;
u8 multicast_igmp_version; /* IGMP 버전 (2 또는 3) */
u8 multicast_mld_version; /* MLD 버전 (1 또는 2) */
bool multicast_disabled; /* Snooping 비활성 여부 */
bool multicast_querier; /* Querier 기능 */
u32 hash_max; /* MDB 해시 테이블 최대 크기 */
bool vlan_enabled; /* VLAN filtering 활성화 */
u16 vlan_proto; /* ETH_P_8021Q 또는 ETH_P_8021AD */
struct net_bridge_vlan_group __rcu *vlgrp; /* VLAN 그룹 */
/* STP 관련 필드 */
bridge_id designated_root;
bridge_id bridge_id;
unsigned char topology_change;
u32 root_path_cost;
struct timer_list hello_timer;
struct timer_list tcn_timer;
struct timer_list topology_change_timer;
/* ... */
};
/* net/bridge/br_private.h — 브리지 포트 구조체 */
struct net_bridge_port {
struct net_bridge *br; /* 소속 브리지 */
struct net_device *dev; /* 포트의 net_device */
struct list_head list;
unsigned long flags;
port_id port_id; /* STP 포트 ID */
u8 state; /* BR_STATE_DISABLED/LISTENING/... */
u8 priority;
struct net_bridge_vlan_group __rcu *vlgrp; /* 포트별 VLAN 그룹 */
u16 backup_port;
/* ... */
};
/* net/bridge/br_private.h — VLAN 엔트리 구조체 */
struct net_bridge_vlan {
struct rhash_head vnode; /* VLAN 해시 테이블 노드 */
struct rhash_head tnode; /* tunnel 해시 테이블 노드 */
u16 vid; /* VLAN ID */
u16 flags; /* VLAN 플래그 */
#define BRIDGE_VLAN_INFO_MASTER BIT(0) /* 브리지 자체 VLAN */
#define BRIDGE_VLAN_INFO_PVID BIT(1) /* Port VLAN ID */
#define BRIDGE_VLAN_INFO_UNTAGGED BIT(2) /* egress 시 태그 제거 */
#define BRIDGE_VLAN_INFO_RANGE_BEGIN BIT(3) /* VLAN 범위 시작 */
#define BRIDGE_VLAN_INFO_RANGE_END BIT(4) /* VLAN 범위 끝 */
#define BRIDGE_VLAN_INFO_BRENTRY BIT(5) /* 브리지 VLAN 엔트리 */
struct pcpu_sw_netstats __percpu *stats; /* per-CPU 통계 */
union {
struct net_bridge_vlan *brvlan; /* 포트 VLAN → 브리지 VLAN 참조 */
struct net_bridge_vlan_group __rcu *br_vg; /* 브리지 VLAN 그룹 */
};
struct rcu_head rcu;
};
FDB (Forwarding Database)
FDB는 MAC 주소와 포트 매핑을 저장하는 테이블입니다. 수신 패킷의 소스 MAC을 학습(learning)하고, 목적지 MAC으로 출력 포트를 결정(forwarding)합니다.
/* net/bridge/br_private.h — FDB 엔트리 */
struct net_bridge_fdb_entry {
struct rhash_head rhnode;
struct net_bridge_port *dst; /* 출력 포트 (NULL=로컬) */
struct {
unsigned char addr[6]; /* MAC 주소 */
u16 vlan_id; /* VLAN ID (0=untagged) */
} key;
unsigned long flags;
#define BR_FDB_LOCAL 0 /* 브리지 자체 MAC */
#define BR_FDB_STATIC 1 /* 정적 엔트리 */
#define BR_FDB_ADDED_BY_USER 2 /* 사용자가 추가 */
unsigned long updated; /* 마지막 갱신 시간 (jiffies) */
unsigned long used; /* 마지막 참조 시간 */
struct rcu_head rcu;
};
# FDB 테이블 조회
bridge fdb show dev br0
# 정적 FDB 엔트리 추가
bridge fdb add 00:11:22:33:44:55 dev eth0 master static
# FDB 에이징 타임 설정 (초)
ip link set br0 type bridge ageing_time 30000
FDB rhashtable 해시 알고리즘 상세
FDB는 rhashtable(Resizable Hash Table)을 사용하여 O(1) 평균 조회 성능을 제공합니다. 해시 키는 MAC 주소(6바이트) + VLAN ID(2바이트)를 결합한 8바이트이며, jhash(Jenkins Hash)로 해시됩니다.
/* net/bridge/br_fdb.c — rhashtable 파라미터 */
static const struct rhashtable_params br_fdb_rht_params = {
.head_offset = offsetof(struct net_bridge_fdb_entry, rhnode),
.key_offset = offsetof(struct net_bridge_fdb_entry, key),
.key_len = sizeof(struct net_bridge_fdb_key), /* 8 bytes: MAC[6] + VID[2] */
.automatic_shrinking = true,
.hashfn = jhash, /* Jenkins Hash */
};
/* FDB 키 구조체 */
struct net_bridge_fdb_key {
unsigned char addr[6]; /* MAC 주소 */
u16 vlan_id; /* VLAN ID (0 = untagged) */
};
/* net/bridge/br_fdb.c — FDB 조회 경로 */
static struct net_bridge_fdb_entry *
br_fdb_find_rcu(struct net_bridge *br,
const unsigned char *addr, __u16 vid)
{
struct net_bridge_fdb_key key;
/* 해시 키 구성: MAC + VLAN ID */
memcpy(key.addr, addr, ETH_ALEN);
key.vlan_id = vid;
/* rhashtable_lookup: jhash(key, 8) → 버킷 → 체이닝 탐색 */
return rhashtable_lookup(&br->fdb_hash_tbl, &key,
br_fdb_rht_params);
/* RCU 보호 하에 lock-free 조회 (fast path) */
}
automatic_shrinking=true로 활성화되어 있어, 대규모 MAC 학습 후 에이징으로 엔트리가 줄어들면 메모리를 자동 반환합니다.
FDB 학습/에이징/통지 경로
FDB 엔트리의 전체 생명주기: 학습(learning) → 갱신(update) → 에이징(aging) → 삭제(cleanup) → 통지(notify).
/* net/bridge/br_fdb.c — FDB 학습 (소스 MAC 갱신) */
bool br_fdb_update(struct net_bridge *br,
struct net_bridge_port *source,
const unsigned char *addr, u16 vid,
unsigned long flags)
{
struct net_bridge_fdb_entry *fdb;
fdb = br_fdb_find_rcu(br, addr, vid);
if (likely(fdb)) {
/* 기존 엔트리: 타임스탬프만 갱신 */
if (unlikely(source != fdb->dst)) {
/* MAC이 다른 포트로 이동 → 포트 갱신 */
fdb->dst = source;
modified = true;
}
if (fdb->updated != jiffies)
fdb->updated = jiffies;
} else {
/* 새 MAC: 엔트리 생성 → 해시 삽입 */
fdb = fdb_create(br, source, addr, vid, flags);
if (!fdb)
return false;
/* rhashtable에 삽입 */
rhashtable_lookup_insert_fast(&br->fdb_hash_tbl,
&fdb->rhnode,
br_fdb_rht_params);
/* switchdev 통지: HW FDB에도 추가 */
br_switchdev_fdb_notify(br, fdb, RTM_NEWNEIGH);
/* Netlink 통지: userspace에 전달 */
br_fdb_notify(br, fdb, RTM_NEWNEIGH, false);
}
return true;
}
/* net/bridge/br_fdb.c — FDB 에이징 (GC 타이머) */
void br_fdb_cleanup(struct work_struct *work)
{
struct net_bridge *br = container_of(work, ...);
unsigned long timeout = br->ageing_time; /* 기본 300초 = 30000 centisec */
/* FDB 리스트 순회 */
hlist_for_each_entry_rcu(f, &br->fdb_list, fdb_node) {
/* 정적(static)/로컬(local) 엔트리는 에이징 대상 아님 */
if (test_bit(BR_FDB_STATIC, &f->flags) ||
test_bit(BR_FDB_LOCAL, &f->flags))
continue;
/* jiffies 비교: 마지막 갱신 후 ageing_time 경과? */
if (time_after(jiffies, f->updated + timeout)) {
/* 에이징 만료: 삭제 + 통지 */
br_switchdev_fdb_notify(br, f, RTM_DELNEIGH);
br_fdb_notify(br, f, RTM_DELNEIGH, true);
fdb_delete(br, f, true);
}
}
/* 다음 GC 주기 스케줄링 (ageing_time / 2 간격) */
mod_delayed_work(system_long_wq, &br->gc_work,
br->ageing_time / 2);
}
# ── FDB 변경 모니터링 (Netlink RTM_NEWNEIGH/RTM_DELNEIGH) ──
# 실시간 FDB 이벤트 감시
bridge monitor fdb
# 출력 예시:
# aa:bb:cc:dd:ee:ff dev eth0 master br0 state learned
# aa:bb:cc:dd:ee:ff dev eth0 master br0 state deleted
# ip monitor로도 가능
ip monitor neigh
# bpftrace로 FDB 학습 추적
bpftrace -e 'kprobe:br_fdb_update {
$mac = (uint8 *)arg2;
printf("FDB update: %02x:%02x:%02x:%02x:%02x:%02x vid=%d\n",
$mac[0], $mac[1], $mac[2], $mac[3], $mac[4], $mac[5], arg3);
}'
FDB 제한/보안
FDB는 보안 관점에서 MAC 스푸핑, MAC flooding 공격에 취약할 수 있습니다. 정적 엔트리, 포트 격리, MAC 제한 등의 기법으로 방어합니다.
| 보안 기법 | 커널 메커니즘 | 설정 방법 | 용도 |
|---|---|---|---|
| 정적 FDB | BR_FDB_STATIC 플래그 | bridge fdb add ... static | MAC 스푸핑 방지: 동적 학습으로 덮어쓸 수 없음 |
| 학습 비활성화 | BR_LEARNING 플래그 해제 | bridge link set dev ethX learning off | 지정된 MAC만 통신 허용 (정적 엔트리와 조합) |
| 포트 격리 | BR_ISOLATED 플래그 | bridge link set dev ethX isolated on | 격리된 포트 간 직접 통신 차단 (Private VLAN 유사) |
| Unicast flood 차단 | BR_FLOOD 플래그 해제 | bridge link set dev ethX flood off | unknown unicast flooding 방지 |
| MAC 제한 | 커널 직접 미지원 | ebtables + 카운터 또는 switchdev HW limit | 포트당 MAC 주소 수 제한 |
# ── FDB 보안 설정 예시 ──
# 1. 정적 FDB: MAC 스푸핑 방지
bridge fdb add 00:11:22:33:44:55 dev eth0 master static
bridge fdb add 00:11:22:33:44:66 dev eth1 master static
# 2. 동적 학습 비활성화 (정적 엔트리 이외 통신 차단)
bridge link set dev eth0 learning off
bridge link set dev eth1 learning off
# 3. 포트 격리: eth0↔eth1 직접 통신 차단 (업링크만 통신 가능)
bridge link set dev eth0 isolated on
bridge link set dev eth1 isolated on
# eth2(업링크)는 isolated off 유지 → eth0/eth1 모두 eth2와만 통신
# 4. Unknown unicast flooding 차단
bridge link set dev eth0 flood off
# FDB에 없는 MAC으로의 패킷이 eth0으로 flood되지 않음
# 5. 포트별 플래그 확인
bridge -d link show dev eth0
# 출력 예: learning off flood off isolated on
# 6. MAC flooding 공격 탐지
# FDB 엔트리 수 모니터링
watch -n1 'bridge fdb show br br0 | wc -l'
# 급증 시 MAC flooding 공격 의심
BR_ISOLATED는 동일 VLAN 내에서도 포트 간 통신을 차단합니다. PVLAN(Private VLAN)의 Isolated 포트와 유사한 동작이며, 게스트 네트워크 분리에 유용합니다. 다만 BR_ISOLATED 포트끼리만 격리되므로, 업링크 포트는 반드시 isolated off로 유지해야 합니다.
STP / RSTP
STP(Spanning Tree Protocol, IEEE 802.1D)는 브리지 루프를 방지합니다. RSTP(Rapid STP, IEEE 802.1w)는 수렴 시간을 크게 단축합니다.
| 포트 상태 | 상수 | 데이터 전달 | MAC 학습 | 설명 |
|---|---|---|---|---|
| Disabled | BR_STATE_DISABLED (0) | X | X | 포트 비활성 |
| Listening | BR_STATE_LISTENING (1) | X | X | BPDU 수신 대기 |
| Learning | BR_STATE_LEARNING (2) | X | O | FDB 학습 중 |
| Forwarding | BR_STATE_FORWARDING (3) | O | O | 정상 동작 |
| Blocking | BR_STATE_BLOCKING (4) | X | X | 루프 차단 |
# 브리지 생성 및 포트 추가
ip link add name br0 type bridge
ip link set br0 up
ip link set eth0 master br0
ip link set eth1 master br0
# STP 활성화
ip link set br0 type bridge stp_state 1
# STP 상태 확인
bridge link show
cat /sys/class/net/br0/bridge/stp_state
# 브리지 우선순위 설정 (루트 브리지 선출)
ip link set br0 type bridge priority 4096
# 포트별 STP 비용 설정
ip link set eth0 type bridge_slave cost 100
브리지 패킷 처리 흐름
브리지로 수신된 패킷은 br_handle_frame()에서 처리됩니다. BPDU인지 확인 후, FDB 룩업을 통해 unicast forwarding 또는 flooding을 수행합니다.
/* net/bridge/br_input.c — 브리지 패킷 수신 흐름 */
/* 1. NIC 드라이버 → netif_receive_skb() → br_handle_frame() */
static rx_handler_result_t br_handle_frame(
struct sk_buff **pskb)
{
struct net_bridge_port *p = br_port_get_rcu(skb->dev);
/* STP BPDU 처리 */
if (unlikely(is_link_local_ether_addr(dest)))
return br_handle_local_finish(skb);
/* 포트 상태가 FORWARDING이 아니면 드롭 */
if (p->state == BR_STATE_FORWARDING) {
/* 2. 소스 MAC 학습 → FDB 갱신 */
br_fdb_update(br, p, eth_hdr(skb)->h_source, vid, ...);
/* 3. VLAN 필터링 (vlan_enabled 시) */
if (!br_allowed_ingress(br, ...))
goto drop;
/* 4. 목적지 MAC으로 FDB 룩업 */
dst = br_fdb_find_rcu(br, dest, vid);
if (dst) {
/* unicast: 해당 포트로 전달 */
br_forward(dst->dst, skb, ...);
} else {
/* unknown unicast: 모든 포트로 flooding */
br_flood(br, skb, ...);
}
}
}
Netfilter 훅을 포함한 완전한 브리지 패킷 처리 경로:
netif_receive_skb
└→ br_handle_frame (rx_handler 등록)
├→ NF_BR_PRE_ROUTING (ebtables PREROUTING)
├→ br_fdb_update (소스 MAC 학습)
├→ br_allowed_ingress (VLAN ingress 필터)
├→ NF_BR_FORWARD (ebtables FORWARD)
├→ br_fdb_find_rcu (목적지 MAC 룩업)
├→ br_forward() (unicast)
│ └→ deliver_clone()
│ └→ br_dev_queue_push_xmit()
│ └→ NF_BR_POST_ROUTING → dev_queue_xmit()
└→ br_flood() (unknown unicast / broadcast)
└→ 각 포트에 deliver_clone() 반복
[switchdev 오프로드 분기]
br_fdb_update() → br_switchdev_fdb_notify()
→ SWITCHDEV_FDB_ADD_TO_DEVICE 이벤트
→ NIC 드라이버 hardware FDB 갱신
VLAN-aware Bridge 동작 원리
Linux Bridge는 커널 4.2+부터 VLAN-aware 모드(vlan_filtering 1)를 지원합니다. 이 모드에서는 브리지 자체가 802.1Q VLAN 필터링을 수행하며, 각 포트에 PVID, tagged, untagged 속성을 독립적으로 설정할 수 있습니다. 기존 VLAN 서브인터페이스 방식(bridge + vlan subif)보다 확장성과 성능이 뛰어납니다.
br_allowed_ingress() / br_allowed_egress() 코드
VLAN-aware 브리지에서 패킷의 ingress/egress 허용 여부를 결정하는 핵심 함수입니다. ingress에서는 untagged 패킷에 PVID를 부여하고, egress에서는 UNTAGGED 플래그에 따라 태그를 제거합니다.
/* net/bridge/br_vlan.c — Ingress VLAN 필터링 */
bool br_allowed_ingress(const struct net_bridge *br,
struct net_bridge_vlan_group *vg,
struct sk_buff *skb, u16 *vid,
u8 *state,
struct net_bridge_vlan **vlan)
{
/* VLAN filtering 비활성이면 항상 허용 */
if (!br->vlan_enabled) {
BR_INPUT_SKB_CB(skb)->vlan_filtered = false;
return true;
}
/* skb에서 VLAN 태그 추출 */
if (!br_vlan_get_tag(skb, vid)) {
/* tagged 패킷: VID가 포트의 allowed 목록에 있는지 확인 */
v = br_vlan_find(vg, *vid);
if (!v || !br_vlan_should_use(v))
goto drop; /* 허용되지 않은 VLAN → 드롭 */
} else {
/* untagged 패킷: PVID 확인 */
if (!br_vlan_get_pvid_state(vg, vid, state))
goto drop; /* PVID 없으면 드롭! */
/* PVID 태그를 skb에 부여 */
__vlan_hwaccel_put_tag(skb, br->vlan_proto, *vid);
}
/* 포트 상태 검사 (STP Blocking이면 드롭) */
if (*state == BR_STATE_FORWARDING ||
*state == BR_STATE_LEARNING) {
*vlan = v;
return true;
}
drop:
kfree_skb(skb);
return false;
}
/* net/bridge/br_vlan.c — Egress VLAN 처리 */
static int br_vlan_egress(const struct net_bridge *br,
const struct net_bridge_vlan *v,
struct sk_buff *skb)
{
/* UNTAGGED 플래그: egress 시 VLAN 태그 제거 */
if (v->flags & BRIDGE_VLAN_INFO_UNTAGGED)
__vlan_hwaccel_clear_tag(skb);
/* tagged 포트: 태그 유지, 물리 NIC이 802.1Q 헤더 삽입 */
return 0;
}
/* br_allowed_egress(): 출력 포트에서 해당 VID 허용 여부 */
bool br_allowed_egress(struct net_bridge_vlan_group *vg,
const struct sk_buff *skb)
{
u16 vid;
/* VLAN filtering 비활성이면 항상 허용 */
if (!BR_INPUT_SKB_CB(skb)->vlan_filtered)
return true;
br_vlan_get_tag(skb, &vid);
if (br_vlan_find(vg, vid))
return true; /* 해당 VID가 출력 포트에 허용됨 */
return false; /* 드롭 */
}
VLAN-aware Bridge 설정
VLAN-aware 브리지 생성부터 포트별 PVID/tagged/untagged 설정까지 완전한 예제입니다.
# ── 1. VLAN-aware 브리지 생성 ──
ip link add name br0 type bridge vlan_filtering 1 vlan_default_pvid 1
ip link set br0 up
# 포트 추가
ip link set eth0 master br0
ip link set eth1 master br0
ip link set eth2 master br0
# ── 2. Access 포트 설정 (eth0: VLAN 1 기본, VLAN 10/20도 허용) ──
# 기본 PVID=1이 자동 설정됨; 추가 VLAN 허용
bridge vlan add dev eth0 vid 10
bridge vlan add dev eth0 vid 20
# ── 3. Access 포트 설정 (eth1: VLAN 10 전용) ──
# 기본 PVID=1 제거 후 PVID=10으로 변경
bridge vlan del dev eth1 vid 1
bridge vlan add dev eth1 vid 10 pvid untagged
# ── 4. Trunk 포트 설정 (eth2: VLAN 10, 20 tagged) ──
bridge vlan del dev eth2 vid 1
bridge vlan add dev eth2 vid 10
bridge vlan add dev eth2 vid 20
# pvid/untagged 플래그 없음 → tagged 트렁크
# ── 5. 브리지 자체(self)에도 VLAN 추가 (L3 라우팅용) ──
bridge vlan add dev br0 vid 10 self
bridge vlan add dev br0 vid 20 self
# VLAN별 IP 할당 (L3 SVI 역할)
ip link add link br0 name br0.10 type vlan id 10
ip addr add 192.168.10.1/24 dev br0.10
ip link set br0.10 up
# ── 6. 설정 확인 ──
bridge vlan show
# port vlan-id
# eth0 1 PVID Egress Untagged
# 10
# 20
# eth1 10 PVID Egress Untagged
# eth2 10
# 20
PVID / Tagged / Untagged 비교
| 속성 | 플래그 | Ingress 동작 | Egress 동작 | 용도 |
|---|---|---|---|---|
| PVID | BRIDGE_VLAN_INFO_PVID | untagged 패킷에 이 VID 태그 부여 | - | 포트의 기본 VLAN 지정 |
| Untagged | BRIDGE_VLAN_INFO_UNTAGGED | - | 이 VID 패킷의 VLAN 태그 제거 | Access 포트 (일반 PC 연결) |
| Tagged | (플래그 없음) | tagged 패킷의 VID가 허용 목록에 있으면 통과 | VLAN 태그 유지 | Trunk 포트 (스위치 간 연결) |
| PVID + Untagged | 두 플래그 조합 | untagged → PVID 부여 | 태그 제거 | 일반적인 Access 포트 설정 |
bridge vlan del dev ethX vid 1 후 새 PVID 미지정) 해당 포트로 수신되는 모든 untagged 패킷이 무조건 드롭됩니다. br_allowed_ingress()에서 br_vlan_get_pvid_state()가 실패하기 때문입니다. Docker/K8s 환경에서 컨테이너 통신이 갑자기 끊기는 원인 중 하나입니다.
802.1ad QinQ (Provider Bridge)
QinQ(802.1ad)는 기존 802.1Q 태그 위에 S-VLAN(Service VLAN) 태그를 추가하는 이중 태깅 기술입니다. ISP가 고객 트래픽을 서비스 VLAN으로 캡슐화하여 공급자 네트워크를 통과시킬 때 사용합니다. Linux Bridge는 vlan_protocol 802.1ad로 S-VLAN 브리지를 구성할 수 있습니다.
| 구분 | 802.1Q | 802.1ad (QinQ) |
|---|---|---|
| EtherType | 0x8100 | 0x88A8 (outer S-VLAN) |
| 태그 수 | 1개 (C-VLAN) | 2개 (S-VLAN + C-VLAN) |
| VLAN 범위 | 4094 | 4094 × 4094 (이론적) |
| 커널 지원 | ETH_P_8021Q | ETH_P_8021AD |
| 용도 | LAN 내 VLAN 분리 | ISP 서비스 분리, 데이터센터 멀티테넌시 |
# ── 802.1ad QinQ 브리지 설정 ──
# S-VLAN 브리지 생성 (vlan_protocol 802.1ad)
ip link add name br-provider type bridge \
vlan_filtering 1 \
vlan_protocol 802.1ad \
vlan_default_pvid 0
ip link set br-provider up
# 고객 포트: S-VLAN 100 할당 (untagged ingress → S-VLAN 100 태그 추가)
ip link set eth0 master br-provider
bridge vlan add dev eth0 vid 100 pvid untagged
# 업링크 포트: S-VLAN 100, 200 trunk
ip link set eth1 master br-provider
bridge vlan add dev eth1 vid 100
bridge vlan add dev eth1 vid 200
# 결과: eth0에서 수신된 고객 패킷(C-VLAN 태그 포함)에
# S-VLAN 100 (EtherType 0x88A8) 태그가 추가되어 eth1로 전달
# 프레임 구조: [DA][SA][0x88A8][S-VID=100][0x8100][C-VID=10][payload]
# QinQ 확인
bridge vlan show
tcpdump -i eth1 -e -nn 'ether proto 0x88a8'
IGMP/MLD Snooping
IGMP Snooping은 멀티캐스트 트래픽을 필요한 포트에만 전달하는 L2 최적화입니다. 브리지가 IGMP 메시지를 감청(snooping)하여 MDB(Multicast Database)를 관리하고, 멀티캐스트 스트림을 등록된 수신자 포트에만 전달합니다.
/* net/bridge/br_multicast.c — MDB 핵심 구조체 */
struct net_bridge_mdb_entry {
struct hlist_node hlist[2]; /* MDB 해시 버킷 */
struct net_bridge *br;
struct br_ip addr; /* 멀티캐스트 그룹 주소 (IPv4/IPv6) */
struct hlist_head ports; /* 수신자 포트 그룹 목록 */
struct timer_list timer; /* 그룹 멤버십 타이머 */
struct rcu_head rcu;
};
struct net_bridge_port_group {
struct net_bridge_port *key.port; /* 포트 참조 */
struct hlist_node mglist;
struct timer_list timer; /* 포트별 멤버십 타이머 */
unsigned char flags;
#define MDB_PG_FLAGS_PERMANENT BIT(0) /* 정적 엔트리 */
#define MDB_PG_FLAGS_OFFLOAD BIT(1) /* HW 오프로드됨 */
#define MDB_PG_FLAGS_FAST_LEAVE BIT(2) /* Fast Leave 활성 */
struct rcu_head rcu;
};
/* 멀티캐스트 처리 핵심 함수 */
/* IGMP/MLD 패킷 수신 시 호출 */
int br_multicast_rcv(struct net_bridge_mcast *brmctx,
struct net_bridge_mcast_port *pmctx,
struct net_bridge_vlan *vlan,
struct sk_buff *skb, u16 vid);
/* MDB 그룹 추가/갱신 */
struct net_bridge_mdb_entry *br_multicast_add_group(
struct net_bridge_mcast *brmctx,
struct net_bridge_mcast_port *pmctx,
struct br_ip *group, ...);
/* 멀티캐스트 패킷 선별 전달 */
static void br_multicast_flood(struct net_bridge_mdb_entry *mdst,
struct sk_buff *skb, ...);
# IGMP Snooping 활성화
ip link set br0 type bridge mcast_snooping 1
# IGMP Querier 활성화 (라우터 없는 환경)
ip link set br0 type bridge mcast_querier 1
# IGMP v3 사용 설정 (SSM 지원)
ip link set br0 type bridge mcast_igmp_version 3
ip link set br0 type bridge mcast_mld_version 2
# MDB (Multicast Database) 조회
bridge mdb show
bridge mdb show dev br0
# 정적 MDB 엔트리 추가
bridge mdb add dev br0 port eth1 grp 239.1.1.1 permanent
# Fast Leave 설정 (포트에 단일 수신자만 있을 때 즉시 탈퇴)
bridge link set dev eth1 mcast_fast_leave on
# VLAN-aware MDB (VLAN별 멀티캐스트 분리)
ip link set br0 type bridge mcast_vlan_snooping 1
bridge mdb add dev br0 port eth1 grp 239.1.1.1 vid 10 permanent
# Snooping 통계 확인
bridge -s mdb show
| MDB 플래그 | 의미 | 적용 시나리오 |
|---|---|---|
permanent | 정적 엔트리 (타이머 없음) | 서버 포트, 라우터 연결 포트 |
temp | 동적 엔트리 (타이머 갱신) | 일반 IGMP Join/Leave로 관리 |
| Fast Leave | IGMP Leave 즉시 탈퇴 | 포트에 수신자 1개만 있을 때 |
| offload | HW 오프로드됨 | switchdev 지원 NIC |
| 항목 | IGMP v1 | IGMP v2 | IGMP v3 |
|---|---|---|---|
| Leave 메커니즘 | 없음 (타이머 만료) | Leave Group | Leave Group |
| Fast Leave | 미지원 | 지원 | 지원 |
| 소스 필터링 | 미지원 | 미지원 | 지원 (SSM) |
| Querier 선출 | 미지원 | 지원 | 지원 |
| Linux 기본값 | - | v2 | 설정 필요 |
RSTP + STP 보안 기능
IEEE 802.1w RSTP(Rapid Spanning Tree Protocol)는 STP를 개선하여 1~2초 내 수렴을 달성합니다. Linux 커널에서 BPDU Guard, Root Guard, PortFast 등의 보안 기능을 통해 STP 공격을 방어합니다.
| 항목 | STP (802.1D) | RSTP (802.1w) |
|---|---|---|
| 수렴 시간 | 30~50초 | 1~2초 |
| 포트 역할 | 3가지 | 5가지 (Alternate, Backup 추가) |
| BPDU | 일방 전달 | 양방향 핸드셰이크 |
| Proposal/Agreement | 없음 | 있음 (빠른 전환 메커니즘) |
| Linux 커널 | STP_STATE=1 | 자동 협상 (RSTP 우선) |
| 포트 역할 | 설명 | 데이터 전달 |
|---|---|---|
| Root | 루트 브리지에 가장 가까운 포트 | O |
| Designated | 세그먼트에서 최적 경로를 제공하는 포트 | O |
| Alternate | Root 포트의 대안 (백업 경로) | X (Blocking) |
| Backup | Designated 포트의 백업 (같은 LAN) | X (Blocking) |
| Disabled | 관리자 비활성화 | X |
/* net/bridge/br_private.h — 포트 플래그 비트 필드 */
#define BR_HAIRPIN_MODE BIT(0) /* 헤어핀: 수신 포트로 재전달 허용 */
#define BR_BPDU_GUARD BIT(1) /* BPDU Guard: BPDU 수신 시 포트 차단 */
#define BR_ROOT_BLOCK BIT(2) /* Root Guard: 루트 경로 변경 차단 */
#define BR_MULTICAST_FAST_LEAVE BIT(3) /* Fast Leave */
#define BR_ADMIN_COST BIT(4) /* 관리자 지정 path cost */
#define BR_LEARNING BIT(5) /* MAC 학습 허용 */
#define BR_FLOOD BIT(6) /* flooding 허용 */
#define BR_AUTO_MASK (BR_FLOOD | BR_LEARNING)
#define BR_PROMISC BIT(7) /* 무차별 모드 */
#define BR_PROXYARP BIT(8) /* Proxy ARP */
#define BR_LEARNING_SYNC BIT(9) /* 학습 동기화 */
#define BR_PORT_LOCKED BIT(13) /* 포트 잠금 */
#define BR_PORT_MAB BIT(14) /* MAC Auth Bypass */
/* BPDU Guard 동작: br_stp_rcv() 내부 */
static void br_stp_rcv(const struct stp_proto *proto,
struct sk_buff *skb,
struct net_device *dev)
{
struct net_bridge_port *p = br_port_get_rtnl(dev);
/* BPDU Guard: 엣지 포트에서 BPDU 수신 시 Error-Disabled */
if (p->flags & BR_BPDU_GUARD) {
br_log_state(p);
br_stp_disable_port(p); /* 포트 비활성화 */
return;
}
/* Root Guard: 더 나은 BPDU 수신해도 루트 경로 변경 차단 */
if (p->flags & BR_ROOT_BLOCK) {
if (br_is_superior_bpdu(p, bpdu)) {
p->state = BR_STATE_LISTENING; /* Root-Inconsistent */
return;
}
}
}
# PortFast: 엣지 포트 즉시 Forwarding (호스트 연결 포트용)
bridge link set dev eth0 portfast on
# PortFast auto: 연결 타입에 따라 자동 판단
bridge link set dev eth0 portfast auto
# BPDU Guard: BPDU 수신 시 포트 Error-Disabled
bridge link set dev eth0 guard on
# Root Guard: 루트 브리지 경로 변경 차단
bridge link set dev eth0 root_block on
# 현재 포트 STP 설정 확인
bridge -d link show
# BPDU Guard로 비활성화된 포트 수동 복구
ip link set eth0 down
ip link set eth0 up
# STP 우선순위 및 포트 비용 설정
ip link set br0 type bridge priority 4096 # 루트 브리지 선출 (낮을수록 유리)
ip link set eth0 type bridge_slave cost 10 # 포트 비용 설정
ip link set eth0 type bridge_slave priority 32 # 포트 우선순위 설정
# Hello Time, Max Age, Forward Delay 설정
ip link set br0 type bridge hello_time 200 # 2초 (단위: 1/100초)
ip link set br0 type bridge max_age 2000 # 20초
ip link set br0 type bridge forward_delay 1500 # 15초
| 보안 기능 | 목적 | 동작 | 적용 포트 |
|---|---|---|---|
| PortFast | 빠른 호스트 연결 | Listening/Learning 건너뛰고 즉시 Forwarding | 호스트 연결 포트 (엣지) |
| BPDU Guard | STP 공격 방어 | BPDU 수신 시 포트 Error-Disabled | PortFast 포트 |
| Root Guard | 루트 브리지 보호 | 더 나은 BPDU 무시, Root-Inconsistent 상태 | 업스트림 포트 제외 |
errdisable recovery는 실제 원인 제거 후에만 수행해야 합니다.
STP 상태 전이 다이어그램
STP(802.1D)에서 포트는 5개의 상태(Disabled → Blocking → Listening → Learning → Forwarding)를 순서대로 전이합니다. RSTP(802.1w)는 Disabled/Blocking/Listening을 Discarding 하나로 통합하여 상태 머신을 단순화하고, Proposal/Agreement 메커니즘으로 수렴 시간을 획기적으로 단축합니다.
BPDU 패킷 형식 상세
BPDU(Bridge Protocol Data Unit)는 브리지 간 토폴로지 정보를 교환하는 L2 프레임입니다. Configuration BPDU(Type 0x00)와 TCN(Topology Change Notification) BPDU(Type 0x80)가 있으며, RSTP는 Version 2 BPDU(Type 0x02)를 사용합니다.
| 필드 | 오프셋 | 크기 (바이트) | 기본값/범위 | 설명 |
|---|---|---|---|---|
| Protocol Identifier | 0 | 2 | 0x0000 | STP 프로토콜 식별자 (항상 0) |
| Protocol Version | 2 | 1 | 0x00(STP) / 0x02(RSTP) | 프로토콜 버전 |
| BPDU Type | 3 | 1 | 0x00(Config) / 0x80(TCN) / 0x02(RST) | BPDU 종류 |
| Flags | 4 | 1 | 비트 필드 | bit0: TC, bit1: Proposal, bit2-3: Port Role, bit4: Learning, bit5: Forwarding, bit6: Agreement, bit7: TCA |
| Root Bridge ID | 5 | 8 | Priority(2) + MAC(6) | 루트 브리지 식별자 (Priority 상위 4비트 + System ID Extension 12비트 + MAC) |
| Root Path Cost | 13 | 4 | 0~200,000,000 | 루트 브리지까지의 누적 경로 비용 |
| Sender Bridge ID | 17 | 8 | Priority(2) + MAC(6) | BPDU를 보낸 브리지 식별자 |
| Port ID | 25 | 2 | Priority(4비트) + Number(12비트) | BPDU를 보낸 포트 식별자 |
| Message Age | 27 | 2 | 1/256초 단위 | BPDU가 루트에서 발생한 후 경과 시간 |
| Max Age | 29 | 2 | 20초 (6~40초) | BPDU 유효 기간 (초과 시 토폴로지 재계산) |
| Hello Time | 31 | 2 | 2초 (1~10초) | BPDU 전송 주기 |
| Forward Delay | 33 | 2 | 15초 (4~30초) | Listening→Learning, Learning→Forwarding 전이 대기 시간 |
01:80:C2:00:00:00(STP 멀티캐스트)으로 전송됩니다.
/* net/bridge/br_private_stp.h — BPDU 구조체 */
struct br_config_bpdu {
unsigned topology_change:1; /* TC 플래그 */
unsigned topology_change_ack:1; /* TCA 플래그 */
bridge_id root; /* Root Bridge ID */
int root_path_cost; /* Root Path Cost */
bridge_id bridge_id; /* Sender Bridge ID */
port_id port_id; /* Port ID */
int message_age; /* Message Age (1/256초) */
int max_age; /* Max Age */
int hello_time; /* Hello Time */
int forward_delay; /* Forward Delay */
};
/* net/bridge/br_stp_bpdu.c — BPDU 수신 파싱 */
void br_stp_rcv(const struct stp_proto *proto,
struct sk_buff *skb, struct net_device *dev)
{
u8 *buf = skb->data;
struct br_config_bpdu bpdu;
/* Protocol ID 확인 (0x0000) */
if (buf[0] || buf[1])
goto out;
/* BPDU Type에 따라 분기 */
switch (buf[3]) {
case BPDU_TYPE_CONFIG: /* 0x00 */
case BPDU_TYPE_RST: /* 0x02 (RSTP) */
br_received_config_bpdu(p, &bpdu);
break;
case BPDU_TYPE_TCN: /* 0x80 */
br_received_tcn_bpdu(p);
break;
}
}
Hairpin 모드와 Proxy ARP
특수한 네트워크 토폴로지에서 브리지 포트의 기본 동작을 수정해야 하는 경우가 있습니다. Hairpin 모드는 수신 포트와 송신 포트가 동일한 경우의 패킷 전달을 허용하고, Proxy ARP는 브리지가 ARP 요청에 대신 응답하여 BUM 트래픽을 줄입니다.
Hairpin 모드 (BR_HAIRPIN_MODE)
Hairpin 설정 및 커널 내부
# Hairpin 모드 활성화
bridge link set dev eth0 hairpin on
# sysfs를 통한 설정 (레거시)
echo 1 > /sys/class/net/br0/brif/eth0/hairpin_mode
# 현재 설정 확인
bridge -d link show dev eth0
# 출력 예: ... hairpin on learning on ...
# Docker에서 자동 설정 확인
bridge -d link show dev vethXXX | grep hairpin
# Docker daemon은 veth 생성 시 자동으로 hairpin on 설정
/* net/bridge/br_forward.c — Hairpin 모드 검사 */
static int deliver_clone(const struct net_bridge_port *prev,
struct sk_buff *skb,
bool local_orig)
{
/* ... */
}
/* br_forward()에서 hairpin 확인 */
static void __br_forward(const struct net_bridge_port *to,
struct sk_buff *skb, bool local_orig)
{
struct net_bridge_port *inp = br_port_get_rcu(skb->dev);
/* 핵심: 입력 포트와 출력 포트가 같으면 hairpin 검사 */
if (inp == to) {
if (!(to->flags & BR_HAIRPIN_MODE))
return; /* hairpin 미설정 → DROP */
}
/* hairpin 설정됨 → 동일 포트로 재전달 허용 */
__br_deliver(to, skb);
}
/* Docker/libnetwork는 veth 생성 시 BR_HAIRPIN_MODE 자동 설정
* drivers/net/veth.c + dockerd bridge driver
* → iptables DNAT로 컨테이너 자체 접근 시 hairpin 필수 */
hairpin on을 설정합니다. --userland-proxy=false 옵션 사용 시 hairpin이 필수입니다.
Proxy ARP (BR_PROXYARP)
브리지의 Proxy ARP 기능은 호스트를 대신하여 ARP 요청에 응답합니다. 이는 VXLAN/EVPN 환경에서 ARP flooding 비용을 줄이거나, 브리지 뒤의 호스트가 직접 ARP 응답을 생성할 수 없는 환경에서 유용합니다.
# Proxy ARP 활성화
bridge link set dev eth0 proxyarp on
# Proxy ARP + WiFi (AP 모드에서 클라이언트 간 ARP 대리 응답)
bridge link set dev wlan0 proxyarp_wifi on
# Neighbor table 확인 (Proxy ARP가 참조하는 정보)
ip neigh show dev br0
bridge fdb show dev eth0
# VXLAN + Proxy ARP 조합
ip link add vxlan10 type vxlan id 10 dstport 4789 local 10.0.0.1 nolearning proxy
bridge link set dev vxlan10 proxyarp on
/* net/bridge/br_input.c — Proxy ARP 처리 */
static int br_do_proxy_arp(struct sk_buff *skb,
struct net_bridge *br,
u16 vid,
struct net_bridge_port *p)
{
struct arphdr *parp;
u8 *arpptr, *sha;
__be32 sip, tip;
struct neighbour *n;
if (!(p->flags & BR_PROXYARP))
return 0;
/* ARP Request(op=1)만 처리 */
if (parp->ar_op != htons(ARPOP_REQUEST))
return 0;
/* 타겟 IP의 neighbor entry 조회 */
n = neigh_lookup(&arp_tbl, &tip, br->dev);
if (n) {
/* neighbor가 존재하면 ARP Reply를 대신 생성 */
arp_send(ARPOP_REPLY, ETH_P_ARP,
sip, skb->dev, tip,
sha, n->ha, sha);
neigh_release(n);
return 1; /* 대리 응답 완료, 원본 ARP 전파 중단 */
}
return 0;
}
Neighbor Suppression
VXLAN 환경에서 Neighbor Suppression은 ARP/ND(IPv6 Neighbor Discovery) 요청이 VXLAN 터널을 통해 불필요하게 flooding되는 것을 방지합니다. 브리지가 로컬 neighbor 테이블을 기반으로 대리 응답하여 BUM(Broadcast/Unknown-unicast/Multicast) 트래픽을 대폭 감소시킵니다.
# VXLAN + Bridge에서 Neighbor Suppression 활성화
ip link add br0 type bridge vlan_filtering 1
ip link add vxlan10 type vxlan id 10 dstport 4789 local 10.0.0.1 nolearning proxy
ip link set vxlan10 master br0
# Neighbor Suppression 활성화 (neigh_suppress)
bridge link set dev vxlan10 neigh_suppress on
# 수동으로 neighbor 엔트리 추가 (EVPN 컨트롤러가 자동 배포)
bridge fdb append 00:11:22:33:44:55 dev vxlan10 dst 10.0.0.2
ip neigh add 192.168.1.100 lladdr 00:11:22:33:44:55 dev vxlan10
# BUM 트래픽 감소 확인
bridge -s link show dev vxlan10
# neigh_suppress on 후 ARP 브로드캐스트가 로컬 응답으로 대체됨
| 기능 | 플래그 | 주요 사용 환경 | 효과 |
|---|---|---|---|
| Hairpin Mode | BR_HAIRPIN_MODE | Docker veth, KVM macvtap | 동일 포트 루프백 허용 |
| Proxy ARP | BR_PROXYARP | VXLAN/EVPN, WiFi AP | ARP flooding 감소 |
| Proxy ARP WiFi | BR_PROXYARP_WIFI | 802.11 AP 브리지 | 무선 클라이언트 ARP 대리 |
| Neighbor Suppress | BR_NEIGH_SUPPRESS | VXLAN + EVPN Fabric | ARP/ND BUM 트래픽 감소 |
switchdev HW 오프로드 프레임워크
switchdev는 리눅스 커널이 하드웨어 스위치 ASIC을 소프트웨어 브리지와 동일한 API로 제어할 수 있게 하는 프레임워크입니다. 사용자 공간에서 ip/bridge/tc 명령으로 설정하면, 커널 브리지가 switchdev 콜백을 통해 하드웨어 FDB/VLAN/MDB 테이블을 직접 프로그래밍합니다.
상세 문서: eSwitch 모드 전환, Representor 포트, SR-IOV 오프로드 등의 상세 내용은 eSwitch 심화 페이지를 참조하세요.
switchdev 이벤트 종류
| 이벤트 상수 | 방향 | 설명 | 트리거 |
|---|---|---|---|
SWITCHDEV_FDB_ADD_TO_DEVICE | 커널 → 드라이버 | FDB 엔트리를 HW에 추가 | MAC 학습 또는 bridge fdb add |
SWITCHDEV_FDB_DEL_TO_DEVICE | 커널 → 드라이버 | FDB 엔트리를 HW에서 삭제 | MAC 에이징 또는 bridge fdb del |
SWITCHDEV_FDB_ADD_TO_BRIDGE | 드라이버 → 커널 | HW에서 학습한 MAC을 SW FDB에 동기화 | HW MAC 학습 이벤트 |
SWITCHDEV_FDB_DEL_TO_BRIDGE | 드라이버 → 커널 | HW에서 에이징된 MAC을 SW FDB에서 삭제 | HW MAC 에이징 |
SWITCHDEV_PORT_OBJ_ADD | 커널 → 드라이버 | 포트 객체 추가 (VLAN, MDB 등) | bridge vlan add, bridge mdb add |
SWITCHDEV_PORT_OBJ_DEL | 커널 → 드라이버 | 포트 객체 삭제 | bridge vlan del, bridge mdb del |
SWITCHDEV_PORT_ATTR_SET | 커널 → 드라이버 | 포트 속성 설정 | STP state, ageing_time, learning 등 |
SWITCHDEV_BRPORT_OFFLOADED | 드라이버 → 커널 | 포트가 HW 오프로드 중임을 알림 | 드라이버 초기화 |
/* net/bridge/br_switchdev.c — switchdev 알림 전송 */
int br_switchdev_fdb_notify(struct net_bridge *br,
const struct net_bridge_fdb_entry *fdb,
int type)
{
struct switchdev_notifier_fdb_info info = {
.addr = fdb->key.addr.addr,
.vid = fdb->key.vlan_id,
.added_by_user = test_bit(BR_FDB_ADDED_BY_USER, &fdb->flags),
.offloaded = test_bit(BR_FDB_OFFLOADED, &fdb->flags),
};
switch (type) {
case RTM_NEWNEIGH:
return call_switchdev_notifiers(
SWITCHDEV_FDB_ADD_TO_DEVICE,
fdb->dst->dev, &info.info, NULL);
case RTM_DELNEIGH:
return call_switchdev_notifiers(
SWITCHDEV_FDB_DEL_TO_DEVICE,
fdb->dst->dev, &info.info, NULL);
}
return 0;
}
/* 드라이버 측: switchdev 이벤트 수신 등록 */
static struct notifier_block mlx5_swdev_nb = {
.notifier_call = mlx5_esw_bridge_switchdev_event,
};
register_switchdev_notifier(&mlx5_swdev_nb);
switchdev 지원 NIC 비교
| 벤더/드라이버 | 칩셋 | FDB 오프로드 | VLAN 오프로드 | MDB 오프로드 | LAG 오프로드 | TC flower | DSA | 비고 |
|---|---|---|---|---|---|---|---|---|
NVIDIA mlx5 | ConnectX-5/6/7, BlueField | O | O | O | O | O | - | 가장 완성도 높은 switchdev 지원, eSwitch 내장 |
Broadcom bnxt | BCM57xxx, Stingray | O | O | △ | O | O | - | TC flower offload 지원 |
Marvell mv88e6xxx | 88E6xxx 시리즈 | O | O | O | O | - | O | DSA 프레임워크 기반, 임베디드 스위치 |
Marvell prestera | Prestera DX, SwitchDev | O | O | O | O | O | - | 데이터센터 스위치 ASIC |
Microchip ksz | KSZ9477, KSZ8795 | O | O | △ | - | - | O | DSA 기반, 산업용/IoT |
NXP felix | VSC9959 (LS1028A) | O | O | O | O | O | O | DSA 기반 + TC flower, TSN 지원 |
Realtek rtl8365mb/rtl8366rb | RTL8365MB, RTL8366RB | O | O | △ | - | - | O | 저가형 임베디드 스위치 |
TI am65-cpsw | AM65x CPSW | O | O | - | - | - | - | 산업용 프로세서 내장 스위치 |
bridge fdb show에서 offloaded 플래그로 HW 오프로드 여부를 확인할 수 있습니다. DSA 드라이버는 임베디드 스위치 칩의 포트를 일반 netdev로 노출하여 표준 ip/bridge 명령으로 제어할 수 있게 합니다.
switchdev + bridge 설정 예제
# ━━━ Mellanox ConnectX-6 switchdev 모드 설정 ━━━
# 1. eSwitch 모드를 switchdev로 전환
devlink dev eswitch set pci/0000:03:00.0 mode switchdev
# 2. VF representor 자동 생성 확인
ip link show | grep enp3s0f0
# enp3s0f0: PF (uplink representor)
# enp3s0f0_0: VF0 representor
# enp3s0f0_1: VF1 representor
# 3. Bridge 생성 및 포트 연결
ip link add br0 type bridge vlan_filtering 1
ip link set br0 up
ip link set enp3s0f0 master br0
ip link set enp3s0f0_0 master br0
ip link set enp3s0f0_1 master br0
ip link set enp3s0f0 up
ip link set enp3s0f0_0 up
ip link set enp3s0f0_1 up
# 4. VLAN 설정 → HW VLAN 테이블에 자동 오프로드
bridge vlan add dev enp3s0f0_0 vid 100 pvid untagged
bridge vlan add dev enp3s0f0_1 vid 200 pvid untagged
bridge vlan add dev enp3s0f0 vid 100
bridge vlan add dev enp3s0f0 vid 200
# 5. 오프로드 상태 확인
bridge fdb show br br0 | grep offloaded
# 00:11:22:33:44:55 dev enp3s0f0_0 master br0 offloaded
# 6. TC flower 규칙도 HW 오프로드 가능
tc qdisc add dev enp3s0f0 ingress
tc filter add dev enp3s0f0 ingress protocol ip flower \
dst_mac 00:11:22:33:44:55 action mirred egress redirect dev enp3s0f0_0
# ━━━ DSA 임베디드 스위치 (mv88e6xxx) 설정 ━━━
# DSA 스위치 포트는 자동으로 netdev 생성
ip link show | grep lan
# lan1, lan2, lan3, lan4: 각 물리 포트
# wan: 업링크 포트
# Bridge에 DSA 포트 연결
ip link add br-lan type bridge vlan_filtering 1
ip link set lan1 master br-lan
ip link set lan2 master br-lan
ip link set lan3 master br-lan
ip link set lan4 master br-lan
# HW 오프로드 자동 적용 확인
bridge -d link show | grep offload
# lan1: ... offload
br_netfilter와 ebtables
br_netfilter 커널 모듈은 브리지 트래픽이 Netfilter(iptables/nftables) 훅을 통과하도록 합니다. 이 모듈 없이는 브리지를 통과하는 패킷이 iptables 규칙의 영향을 받지 않아 Docker/Kubernetes 환경에서 네트워크 정책이 제대로 동작하지 않습니다.
br_netfilter 미로드 시 Pod 간 통신에 iptables 규칙(NetworkPolicy, kube-proxy)이 적용되지 않습니다. 컨테이너 환경에서는 반드시 로드해야 합니다.
/* net/bridge/br_netfilter_hooks.c — br_netfilter 훅 등록 */
/* PRE_ROUTING 훅: L3 주소를 위한 skb 전처리 */
static unsigned int br_nf_pre_routing(void *priv,
struct sk_buff *skb,
const struct nf_hook_state *state)
{
struct net_bridge_port *p = br_port_get_rcu(skb->dev);
/* skb->dev를 브리지 디바이스로 변경 (iptables가 브리지를 인식하도록) */
skb->dev = p->br->dev;
/* IPv4/IPv6 분기 처리 */
if (skb->protocol == htons(ETH_P_IP))
return br_nf_pre_routing_ipv4(priv, skb, state);
return NF_ACCEPT;
}
/* br_netfilter 훅 배열 */
static const struct nf_hook_ops br_nf_ops[] = {
{
.hook = br_nf_pre_routing,
.pf = NFPROTO_BRIDGE,
.hooknum = NF_BR_PRE_ROUTING,
.priority = NF_BR_PRI_BRNF,
},
{
.hook = br_nf_forward_ip,
.pf = NFPROTO_BRIDGE,
.hooknum = NF_BR_FORWARD,
.priority = NF_BR_PRI_BRNF,
},
/* ... POST_ROUTING, LOCAL_IN, LOCAL_OUT ... */
};
# br_netfilter 모듈 로드
modprobe br_netfilter
# 부팅 시 자동 로드 설정
echo "br_netfilter" > /etc/modules-load.d/br_netfilter.conf
# sysctl 설정 (브리지 트래픽 → iptables/nftables 적용)
sysctl net.bridge.bridge-nf-call-iptables=1
sysctl net.bridge.bridge-nf-call-ip6tables=1
sysctl net.bridge.bridge-nf-call-arptables=1
# 영구 설정 (재부팅 후에도 유지)
cat > /etc/sysctl.d/k8s.conf <<EOF
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
EOF
sysctl --system
# ebtables: L2 수준 MAC 주소 필터링
ebtables -A FORWARD -s 00:11:22:33:44:55 -j DROP
# ebtables: VLAN 태그 기반 필터링
ebtables -A FORWARD --vlan-id 100 -j DROP
# nftables bridge 체인 (ebtables 대체 권장)
nft add table bridge filter
nft add chain bridge filter forward { type filter hook forward priority 0 \; }
nft add rule bridge filter forward vlan id 100 drop
# br_netfilter 상태 확인
lsmod | grep br_netfilter
sysctl net.bridge
| sysctl 파라미터 | 기본값 | 설명 |
|---|---|---|
net.bridge.bridge-nf-call-iptables | 1 | 브리지 IPv4 트래픽을 iptables로 전달 |
net.bridge.bridge-nf-call-ip6tables | 1 | 브리지 IPv6 트래픽을 ip6tables로 전달 |
net.bridge.bridge-nf-call-arptables | 1 | 브리지 ARP 트래픽을 arptables로 전달 |
net.bridge.bridge-nf-filter-vlan-tagged | 0 | VLAN 태그 트래픽도 필터링 |
net.bridge.bridge-nf-filter-pppoe-tagged | 0 | PPPoE 태그 트래픽도 필터링 |
net.bridge.bridge-nf-pass-vlan-input-dev | 0 | VLAN 디바이스를 입력 디바이스로 전달 |
Docker/Kubernetes Bridge 네트워킹
컨테이너 네트워킹은 Linux Bridge를 핵심 인프라로 사용합니다. Docker는 docker0 브리지와 veth pair를 통해 컨테이너에 L2 연결을 제공하고, Kubernetes는 CNI 플러그인 체인(bridge + IPAM)을 통해 Pod 네트워크를 구성합니다. br_netfilter 모듈은 브리지 트래픽에 iptables 규칙을 적용하여 NetworkPolicy와 kube-proxy가 정상 동작하도록 합니다.
Docker bridge 네트워크 내부 동작
Docker의 기본 네트워크 드라이버인 bridge는 Linux Bridge를 기반으로 합니다. docker network create 시 브리지 netdev가 생성되고, docker run 시 veth pair가 생성되어 컨테이너의 네트워크 네임스페이스에 연결됩니다.
# ━━━ Docker bridge 네트워크 생성 흐름 ━━━
# 기본 docker0 브리지 확인
ip link show docker0
bridge link show master docker0
ip addr show docker0
# docker0: 172.17.0.1/16
# 커스텀 브리지 네트워크 생성
docker network create --driver bridge \
--subnet 192.168.100.0/24 \
--gateway 192.168.100.1 \
--opt com.docker.network.bridge.name=br-custom \
my-network
# 생성된 브리지 확인
ip link show br-custom
bridge fdb show br br-custom
# ━━━ 컨테이너 실행 시 네트워크 설정 과정 ━━━
# 1. veth pair 생성
docker run -d --name web --net my-network -p 8080:80 nginx
# 2. 호스트 측 veth 확인 (br-custom에 연결)
bridge link show master br-custom
# vethXXX: master br-custom state forwarding
# 3. 컨테이너 측 네트워크 확인
docker exec web ip addr show eth0
# eth0: 192.168.100.2/24
docker exec web ip route
# default via 192.168.100.1 dev eth0
# 4. Hairpin 자동 설정 확인
bridge -d link show dev vethXXX | grep hairpin
# hairpin on
# ━━━ NAT / 포트 포워딩 iptables 규칙 ━━━
# MASQUERADE: 컨테이너 → 외부 통신 시 호스트 IP로 SNAT
iptables -t nat -L POSTROUTING -n -v | grep 192.168.100
# MASQUERADE all -- 192.168.100.0/24 0.0.0.0/0
# DNAT: -p 8080:80 포트 포워딩
iptables -t nat -L DOCKER -n -v
# DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:8080 to:192.168.100.2:80
# 컨테이너 간 통신 허용 (ICC: Inter-Container Communication)
iptables -L DOCKER-ISOLATION-STAGE-1 -n -v
# 서로 다른 브리지 네트워크 간 격리 규칙
Kubernetes CNI bridge 플러그인
Kubernetes는 CNI(Container Network Interface) 사양을 통해 네트워크를 추상화합니다. bridge CNI 플러그인은 가장 기본적인 구현으로, Linux Bridge + veth pair + IPAM으로 Pod 네트워크를 구성합니다.
// /etc/cni/net.d/10-bridge.conflist — CNI bridge 설정
{
"cniVersion": "1.0.0",
"name": "k8s-pod-network",
"plugins": [
{
"type": "bridge",
"bridge": "cni0",
"isGateway": true,
"isDefaultGateway": true,
"hairpinMode": true,
"ipMasq": true,
"ipam": {
"type": "host-local",
"ranges": [[{"subnet": "10.244.1.0/24"}]],
"routes": [{"dst": "0.0.0.0/0"}]
}
},
{
"type": "portmap",
"capabilities": {"portMappings": true}
}
]
}
# ━━━ CNI bridge 플러그인 실행 과정 ━━━
# 1. kubelet → CRI (containerd) → CNI 호출
# CNI_COMMAND=ADD CNI_CONTAINERID=xxx CNI_NETNS=/proc/.../ns/net
# 2. bridge 플러그인: cni0 브리지 생성 (없으면)
ip link add cni0 type bridge
ip link set cni0 up
ip addr add 10.244.1.1/24 dev cni0
# 3. veth pair 생성 + 연결
ip link add vethXXX type veth peer name eth0 netns $POD_NS
ip link set vethXXX master cni0
ip link set vethXXX up
# 4. Pod 네임스페이스 내 설정
ip -n $POD_NS addr add 10.244.1.2/24 dev eth0
ip -n $POD_NS link set eth0 up
ip -n $POD_NS route add default via 10.244.1.1
# 5. hairpin 모드 설정
bridge link set dev vethXXX hairpin on
# ━━━ Pod-to-Pod 통신 확인 ━━━
# 같은 노드: L2 bridge forwarding
kubectl exec pod-a -- ping 10.244.1.3
# → veth → cni0 FDB 조회 → veth → pod-b (직접 L2)
# 다른 노드: L3 routing (또는 overlay)
kubectl exec pod-a -- ping 10.244.2.5
# → veth → cni0 → host routing → eth0 → remote-node → cni0 → veth → pod
# cni0 브리지 FDB 확인
bridge fdb show br cni0
bridge link show master cni0
컨테이너 네트워크 트러블슈팅
| 증상 | 원인 | 확인 방법 | 해결법 |
|---|---|---|---|
| Pod 간 통신 안 됨, Service 접근 실패 | br_netfilter 모듈 미로드 | lsmod | grep br_netfilter | modprobe br_netfilter + /etc/modules-load.d/에 영구 설정 |
| ClusterIP Service 접근 불가 | bridge-nf-call-iptables=0 | sysctl net.bridge.bridge-nf-call-iptables | sysctl -w net.bridge.bridge-nf-call-iptables=1 + /etc/sysctl.d/에 영구화 |
| 컨테이너에서 외부 통신 안 됨 | ip_forward 비활성 | sysctl net.ipv4.ip_forward | sysctl -w net.ipv4.ip_forward=1 |
| 컨테이너가 자기 published 포트 접근 실패 | veth에 hairpin 미설정 | bridge -d link show | grep hairpin | bridge link set dev vethXXX hairpin on 또는 Docker --userland-proxy=true |
| Docker 포트 포워딩 미동작 | iptables DNAT 규칙 누락 | iptables -t nat -L DOCKER -n | Docker daemon 재시작, iptables 체인 복원 |
| NetworkPolicy 미적용 | CNI 플러그인이 NetworkPolicy 미지원 | bridge CNI는 NetworkPolicy 미지원 | Calico/Cilium 등 NetworkPolicy 지원 CNI 사용 |
| Pod IP 할당 실패 (IPAM 고갈) | host-local IPAM 서브넷 소진 | ls /var/lib/cni/networks/ | 서브넷 확장 또는 종료된 Pod IP 정리 |
| veth 한쪽만 보임 (orphan) | 비정상 컨테이너 종료로 veth 정리 실패 | ip link show | grep veth vs bridge link | ip link del vethXXX으로 수동 정리 |
| Docker 네트워크 간 격리 실패 | DOCKER-ISOLATION 체인 손상 | iptables -L DOCKER-ISOLATION-STAGE-1 | Docker daemon 재시작으로 iptables 규칙 재생성 |
| DNS 해석 실패 (Pod 내부) | 브리지에서 UDP 53 트래픽 드롭 | tcpdump -i cni0 port 53 | bridge-nf-call-iptables + conntrack 규칙 확인 |
net.ipv4.ip_forward=1 미설정과 (2) bridge-nf-call-iptables=1 미설정입니다. 이 두 가지 sysctl이 올바르게 설정되어 있는지 항상 먼저 확인하세요. kubeadm은 초기화 시 이 값을 자동 검사하지만, 수동 클러스터 구성이나 OS 업그레이드 후 리셋되는 경우가 많습니다.
# 필수 sysctl 한 번에 확인
sysctl net.ipv4.ip_forward net.bridge.bridge-nf-call-iptables net.bridge.bridge-nf-call-ip6tables
# 모두 1이어야 정상
# 영구 설정
cat <<EOF > /etc/sysctl.d/99-kubernetes.conf
net.ipv4.ip_forward = 1
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
EOF
sysctl --system
TC flower 오프로드와 XDP redirect
소프트웨어 브리지의 CPU 부담을 극복하기 위해 두 가지 하드웨어 가속 방식을 사용합니다: TC flower는 NIC 하드웨어에서 포워딩을 처리하고, XDP redirect는 커널 스택 초기에 패킷을 리다이렉트하여 SW 브리지 오버헤드를 완전히 우회합니다.
/* drivers/net/ethernet/intel/i40e/i40e_main.c — TC flower 오프로드 패턴 */
/* ndo_setup_tc: TC_SETUP_CLSFLOWER 처리 */
static int i40e_setup_tc(struct net_device *netdev,
enum tc_setup_type type, void *type_data)
{
switch (type) {
case TC_SETUP_CLSFLOWER:
return i40e_setup_tc_cls_flower(np, type_data);
/* ... */
}
}
/* XDP BPF 브리지: bpf_redirect()로 직접 포워딩 */
SEC("xdp")
int xdp_bridge(struct xdp_md *ctx) {
void *data_end = (void *)(long)ctx->data_end;
void *data = (void *)(long)ctx->data;
struct ethhdr *eth = data;
if (eth + 1 > data_end)
return XDP_DROP;
/* FDB BPF map에서 목적지 포트 ifindex 조회 */
__u32 *ifindex = bpf_map_lookup_elem(&fdb_map, eth->h_dest);
if (!ifindex)
return XDP_PASS; /* SW 경로로 fallback */
return bpf_redirect(*ifindex, 0); /* 목적지 NIC으로 직접 전달 */
}
# TC flower 오프로드: VLAN 10 트래픽을 eth1로 리다이렉트
tc qdisc add dev eth0 ingress
tc filter add dev eth0 ingress \
flower vlan_id 10 vlan_ethtype ip \
action mirred egress redirect dev eth1
# 오프로드 확인 (in_hw 표시)
tc filter show dev eth0 ingress
# filter protocol 802.1Q pref 49152 flower chain 0 handle 0x1
# in_hw in_hw_count 1 ← 하드웨어 오프로드 확인
# TC flower: MAC 주소 기반 포워딩
tc filter add dev eth0 ingress \
flower dst_mac 00:11:22:33:44:55 \
action mirred egress redirect dev eth2
# XDP 프로그램 로드
ip link set eth0 xdp obj bridge_xdp.o sec xdp
# XDP 상태 확인
ip link show eth0
# xdp/id:42 ← XDP 프로그램 로드됨
# XDP 제거
ip link set eth0 xdp off
| 방식 | PPS | CPU 사용 | 지연 | 필요 조건 | 적합한 환경 |
|---|---|---|---|---|---|
| SW Bridge | ~2Mpps | 높음 | 5~20μs | 없음 | 일반 VM/컨테이너 환경 |
| TC flower HW | NIC 성능 | 거의 0 | ~1μs | HW offload 지원 NIC | SmartNIC, SR-IOV 배포 |
| XDP redirect | 20~30Mpps | 낮음 | <1μs | XDP native 지원 NIC | 고성능 라우터/방화벽 |
Open vSwitch vs Linux Bridge 비교
가상화·클라우드 네트워킹에서 Linux Bridge와 Open vSwitch(OVS)는 가장 널리 쓰이는 소프트웨어 스위치입니다. Linux Bridge는 커널 내장 L2 스위치로 단순하고 안정적이며, OVS는 OpenFlow 기반의 유연한 SDN 데이터플레인을 제공합니다. OVS-DPDK는 커널을 완전히 우회하여 극한의 성능을 달성합니다.
상세 비교 테이블
| 항목 | Linux Bridge | Open vSwitch (커널) | OVS-DPDK |
|---|---|---|---|
| 아키텍처 | 커널 전용 (net/bridge/) | 유저스페이스 데몬 + 커널 모듈 (openvswitch.ko) | 유저스페이스 전용 (DPDK PMD) |
| OpenFlow 지원 | ✗ 미지원 | ✓ OpenFlow 1.0–1.5 | ✓ OpenFlow 1.0–1.5 |
| 터널링 | 제한적 (VXLAN은 별도 장치) | ✓ VXLAN, Geneve, GRE, STT, ERSPAN | ✓ VXLAN, Geneve (DPDK vHost) |
| QoS | TC qdisc/filter 외부 연동 | OpenFlow meters, DSCP 리마킹, 큐 관리 | OpenFlow meters + DPDK QoS |
| 플로우 매칭 | L2 (MAC + VLAN) | L2–L4 + Metadata (in_port, tunnel_id, ct_state) | L2–L4 + Metadata |
| 처리 성능 (64B) | ~2Mpps | ~3–5Mpps | 20Mpps+ (단일 코어 14.88Mpps 10GbE line rate) |
| 지연 시간 | 5–20μs | 10–30μs (slow path 100μs+) | 3–10μs |
| 메모리 사용 | ~2–5MB (모듈 + FDB) | ~50–200MB (ovs-vswitchd + ovsdb) | 1–4GB (hugepages 필수) |
| 설정 복잡도 | 낮음 (ip/bridge 명령) | 중간 (ovs-vsctl, ovs-ofctl) | 높음 (DPDK 빌드, hugepages, CPU pinning) |
| 컨테이너 런타임 | Docker 기본, libvirt 기본 | OpenStack Neutron, OVN, Antrea | NFV, 통신사 코어 워크로드 |
| 하드웨어 오프로드 | switchdev (mlx5, bnxt) | OVS-TC offload (mlx5 SmartNIC) | DPDK Flow API (rte_flow) |
| 모니터링 도구 | bridge fdb/vlan, ip link | ovs-ofctl dump-flows, ovsdb-client, ovs-appctl | ovs-appctl dpif-netdev/pmd-stats-show |
| STP/RSTP | ✓ 내장 (커널 STP) | ✓ (ovs-vswitchd 관리) | ✓ (ovs-vswitchd 관리) |
| Connection Tracking | br_netfilter 경유 nf_conntrack | ✓ 내장 CT (OpenFlow ct() action) | ✓ 내장 CT |
| 라이선스 | GPLv2 (커널 일부) | Apache 2.0 | Apache 2.0 |
| 커널 버전 요구 | 2.2+ (내장) | 3.10+ (openvswitch.ko) | 커널 무관 (유저스페이스) |
OVS-DPDK 고성능 설정 예시
# 1. Hugepages 설정 (2MB × 2048 = 4GB)
echo 2048 > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages
mkdir -p /dev/hugepages
mount -t hugetlbfs nodev /dev/hugepages
# 2. DPDK 드라이버 바인딩 (VFIO-PCI 권장)
modprobe vfio-pci
dpdk-devbind --bind=vfio-pci 0000:03:00.0
dpdk-devbind --bind=vfio-pci 0000:03:00.1
# 3. OVS DPDK 초기화
ovs-vsctl --no-wait set Open_vSwitch . other_config:dpdk-init=true
ovs-vsctl --no-wait set Open_vSwitch . other_config:dpdk-socket-mem="1024,1024"
ovs-vsctl --no-wait set Open_vSwitch . other_config:dpdk-lcore-mask="0x2"
# 4. OVS 재시작
systemctl restart openvswitch
# 5. DPDK 브리지 생성
ovs-vsctl add-br br-dpdk -- set bridge br-dpdk datapath_type=netdev
# 6. DPDK 포트 추가 (PMD 스레드가 직접 폴링)
ovs-vsctl add-port br-dpdk dpdk-p0 -- set Interface dpdk-p0 \
type=dpdk options:dpdk-devargs=0000:03:00.0
ovs-vsctl add-port br-dpdk dpdk-p1 -- set Interface dpdk-p1 \
type=dpdk options:dpdk-devargs=0000:03:00.1
# 7. PMD 스레드 CPU 고정 (NUMA 인지 배치)
ovs-vsctl set Open_vSwitch . other_config:pmd-cpu-mask="0xC" # CPU 2,3
# 8. Rx 큐 개수 (멀티큐 → 멀티 PMD)
ovs-vsctl set Interface dpdk-p0 options:n_rxq=2
ovs-vsctl set Interface dpdk-p1 options:n_rxq=2
# 9. OpenFlow 플로우 설정 (L2 포워딩)
ovs-ofctl add-flow br-dpdk "in_port=dpdk-p0,actions=output:dpdk-p1"
ovs-ofctl add-flow br-dpdk "in_port=dpdk-p1,actions=output:dpdk-p0"
# 10. PMD 통계 확인
ovs-appctl dpif-netdev/pmd-stats-show
ovs-appctl dpif-netdev/pmd-rxq-show
- Linux Bridge를 선택하세요: 단순 VM/컨테이너 네트워킹, Docker 기본 브리지, libvirt NAT/브리지, 추가 데몬 없이 안정적 운영이 목표일 때. 커널 업그레이드만으로 유지보수가 가능하며 메모리 오버헤드가 최소입니다.
- OVS (커널 datapath)를 선택하세요: OpenStack/OVN 환경, SDN 제어기(ONOS, Floodlight) 연동, VXLAN/Geneve 기반 오버레이 네트워크, OpenFlow 기반 세밀한 플로우 제어, Connection Tracking이 내장된 스테이트풀 방화벽이 필요할 때.
- OVS-DPDK를 선택하세요: NFV/통신사 코어 (vEPC, vBNG), 10GbE 이상 와이어레이트 패킷 처리, 마이크로초 지연 요구 환경. hugepages와 전용 CPU 코어를 투자할 수 있는 성능 최우선 워크로드에 적합합니다.
주의: OVS-DPDK는 hugepages 4GB+, 전용 PMD 코어, NUMA 인지 배치가 필수이며, VM에서는 vHost-user 소켓으로 연결해야 합니다. 운영 복잡도가 높으므로 성능 요구가 명확할 때만 도입하세요.
Bridge 보안 강화
Linux Bridge는 단순한 L2 스위치를 넘어 다양한 보안 기능을 제공합니다. 포트 격리, MAC 잠금, 802.1X 인증, 브로드캐스트 폭풍 제어를 조합하면 엔터프라이즈급 보안 요구사항을 충족할 수 있습니다.
포트 격리 (BR_ISOLATED)
BR_ISOLATED 플래그가 설정된 포트끼리는 직접 통신이 불가능합니다. 격리된 포트는 비격리(uplink) 포트로만 트래픽을 전달할 수 있으며, 멀티 테넌트 환경이나 공용 WiFi AP에서 클라이언트 간 격리에 사용됩니다.
# 포트 격리 설정
bridge link set dev eth1 isolated on
bridge link set dev eth2 isolated on
# 확인: isolated 플래그
bridge link show dev eth1
# 출력: ... state forwarding ... isolated
# eth1 ↔ eth2 직접 통신 차단, eth1 → uplink(eth0)는 허용
bridge link set dev eth0 isolated off # uplink는 비격리
커널 내부에서는 br_forward()에서 격리 검사가 수행됩니다:
/* net/bridge/br_forward.c */
static bool should_deliver(const struct net_bridge_port *p,
const struct sk_buff *skb)
{
struct net_bridge_port *src = br_port_get_rcu(skb->dev);
/* 격리 포트끼리는 포워딩 금지 */
if (src && (src->flags & BR_ISOLATED) &&
(p->flags & BR_ISOLATED))
return false;
return p->state == BR_STATE_FORWARDING &&
br_allowed_egress(p, skb) &&
nbp_switchdev_allowed_egress(p, skb);
}
bridge link set 명령으로 동적 설정이 가능합니다. switchdev 오프로드를 지원하는 NIC(mlx5, bnxt)에서는 하드웨어 수준에서 격리가 적용됩니다.
MAC 주소 제한 및 포트 잠금
BR_PORT_LOCKED 플래그는 동적 MAC 학습을 비활성화하여 명시적으로 등록된 정적 FDB 엔트리만 허용합니다. BR_PORT_MAB(MAC Auth Bypass)는 802.1X 인증 실패 시 MAC 주소 기반 대체 인증을 제공합니다.
# 포트 잠금: 동적 학습 차단, 정적 FDB만 허용
bridge link set dev eth1 locked on
# 허용할 MAC 주소를 정적으로 등록
bridge fdb add aa:bb:cc:dd:ee:01 dev eth1 master static
bridge fdb add aa:bb:cc:dd:ee:02 dev eth1 master static
# MAC Auth Bypass (802.1X 대체 — 커널 6.1+)
bridge link set dev eth1 locked on mab on
# 잠금 상태 확인
bridge -d link show dev eth1
# ... locked mab ...
# 등록되지 않은 MAC → FDB 미학습, 패킷 드롭
BPF_PROG_TYPE_SCHED_CLS)으로 포트별 MAC 카운트를 추적하고 초과 시 드롭하거나, bridge fdb 모니터링 스크립트와 함께 ageing_time을 짧게 설정하세요.
802.1X 통합 — hostapd
Linux Bridge와 hostapd를 연동하면 유선 이더넷 포트에서도 802.1X 포트 기반 인증을 구현할 수 있습니다. 인증 전에는 포트가 잠겨(locked) EAP 트래픽만 허용되고, RADIUS 인증 성공 후 포트가 열립니다.
# /etc/hostapd/wired.conf — 유선 802.1X 설정
interface=eth1
driver=wired
ieee8021x=1
eap_reauth_period=3600
use_pae_group_addr=1
# RADIUS 서버
auth_server_addr=192.168.1.10
auth_server_port=1812
auth_server_shared_secret=radius_secret
# 브리지 포트 사전 잠금
bridge link set dev eth1 locked on
# hostapd 시작 → PAE(Port Access Entity) 인증 시작
hostapd /etc/hostapd/wired.conf
# 인증 플로우:
# 1. 클라이언트 → EAPoL Start → eth1 (PAE 포트)
# 2. hostapd → EAP-Request/Identity → 클라이언트
# 3. 클라이언트 → EAP-Response → hostapd → RADIUS
# 4. RADIUS → Access-Accept → hostapd
# 5. hostapd → bridge fdb add → 포트 인증 완료, 트래픽 허용
Storm Control (브로드캐스트 폭풍 방지)
브로드캐스트/멀티캐스트 폭풍은 브리지 전체를 마비시킬 수 있습니다. TC police 액션으로 브리지 포트별 속도 제한을 적용합니다.
# 브로드캐스트 폭풍 방지: ARP 브로드캐스트 100kbit 제한
tc qdisc add dev eth1 ingress
tc filter add dev eth1 ingress protocol arp \
flower \
eth_type 0x0806 \
action police rate 100kbit burst 32k conform-exceed drop/continue
# 멀티캐스트 제한: 전체 멀티캐스트 1Mbit 제한
tc filter add dev eth1 ingress protocol ip \
flower \
dst_mac 01:00:00:00:00:00/01:00:00:00:00:00 \
action police rate 1mbit burst 64k conform-exceed drop/continue
# 전체 브로드캐스트 (ff:ff:ff:ff:ff:ff) 500kbit 제한
tc filter add dev eth1 ingress protocol all \
flower \
dst_mac ff:ff:ff:ff:ff:ff \
action police rate 500kbit burst 32k conform-exceed drop/continue
# 알 수 없는 유니캐스트(flooding) 제한
# → FDB에 없는 목적지 MAC → 모든 포트로 flood → 제한 필요
tc filter add dev eth1 ingress protocol all \
flower \
action police rate 10mbit burst 128k conform-exceed drop/continue
# 적용된 필터 확인
tc filter show dev eth1 ingress
Bridge 보안 체크리스트
| 보안 기능 | 기본 상태 | 권장 설정 | 설정 명령 |
|---|---|---|---|
| 포트 격리 (BR_ISOLATED) | off | 테넌트 포트: on | bridge link set dev ethX isolated on |
| 포트 잠금 (BR_PORT_LOCKED) | off | 인증 포트: on | bridge link set dev ethX locked on |
| MAC Auth Bypass | off | 802.1X 환경: on | bridge link set dev ethX locked on mab on |
| STP BPDU Guard | off | 엣지 포트: on | bridge link set dev ethX guard on |
| Root Guard | off | 다운스트림 포트: on | bridge link set dev ethX root_block on |
| Learning 비활성화 | on | 정적 환경: off | bridge link set dev ethX learning off |
| Unicast Flood 차단 | on | 알려진 호스트 전용: off | bridge link set dev ethX flood off |
| Multicast Flood 차단 | on | IGMP 환경: off | bridge link set dev ethX mcast_flood off |
| Broadcast Flood 차단 | on | 특수 포트: off | bridge link set dev ethX bcast_flood off |
| Storm Control (ARP) | 제한 없음 | 100kbit | tc filter ... flower eth_type 0x0806 action police rate 100kbit |
| Storm Control (Multicast) | 제한 없음 | 1Mbit | tc filter ... flower dst_mac 01:00:5e:00:00:00/ff:ff:ff:00:00:00 action police |
| VLAN 필터링 | off | on | ip link set br0 type bridge vlan_filtering 1 |
| br_netfilter | 로드 안 됨 | 필요 시만 로드 | modprobe br_netfilter |
| ARP Proxy | off | 오버레이 환경: on | echo 1 > /proc/sys/net/ipv4/conf/br0/proxy_arp |
고급 진단: ftrace / bpftrace / perf
브리지 동작을 커널 내부까지 추적하려면 ftrace, bpftrace, perf를 활용합니다. FDB 학습/에이징, VLAN 드롭, STP 상태 전환, 멀티캐스트 그룹 변경 등을 실시간으로 관찰할 수 있습니다.
ftrace로 브리지 내부 추적
# function_graph: br_handle_frame 내부 호출 트리 추적
echo function_graph > /sys/kernel/debug/tracing/current_tracer
echo br_handle_frame > /sys/kernel/debug/tracing/set_graph_function
echo 1 > /sys/kernel/debug/tracing/tracing_on
# ... 트래픽 발생 ...
cat /sys/kernel/debug/tracing/trace | head -60
echo 0 > /sys/kernel/debug/tracing/tracing_on
# kprobe: FDB 학습 이벤트 캡처 (소스 MAC, VLAN ID)
echo 'p:fdb_learn br_fdb_update br=%di port=%si src=+0(%dx):x64 vid=%cx' \
> /sys/kernel/debug/tracing/kprobe_events
echo 1 > /sys/kernel/debug/tracing/events/kprobes/fdb_learn/enable
cat /sys/kernel/debug/tracing/trace_pipe
# STP 상태 전환 추적
echo 'p:stp_change br_set_state port=%di state=%si' \
> /sys/kernel/debug/tracing/kprobe_events
echo 1 > /sys/kernel/debug/tracing/events/kprobes/stp_change/enable
bpftrace로 브리지 통계
# FDB 히트/미스 통계 (1초마다 출력)
bpftrace -e '
kprobe:br_fdb_find_rcu { @fdb_lookup = count(); }
kretprobe:br_fdb_find_rcu / retval == 0 / { @fdb_miss = count(); }
interval:s:1 {
printf("FDB lookup: %d, miss: %d, miss rate: %d%%\n",
@fdb_lookup, @fdb_miss,
@fdb_lookup > 0 ? @fdb_miss * 100 / @fdb_lookup : 0);
clear(@fdb_lookup); clear(@fdb_miss);
}'
# VLAN ingress 드롭 추적 (드롭된 VLAN ID 빈도)
bpftrace -e '
kprobe:br_vlan_check_allowed {
@drops[arg0] = count(); // arg0: vlan_id
}
interval:s:5 { print(@drops); clear(@drops); }'
# STP 상태 전환 감지 (포트명, old/new 상태)
bpftrace -e '
kprobe:br_set_state {
$port = (struct net_bridge_port *)arg0;
printf("STP state change: %s %d -> %d\n",
str($port->dev->name), $port->state, arg1);
}'
# IGMP 멤버십 보고서 수신 추적
bpftrace -e '
kprobe:br_multicast_rcv { @igmp_rcv = count(); }
kprobe:br_multicast_add_group { @group_add = count(); }
interval:s:5 {
printf("IGMP rcv: %d, group_add: %d\n", @igmp_rcv, @group_add);
clear(@igmp_rcv); clear(@group_add);
}'
perf 및 기타 도구
# perf: 브리지 관련 커널 함수 프로파일링
perf record -g -e cycles:u -p $(pgrep dhcpcd) -- sleep 10
perf record -g -F 99 -- sleep 10
perf report --stdio | grep br_
# perf stat: 네트워크 이벤트 카운트
perf stat -e net:netif_receive_skb,net:net_dev_xmit sleep 10
# bridge 내장 통계
bridge -s link show # 포트별 TX/RX 통계
bridge -s fdb show # FDB 통계
watch -n 1 'bridge -s link show' # 실시간 모니터링
# nft trace: nftables를 통한 패킷 추적
nft add rule bridge filter forward meta nftrace set 1
nft monitor trace
# dropwatch: 커널 패킷 드롭 위치 추적
dropwatch -l kas
# VLAN 통계 확인
bridge vlan show dev eth0 -s # VLAN별 RX/TX 바이트/패킷
# ethtool로 NIC 오프로드 상태 확인
ethtool -k eth0 | grep -E 'tx-checksum|rx-checksum|hw-tc-offload'
# 브리지 상세 정보 (STP, VLAN, 멀티캐스트 설정)
bridge -d link show
bridge -d vlan show
커널 소스 구조
Linux Bridge 전체 소스는 net/bridge/ 디렉터리에 위치하며, 약 20개의 C 파일과 1개의 내부 헤더로 구성됩니다. 각 파일은 명확한 단일 책임을 갖습니다.
소스 파일 목록
net/bridge/
├── br.c # 모듈 초기화 (__init br_init, br_fdb_init, br_netfilter_init)
├── br_device.c # net_device_ops 구현 (br_dev_xmit, br_dev_ioctl, br_change_mtu)
├── br_fdb.c # FDB 관리 (rhashtable, 학습, 에이징, fdb_insert, fdb_delete)
├── br_forward.c # 패킷 포워딩 로직 (br_forward, br_flood, __br_forward)
├── br_if.c # 포트 추가/제거 (br_add_if, br_del_if, new_nbp)
├── br_input.c # 수신 처리 진입점 (br_handle_frame, br_handle_frame_finish)
├── br_ioctl.c # Legacy ioctl 인터페이스 (brctl 호환)
├── br_multicast.c # IGMP/MLD snooping (br_multicast_rcv, mdb 관리)
├── br_multicast_eht.c # Explicit Host Tracking (IGMPv3/MLDv2 소스별 추적)
├── br_mst.c # Multiple Spanning Tree (MST) 지원
├── br_netfilter_hooks.c # br_netfilter 훅 (br_nf_pre_routing, br_nf_forward 등)
├── br_netfilter_ipv6.c # br_netfilter IPv6 전용 처리
├── br_netlink.c # Netlink RTNL 인터페이스 (iproute2/bridge 명령 통신)
├── br_nf_core.c # br_netfilter 코어 (가짜 dst_entry 관리)
├── br_private.h # 내부 헤더 (net_bridge, net_bridge_port, net_bridge_fdb_entry)
├── br_private_stp.h # STP 내부 구조체/매크로
├── br_stp.c # STP 상태 머신 (br_received_config_bpdu, br_topology_change)
├── br_stp_bpdu.c # BPDU 프레임 송수신 (br_send_config_bpdu, br_stp_rcv)
├── br_stp_if.c # STP 포트 인터페이스 (br_stp_enable_port, br_stp_set_path_cost)
├── br_stp_timer.c # STP 타이머 (hello, max_age, forward_delay)
├── br_switchdev.c # switchdev 이벤트 통지 (FDB/VLAN 오프로드)
├── br_sysfs_br.c # sysfs 브리지 속성 (/sys/class/net/br0/bridge/*)
├── br_sysfs_if.c # sysfs 포트 속성 (/sys/class/net/br0/brif/eth0/*)
├── br_vlan.c # VLAN 필터링 (nbp_vlan_add, br_allowed_ingress/egress)
├── br_vlan_options.c # VLAN 옵션 Netlink 처리
├── br_vlan_tunnel.c # VLAN-to-tunnel ID 매핑 (VXLAN 연동)
└── netfilter/ # ebtables 모듈 (ebt_*.c, ebtable_*.c)
├── ebtables.c # ebtables 코어 프레임워크
├── ebt_among.c # MAC/IP 쌍 매칭
├── ebt_arp.c # ARP 필드 매칭
├── ebt_ip.c # IPv4 헤더 매칭
├── ebt_ip6.c # IPv6 헤더 매칭
├── ebt_stp.c # STP BPDU 필드 매칭
└── ebt_vlan.c # VLAN 태그 매칭
주요 함수/심볼 테이블
| 심볼 | 파일 | 역할 |
|---|---|---|
br_handle_frame() | br_input.c | 수신 패킷 진입점 — rx_handler로 등록, STP/LLDP 분기 |
br_handle_frame_finish() | br_input.c | FDB 학습 → 유니캐스트/플러드 결정 |
br_forward() | br_forward.c | 단일 포트 유니캐스트 포워딩 |
br_flood() | br_forward.c | 브로드캐스트/unknown 유니캐스트 플러딩 |
__br_forward() | br_forward.c | 실제 dev_queue_xmit() 호출, VLAN 처리 |
br_dev_xmit() | br_device.c | 브리지 장치의 ndo_start_xmit (로컬 발신 패킷) |
br_fdb_find_rcu() | br_fdb.c | FDB 조회 (rhashtable lookup, RCU 보호) |
br_fdb_update() | br_fdb.c | 소스 MAC 학습 / 타임스탬프 갱신 |
fdb_create() | br_fdb.c | 새 FDB 엔트리 할당 + rhashtable 삽입 |
br_fdb_cleanup() | br_fdb.c | 에이징 타이머 — 만료된 FDB 엔트리 삭제 |
br_add_if() | br_if.c | 브리지에 포트 추가 (rx_handler 등록, STP 시작) |
br_del_if() | br_if.c | 브리지에서 포트 제거 |
br_stp_rcv() | br_stp_bpdu.c | BPDU 수신 처리 (Config/TCN BPDU 파싱) |
br_send_config_bpdu() | br_stp_bpdu.c | Config BPDU 전송 |
br_received_config_bpdu() | br_stp.c | Config BPDU 처리 — 루트/지정 브리지 선출 |
br_topology_change_detection() | br_stp.c | 토폴로지 변경 감지 → TC 플래그 설정 |
br_multicast_rcv() | br_multicast.c | IGMP/MLD 패킷 스누핑 처리 |
br_allowed_ingress() | br_vlan.c | VLAN 인그레스 필터 — 허용된 VLAN만 통과 |
br_allowed_egress() | br_vlan.c | VLAN 이그레스 필터 — 태그/언태그 처리 |
nbp_vlan_add() | br_vlan.c | 포트에 VLAN 추가 |
br_nf_pre_routing() | br_netfilter_hooks.c | br_netfilter PRE_ROUTING 훅 — L3 iptables 적용 |
br_switchdev_fdb_notify() | br_switchdev.c | FDB 변경을 switchdev 드라이버에 통지 |
br_init() | br.c | 모듈 초기화 — FDB 캐시, STP, netfilter 등록 |
성능 튜닝 및 벤치마킹
Linux Bridge의 성능은 NIC 하드웨어 오프로드부터 커널 파라미터, 브리지 자체 설정까지 다계층 튜닝이 가능합니다. 체계적 벤치마킹으로 병목을 식별하고 단계별로 최적화하는 것이 중요합니다.
성능 튜닝 파라미터 테이블
| 카테고리 | 파라미터 | 기본값 | 권장값 | 설명 |
|---|---|---|---|---|
| sysctl (커널) | net.core.netdev_budget | 300 | 600–1200 | NAPI 폴링 1회당 처리할 최대 패킷 수 |
net.core.netdev_budget_usecs | 2000 | 4000–8000 | NAPI 폴링 시간 제한 (μs) | |
net.core.rmem_max | 212992 | 16777216 | 소켓 수신 버퍼 최대 크기 | |
net.core.busy_poll | 0 | 50 | busy polling 활성화 (μs, 지연 최소화) | |
| Bridge 파라미터 | ageing_time | 300초 | 30–120초 | FDB 에이징 — 짧으면 메모리 절약, 재학습 증가 |
hash_max | 4096 | 16384–65536 | 멀티캐스트 그룹 해시 테이블 크기 | |
vlan_filtering | 0 | 1 | VLAN 필터링 활성화 — 불필요 트래픽 차단 | |
multicast_snooping | 1 | 1 | IGMP snooping — 멀티캐스트 flood 방지 | |
multicast_querier | 0 | 1 | 라우터 없는 환경에서 IGMP 쿼리어 역할 | |
| NIC (ethtool) | -L combined | NIC 기본값 | CPU 코어 수 | 멀티큐 채널 수 = RSS 분산 단위 |
-G rx/tx | 256/256 | 4096/4096 | 링 버퍼 크기 — 버스트 흡수 | |
-C rx-usecs | NIC 기본값 | 50–100 | 인터럽트 지연 (μs) — PPS vs 지연 트레이드오프 | |
-K gro/tso/tx-checksum | on | on | 하드웨어 오프로드 확인 및 유지 | |
| IRQ 친화성 | /proc/irq/N/smp_affinity | 모든 CPU | 큐별 CPU 고정 | IRQ → 특정 CPU 바인딩 (irqbalance 비활성화) |
RPS (rps_cpus) | 0 | NUMA 로컬 CPU 마스크 | HW RSS 미지원 NIC에서 소프트웨어 분산 |
벤치마킹 방법론
# ============================================================
# 1. iperf3 처리량 테스트 (대역폭 측정)
# ============================================================
# 서버 측 (브리지 뒤 호스트)
iperf3 -s -B 192.168.1.2
# 클라이언트 측 (단일 스트림)
iperf3 -c 192.168.1.2 -t 30 -P 1
# 클라이언트 측 (멀티 스트림 — CPU 병렬 활용)
iperf3 -c 192.168.1.2 -t 30 -P 8
# UDP PPS 측정 (64바이트 패킷)
iperf3 -c 192.168.1.2 -u -b 10G -l 64 -t 30
# ============================================================
# 2. pktgen — 커널 패킷 생성기 (PPS 측정)
# ============================================================
modprobe pktgen
# pktgen 스레드 설정 (CPU 0)
echo "rem_device_all" > /proc/net/pktgen/kpktgend_0
echo "add_device eth1" > /proc/net/pktgen/kpktgend_0
# 패킷 설정
echo "pkt_size 64" > /proc/net/pktgen/eth1
echo "count 10000000" > /proc/net/pktgen/eth1
echo "dst 192.168.1.2" > /proc/net/pktgen/eth1
echo "dst_mac aa:bb:cc:dd:ee:02" > /proc/net/pktgen/eth1
echo "delay 0" > /proc/net/pktgen/eth1
# 실행
echo "start" > /proc/net/pktgen/pgctrl
# 결과 확인
cat /proc/net/pktgen/eth1
# Result: OK: ... (pps) ... (Mb/sec)
# ============================================================
# 3. perf — CPU 오버헤드 프로파일링
# ============================================================
# 브리지 관련 함수 핫스팟 확인
perf top -g -p 0 -- sleep 10
# 브리지 포워딩 경로 프로파일링
perf record -g -a -- sleep 10
perf report --sort comm,dso,symbol | grep "br_"
# 함수별 시간 비율 (상위 20개)
perf report --stdio --sort symbol --no-children | head -30
# 특정 브리지 함수 추적
perf stat -e "cycles,instructions,cache-misses" \
-a -- sleep 10
# ============================================================
# 4. 브리지 통계 실시간 모니터링
# ============================================================
# 포트별 패킷 카운터
watch -n 1 "ip -s link show master br0"
# FDB 엔트리 수 모니터링
watch -n 1 "bridge fdb show br br0 | wc -l"
# softirq CPU 사용률
watch -n 1 "cat /proc/softirqs | grep NET"
FDB/MDB 크기별 성능 영향
| FDB 엔트리 수 | 메모리 사용 | Lookup 시간 (평균) | PPS 영향 | 비고 |
|---|---|---|---|---|
| 100 | ~50KB | ~50ns | 기준 (100%) | 소규모 환경, 캐시 히트율 높음 |
| 1,000 | ~500KB | ~60ns | ~99% | 일반적 VM/컨테이너 환경 |
| 10,000 | ~5MB | ~80ns | ~95% | 중대형 데이터센터, rhashtable 리사이즈 발생 가능 |
| 50,000 | ~25MB | ~120ns | ~88% | 대형 L2 도메인, 캐시 미스 증가 |
| 100,000 | ~50MB | ~180ns | ~80% | rhashtable 버킷 체인 길어짐, ageing_time 단축 권장 |
| 500,000+ | ~250MB+ | ~350ns+ | ~65% | 비정상 — MAC 폭주 의심, FDB 제한 필요 |
ageing_time을 30초 이하로 설정하고, switchdev 오프로드를 적극 활용하세요.
ethtool -k ethX로 GRO/TSO/tx-checksum 오프로드 활성 확인ethtool -l ethX로 멀티큐 채널 수 확인 → CPU 코어 수에 맞춤ethtool -g ethX로 링 버퍼 크기 확인 → 최대치로 설정- IRQ affinity를 큐별 CPU에 수동 고정 (irqbalance 비활성화)
ip link set br0 type bridge ageing_time 3000(30초) — 대규모 환경 FDB 정리 가속- VLAN 필터링 활성화 — 불필요 브로드캐스트 도메인 축소
- IGMP snooping + querier — 멀티캐스트 flood 방지
- switchdev 오프로드 가능 NIC(mlx5, bnxt)라면
devlink dev eswitch set으로 HW 오프로드 활성화
관련 문서
이 주제와 관련된 다른 문서를 더 깊이 이해하고 싶다면 다음을 참고하세요.