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 진단까지 실무 관점으로 다룹니다.

전제 조건: 네트워크 스택라우팅 문서를 먼저 읽으세요. Linux Bridge는 소프트웨어 L2 스위치이므로 VLAN/STP/멀티캐스트 개념을 이해해야 합니다. Docker/K8s 환경에서 br_netfilter 없이 iptables 규칙이 적용되지 않는 이유도 이 문서에서 다룹니다.
일상 비유: Linux Bridge는 지능형 허브가 아닌 스마트 스위치와 같습니다. 일반 허브는 모든 포트에 패킷을 뿌리지만, 스위치는 MAC 주소 테이블(FDB)로 필요한 포트에만 전달합니다. VLAN은 스위치 내 논리적 칸막이, STP는 루프 방지 안전장치입니다.

핵심 요약

  • 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) 구현에 활용됩니다.

단계별 이해

  1. 브리지 생성과 포트 연결: ip link add br0 type bridge로 net_bridge 구조체가 생성되고, ip link set eth0 master br0로 NIC의 rx_handler에 br_handle_frame()이 등록됩니다. 이후 해당 NIC로 수신되는 모든 패킷은 브리지 코드로 진입합니다.
  2. 패킷 수신과 MAC 학습: 패킷이 NIC에 도착하면 br_handle_frame()br_fdb_update()로 소스 MAC 주소가 FDB 해시 테이블에 기록됩니다. 이미 존재하는 엔트리는 타임스탬프(jiffies)만 갱신합니다.
  3. FDB 룩업과 포워딩 결정: 목적지 MAC으로 br_fdb_find_rcu()를 호출합니다. 일치하는 엔트리가 있으면 해당 포트로 unicast 전달, 없으면 모든 포트로 flooding합니다. 브로드캐스트/멀티캐스트도 이 경로를 따릅니다.
  4. VLAN 필터링 적용: vlan_filtering 활성 시 br_allowed_ingress()에서 untagged 패킷에 PVID 태그를 부여하고 허용 VLAN 목록을 검사합니다. 송신 시 br_allowed_egress()에서 UNTAGGED 플래그가 있으면 태그를 제거합니다.
  5. STP 루프 방지: STP 활성 시 BPDU 교환으로 루트 브리지를 선출하고 중복 경로를 Blocking 상태로 전환합니다. 토폴로지 변경 감지 시 FDB를 빠르게 에이징하여 수렴합니다.
  6. 멀티캐스트 최적화: IGMP/MLD Snooping이 활성화되면 Join/Leave 메시지를 감청하여 MDB를 구성합니다. 멀티캐스트 패킷은 MDB에 등록된 포트에만 전달되어 불필요한 대역폭 낭비를 방지합니다.
  7. Netfilter 연동: br_netfilter 모듈 로드 시 브리지 패킷이 iptables FORWARD 체인을 통과합니다. net.bridge.bridge-nf-call-iptables=1 sysctl로 제어하며, 컨테이너 환경에서 네트워크 정책 적용의 핵심입니다.
  8. 하드웨어 오프로드: switchdev 지원 NIC에서는 FDB/VLAN/MDB 변경이 자동으로 HW ASIC에 반영됩니다. TC flower로 매칭 규칙을 HW에 내려보내거나, XDP redirect로 드라이버 레벨 고속 포워딩을 구현할 수 있습니다.
Linux Bridge 전체 아키텍처 User Space ip link / bridge tc filter/flower ebtables/nftables bridge monitor bpftrace/perf struct net_bridge (br0) — Kernel Space port_list → net_bridge_port eth0 (port 1) eth1 (port 2) veth0 (port 3) ... (port N) fdb_hash_tbl (rhashtable) net_bridge_fdb_entry AA:BB:CC → port 1 DD:EE:FF → port 2 11:22:33 → port 3 jhash(MAC+VID) vlgrp → net_bridge_vlan VID 1 VID 10 VID 20 MDB → net_bridge_mdb_entry 239.1.1.1 → {port1, port3} | ff02::1 → {port2} STP State Machine hello_timer(2s) | tcn_timer | topology_change_timer(max_age+fwd_delay) bridge_id / designated_root / root_path_cost / port states br_netfilter → NF_BR_PRE/FORWARD/POST_ROUTING → iptables switchdev: SWITCHDEV_FDB_ADD → HW ASIC notify Physical / Virtual NICs — rx_handler = br_handle_frame() eth0 (물리 NIC) netif_receive_skb() → rx_handler() switchdev offload eth1 (물리 NIC) netif_receive_skb() → rx_handler() veth0 (가상) Docker/K8s 컨테이너 hairpin mode veth1 (가상) peer: ns1 내부 ▲ ingress FDB lookup → forward/flood ▼ egress: dev_queue_xmit() netlink HW offload

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) */
}
rhashtable 자동 리사이즈: FDB 엔트리가 증가하면 rhashtable이 자동으로 버킷 수를 2배로 확장(grow)합니다. 축소(shrink)도 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 제한 등의 기법으로 방어합니다.

보안 기법커널 메커니즘설정 방법용도
정적 FDBBR_FDB_STATIC 플래그bridge fdb add ... staticMAC 스푸핑 방지: 동적 학습으로 덮어쓸 수 없음
학습 비활성화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 offunknown 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 공격 의심
포트 격리 vs VLAN: 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 학습설명
DisabledBR_STATE_DISABLED (0)XX포트 비활성
ListeningBR_STATE_LISTENING (1)XXBPDU 수신 대기
LearningBR_STATE_LEARNING (2)XOFDB 학습 중
ForwardingBR_STATE_FORWARDING (3)OO정상 동작
BlockingBR_STATE_BLOCKING (4)XX루프 차단
# 브리지 생성 및 포트 추가
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 갱신
성능 주의: 브리지는 소프트웨어 L2 스위칭이므로 대량 트래픽에서는 CPU 부하가 발생합니다. 고성능 환경에서는 switchdev 프레임워크를 통한 하드웨어 오프로드 또는 Open vSwitch(DPDK 가속)를 검토하세요.

VLAN-aware Bridge 동작 원리

Linux Bridge는 커널 4.2+부터 VLAN-aware 모드(vlan_filtering 1)를 지원합니다. 이 모드에서는 브리지 자체가 802.1Q VLAN 필터링을 수행하며, 각 포트에 PVID, tagged, untagged 속성을 독립적으로 설정할 수 있습니다. 기존 VLAN 서브인터페이스 방식(bridge + vlan subif)보다 확장성과 성능이 뛰어납니다.

VLAN-aware Bridge 패킷 처리 흐름 br0 (vlan_enabled=1, vlan_proto=ETH_P_8021Q) eth0 (Access) PVID=1, untagged allowed: {1, 10, 20} VID 1 ★ VID 10 VID 20 ★ = PVID + UNTAGGED flag eth1 (Access V10) PVID=10, untagged allowed: {10} VID 10 ★ eth2 (Trunk) tagged VLANs allowed: {10, 20} VID 10 VID 20 태그 유지 (no UNTAGGED flag) FDB per-VLAN AA:BB VID=1 → eth0 AA:BB VID=10 → eth1 CC:DD VID=10 → eth2 CC:DD VID=20 → eth2 같은 MAC, 다른 VLAN Ingress 경로: br_allowed_ingress() Untagged 패킷 수신 → PVID 태그 부여 (PVID 없으면 → DROP) Allowed VLAN 검사 VID ∈ port->vlgrp? YES → continue / NO → DROP Tagged 패킷 수신 기존 태그 유지 → Allowed VLAN 검사 FDB Lookup key = MAC + VID → forward / flood Egress 경로: br_allowed_egress() → br_vlan_egress() 출력 포트 VLAN 검사 VID ∈ egress port->vlgrp? NO → DROP UNTAGGED 플래그 검사 UNTAGGED → VLAN 태그 제거 tagged → 태그 유지 dev_queue_xmit() Access: 태그 없는 프레임 전송 Trunk: 802.1Q 태그 포함 전송

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 동작용도
PVIDBRIDGE_VLAN_INFO_PVIDuntagged 패킷에 이 VID 태그 부여-포트의 기본 VLAN 지정
UntaggedBRIDGE_VLAN_INFO_UNTAGGED-이 VID 패킷의 VLAN 태그 제거Access 포트 (일반 PC 연결)
Tagged(플래그 없음)tagged 패킷의 VID가 허용 목록에 있으면 통과VLAN 태그 유지Trunk 포트 (스위치 간 연결)
PVID + Untagged두 플래그 조합untagged → PVID 부여태그 제거일반적인 Access 포트 설정
PVID 미설정 시 주의: 포트에 PVID가 설정되지 않으면(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.1Q802.1ad (QinQ)
EtherType0x81000x88A8 (outer S-VLAN)
태그 수1개 (C-VLAN)2개 (S-VLAN + C-VLAN)
VLAN 범위40944094 × 4094 (이론적)
커널 지원ETH_P_8021QETH_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'
QinQ 성능: 이중 태깅으로 인해 MTU가 4바이트 추가 소비됩니다(S-VLAN 4B + C-VLAN 4B = 8B). Baby Giant Frame(1504B) 또는 Jumbo Frame(9000B+)을 사용하여 fragmentation을 방지하세요. switchdev 지원 NIC(Memory SmartNIC, Mellanox ConnectX 등)에서는 QinQ 처리도 HW 오프로드됩니다.

IGMP/MLD Snooping

IGMP Snooping은 멀티캐스트 트래픽을 필요한 포트에만 전달하는 L2 최적화입니다. 브리지가 IGMP 메시지를 감청(snooping)하여 MDB(Multicast Database)를 관리하고, 멀티캐스트 스트림을 등록된 수신자 포트에만 전달합니다.

멀티캐스트 소스 224.0.0.1 Linux Bridge (br0) br_multicast_rcv() IGMP 메시지 파싱 MDB (Multicast Database) 224.1.1.1 → {eth1, eth2} 224.2.2.2 → {eth3} net_bridge_mdb_entry br_multicast_flood() MDB 기반 선별 전달 eth1 수신자 eth2 수신자 eth3 미등록 → 차단 MDB 구조 net_bridge_mdb_entry ├ addr (멀티캐스트 IP) ├ vid (VLAN ID) └ ports (수신자 목록) net_bridge_port_group ├ port (포트 참조) ├ timer (멤버십 타이머) └ flags (Fast Leave 등) IGMP v3 이점 소스 필터링 지원 (Include/Exclude) SSM (Source-Specific Multicast) 가능
/* 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 LeaveIGMP Leave 즉시 탈퇴포트에 수신자 1개만 있을 때
offloadHW 오프로드됨switchdev 지원 NIC
항목IGMP v1IGMP v2IGMP v3
Leave 메커니즘없음 (타이머 만료)Leave GroupLeave 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
AlternateRoot 포트의 대안 (백업 경로)X (Blocking)
BackupDesignated 포트의 백업 (같은 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 GuardSTP 공격 방어BPDU 수신 시 포트 Error-DisabledPortFast 포트
Root Guard루트 브리지 보호더 나은 BPDU 무시, Root-Inconsistent 상태업스트림 포트 제외
PortFast + BPDU Guard 조합 필수: PortFast만 설정하면 호스트가 악의적 BPDU를 전송해 STP 토폴로지를 교란할 수 있습니다. 항상 BPDU Guard를 함께 적용하세요. 복구 후 재발 방지를 위해 errdisable recovery는 실제 원인 제거 후에만 수행해야 합니다.

STP 상태 전이 다이어그램

STP(802.1D)에서 포트는 5개의 상태(Disabled → Blocking → Listening → Learning → Forwarding)를 순서대로 전이합니다. RSTP(802.1w)는 Disabled/Blocking/Listening을 Discarding 하나로 통합하여 상태 머신을 단순화하고, Proposal/Agreement 메커니즘으로 수렴 시간을 획기적으로 단축합니다.

STP (802.1D) 포트 상태 전이 Disabled 관리자 비활성 link up Blocking BPDU 수신만 가능 선출됨 Listening BPDU 처리, MAC 미학습 forward_delay (15초) Learning MAC 학습, 전달 안 함 forward_delay (15초) Forwarding MAC 학습 + 전달 Superior BPDU 수신 → Blocking 복귀 STP 타이머: hello_time = 2초 (BPDU 전송 주기) │ forward_delay = 15초 (Listening↔Learning 대기) │ max_age = 20초 (BPDU 만료) RSTP (802.1w) 포트 상태 전이 — 간소화 Discarding = Disabled + Blocking + Listening BPDU 처리, 전달/학습 안 함 선출됨 Learning MAC 학습 시작 전달 안 함 Proposal/ Agreement ~1~2초 Forwarding MAC 학습 + 전달 트래픽 정상 흐름 RSTP 포트 역할과 상태 매핑 Root Port 루트 방향 최적 경로 → Forwarding Designated Port 세그먼트 대표 포트 → Forwarding Alternate Port Root 대안 경로 → Discarding Backup Port Designated 대안 → Discarding STP 수렴: 30~50초 (2 × forward_delay) RSTP 수렴: 1~2초 (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 Identifier020x0000STP 프로토콜 식별자 (항상 0)
Protocol Version210x00(STP) / 0x02(RSTP)프로토콜 버전
BPDU Type310x00(Config) / 0x80(TCN) / 0x02(RST)BPDU 종류
Flags41비트 필드bit0: TC, bit1: Proposal, bit2-3: Port Role, bit4: Learning, bit5: Forwarding, bit6: Agreement, bit7: TCA
Root Bridge ID58Priority(2) + MAC(6)루트 브리지 식별자 (Priority 상위 4비트 + System ID Extension 12비트 + MAC)
Root Path Cost1340~200,000,000루트 브리지까지의 누적 경로 비용
Sender Bridge ID178Priority(2) + MAC(6)BPDU를 보낸 브리지 식별자
Port ID252Priority(4비트) + Number(12비트)BPDU를 보낸 포트 식별자
Message Age2721/256초 단위BPDU가 루트에서 발생한 후 경과 시간
Max Age29220초 (6~40초)BPDU 유효 기간 (초과 시 토폴로지 재계산)
Hello Time3122초 (1~10초)BPDU 전송 주기
Forward Delay33215초 (4~30초)Listening→Learning, Learning→Forwarding 전이 대기 시간
BPDU 크기: Configuration BPDU는 총 35바이트, TCN BPDU는 4바이트(Protocol ID + Version + Type)입니다. RSTP BPDU는 Configuration BPDU와 동일한 크기이지만 Flags 필드의 비트 활용이 확장되어 Port Role, Proposal, Agreement 정보를 전달합니다. BPDU는 목적지 MAC 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)

Normal 모드 (Hairpin OFF) Hairpin 모드 (Hairpin ON) Linux Bridge (br0) FDB: MAC-B → eth0 eth0 (port) VM / 컨테이너 MAC-A (src) → MAC-B (dst) 둘 다 같은 vNIC 뒤에 존재 ingress DROP ingress == egress 동일 포트 차단 Linux Bridge (br0) FDB: MAC-B → eth0 (hairpin) eth0 (hairpin) VM / 컨테이너 MAC-A (src) → MAC-B (dst) 둘 다 같은 vNIC 뒤에 존재 ingress egress ALLOW 주요 사용 사례: Docker veth pair, KVM macvtap 다중 MAC, SR-IOV VF 뒤 다중 VM, VEPA 모드 가상 스위치

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 필수 */
Docker와 Hairpin: Docker는 컨테이너가 자신의 published 포트로 접근할 때 iptables DNAT가 동일 컨테이너를 가리키므로, 패킷이 같은 veth로 돌아와야 합니다. 이를 위해 Docker daemon은 veth pair 생성 시 자동으로 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 ModeBR_HAIRPIN_MODEDocker veth, KVM macvtap동일 포트 루프백 허용
Proxy ARPBR_PROXYARPVXLAN/EVPN, WiFi APARP flooding 감소
Proxy ARP WiFiBR_PROXYARP_WIFI802.11 AP 브리지무선 클라이언트 ARP 대리
Neighbor SuppressBR_NEIGH_SUPPRESSVXLAN + EVPN FabricARP/ND BUM 트래픽 감소

switchdev HW 오프로드 프레임워크

switchdev는 리눅스 커널이 하드웨어 스위치 ASIC을 소프트웨어 브리지와 동일한 API로 제어할 수 있게 하는 프레임워크입니다. 사용자 공간에서 ip/bridge/tc 명령으로 설정하면, 커널 브리지가 switchdev 콜백을 통해 하드웨어 FDB/VLAN/MDB 테이블을 직접 프로그래밍합니다.

상세 문서: eSwitch 모드 전환, Representor 포트, SR-IOV 오프로드 등의 상세 내용은 eSwitch 심화 페이지를 참조하세요.

Userspace ip link/bridge bridge fdb/vlan tc flower bridge mdb devlink Netlink Kernel — net/bridge/ net_bridge (SW FDB) br_switchdev.c switchdev_notifier_chain SWITCHDEV_FDB_ADD SWITCHDEV_PORT_OBJ_ADD SW fallback switchdev_ops 콜백 호출 NIC Driver — switchdev 구현 mlx5 (NVIDIA) ConnectX, BlueField bnxt (Broadcom) BCM57xxx DSA (Marvell) mv88e6xxx 등 prestera (Marvell) Memory ASIC KSZ Microchip Hardware ASIC eSwitch FDB / TCAM HW FDB / ATU HW TCAM / FDB DSA(Distributed Switch Architecture): 임베디드 스위치 칩을 표준 Linux bridge/switchdev API로 제어 — 라우터, AP, IoT 장비에 사용

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 flowerDSA비고
NVIDIA mlx5ConnectX-5/6/7, BlueFieldOOOOO-가장 완성도 높은 switchdev 지원, eSwitch 내장
Broadcom bnxtBCM57xxx, StingrayOOOO-TC flower offload 지원
Marvell mv88e6xxx88E6xxx 시리즈OOOO-ODSA 프레임워크 기반, 임베디드 스위치
Marvell presteraPrestera DX, SwitchDevOOOOO-데이터센터 스위치 ASIC
Microchip kszKSZ9477, KSZ8795OO--ODSA 기반, 산업용/IoT
NXP felixVSC9959 (LS1028A)OOOOOODSA 기반 + TC flower, TSN 지원
Realtek rtl8365mb/rtl8366rbRTL8365MB, RTL8366RBOO--O저가형 임베디드 스위치
TI am65-cpswAM65x CPSWOO----산업용 프로세서 내장 스위치
SW fallback: switchdev 오프로드가 실패하면 커널 브리지는 자동으로 소프트웨어 경로로 폴백합니다. 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 환경에서 네트워크 정책이 제대로 동작하지 않습니다.

Docker/Kubernetes 필수 설정: br_netfilter 미로드 시 Pod 간 통신에 iptables 규칙(NetworkPolicy, kube-proxy)이 적용되지 않습니다. 컨테이너 환경에서는 반드시 로드해야 합니다.
브리지 패킷 경로 NIC 수신 → netif_receive_skb() NF_BR_PRE_ROUTING (ebtables) br_handle_frame() + FDB 학습 NF_BR_FORWARD (ebtables) NF_BR_POST_ROUTING (ebtables) NIC 송신 → dev_queue_xmit() br_netfilter 추가 훅 NF_INET_PRE_ROUTING (iptables) NF_INET_FORWARD (iptables) NF_INET_POST_ROUTING (iptables) ebtables (L2 MAC/VLAN 필터) iptables/nftables (br_netfilter 로드 시 추가 호출) 점선 = br_netfilter 훅
/* 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-iptables1브리지 IPv4 트래픽을 iptables로 전달
net.bridge.bridge-nf-call-ip6tables1브리지 IPv6 트래픽을 ip6tables로 전달
net.bridge.bridge-nf-call-arptables1브리지 ARP 트래픽을 arptables로 전달
net.bridge.bridge-nf-filter-vlan-tagged0VLAN 태그 트래픽도 필터링
net.bridge.bridge-nf-filter-pppoe-tagged0PPPoE 태그 트래픽도 필터링
net.bridge.bridge-nf-pass-vlan-input-dev0VLAN 디바이스를 입력 디바이스로 전달

Docker/Kubernetes Bridge 네트워킹

컨테이너 네트워킹은 Linux Bridge를 핵심 인프라로 사용합니다. Docker는 docker0 브리지와 veth pair를 통해 컨테이너에 L2 연결을 제공하고, Kubernetes는 CNI 플러그인 체인(bridge + IPAM)을 통해 Pod 네트워크를 구성합니다. br_netfilter 모듈은 브리지 트래픽에 iptables 규칙을 적용하여 NetworkPolicy와 kube-proxy가 정상 동작하도록 합니다.

Docker 네트워킹 Host Network Namespace docker0 (br) iptables NAT eth0 (호스트) veth-A veth-B Container A Network NS eth0: 172.17.0.2/16 Container B Network NS eth0: 172.17.0.3/16 Docker 패킷 경로: ContA → veth → docker0(bridge) → veth → ContB (같은 호스트) ContA → veth → docker0 → iptables MASQUERADE → eth0 (외부) 외부 → eth0 → iptables DNAT(-p 8080:80) → docker0 → veth → ContA br_netfilter 역할: 브리지 L2 패킷이 iptables/nftables 훅 통과 → DNAT/SNAT 적용 미로드 시 포트 포워딩(-p), NetworkPolicy 미동작 Docker bridge 드라이버 순서: network create ip link add veth pair IPAM hairpin docker0(172.17.0.1/16) → veth pair → setns → IP 할당 → route → hairpin on → iptables Kubernetes 네트워킹 K8s Node Network Namespace cni0 (br) kube-proxy IPVS/ipt eth0 veth veth Pod A Network NS eth0: 10.244.1.2/24 Pod B Network NS eth0: 10.244.1.3/24 K8s 패킷 경로: Pod→Pod 같은 노드: veth → cni0(bridge) → veth (L2 포워딩) Pod→Pod 다른 노드: veth → cni0 → routing → eth0 → 원격 노드 Pod→Service: veth → cni0 → kube-proxy(iptables/IPVS) → endpoint Pod kube-proxy + br_netfilter: ClusterIP DNAT → iptables/IPVS 규칙이 브리지 패킷에도 적용 bridge-nf-call-iptables=1 필수 (미설정 시 Service 라우팅 실패) CNI bridge 플러그인 체인: bridge IPAM veth route done kubelet → CRI → CNI bridge → ip link add cni0 → veth pair → setns → IPAM → route add

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_netfiltermodprobe br_netfilter + /etc/modules-load.d/에 영구 설정
ClusterIP Service 접근 불가bridge-nf-call-iptables=0sysctl net.bridge.bridge-nf-call-iptablessysctl -w net.bridge.bridge-nf-call-iptables=1 + /etc/sysctl.d/에 영구화
컨테이너에서 외부 통신 안 됨ip_forward 비활성sysctl net.ipv4.ip_forwardsysctl -w net.ipv4.ip_forward=1
컨테이너가 자기 published 포트 접근 실패veth에 hairpin 미설정bridge -d link show | grep hairpinbridge link set dev vethXXX hairpin on 또는 Docker --userland-proxy=true
Docker 포트 포워딩 미동작iptables DNAT 규칙 누락iptables -t nat -L DOCKER -nDocker 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 linkip link del vethXXX으로 수동 정리
Docker 네트워크 간 격리 실패DOCKER-ISOLATION 체인 손상iptables -L DOCKER-ISOLATION-STAGE-1Docker daemon 재시작으로 iptables 규칙 재생성
DNS 해석 실패 (Pod 내부)브리지에서 UDP 53 트래픽 드롭tcpdump -i cni0 port 53bridge-nf-call-iptables + conntrack 규칙 확인
컨테이너 네트워크 2대 이슈: 컨테이너 환경에서 가장 빈번한 네트워크 장애 원인은 (1) 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 브리지 오버헤드를 완전히 우회합니다.

SW Bridge (CPU 처리) TC flower (NIC HW) XDP redirect (Bypass) NIC 수신 → DMA → skb 할당 br_handle_frame() + FDB 조회 dev_queue_xmit() → NIC 송신 성능 PPS: ~2Mpps (1GbE) CPU: 높음 (per-packet) 지연: ~5~20μs 적용: 일반 가상화 환경 TC ingress qdisc 설정 flower filter → NIC HW 오프로드 NIC 직접 포워딩 (CPU 미관여) 성능 PPS: NIC 와이어레이트 CPU: 거의 0 (HW 처리) 지연: ~1μs 적용: SmartNIC, SRIOV XDP BPF 프로그램 로드 bpf_redirect() + FDB BPF map XDP_TX / bpf_redirect_map() 성능 PPS: ~20~30Mpps CPU: 낮음 (early drop) 지연: <1μs 적용: 고성능 라우터/방화벽
/* 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
방식PPSCPU 사용지연필요 조건적합한 환경
SW Bridge~2Mpps높음5~20μs없음일반 VM/컨테이너 환경
TC flower HWNIC 성능거의 0~1μsHW offload 지원 NICSmartNIC, SR-IOV 배포
XDP redirect20~30Mpps낮음<1μsXDP native 지원 NIC고성능 라우터/방화벽

Open vSwitch vs Linux Bridge 비교

가상화·클라우드 네트워킹에서 Linux BridgeOpen vSwitch(OVS)는 가장 널리 쓰이는 소프트웨어 스위치입니다. Linux Bridge는 커널 내장 L2 스위치로 단순하고 안정적이며, OVS는 OpenFlow 기반의 유연한 SDN 데이터플레인을 제공합니다. OVS-DPDK는 커널을 완전히 우회하여 극한의 성능을 달성합니다.

Linux Bridge (커널 내장) Kernel Space NIC (eth0, veth) net_bridge FDB (rhashtable) br_forward() 포트 1 (eth1) 포트 2 (veth0) ~2Mpps | L2 전용 | 단순·안정 ✓ 커널 내장, 추가 데몬 없음, 낮은 메모리 사용 Open vSwitch (유저스페이스 + 커널) Userspace ovs-vswitchd ovsdb-server Kernel Space (openvswitch.ko) Megaflow Cache (fast path — 캐시 히트) upcall (slow path) Flow Table Datapath Forward OVS-DPDK (Userspace Datapath) 커널 우회 — PMD 스레드가 NIC 직접 폴링 → 유저스페이스 포워딩 hugepages + DPDK PMD → 20Mpps+ 달성 3-5Mpps (커널) | 20Mpps+ (DPDK) | L2-L4 + OpenFlow ✓ OpenFlow, VXLAN/Geneve, QoS, 풍부한 매칭

상세 비교 테이블

항목Linux BridgeOpen 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)
QoSTC 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–5Mpps20Mpps+ (단일 코어 14.88Mpps 10GbE line rate)
지연 시간5–20μs10–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, AntreaNFV, 통신사 코어 워크로드
하드웨어 오프로드switchdev (mlx5, bnxt)OVS-TC offload (mlx5 SmartNIC)DPDK Flow API (rte_flow)
모니터링 도구bridge fdb/vlan, ip linkovs-ofctl dump-flows, ovsdb-client, ovs-appctlovs-appctl dpif-netdev/pmd-stats-show
STP/RSTP✓ 내장 (커널 STP)✓ (ovs-vswitchd 관리)✓ (ovs-vswitchd 관리)
Connection Trackingbr_netfilter 경유 nf_conntrack✓ 내장 CT (OpenFlow ct() action)✓ 내장 CT
라이선스GPLv2 (커널 일부)Apache 2.0Apache 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);
}
참고: 포트 격리는 커널 4.18+에서 지원되며, 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 미학습, 패킷 드롭
FDB per-port 제한: Linux Bridge는 네이티브 per-port FDB 제한 기능이 없습니다. 대규모 환경에서 FDB 폭증을 방지하려면 eBPF 프로그램(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테넌트 포트: onbridge link set dev ethX isolated on
포트 잠금 (BR_PORT_LOCKED)off인증 포트: onbridge link set dev ethX locked on
MAC Auth Bypassoff802.1X 환경: onbridge link set dev ethX locked on mab on
STP BPDU Guardoff엣지 포트: onbridge link set dev ethX guard on
Root Guardoff다운스트림 포트: onbridge link set dev ethX root_block on
Learning 비활성화on정적 환경: offbridge link set dev ethX learning off
Unicast Flood 차단on알려진 호스트 전용: offbridge link set dev ethX flood off
Multicast Flood 차단onIGMP 환경: offbridge link set dev ethX mcast_flood off
Broadcast Flood 차단on특수 포트: offbridge link set dev ethX bcast_flood off
Storm Control (ARP)제한 없음100kbittc filter ... flower eth_type 0x0806 action police rate 100kbit
Storm Control (Multicast)제한 없음1Mbittc filter ... flower dst_mac 01:00:5e:00:00:00/ff:ff:ff:00:00:00 action police
VLAN 필터링offonip link set br0 type bridge vlan_filtering 1
br_netfilter로드 안 됨필요 시만 로드modprobe br_netfilter
ARP Proxyoff오버레이 환경: onecho 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.cFDB 학습 → 유니캐스트/플러드 결정
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.cFDB 조회 (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.cBPDU 수신 처리 (Config/TCN BPDU 파싱)
br_send_config_bpdu()br_stp_bpdu.cConfig BPDU 전송
br_received_config_bpdu()br_stp.cConfig BPDU 처리 — 루트/지정 브리지 선출
br_topology_change_detection()br_stp.c토폴로지 변경 감지 → TC 플래그 설정
br_multicast_rcv()br_multicast.cIGMP/MLD 패킷 스누핑 처리
br_allowed_ingress()br_vlan.cVLAN 인그레스 필터 — 허용된 VLAN만 통과
br_allowed_egress()br_vlan.cVLAN 이그레스 필터 — 태그/언태그 처리
nbp_vlan_add()br_vlan.c포트에 VLAN 추가
br_nf_pre_routing()br_netfilter_hooks.cbr_netfilter PRE_ROUTING 훅 — L3 iptables 적용
br_switchdev_fdb_notify()br_switchdev.cFDB 변경을 switchdev 드라이버에 통지
br_init()br.c모듈 초기화 — FDB 캐시, STP, netfilter 등록

성능 튜닝 및 벤치마킹

Linux Bridge의 성능은 NIC 하드웨어 오프로드부터 커널 파라미터, 브리지 자체 설정까지 다계층 튜닝이 가능합니다. 체계적 벤치마킹으로 병목을 식별하고 단계별로 최적화하는 것이 중요합니다.

성능 튜닝 계층 (하단 → 상단: 효과 큰 순) L1 HW Offload switchdev offload | TC flower HW | XDP redirect | Checksum/TSO/GRO offload 효과: ★★★★★ L2 NIC 멀티큐 / RSS ethtool -L ethX combined N | RSS indirection table | IRQ affinity (irqbalance off) 효과: ★★★★ L3 소프트웨어 분산 RPS (Receive Packet Steering) | RFS (Receive Flow Steering) | XPS (Transmit Packet Steering) 효과: ★★★ L4 Bridge 파라미터 ageing_time | hash_max | multicast_querier | group_fwd_mask | VLAN filtering 효과: ★★ L5 커널 sysctl / NAPI net.core.netdev_budget | net.core.rmem_max | NAPI weight | busy_poll 효과: ★ L6 switchdev 완전 오프로드 (궁극) CPU 0% — 와이어레이트

성능 튜닝 파라미터 테이블

카테고리파라미터기본값권장값설명
sysctl (커널)net.core.netdev_budget300600–1200NAPI 폴링 1회당 처리할 최대 패킷 수
net.core.netdev_budget_usecs20004000–8000NAPI 폴링 시간 제한 (μs)
net.core.rmem_max21299216777216소켓 수신 버퍼 최대 크기
net.core.busy_poll050busy polling 활성화 (μs, 지연 최소화)
Bridge 파라미터ageing_time300초30–120초FDB 에이징 — 짧으면 메모리 절약, 재학습 증가
hash_max409616384–65536멀티캐스트 그룹 해시 테이블 크기
vlan_filtering01VLAN 필터링 활성화 — 불필요 트래픽 차단
multicast_snooping11IGMP snooping — 멀티캐스트 flood 방지
multicast_querier01라우터 없는 환경에서 IGMP 쿼리어 역할
NIC (ethtool)-L combinedNIC 기본값CPU 코어 수멀티큐 채널 수 = RSS 분산 단위
-G rx/tx256/2564096/4096링 버퍼 크기 — 버스트 흡수
-C rx-usecsNIC 기본값50–100인터럽트 지연 (μs) — PPS vs 지연 트레이드오프
-K gro/tso/tx-checksumonon하드웨어 오프로드 확인 및 유지
IRQ 친화성/proc/irq/N/smp_affinity모든 CPU큐별 CPU 고정IRQ → 특정 CPU 바인딩 (irqbalance 비활성화)
RPS (rps_cpus)0NUMA 로컬 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 제한 필요
측정 조건: 위 수치는 x86_64 기준 (Xeon E5-2680v4, 3.3GHz, DDR4-2400) Linux 6.1 커널에서 pktgen 64바이트 패킷 기준 측정값입니다. rhashtable은 O(1) 평균 조회를 제공하지만, 엔트리 증가에 따라 해시 충돌과 캐시 미스가 늘어나 실질 지연이 증가합니다. FDB 10만 이상 환경에서는 ageing_time을 30초 이하로 설정하고, switchdev 오프로드를 적극 활용하세요.
빠른 튜닝 체크리스트:
  1. ethtool -k ethX로 GRO/TSO/tx-checksum 오프로드 활성 확인
  2. ethtool -l ethX로 멀티큐 채널 수 확인 → CPU 코어 수에 맞춤
  3. ethtool -g ethX로 링 버퍼 크기 확인 → 최대치로 설정
  4. IRQ affinity를 큐별 CPU에 수동 고정 (irqbalance 비활성화)
  5. ip link set br0 type bridge ageing_time 3000 (30초) — 대규모 환경 FDB 정리 가속
  6. VLAN 필터링 활성화 — 불필요 브로드캐스트 도메인 축소
  7. IGMP snooping + querier — 멀티캐스트 flood 방지
  8. switchdev 오프로드 가능 NIC(mlx5, bnxt)라면 devlink dev eswitch set으로 HW 오프로드 활성화

이 주제와 관련된 다른 문서를 더 깊이 이해하고 싶다면 다음을 참고하세요.