802.1Q VLAN 심화

802.1Q는 이더넷 프레임에 VLAN 태그를 넣어 L2 도메인을 분리하는 IEEE 표준입니다. 이 문서는 Linux 커널의 8021q 모듈 내부 구현, vlan_dev 구조체, VLAN 필터링, Q-in-Q(802.1ad), bridge VLAN filtering, 하드웨어 오프로드, 네트워크 네임스페이스 연동, 디버깅까지 실전 운영에 필요한 전 영역을 깊이 있게 다룹니다.

전제 조건: 이더넷Bridge/VLAN/Bonding 문서를 먼저 읽으세요. 프레임 헤더 구조와 브리지 개념이 있어야 태그 처리 경로를 정확히 이해할 수 있습니다.
일상 비유: 이 개념은 한 건물 안 층별 출입 카드와 비슷합니다. 같은 케이블(출입구)을 쓰더라도 VLAN ID로 어느 층 네트워크인지 구분합니다.

핵심 요약

  • TPID -- VLAN 태그 존재를 나타내는 타입(일반적으로 0x8100)
  • TCI -- PCP/DEI/VID를 담는 16비트 필드
  • PVID -- untagged 입력 프레임에 기본 부여되는 VLAN ID
  • tagged/untagged egress -- 출력 시 태그 유지 또는 제거 정책
  • QinQ -- 서비스/고객 VLAN을 중첩하는 802.1ad 방식
  • vlan_dev_priv -- VLAN 서브인터페이스의 커널 내부 구조체
  • NETIF_F_HW_VLAN_CTAG_* -- NIC 하드웨어 VLAN 오프로드 플래그

단계별 이해

  1. 프레임 수신
    NIC가 VLAN 태그를 하드웨어 또는 소프트웨어로 해석합니다.
  2. VID 기반 분류
    bridge/VLAN 서브인터페이스 정책에 따라 포워딩 대상을 결정합니다.
  3. 출력 정책 적용
    포트별 tagged/untagged 설정에 따라 태그를 유지하거나 제거합니다.
  4. 모니터링
    bridge vlan show, tcpdump -e로 실제 태그 동작을 검증합니다.

802.1Q VLAN 태그 구조

IEEE 802.1Q 프레임 포맷

IEEE 802.1Q는 이더넷 프레임의 소스 MAC 주소와 EtherType/Length 필드 사이에 4바이트 VLAN 태그를 삽입합니다. 이 4바이트는 2바이트 TPID(Tag Protocol Identifier)와 2바이트 TCI(Tag Control Information)로 구성됩니다. 태그 삽입으로 인해 최대 프레임 크기가 기존 1518바이트에서 1522바이트로 증가합니다.

802.1Q 태그 포함 이더넷 프레임 Dst MAC (6 bytes) Src MAC (6 bytes) TPID (2 bytes) TCI (2 bytes) EtherType (2 bytes) Payload (46-1500 bytes) FCS (4 bytes) 4바이트 VLAN 태그 (삽입) TPID = 0x8100 (802.1Q 식별자) PCP (3bit) DEI (1bit) VID (12bit) 우선순위 0~7 Drop Eligible VLAN ID 0~4095 VID 0: 우선순위 태깅 전용 (priority-tagged) VID 1~4094: 유효 VLAN ID VID 4095: 예약 (사용 불가) 일반 프레임 최대: 1518 bytes | 802.1Q 프레임 최대: 1522 bytes (+4) Q-in-Q(802.1ad) 프레임 최대: 1526 bytes (+8)

TPID(Tag Protocol Identifier)

TPID는 프레임에 VLAN 태그가 존재함을 수신 측에 알려주는 2바이트 식별자입니다. 표준 802.1Q VLAN의 TPID 값은 0x8100이며, 이 값은 원래 EtherType 필드 위치에 놓입니다. 수신 NIC이나 커널 스택은 EtherType 위치에서 0x8100을 발견하면 다음 2바이트를 TCI로 해석하고, 그 뒤의 2바이트를 실제 EtherType(또는 Length)로 처리합니다.

TPID 값표준용도
0x8100IEEE 802.1Q일반 C-VLAN 태그 (Customer Tag)
0x88a8IEEE 802.1adS-VLAN 태그 (Service Provider Tag, Q-in-Q)
0x9100비표준일부 벤더의 레거시 이중 태깅
0x9200비표준QinQ 이전 벤더 확장 (드물게 사용)

TCI(Tag Control Information) 상세

TCI 16비트는 세 개의 서브필드로 나뉩니다.

비트필드설명
15~13 (3bit)PCP (Priority Code Point)IEEE 802.1p QoS 우선순위. 0(Best Effort)~7(Network Control). 커널에서 skb->priority와 매핑됩니다.
12 (1bit)DEI (Drop Eligible Indicator)구 CFI(Canonical Format Indicator). 혼잡 시 우선 드롭 대상을 표시합니다.
11~0 (12bit)VID (VLAN Identifier)0~4095 범위. 0은 우선순위 전용 태깅, 4095는 예약. 실효 범위 1~4094.

PCP 우선순위 레벨 매핑

PCP 값약어트래픽 유형설명
0BEBest Effort기본 트래픽 (태그 없는 프레임의 기본값)
1BKBackground백그라운드 전송 (낮은 우선순위)
2EEExcellent Effort중요하지 않은 비즈니스 트래픽
3CACritical ApplicationsSAN, ERP 등 업무 핵심 트래픽
4VIVideo비디오 스트리밍 (지터 민감)
5VOVoiceVoIP 음성 (최소 지연 요구)
6ICInternetwork Control라우팅 프로토콜 (OSPF, BGP 등)
7NCNetwork ControlSTP BPDU 등 네트워크 제어 (최고 우선순위)

태그 삽입/제거 커널 경로

커널의 VLAN 태그 처리는 NIC 오프로드 지원 여부에 따라 크게 두 갈래로 나뉩니다.

/* include/linux/if_vlan.h -- skb의 VLAN 메타데이터 */
/* skb->vlan_present: VLAN 태그 존재 여부 (1bit)
   skb->vlan_tci:     TCI 필드 값 (PCP + DEI + VID)
   skb->vlan_proto:   TPID 값 (0x8100 또는 0x88a8) */

static inline int __vlan_insert_tag(
    struct sk_buff *skb,
    __be16 vlan_proto, u16 vlan_tci)
{
    struct vlan_ethhdr *veth;

    /* headroom 확보 후 4바이트 VLAN 헤더 삽입 */
    if (skb_cow_head(skb, VLAN_HLEN) < 0)
        return -ENOMEM;

    veth = skb_push(skb, VLAN_HLEN);
    memmove(skb->data, skb->data + VLAN_HLEN, 2 * ETH_ALEN);

    veth->h_vlan_proto = vlan_proto;
    veth->h_vlan_TCI = htons(vlan_tci);
    return 0;
}
성능 포인트: NIC이 VLAN 오프로드를 지원하면 skb->vlan_tci에 메타데이터만 기록되므로, 커널이 실제 이더넷 헤더를 수정(memmove)할 필요가 없습니다. 10 Gbps 이상 환경에서는 이 차이가 수십만 pps 처리량 차이를 만듭니다.

VLAN RX/TX 커널 처리 경로

수신(RX) 경로: VLAN 태그 해석

패킷이 NIC에 도착하면, VLAN 태그는 두 가지 경로로 처리됩니다: 하드웨어 VLAN 스트리핑(NIC가 태그를 제거하고 메타데이터로 전달)과 소프트웨어 처리(커널이 직접 태그 파싱). 대부분의 최신 NIC는 NETIF_F_HW_VLAN_CTAG_RX 기능으로 하드웨어 스트리핑을 지원합니다.

VLAN 패킷 수신(RX) 경로 Wire (이더넷) NIC 하드웨어 VLAN 태그 스트리핑 (HW) A: HW VLAN 스트리핑 __vlan_hwaccel_put_tag(skb) B: SW VLAN 파싱 skb→protocol == ETH_P_8021Q skb→vlan_tci = (PCP << 13) | VID skb→vlan_proto = ETH_P_8021Q (0x8100) __netif_receive_skb_core() vlan_do_receive(skb) VID → vlan_dev 매핑 eth0.10 (vlan_dev) eth0.20 (vlan_dev) → IP/ARP 프로토콜 스택 → IP/ARP 프로토콜 스택 송신(TX) 경로 ① 상위 프로토콜 → eth0.10 dev_queue_xmit(skb) ② vlan_dev_hard_start_xmit() skb에 VLAN 태그 삽입 ③ HW 삽입 체크 NETIF_F_HW_VLAN_CTAG_TX → __vlan_hwaccel_put_tag() ④ SW 삽입 폴백 vlan_insert_tag(skb) → 이더넷 헤더에 4B 삽입 ⑤ 실제 NIC로 전달 skb→dev = real_dev dev_queue_xmit(skb) ⑥ NIC 드라이버 ndo_start_xmit() → DMA 전송 → Wire

핵심 커널 함수 상세

/* net/8021q/vlan_core.c - 수신 경로 핵심 */
bool vlan_do_receive(struct sk_buff **skbp)
{
    struct sk_buff *skb = *skbp;
    __be16 vlan_proto = skb->vlan_proto;
    u16 vlan_id = skb_vlan_tag_get_id(skb);
    struct net_device *vlan_dev;
    struct vlan_pcpu_stats *rx_stats;

    /* real_dev의 VLAN 그룹에서 VID로 vlan_dev 검색 */
    vlan_dev = vlan_find_dev(skb->dev, vlan_proto, vlan_id);
    if (!vlan_dev)
        return false;  /* 해당 VID의 vlan_dev 없음 → drop */

    /* skb의 dev를 vlan_dev로 교체 */
    skb->dev = vlan_dev;
    /* VLAN 태그 정보를 skb에서 제거 (이미 처리됨) */
    skb->vlan_tci = 0;

    /* per-CPU 통계 갱신 */
    rx_stats = this_cpu_ptr(vlan_dev_priv(vlan_dev)->vlan_pcpu_stats);
    u64_stats_update_begin(&rx_stats->syncp);
    rx_stats->rx_packets++;
    rx_stats->rx_bytes += skb->len;
    /* ... */
    u64_stats_update_end(&rx_stats->syncp);

    return true;
}

/* net/8021q/vlan_dev.c - 송신 경로 핵심 */
static netdev_tx_t vlan_dev_hard_start_xmit(struct sk_buff *skb,
                                             struct net_device *dev)
{
    struct vlan_dev_priv *vlan = vlan_dev_priv(dev);
    struct vlan_ethhdr *veth;
    unsigned int len;
    int ret;

    /* HW VLAN TX 오프로드 지원 여부 확인 */
    if (vlan->real_dev->features & NETIF_F_HW_VLAN_CTAG_TX) {
        /* skb 메타데이터로 VLAN 태그 전달 (HW가 삽입) */
        __vlan_hwaccel_put_tag(skb, vlan->vlan_proto, vlan_tci);
    } else {
        /* 소프트웨어로 이더넷 헤더에 4바이트 VLAN 태그 삽입 */
        skb = vlan_insert_tag_set_proto(skb, vlan->vlan_proto, vlan_tci);
    }

    skb->dev = vlan->real_dev;  /* 실제 NIC로 교체 */
    len = skb->len;
    ret = dev_queue_xmit(skb);  /* 실제 NIC의 TX 큐로 전달 */

    return ret;
}

VLAN 태그 스트리핑/삽입 성능 영향

방식CPU 사용지연비고
HW RX 스트리핑 (NETIF_F_HW_VLAN_CTAG_RX)없음0NIC가 DMA 전 태그 제거
SW RX 파싱약간~50nsskb 포인터 조작
HW TX 삽입 (NETIF_F_HW_VLAN_CTAG_TX)없음0NIC가 DMA 시 태그 삽입
SW TX 삽입약간~100nsskb_cow_head() + memmove
HW VLAN 필터링 (NETIF_F_HW_VLAN_CTAG_FILTER)없음0불필요 VLAN 패킷 NIC에서 드롭

802.1p QoS와 TC 연동

PCP(Priority Code Point) 필드

802.1Q 태그의 상위 3비트(PCP)는 0~7의 우선순위를 나타냅니다. IEEE 802.1p는 이 PCP 값과 트래픽 클래스 매핑을 정의합니다. 리눅스 커널은 egress-qos-mapingress-qos-map으로 skb 우선순위(SO_PRIORITY)와 PCP 값을 매핑합니다.

802.1p PCP ↔ 리눅스 QoS 매핑 IEEE 802.1p PCP 값 PCP 우선순위 트래픽 유형 1 최저 (0) Background (BK) 0 기본 (1) Best Effort (BE) 2 우수 (2) Excellent Effort (EE) 3 중요 (3) Critical App (CA) 4 영상 (4) Video (<100ms) 5 음성 (5) Voice (<10ms) 6 제어 (6) Internetwork Control 7 최고 (7) Network Control 리눅스 TC (Traffic Control) skb→priority (SO_PRIORITY) ↕ ingress-qos-map tc qdisc (큐 관리) mqprio / prio / htb / ets tc filter (분류) flower / u32 / vlan 필터 egress-qos-map skb→priority → PCP 매핑 QoS 파이프라인: 수신 → 처리 → 송신 ① 수신 PCP → skb→priority ingress-qos-map 0:0 1:1 ... 7:7 ② TC qdisc 분류/스케줄링 mqprio num_tc 4 map 0 0 1 1 2 2 3 3 ③ 송신 skb→priority → PCP egress-qos-map 0:0 1:1 ... 7:7 DCB(Data Center Bridging): PFC + ETS + DCBX와 연동 시 lossless 이더넷 구현 가능

ingress/egress QoS 매핑 설정

# VLAN 인터페이스의 ingress QoS 매핑 (PCP → skb priority)
# 형식: FROM:TO (PCP:priority)
ip link add link eth0 name eth0.10 type vlan id 10 \
    ingress-qos-map 0:0 1:1 2:2 3:3 4:4 5:5 6:6 7:7

# 기존 인터페이스에 매핑 추가
ip link set eth0.10 type vlan ingress-qos-map 0:0 1:1 2:2 3:3 4:4 5:5 6:6 7:7

# egress QoS 매핑 (skb priority → PCP)
ip link set eth0.10 type vlan egress-qos-map 0:0 1:1 2:2 3:3 4:4 5:5 6:6 7:7

# 매핑 확인
cat /proc/net/vlan/eth0.10
# INGRESS priority mappings: 0:0  1:1  2:2  3:3  4:4  5:5  6:6  7:7
# EGRESS priority mappings: 0:0 1:1 2:2 3:3 4:4 5:5 6:6 7:7

TC(mqprio)와 VLAN QoS 연동

# mqprio qdisc: NIC의 하드웨어 큐와 트래픽 클래스 매핑
tc qdisc add dev eth0 root mqprio \
    num_tc 4 \
    map 0 0 1 1 2 2 3 3 0 0 0 0 0 0 0 0 \
    queues 1@0 1@1 1@2 1@3 \
    hw 1  # 하드웨어 오프로드

# TC 0: priority 0,1 (Best Effort)
# TC 1: priority 2,3 (Critical)
# TC 2: priority 4,5 (Video/Voice)
# TC 3: priority 6,7 (Control)

# VLAN PCP를 기반으로 tc filter 분류
tc filter add dev eth0 parent 1: protocol 802.1Q \
    flower vlan_prio 5 action skbedit priority 5

# DCB(Data Center Bridging) 연동
# PFC(Priority Flow Control) - lossless 큐 설정
mlnx_qos -i eth0 --pfc 0,0,0,1,0,1,0,0
# priority 3, 5에 대해 PFC 활성화 (RoCE, iSCSI 등)

# ETS(Enhanced Transmission Selection) - 대역폭 할당
mlnx_qos -i eth0 --tc_bw 10,10,30,50
# TC 0: 10%, TC 1: 10%, TC 2: 30%, TC 3: 50%
주의: egress-qos-map이 설정되지 않으면 모든 송신 패킷의 PCP가 0이 됩니다. VoIP나 실시간 영상 트래픽에 올바른 QoS를 적용하려면 반드시 egress 매핑을 설정해야 합니다. 애플리케이션이 setsockopt(SO_PRIORITY)로 우선순위를 설정해야 매핑이 동작합니다.

커널 vlan_dev 구현

vlan_dev_priv 구조체

Linux 커널에서 VLAN 서브인터페이스(예: eth0.10)는 독립적인 net_device로 생성됩니다. 각 VLAN 장치의 사적 데이터는 struct vlan_dev_priv에 저장되며, net/8021q/vlan.h에 정의되어 있습니다.

/* net/8021q/vlan.h (Linux 6.x, 주요 필드 발췌) */
struct vlan_dev_priv {
    /* vlan_id: 이 장치에 할당된 VLAN ID (1~4094) */
    unsigned short             vlan_id;

    /* vlan_proto: TPID 값 (__be16, 0x8100 or 0x88a8) */
    __be16                     vlan_proto;

    /* flags: VLAN_FLAG_REORDER_HDR, VLAN_FLAG_GVRP 등 */
    u16                        flags;

    /* real_dev: 하위 물리 장치 (예: eth0) */
    struct net_device          *real_dev;

    /* real_dev_addr: real_dev의 MAC 주소 스냅샷 */
    unsigned char              real_dev_addr[ETH_ALEN];

    /* dent: /proc/net/vlan/ 디렉토리 엔트리 */
    struct proc_dir_entry     *dent;

    /* vlan_pcpu_stats: per-CPU TX/RX 통계 */
    struct vlan_pcpu_stats __percpu *vlan_pcpu_stats;

    /* nr_ingress_mappings: ingress QoS 매핑 수 */
    unsigned int               nr_ingress_mappings;

    /* egress_priority_map: PCP 값과 커널 우선순위 매핑 */
    struct vlan_priority_tci_mapping *egress_priority_map[16];

    /* ingress_priority_map: ingress PCP → skb->priority 매핑 */
    u32                        ingress_priority_map[8];
};
필드 상세 설명
  • vlan_id 이 VLAN 장치가 속한 VLAN 식별자. ip link add에서 id 파라미터로 지정됩니다.
  • vlan_proto 사용할 TPID. 기본값 0x8100(C-VLAN)이며, protocol 802.1ad를 지정하면 0x88a8이 됩니다.
  • real_dev VLAN이 생성된 하위 물리 장치 포인터. eth0.10이면 eth0을 가리킵니다.
  • vlan_pcpu_stats per-CPU RX/TX 패킷/바이트 카운터. 락 없이 원자적으로 갱신되어 성능에 영향을 주지 않습니다.
  • ingress_priority_map 수신 프레임의 PCP 값을 skb->priority로 변환하는 테이블. ip link setingress-qos-map으로 설정합니다.
  • egress_priority_map 송신 시 skb->priority를 PCP 값으로 변환하는 해시 테이블. egress-qos-map으로 설정합니다.

vlan_group과 VLAN 장치 검색

vlan_group은 하나의 물리 장치(real_dev)에 연결된 모든 VLAN 장치를 관리하는 컨테이너입니다. 내부적으로 VID를 해시 키로 사용하여 O(1) 시간에 VLAN 장치를 찾습니다.

/* net/8021q/vlan.h */
#define VLAN_GROUP_ARRAY_LEN  (4096 / VLAN_GROUP_ARRAY_PART_LEN)

struct vlan_group {
    /* nr_vlan_devs: 등록된 VLAN 장치 총 수 */
    unsigned int      nr_vlan_devs;

    /* vlan_devices_arrays: VID → net_device 매핑 배열
       2차원 배열로 메모리 효율성 확보 (전체 4096 슬롯을 한 번에 할당하지 않음) */
    struct net_device ***vlan_devices_arrays[VLAN_PROTO_NUM];
};

/* VID로 VLAN 장치 조회 -- RX 경로 핫패스 */
static inline struct net_device *vlan_group_get_device(
    struct vlan_group *vg,
    __be16 vlan_proto, u16 vlan_id)
{
    struct net_device **array;
    array = vg->vlan_devices_arrays[vlan_proto_idx(vlan_proto)]
                                   [vlan_id / VLAN_GROUP_ARRAY_PART_LEN];
    return array ? array[vlan_id % VLAN_GROUP_ARRAY_PART_LEN] : NULL;
}

register_vlan_dev -- VLAN 장치 등록

ip link add link eth0 name eth0.10 type vlan id 10 명령은 커널 내부에서 register_vlan_dev()를 호출합니다. 이 함수는 다음 단계를 수행합니다.

  1. real_devvlan_info에서 vlan_group을 가져오거나 새로 생성
  2. VID 중복 검사 수행
  3. vlan_group에 새 VLAN 장치를 VID 슬롯에 등록
  4. real_devndo_vlan_rx_add_vid 콜백 호출 (하드웨어 VLAN 필터 등록)
  5. net_deviceregister_netdevice()로 네트워크 스택에 등록

vlan_dev_hard_start_xmit -- TX 경로

VLAN 서브인터페이스로 패킷을 전송하면 vlan_dev_hard_start_xmit()가 호출됩니다. 이 함수는 skb에 VLAN 태그를 설정한 뒤 하위 물리 장치의 TX 큐로 전달합니다.

/* net/8021q/vlan_dev.c (개념 코드) */
static netdev_tx_t vlan_dev_hard_start_xmit(
    struct sk_buff *skb,
    struct net_device *dev)
{
    struct vlan_dev_priv *vlan = vlan_dev_priv(dev);
    u16 vlan_tci;

    /* egress QoS 매핑 적용: skb->priority → PCP */
    vlan_tci = vlan->vlan_id;
    vlan_tci |= vlan_dev_get_egress_qos_mask(dev, skb->priority);

    /* HW 오프로드 지원 시 메타데이터만 설정 */
    if (vlan->real_dev->features & NETIF_F_HW_VLAN_CTAG_TX)
        __vlan_hwaccel_put_tag(skb, vlan->vlan_proto, vlan_tci);
    else
        /* SW 삽입: 실제 프레임 헤더에 4바이트 추가 */
        __vlan_insert_tag(skb, vlan->vlan_proto, vlan_tci);

    skb->dev = vlan->real_dev;
    return dev_queue_xmit(skb);
}

vlan_do_receive -- RX 경로

패킷 수신 시 __netif_receive_skb_core()에서 VLAN 프레임이 감지되면 vlan_do_receive()가 호출됩니다. 이 함수는 VID를 기반으로 해당 VLAN 서브인터페이스를 찾고, skb의 dev를 VLAN 장치로 전환합니다.

/* net/8021q/vlan_core.c (개념 코드) */
bool vlan_do_receive(struct sk_buff **skbp)
{
    struct sk_buff *skb = *skbp;
    __be16 vlan_proto = skb->vlan_proto;
    u16 vlan_id = skb_vlan_tag_get_id(skb);
    struct net_device *vlan_dev;
    struct vlan_pcpu_stats *rx_stats;

    /* vlan_group에서 VID로 VLAN 장치 검색 */
    vlan_dev = vlan_find_dev(skb->dev, vlan_proto, vlan_id);
    if (!vlan_dev)
        return false;

    /* skb의 수신 장치를 VLAN 장치로 전환 */
    skb->dev = vlan_dev;
    skb->pkt_type = PACKET_HOST;

    /* ingress QoS 매핑: PCP → skb->priority */
    skb->priority = vlan_get_ingress_priority(vlan_dev, skb->vlan_tci);

    /* VLAN 메타데이터 제거 (상위 스택은 태그 없는 것처럼 처리) */
    __vlan_hwaccel_clear_tag(skb);

    /* per-CPU 통계 갱신 */
    rx_stats = this_cpu_ptr(vlan_dev_priv(vlan_dev)->vlan_pcpu_stats);
    u64_stats_update_begin(&rx_stats->syncp);
    rx_stats->rx_packets++;
    rx_stats->rx_bytes += skb->len;
    u64_stats_update_end(&rx_stats->syncp);

    return true;
}

VLAN 필터링

NIC 하드웨어 VLAN 필터링 개요

대부분의 현대 NIC은 하드웨어 수준에서 VLAN 필터링을 지원합니다. NIC이 VLAN 필터 테이블을 관리하면, 등록되지 않은 VID를 가진 프레임은 NIC에서 즉시 폐기되어 CPU에 도달하지 않습니다. 이는 불필요한 인터럽트와 메모리 복사를 방지하여 성능을 크게 향상시킵니다.

NETIF_F_HW_VLAN_CTAG_FILTER

NIC 드라이버가 NETIF_F_HW_VLAN_CTAG_FILTER feature 플래그를 설정하면, 커널은 VLAN 장치를 등록/해제할 때 드라이버의 콜백 함수를 호출하여 NIC의 VLAN 필터 테이블을 동기화합니다.

Feature 플래그설명관련 콜백
NETIF_F_HW_VLAN_CTAG_FILTER C-VLAN(0x8100) 하드웨어 필터링 ndo_vlan_rx_add_vid / ndo_vlan_rx_kill_vid
NETIF_F_HW_VLAN_STAG_FILTER S-VLAN(0x88a8) 하드웨어 필터링 ndo_vlan_rx_add_vid / ndo_vlan_rx_kill_vid

ndo_vlan_rx_add_vid / ndo_vlan_rx_kill_vid

이 두 콜백은 struct net_device_ops에 정의되며, VLAN 장치가 등록되거나 해제될 때 호출됩니다. 드라이버는 이 콜백에서 NIC의 VLAN 필터 테이블에 VID를 추가하거나 제거합니다.

/* 드라이버 VLAN 필터 콜백 구현 예시 (개념 코드) */
static int my_driver_vlan_rx_add_vid(
    struct net_device *dev,
    __be16 proto, u16 vid)
{
    struct my_adapter *adapter = netdev_priv(dev);

    /* NIC 하드웨어 VLAN 필터 테이블에 VID 등록 */
    set_bit(vid, adapter->active_vlans);
    my_hw_write_vlan_filter(adapter, vid, 1);

    return 0;
}

static int my_driver_vlan_rx_kill_vid(
    struct net_device *dev,
    __be16 proto, u16 vid)
{
    struct my_adapter *adapter = netdev_priv(dev);

    /* NIC 하드웨어 VLAN 필터 테이블에서 VID 제거 */
    clear_bit(vid, adapter->active_vlans);
    my_hw_write_vlan_filter(adapter, vid, 0);

    return 0;
}

static const struct net_device_ops my_netdev_ops = {
    .ndo_vlan_rx_add_vid  = my_driver_vlan_rx_add_vid,
    .ndo_vlan_rx_kill_vid = my_driver_vlan_rx_kill_vid,
    /* ... */
};

소프트웨어 VLAN 필터링

NIC이 하드웨어 VLAN 필터링을 지원하지 않는 경우, 커널은 소프트웨어 수준에서 필터링을 수행합니다. vlan_do_receive()에서 VID에 해당하는 VLAN 장치가 없으면 프레임을 드롭합니다. 다만 이 경우 프레임이 이미 CPU 메모리로 DMA 전송되었으므로 하드웨어 필터링에 비해 비효율적입니다.

주의: NIC을 promiscuous 모드로 설정하면 (ip link set eth0 promisc on) VLAN 하드웨어 필터도 비활성화될 수 있습니다. tcpdump 등 패킷 캡처 도구를 사용할 때는 불필요한 VLAN 트래픽이 CPU에 도달할 수 있음을 인지해야 합니다.

GVRP/MVRP 동적 VLAN 등록

GVRP와 MVRP 개요

GVRP(GARP VLAN Registration Protocol, IEEE 802.1Q)와 MVRP(Multiple VLAN Registration Protocol, IEEE 802.1ak)는 스위치 간에 VLAN 멤버십을 자동으로 전파하는 프로토콜입니다. 수동으로 모든 스위치에 VLAN을 설정하는 대신, 한 곳에서 VLAN을 생성하면 인접 스위치로 자동 전파됩니다.

특성GVRPMVRP
표준IEEE 802.1Q (1998)IEEE 802.1ak (2007)
기반 프로토콜GARP (Generic Attribute Registration Protocol)MRP (Multiple Registration Protocol)
수렴 속도느림 (Join/Leave 타이머)빠름 (in-order delivery)
확장성4094 VLANs4094 VLANs + MST 통합
Ethertype0x8100 (GARP PDU)0x88F5
멀티캐스트 주소01:80:C2:00:00:2101:80:C2:00:00:21
리눅스 커널 지원vlan 모듈 (제한적)미지원 (userspace 필요)

리눅스에서 GVRP 활성화

# VLAN 인터페이스 생성 시 GVRP 활성화
ip link add link eth0 name eth0.10 type vlan id 10 gvrp on

# 기존 VLAN 인터페이스에서 GVRP 토글
ip link set eth0.10 type vlan gvrp on

# GVRP 상태 확인
cat /proc/net/vlan/eth0.10
# GVRP flag: 1 (활성화)

# MVRP 활성화 (커널 지원 시)
ip link add link eth0 name eth0.20 type vlan id 20 mvrp on

# VLAN 플래그 전체 확인
ip -d link show eth0.10
# vlan protocol 802.1Q id 10 <GVRP>

GVRP 동작 원리

GVRP는 GARP 프레임워크 위에서 동작합니다. 각 포트는 VLAN에 대해 세 가지 등록 상태를 가집니다:

GARP 메시지 타입은 다음과 같습니다:

실무 팁: 대규모 네트워크에서 GVRP/MVRP는 VLAN 설정 자동화에 유용하지만, 보안 관점에서는 위험할 수 있습니다. trunk 포트에서만 활성화하고, access 포트에서는 반드시 비활성화해야 합니다 (VLAN Hopping 방어).

Q-in-Q (802.1ad) 이중 태깅

S-VLAN과 C-VLAN 개념

Q-in-Q(802.1ad)는 기존 802.1Q 태그 위에 추가 VLAN 태그를 중첩하는 기술입니다. 서비스 프로바이더가 고객별 VLAN을 통째로 캡슐화하여 자신의 망 내에서 단일 S-VLAN으로 전달합니다.

Q-in-Q (802.1ad) 이중 태깅 프레임 Dst MAC Src MAC S-TPID 0x88a8 S-TCI S-VID C-TPID 0x8100 C-TCI C-VID EtherType Payload FCS S-Tag (외부, 프로바이더) C-Tag (내부, 고객) 프로바이더 네트워크 구간에서의 흐름 고객 A (C-VID 10, 20) S:100 프로바이더 코어 고객 A (C-VID 유지) 고객 B (C-VID 10, 30) S:200 고객 B (C-VID 유지)

Linux에서 Q-in-Q 구성

Linux에서 Q-in-Q를 구성하려면 먼저 protocol 802.1ad VLAN 장치(S-Tag)를 생성하고, 그 위에 일반 802.1Q VLAN 장치(C-Tag)를 중첩합니다.

# 1단계: S-VLAN 생성 (TPID 0x88a8)
ip link add link eth0 name eth0.100 type vlan \
    protocol 802.1ad id 100

# 2단계: C-VLAN 생성 (S-VLAN 위에 중첩)
ip link add link eth0.100 name eth0.100.10 type vlan id 10

# 3단계: 인터페이스 활성화
ip link set eth0.100 up
ip link set eth0.100.10 up

# 4단계: IP 주소 할당 (C-VLAN에)
ip addr add 192.168.10.1/24 dev eth0.100.10

# 확인: 인터페이스 상세 정보
ip -d link show eth0.100
# 출력 예:
#   vlan protocol 802.1ad id 100 <REORDER_HDR>
#   ...

ip -d link show eth0.100.10
# 출력 예:
#   vlan protocol 802.1Q id 10 <REORDER_HDR>
#   ...

Q-in-Q 운영 시 주의사항

항목세부 사항권장 조치
MTU 증가 S-Tag 4바이트 + C-Tag 4바이트 = 최대 8바이트 추가 오버헤드 물리 인터페이스 MTU를 1508 이상으로 설정 (ip link set eth0 mtu 1508)
NIC 오프로드 모든 NIC이 802.1ad 오프로드를 지원하지 않음 ethtool -k eth0 | grep vlan으로 지원 여부 확인
TPID 불일치 일부 레거시 장비가 0x9100을 S-Tag로 사용 양단 장비의 TPID 설정을 반드시 일치시킬 것
스위치 호환성 중간 스위치가 이중 태그를 올바르게 전달해야 함 trunk 포트에서 S-VLAN + C-VLAN 모두 허용 설정
VLAN ID 충돌 고객 간 C-VID 중복은 S-VID로 격리 S-VID를 고객별로 고유하게 할당

VLAN과 Bonding/Team 상호작용

Bonding + VLAN 토폴로지

서버 환경에서 NIC bonding과 VLAN을 결합하면 고가용성과 네트워크 분리를 동시에 달성할 수 있습니다. 올바른 스택 순서는 Physical NIC → Bond → VLAN입니다. 반대로 VLAN 위에 Bond를 구성하는 것은 가능하지만 권장되지 않습니다.

권장: Physical → Bond → VLAN eth0 eth1 bond0 (802.3ad) bond0.10 bond0.20 10.10.0.1/24 10.20.0.1/24 비권장: Physical → VLAN → Bond eth0 eth1 eth0.10 eth0.20 eth1.10 eth1.20 문제점: • LACP가 VLAN별로 분리됨 • bond 인스턴스 N×2개 필요 • HW 오프로드 불가 • failover 시 VLAN별 독립 동작 Bonding 모드별 VLAN 지원 mode=0 (balance-rr): VLAN OK | mode=1 (active-backup): VLAN OK | mode=2 (balance-xor): VLAN OK mode=4 (802.3ad/LACP): VLAN 권장 | mode=5 (balance-tlb): VLAN 주의 | mode=6 (balance-alb): VLAN 주의

Bonding + VLAN 설정

# 1. Bond 인터페이스 생성 (LACP/802.3ad)
ip link add bond0 type bond mode 802.3ad
ip link set bond0 up

# 2. Slave 인터페이스 추가
ip link set eth0 down
ip link set eth0 master bond0
ip link set eth1 down
ip link set eth1 master bond0

# 3. Bond 위에 VLAN 서브인터페이스 생성
ip link add link bond0 name bond0.10 type vlan id 10
ip link add link bond0 name bond0.20 type vlan id 20
ip link set bond0.10 up
ip link set bond0.20 up

# 4. IP 주소 할당
ip addr add 10.10.0.1/24 dev bond0.10
ip addr add 10.20.0.1/24 dev bond0.20

# 5. Bond 상태 확인
cat /proc/net/bonding/bond0
# 각 slave의 LACP 상태, speed, 활성 여부 확인

# 6. VLAN 필터 확인 (bond이 하위 NIC에 전파)
cat /proc/net/vlan/bond0.10

Team + VLAN 설정

teaming은 bonding의 대안으로, 더 유연한 링크 관리를 제공합니다. VLAN과의 결합 방식은 bonding과 동일합니다.

# Team 인터페이스 생성 (LACP)
ip link add team0 type team

# teamd 설정 (JSON config)
teamd -d -t team0 -c '{
    "runner": {"name": "lacp", "active": true, "fast_rate": true},
    "link_watch": {"name": "ethtool"},
    "ports": {"eth0": {}, "eth1": {}}
}'

# Team 위에 VLAN 생성
ip link add link team0 name team0.10 type vlan id 10
ip link add link team0 name team0.20 type vlan id 20

# 상태 확인
teamdctl team0 state
LACP + VLAN 스위치 설정: Bond 하위의 NIC가 연결된 물리 스위치 포트에서도 동일한 LACP 채널 그룹과 trunk VLAN 설정이 필요합니다. 양쪽 설정이 불일치하면 LACP 협상 실패 또는 특정 VLAN 트래픽이 손실됩니다.

VLAN-aware Bridge 동작

bridge VLAN filtering 개요

Linux bridge는 vlan_filtering을 활성화하면 물리 스위치와 유사한 VLAN-aware 동작을 합니다. 각 브리지 포트에 허용 VLAN 목록, PVID(Port VLAN ID), untagged 설정을 부여하여 access/trunk 포트 개념을 구현합니다.

상세 문서: Linux Bridge의 VLAN-aware 모드 설정, IGMP Snooping, STP 등의 상세 내용은 Linux Bridge 심화 페이지를 참조하세요.

# VLAN-aware bridge 생성
ip link add br0 type bridge vlan_filtering 1
ip link set br0 up

# 포트 추가
ip link set eth1 master br0
ip link set eth2 master br0
ip link set eth1 up
ip link set eth2 up

# access 포트 설정 (eth1: VLAN 10 전용)
bridge vlan add dev eth1 vid 10 pvid untagged

# trunk 포트 설정 (eth2: VLAN 10, 20 태그 유지)
bridge vlan add dev eth2 vid 10
bridge vlan add dev eth2 vid 20

# bridge 자체의 VLAN 설정 (로컬 트래픽용)
bridge vlan add dev br0 vid 10 self pvid untagged

# 기본 VLAN 1 제거 (보안 강화)
bridge vlan del dev eth1 vid 1
bridge vlan del dev eth2 vid 1
bridge vlan del dev br0 vid 1 self

PVID, tagged, untagged 동작 상세

설정Ingress 동작Egress 동작용도
pvid untagged 수신 프레임에 지정 VID를 부여 - access 포트의 기본 VLAN 지정
untagged - 해당 VID 프레임 송신 시 VLAN 태그 제거 VLAN 미지원 단말 연결
vid (tagged) 해당 VID 태그된 프레임 수신 허용 해당 VID 프레임 송신 시 태그 유지 trunk 포트의 VLAN 허용 목록
VLAN-aware Bridge 패킷 처리 흐름 Linux Bridge (br0, vlan_filtering=1) eth1 (access) PVID 10, untagged untagged 프레임 Ingress 정책 1. untagged → PVID 부여 2. VID 허용 목록 확인 FDB 조회 (MAC+VID) VLAN별 MAC 테이블 참조 Egress 정책 1. 포트 VID 허용 확인 2. untagged면 태그 제거 eth2 (trunk) VID 10,20 tagged 태그 유지 프레임 access 포트(eth1)에서 untagged 프레임 수신 → PVID 10 부여 → FDB 조회 → trunk 포트(eth2)로 VID 10 tagged 프레임 송신 (반대 방향: trunk에서 tagged 수신 → access에서 untagged 송신)

커널 내부: br_vlan_add / br_vlan_delete

bridge vlan add 명령은 커널 내부에서 br_vlan_add()를 호출합니다. 이 함수는 브리지 포트의 net_bridge_vlan_group에 VLAN 엔트리를 추가하고, 필요한 경우 하위 NIC의 ndo_vlan_rx_add_vid도 호출합니다.

/* net/bridge/br_vlan.c (개념 코드) */
int br_vlan_add(struct net_bridge *br,
                u16 vid, u16 flags, bool *changed,
                struct netlink_ext_ack *extack)
{
    struct net_bridge_vlan_group *vg;
    struct net_bridge_vlan *vlan;

    vg = br_vlan_group(br);

    /* 기존 VLAN 검색 */
    vlan = br_vlan_find(vg, vid);
    if (vlan) {
        /* 이미 존재하면 플래그만 갱신 (pvid/untagged 변경) */
        return br_vlan_flags(vlan, flags, changed);
    }

    /* 새 VLAN 엔트리 생성 및 등록 */
    vlan = kzalloc(sizeof(*vlan), GFP_KERNEL);
    vlan->vid = vid;
    vlan->flags = flags;

    /* rhashtable과 정렬된 리스트에 동시 추가 */
    rhashtable_lookup_insert_fast(&vg->vlan_hash, ...);
    list_add_tail_rcu(&vlan->vlist, ...);

    /* PVID 설정 */
    if (flags & BRIDGE_VLAN_INFO_PVID)
        br_vlan_set_pvid(vg, vid);

    *changed = true;
    return 0;
}

VLAN-aware bridge 포트 시나리오

시나리오설정 명령동작 설명
단순 access 포트 bridge vlan add dev eth1 vid 10 pvid untagged untagged 수신 → VID 10 부여, 송신 시 태그 제거
단순 trunk 포트 bridge vlan add dev eth2 vid 10
bridge vlan add dev eth2 vid 20
VID 10, 20 tagged 프레임만 수신/송신 허용
native VLAN trunk bridge vlan add dev eth3 vid 10 pvid untagged
bridge vlan add dev eth3 vid 20
untagged → VID 10 (native), VID 20은 tagged 전달
포트 격리 bridge link set dev eth1 isolated on 같은 VLAN이라도 isolated 포트 간 직접 통신 불가

VLAN 보안

VLAN Hopping 공격과 방어

VLAN Hopping은 공격자가 다른 VLAN의 트래픽에 접근하는 공격 기법입니다. 크게 두 가지 방식이 있습니다: Switch SpoofingDouble Tagging 공격입니다.

VLAN Hopping 공격 유형 ① Switch Spoofing 공격자 PC 스위치 DTP 협상 공격자가 DTP를 이용해 trunk 포트로 전환 → 모든 VLAN 접근 가능 방어: DTP 비활성화, access port 고정 ② Double Tagging 공격자 SW1 SW2 외부 태그=native VLAN(제거됨) 내부 태그=타겟 VLAN → SW2에서 포워딩 방어: native VLAN 미사용/태깅 강제 리눅스 커널 VLAN 보안 설정 ① Native VLAN 태깅 강제 bridge vlan add dev ethX vid 1 tagged (untagged 제거) ② PVID를 미사용 VLAN으로 변경 bridge vlan add dev ethX vid 4094 pvid untagged ③ 불필요한 VLAN 제거 bridge vlan del dev ethX vid 1 ④ MAC 기반 포트 보안 bridge fdb add ... static master br0 ⑤ 포트별 VLAN 필터링 활성화 bridge vlan add vid 10 dev eth0 pvid untagged ⑥ ebtables/nftables 브리지 필터링

리눅스 VLAN 보안 강화 실전

# 1. Native VLAN을 미사용 VLAN으로 변경 (Double Tagging 방어)
bridge vlan del dev eth0 vid 1
bridge vlan add dev eth0 vid 999 pvid untagged  # 미사용 VLAN을 PVID로

# 2. trunk 포트에서 native VLAN 태깅 강제
bridge vlan add dev eth0 vid 1 tagged  # VID 1도 반드시 tagged

# 3. 허용 VLAN 최소화 (불필요 VLAN 제거)
for vid in $(seq 2 998); do
    bridge vlan del dev eth0 vid $vid 2>/dev/null
done
# 필요한 VLAN만 추가
bridge vlan add dev eth0 vid 10
bridge vlan add dev eth0 vid 20

# 4. MAC 주소 제한 (포트 보안)
# 동적 학습 비활성화
bridge link set dev eth0 learning off
# 정적 MAC만 허용
bridge fdb add 00:11:22:33:44:55 dev eth0 master static
bridge fdb add 00:11:22:33:44:66 dev eth0 master static

# 5. 알 수 없는 유니캐스트/멀티캐스트 차단
bridge link set dev eth0 flood off
bridge link set dev eth0 mcast_flood off

# 6. nftables로 inter-VLAN 라우팅 제한
nft add table bridge filter
nft add chain bridge filter forward { type filter hook forward priority 0 \; }
# VLAN 10 → VLAN 20 차단
nft add rule bridge filter forward vlan id 10 oif "eth0.20" drop
# VLAN 20 → VLAN 10 차단
nft add rule bridge filter forward vlan id 20 oif "eth0.10" drop

Private VLAN (PVLAN) 에뮬레이션

Private VLAN은 같은 VLAN 내에서도 포트 간 통신을 제한하는 기법입니다. 리눅스에서는 bridge의 포트 격리 기능으로 유사하게 구현할 수 있습니다.

# 포트 격리 (Private VLAN Isolated 포트 에뮬레이션)
# 같은 브리지 내에서 peer-to-peer 통신 차단
bridge link set dev lan1 isolated on
bridge link set dev lan2 isolated on
# lan3 (uplink)은 isolated 아님 → lan1, lan2 모두 lan3와만 통신

# 확인
bridge -d link show dev lan1
# ... isolated on ...
참고: 리눅스 bridge의 isolated 기능은 커널 4.18부터 지원됩니다. 이 기능은 PVLAN의 Isolated 포트 역할만 수행하며, Community 포트는 ebtables/nftables로 구현해야 합니다.

per-VLAN STP/RSTP

STP와 VLAN의 관계

Spanning Tree Protocol(STP)은 L2 루프를 방지하는 프로토콜입니다. 기본 STP(IEEE 802.1D)는 전체 네트워크에 하나의 스패닝 트리를 구성하지만, VLAN 환경에서는 VLAN별로 다른 토폴로지가 필요할 수 있습니다.

프로토콜표준VLAN 지원리눅스 커널 지원
STPIEEE 802.1D단일 인스턴스bridge STP (기본)
RSTPIEEE 802.1w단일 인스턴스 (빠른 수렴)bridge STP + userspace
MSTPIEEE 802.1sVLAN-to-Instance 매핑mstpd (userspace)
PVST+Cisco 독점per-VLAN 인스턴스미지원
per-VLAN RSTP비표준per-VLAN 인스턴스커널 6.x+

리눅스 bridge STP 설정

# STP 활성화
ip link set br0 type bridge stp_state 1

# bridge 우선순위 설정 (낮을수록 루트 브리지 선출 유리)
ip link set br0 type bridge priority 4096

# 포트별 STP 비용/우선순위 설정
ip link set lan1 type bridge_slave priority 32
ip link set lan1 type bridge_slave cost 100

# 포트 STP 상태 확인
bridge link show dev lan1
# state forwarding|blocking|listening|learning|disabled

# RSTP (802.1w) 활성화 — forward_delay 단축
ip link set br0 type bridge forward_delay 400  # 4초 (1/100초 단위)

# Edge 포트 설정 (RSTP PortFast 해당)
# 호스트가 직접 연결된 포트 → 즉시 forwarding
bridge link set dev lan1 guard on  # BPDU guard

MSTP (Multiple Spanning Tree Protocol)

MSTP는 여러 VLAN을 하나의 STP 인스턴스에 매핑하여 관리합니다. 리눅스에서는 mstpd 데몬을 사용합니다.

# mstpd 설치 및 활성화
apt install mstpd
systemctl enable --now mstpd

# MSTP 모드로 bridge 설정
mstpctl setforcevers br0 mstp

# MST Instance 0: VLAN 1-10
# MST Instance 1: VLAN 11-20
mstpctl settreeprio br0 1 4096   # Instance 1 우선순위
mstpctl setportpathcost br0 lan1 1 200000  # Instance 1에서 lan1 비용

# 상태 확인
mstpctl showbridge br0
mstpctl showport br0 lan1
성능 팁: 단순한 L2 네트워크에서는 STP 대신 각 포트에서 bridge link set dev ethX guard on으로 BPDU guard를 설정하고, 루프 가능성이 없는 포트는 bpdu_filter를 활성화하는 것이 수렴 시간을 줄입니다.

실전 구성

ip link를 이용한 VLAN 생성/관리

# 기본 VLAN 서브인터페이스 생성
ip link add link eth0 name eth0.10 type vlan id 10
ip link set eth0.10 up
ip addr add 10.0.10.1/24 dev eth0.10

# 여러 VLAN 동시 생성
for vid in 10 20 30; do
    ip link add link eth0 name eth0.$vid type vlan id $vid
    ip link set eth0.$vid up
done

# VLAN 인터페이스 상세 정보 확인
ip -d link show eth0.10
# 출력 예:
# eth0.10@eth0: <BROADCAST,MULTICAST,UP> mtu 1500 ...
#     vlan protocol 802.1Q id 10 <REORDER_HDR>

# VLAN 서브인터페이스 삭제
ip link del eth0.10

# QoS 매핑 설정 (ingress: PCP→priority, egress: priority→PCP)
ip link set eth0.10 type vlan \
    ingress-qos-map 0:0 1:1 2:2 3:3 4:4 5:5 6:6 7:7
ip link set eth0.10 type vlan \
    egress-qos-map 0:0 1:1 2:2 3:3 4:4 5:5 6:6 7:7

# VLAN 플래그 설정
ip link set eth0.10 type vlan reorder_hdr on
ip link set eth0.10 type vlan gvrp on
ip link set eth0.10 type vlan loose_binding off

/proc/net/vlan 인터페이스

8021q 모듈이 로드되면 /proc/net/vlan/ 디렉토리에 VLAN 관련 정보가 노출됩니다.

# 전체 VLAN 구성 목록
cat /proc/net/vlan/config
# 출력 예:
# VLAN Dev name    | VLAN ID
# Name-Type: VLAN_NAME_TYPE_RAW_PLUS_VID_NO_PAD
# eth0.10        | 10  | eth0
# eth0.20        | 20  | eth0

# 개별 VLAN 장치 상세
cat /proc/net/vlan/eth0.10
# 출력 예:
# eth0.10  VID: 10    REORDER_HDR: 1  dev->priv_flags: 4001
#          total frames received            12345
#          total bytes received          1234567
#          Broadcast/Multicast Rcvd         100
#          total frames transmitted          5678
#          total bytes transmitted         567800

트러블슈팅 체크리스트

증상점검 항목해결 방법
VLAN 통신 불가 물리 인터페이스 UP 상태 확인 ip link set eth0 up
VLAN 통신 불가 상대 스위치 trunk/access 설정 확인 스위치 포트에서 해당 VID 허용 여부 확인
큰 패킷 손실 MTU 설정 확인 (VLAN 태그 4바이트 오버헤드) 물리 인터페이스 MTU를 1504 이상으로 설정하거나, VLAN MTU를 1496으로 축소
bridge에서 VLAN 격리 안 됨 vlan_filtering 활성화 여부 ip link set br0 type bridge vlan_filtering 1
VLAN 태그가 보이지 않음 NIC VLAN 오프로드 상태 ethtool -k eth0 | grep vlan으로 확인, 필요 시 ethtool -K eth0 rx-vlan-offload off
Q-in-Q 프레임 깨짐 중간 스위치 TPID 설정 모든 경로 장비의 S-Tag TPID를 0x88a8로 통일

성능과 오프로드

VLAN 하드웨어 오프로드 플래그

Linux 커널은 NIC의 VLAN 오프로드 능력을 net_device->features 비트마스크로 표현합니다. 이 플래그들은 ethtool -k로 확인하고 ethtool -K로 제어할 수 있습니다.

Feature 플래그ethtool 이름동작
NETIF_F_HW_VLAN_CTAG_TX tx-vlan-offload TX 시 NIC이 skb->vlan_tci를 읽어 프레임에 C-Tag 삽입
NETIF_F_HW_VLAN_CTAG_RX rx-vlan-offload RX 시 NIC이 C-Tag를 프레임에서 분리하여 skb->vlan_tci에 저장
NETIF_F_HW_VLAN_CTAG_FILTER rx-vlan-filter 등록되지 않은 C-VID 프레임을 NIC에서 드롭
NETIF_F_HW_VLAN_STAG_TX tx-vlan-stag-offload TX 시 NIC이 S-Tag(0x88a8) 삽입
NETIF_F_HW_VLAN_STAG_RX rx-vlan-stag-offload RX 시 NIC이 S-Tag 분리
NETIF_F_HW_VLAN_STAG_FILTER rx-vlan-stag-filter 등록되지 않은 S-VID 프레임을 NIC에서 드롭
# VLAN 오프로드 상태 확인
ethtool -k eth0 | grep vlan
# 출력 예:
# rx-vlan-offload: on
# tx-vlan-offload: on [fixed]
# rx-vlan-filter: on [fixed]
# rx-vlan-stag-offload: off [fixed]
# tx-vlan-stag-offload: off [fixed]
# rx-vlan-stag-filter: off [fixed]

# RX VLAN 오프로드 비활성화 (tcpdump에서 VLAN 태그 확인 시)
ethtool -K eth0 rx-vlan-offload off

# TX VLAN 오프로드 비활성화
ethtool -K eth0 tx-vlan-offload off

GRO/GSO와 VLAN 상호작용

GRO(Generic Receive Offload)와 GSO(Generic Segmentation Offload)는 VLAN 태그가 있는 패킷도 처리합니다. VLAN 서브인터페이스의 GRO/GSO 기능은 하위 물리 장치의 기능을 상속받되, 몇 가지 제약이 있습니다.

/* net/8021q/vlan_dev.c -- VLAN 장치 feature 상속 (개념 코드) */
static netdev_features_t vlan_dev_fix_features(
    struct net_device *dev,
    netdev_features_t features)
{
    struct net_device *real_dev = vlan_dev_priv(dev)->real_dev;

    /* 하위 장치 features와 교차 */
    features = netdev_intersect_features(features,
                    real_dev->vlan_features);

    /* 하위 장치의 encapsulation 오프로드 상속 */
    features |= real_dev->features & NETIF_F_SOFT_FEATURES;
    features |= NETIF_F_LLTX;

    return features;
}

성능 최적화 팁

항목권장 설정효과
VLAN TX/RX 오프로드 ethtool -K eth0 tx-vlan-offload on rx-vlan-offload on CPU 오버헤드 감소, memmove 제거
VLAN 필터링 rx-vlan-filter on (지원 시) 불필요한 VID 트래픽의 DMA 차단
GRO 활성화 ethtool -K eth0 gro on VLAN 프레임도 GRO 병합 가능
REORDER_HDR 플래그 ip link set eth0.10 type vlan reorder_hdr on (기본값) 수신 경로에서 VLAN 헤더 재배치 최적화
XPS/RPS 설정 VLAN 장치가 아닌 물리 장치에 설정 실제 TX/RX 큐는 물리 장치에 있음

DSA/switchdev VLAN 오프로드

DSA(Distributed Switch Architecture) 개요

DSA는 리눅스 커널에서 임베디드 이더넷 스위치 칩을 네트워크 디바이스로 모델링하는 프레임워크입니다. 각 스위치 포트가 독립된 net_device로 표현되므로, 표준 ipbridge 명령으로 VLAN 설정을 그대로 적용할 수 있습니다. DSA 태그 프로토콜은 CPU 포트와 스위치 ASIC 사이에서 포트 식별과 VLAN 메타데이터를 전달합니다.

DSA 지원 스위치 칩셋 예시: Marvell 88E6xxx 시리즈, Microchip KSZ 시리즈, Realtek RTL83xx, MediaTek MT7530, Broadcom SF2/Memory-mapped 등. 각 칩셋은 drivers/net/dsa/ 아래 개별 드라이버로 구현됩니다.

DSA VLAN 오프로드 아키텍처

User Space ip link / bridge vlan iproute2 switchctl Netlink (RTM_NEWVLAN) bridge (VLAN-aware) br_vlan_add() → switchdev_port_obj_add() switchdev notifier chain SWITCHDEV_OBJ_PORT_VLAN DSA Core (net/dsa/) dsa_port_vlan_add() → ds->ops->port_vlan_add() Switch Driver (e.g., mv88e6xxx) VTU(VLAN Table Unit) 레지스터 프로그래밍 Hardware Switch ASIC (VTU/PVLAN/Port-based VLAN) Wire-speed VLAN 태깅/필터링/포워딩 P0 P1 P2 CPU

switchdev VLAN 오브젝트 처리 흐름

switchdev은 커널 네트워킹 스택과 하드웨어 스위치 ASIC 사이의 추상화 레이어입니다. VLAN 관련 작업은 SWITCHDEV_OBJ_PORT_VLAN 오브젝트를 통해 전달됩니다.

/* include/net/switchdev.h - VLAN 오브젝트 구조체 */
struct switchdev_obj_port_vlan {
    struct switchdev_obj obj;
    u16 flags;        /* BRIDGE_VLAN_INFO_* 플래그 */
    u16 vid;          /* VLAN ID (1-4094) */
};

/* 주요 플래그 */
#define BRIDGE_VLAN_INFO_MASTER     (1<<0)  /* 브리지 자체에 추가 */
#define BRIDGE_VLAN_INFO_PVID       (1<<1)  /* 포트 PVID로 설정 */
#define BRIDGE_VLAN_INFO_UNTAGGED   (1<<2)  /* untagged 포트로 설정 */
#define BRIDGE_VLAN_INFO_RANGE_BEGIN (1<<3) /* 범위 시작 */
#define BRIDGE_VLAN_INFO_RANGE_END  (1<<4)  /* 범위 종료 */

DSA 드라이버 VLAN 콜백 구현

/* drivers/net/dsa/mv88e6xxx/chip.c - Marvell 88E6xxx 예시 */
static int mv88e6xxx_port_vlan_add(struct dsa_switch *ds, int port,
                                   const struct switchdev_obj_port_vlan *vlan,
                                   struct netlink_ext_ack *extack)
{
    struct mv88e6xxx_chip *chip = ds->priv;
    int err;

    mv88e6xxx_reg_lock(chip);

    /* VTU(VLAN Table Unit)에 VLAN 엔트리 추가 */
    err = mv88e6xxx_port_vlan_join(chip, port, vlan->vid, vlan->flags, extack);

    mv88e6xxx_reg_unlock(chip);
    return err;
}

/* DSA 드라이버 ops 등록 */
static const struct dsa_switch_ops mv88e6xxx_switch_ops = {
    .port_vlan_add       = mv88e6xxx_port_vlan_add,
    .port_vlan_del       = mv88e6xxx_port_vlan_del,
    .port_vlan_filtering = mv88e6xxx_port_vlan_filtering,
    .port_fdb_add        = mv88e6xxx_port_fdb_add,
    /* ... */
};

DSA VLAN 설정 실전 예시

# DSA 스위치 포트 확인
ls /sys/class/net/lan*/
# lan1, lan2, lan3, lan4 (각 스위치 포트)
# eth0 = CPU 포트 (DSA master)

# VLAN-aware 브리지 생성
ip link add br0 type bridge vlan_filtering 1 vlan_default_pvid 1
ip link set br0 up

# 스위치 포트를 브리지에 추가
for port in lan1 lan2 lan3 lan4; do
    ip link set $port master br0
    ip link set $port up
done

# 포트별 VLAN 할당 (하드웨어에서 처리됨)
# lan1, lan2 → VLAN 10 (untagged access port)
bridge vlan add dev lan1 vid 10 pvid untagged
bridge vlan add dev lan2 vid 10 pvid untagged
bridge vlan del dev lan1 vid 1
bridge vlan del dev lan2 vid 1

# lan3 → VLAN 20 (untagged access port)
bridge vlan add dev lan3 vid 20 pvid untagged
bridge vlan del dev lan3 vid 1

# lan4 → trunk port (VLAN 10, 20 tagged)
bridge vlan add dev lan4 vid 10
bridge vlan add dev lan4 vid 20

# CPU 포트에도 VLAN 허용
bridge vlan add dev br0 vid 10 self
bridge vlan add dev br0 vid 20 self

# 설정 확인 — 하드웨어 오프로드 여부 표시
bridge vlan show
bridge -d vlan show  # offload 표시 확인
하드웨어 오프로드 확인: bridge -d vlan show 출력에서 offload 플래그가 표시되면 VLAN 필터링이 하드웨어에서 수행됩니다. DSA 드라이버가 올바르게 구현되면 모든 VLAN 포워딩이 wire-speed로 처리됩니다.

switchdev 오프로드 vs 소프트웨어 폴백

동작하드웨어 오프로드소프트웨어 폴백
VLAN 태깅/언태깅스위치 ASIC (wire-speed)커널 vlan_dev
VLAN 필터링VTU 테이블 검색br_vlan_check()
MAC 학습ATU(Address Table Unit)br_fdb_update()
STP 상태포트 상태 레지스터br_stp_change_bridge_id()
멀티캐스트 스누핑ATU 멀티캐스트 엔트리br_multicast_rcv()
ACL/tc flowerTCAM 엔트리cls_flower 소프트웨어 분류
포트 미러링모니터 포트 레지스터tc mirred 리다이렉트
CPU 부하거의 없음모든 프레임 CPU 처리

VLAN과 네트워크 네임스페이스

VLAN vs macvlan vs ipvlan 비교

컨테이너 네트워킹에서 L2 격리를 구현하는 방법은 여러 가지가 있습니다. VLAN, macvlan, ipvlan은 각각 다른 방식으로 격리와 연결성을 제공합니다.

항목802.1Q VLANmacvlanipvlan
격리 단위 VID (12bit, 최대 4094) MAC 주소 IP 주소
MAC 주소 하위 장치와 동일(또는 별도 설정) 인스턴스마다 고유 MAC 하위 장치와 동일 MAC
스위치 요구사항 VLAN trunk 설정 필요 MAC 학습 지원 필요 없음
L2 브로드캐스트 동일 VID 내에서만 모든 macvlan에 전달 L2 모드에서만
호스트-컨테이너 통신 라우팅 또는 bridge 필요 bridge 모드에서 가능 L3 모드에서 가능
확장성 4094개 VID 제한 NIC MAC 테이블 제한 높음 (MAC 테이블 부담 없음)
주 용도 전통적 네트워크 분할 다수 컨테이너 L2 격리 대규모 컨테이너, 클라우드

네트워크 네임스페이스에서 VLAN 사용

VLAN 서브인터페이스는 네트워크 네임스페이스로 이동시킬 수 있습니다. 이를 통해 네임스페이스별로 독립적인 L2 도메인을 구성할 수 있습니다.

# 네임스페이스 생성
ip netns add ns-web
ip netns add ns-db

# VLAN 서브인터페이스 생성
ip link add link eth0 name eth0.10 type vlan id 10
ip link add link eth0 name eth0.20 type vlan id 20

# VLAN 인터페이스를 네임스페이스로 이동
ip link set eth0.10 netns ns-web
ip link set eth0.20 netns ns-db

# 각 네임스페이스 내에서 설정
ip netns exec ns-web ip addr add 10.0.10.1/24 dev eth0.10
ip netns exec ns-web ip link set eth0.10 up
ip netns exec ns-web ip link set lo up

ip netns exec ns-db ip addr add 10.0.20.1/24 dev eth0.20
ip netns exec ns-db ip link set eth0.20 up
ip netns exec ns-db ip link set lo up

# 확인
ip netns exec ns-web ip addr show
ip netns exec ns-db ip addr show

컨테이너 네트워킹 구성 패턴

Docker, Kubernetes 등 컨테이너 런타임에서 VLAN을 활용하는 대표적인 패턴들입니다.

패턴구조장점단점
VLAN + bridge VID별 bridge 생성, 각 bridge에 컨테이너 veth 연결 전통적이고 이해하기 쉬움 bridge 오버헤드, VID 수 제한
VLAN-aware bridge 단일 bridge에 vlan_filtering 활성화, 포트별 VID 할당 bridge 하나로 여러 VLAN 처리, 메모리 효율적 설정 복잡도 증가
macvlan + VLAN VLAN 서브인터페이스 위에 macvlan 장치 생성 VLAN 격리 + MAC 격리 이중 보안 MAC 테이블 소모
ipvlan L3 + VLAN VLAN 서브인터페이스에 ipvlan L3 장치 연결 대규모 환경에서 높은 확장성 L2 기능(ARP, DHCP) 제한
# macvlan + VLAN 조합 예시
ip link add link eth0 name eth0.100 type vlan id 100
ip link set eth0.100 up

# VLAN 위에 macvlan 생성
ip link add link eth0.100 name mv-c1 type macvlan mode bridge
ip link set mv-c1 netns container1

# ipvlan L3 + VLAN 조합 예시
ip link add link eth0 name eth0.200 type vlan id 200
ip link set eth0.200 up

ip link add link eth0.200 name iv-c2 type ipvlan mode l3
ip link set iv-c2 netns container2

디버깅과 모니터링

tcpdump VLAN 캡처

VLAN 태그가 포함된 패킷을 캡처하려면 NIC의 RX VLAN 오프로드 상태에 주의해야 합니다. 오프로드가 활성화되면 NIC이 태그를 분리하므로 tcpdump에서 태그가 보이지 않을 수 있습니다.

# 기본 VLAN 캡처 (물리 인터페이스에서)
tcpdump -i eth0 -e -nn vlan
# -e: 이더넷 헤더 표시 (VLAN 태그 포함)
# -nn: 이름 해석 비활성화
# vlan: VLAN 태그가 있는 프레임만 필터

# 특정 VLAN ID 필터
tcpdump -i eth0 -e -nn 'vlan 10'

# VLAN 태그 내부의 특정 프로토콜 필터
tcpdump -i eth0 -e -nn 'vlan 10 and icmp'

# Q-in-Q 이중 태그 캡처
tcpdump -i eth0 -e -nn 'vlan and vlan'

# VLAN 태그가 안 보일 때: RX 오프로드 비활성화
ethtool -K eth0 rx-vlan-offload off
tcpdump -i eth0 -e -nn vlan
# 캡처 후 복원
ethtool -K eth0 rx-vlan-offload on

# VLAN 서브인터페이스에서 직접 캡처 (태그 없는 패킷으로 보임)
tcpdump -i eth0.10 -nn
팁: tcpdump로 VLAN 태그를 반드시 확인해야 한다면, 캡처 전에 ethtool -K eth0 rx-vlan-offload off를 실행하세요. 그렇지 않으면 NIC이 태그를 분리하여 skb->vlan_tci에만 저장하므로 tcpdump에서 태그가 보이지 않습니다.

ip -d link show 활용

# VLAN 장치 상세 정보
ip -d link show eth0.10
# 출력 예:
# 5: eth0.10@eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 ...
#     link/ether aa:bb:cc:dd:ee:ff brd ff:ff:ff:ff:ff:ff
#     vlan protocol 802.1Q id 10 <REORDER_HDR>
#     numtxqueues 1 numrxqueues 1 gso_max_size 65536 ...

# Q-in-Q 장치 확인
ip -d link show eth0.100
# 출력 예:
# 6: eth0.100@eth0: ...
#     vlan protocol 802.1ad id 100 <REORDER_HDR>

# 모든 VLAN 장치 목록
ip -d link show type vlan

bridge vlan show 활용

# 브리지 포트별 VLAN 설정 확인
bridge vlan show
# 출력 예:
# port    vlan-id
# eth1    10 PVID Egress Untagged
# eth2    10
#         20
# br0     10 PVID Egress Untagged

# 특정 장치의 VLAN 설정만
bridge vlan show dev eth1

# JSON 형식 출력 (스크립트 파싱용)
bridge -j vlan show

# 통계 포함 (커널 5.9+)
bridge -s vlan show

/proc/net/vlan 파일

# 8021q 모듈 로드 확인
lsmod | grep 8021q

# VLAN 설정 목록
cat /proc/net/vlan/config

# 개별 VLAN 통계
cat /proc/net/vlan/eth0.10
# 표시 항목:
# - VID, 플래그, dev->priv_flags
# - total frames/bytes received/transmitted
# - Broadcast/Multicast 수신 수
# - VLAN device MAC 주소

# VLAN 장치 존재 여부 빠른 확인
ls /proc/net/vlan/

커널 로그와 트레이싱

# VLAN 관련 커널 메시지
dmesg | grep -i vlan
# 출력 예:
# 8021q: adding VLAN 0 to HW filter on device eth0
# 8021q: 802.1Q VLAN Support v1.8

# perf로 VLAN 수신 경로 프로파일링
perf record -g -e net:netif_receive_skb -- sleep 10
perf report

# ftrace로 vlan_do_receive 호출 추적
echo vlan_do_receive > /sys/kernel/debug/tracing/set_ftrace_filter
echo function > /sys/kernel/debug/tracing/current_tracer
cat /sys/kernel/debug/tracing/trace_pipe

# bpftrace로 VLAN RX 이벤트 모니터링 (실시간)
bpftrace -e 'kprobe:vlan_do_receive { @[comm] = count(); }'

일반적인 디버깅 시나리오

문제진단 명령확인 포인트
VLAN 장치에서 패킷 수신 안 됨 tcpdump -i eth0 -e -nn vlan 10 물리 인터페이스에서 해당 VID 프레임이 도착하는지 확인
VLAN 통계가 증가하지 않음 cat /proc/net/vlan/eth0.10 RX/TX 카운터가 0인지 확인, 물리 인터페이스 통계와 비교
bridge VLAN 필터링 동작 안 함 bridge vlan show
ip -d link show br0
vlan_filtering 1 설정 확인, 포트별 VID/PVID 확인
VLAN 간 라우팅 안 됨 sysctl net.ipv4.ip_forward IP 포워딩 활성화 여부, iptables FORWARD 체인 정책 확인
Q-in-Q 외부 태그 누락 ip -d link show eth0.100 vlan protocol 802.1ad인지 확인, TPID 불일치 점검

커널 설정

필수/권장 커널 옵션

옵션설명권장비고
CONFIG_VLAN_8021Q 802.1Q VLAN 지원 m (모듈) 기본 VLAN 기능. modprobe 8021q로 로드
CONFIG_VLAN_8021Q_GVRP GVRP(GARP VLAN Registration Protocol) 지원 n (불필요 시) 동적 VLAN 등록이 필요한 환경에서만 활성화
CONFIG_VLAN_8021Q_MVRP MVRP(Multiple VLAN Registration Protocol) 지원 n (불필요 시) GVRP의 후속 프로토콜. 802.1ak
CONFIG_BRIDGE 이더넷 브리지 지원 m VLAN-aware bridge 사용 시 필수
CONFIG_BRIDGE_VLAN_FILTERING 브리지 VLAN 필터링 y vlan_filtering 1 기능 활성화
CONFIG_NET_SWITCHDEV 하드웨어 스위치 오프로드 연동 y DSA/switchdev 기반 VLAN 오프로드 시 필요
CONFIG_MACVLAN macvlan 가상 장치 m VLAN + macvlan 조합 사용 시 필요
CONFIG_IPVLAN ipvlan 가상 장치 m VLAN + ipvlan 조합 사용 시 필요

모듈 로드와 확인

# 8021q 모듈 로드
modprobe 8021q

# 로드 확인
lsmod | grep 8021q
# 출력 예:
# 8021q                  36864  0
# garp                   16384  1 8021q
# mrp                    20480  1 8021q

# 자동 로드 설정 (/etc/modules-load.d/)
echo 8021q >> /etc/modules-load.d/vlan.conf

# 커널 빌드 설정 확인
zcat /proc/config.gz | grep VLAN
# 또는
grep VLAN /boot/config-$(uname -r)

sysctl 관련 설정

# VLAN 간 라우팅에 필요한 IP 포워딩
sysctl -w net.ipv4.ip_forward=1
sysctl -w net.ipv6.conf.all.forwarding=1

# ARP 관련 (VLAN 환경에서 proxy ARP 필요 시)
sysctl -w net.ipv4.conf.all.proxy_arp=1

# bridge에서 iptables 통합 (Docker 환경 등)
sysctl -w net.bridge.bridge-nf-call-iptables=1

# 영구 설정 (/etc/sysctl.d/99-vlan.conf)
cat > /etc/sysctl.d/99-vlan.conf <<EOF
net.ipv4.ip_forward = 1
net.ipv6.conf.all.forwarding = 1
EOF
sysctl --system

VLAN과 Netfilter 상호작용

iptables/nftables에서 VLAN 태그를 매칭하거나, bridge의 Netfilter 통합과 VLAN이 만나는 지점은 실무에서 혼동이 많은 영역입니다. 핵심은 VLAN 서브인터페이스로 올라간 패킷에서는 이미 태그가 제거된 상태라는 것입니다.

VLAN과 Netfilter 훅 — 패킷이 보이는 위치 NIC (eth0) VLAN 태그 처리 HW/SW strip → skb->vlan_tci iptables -i eth0 태그 있음 (PREROUTING 전) iptables -i eth0.10 태그 없음 (이미 strip됨) 로컬 프로세스 Netfilter에서 VLAN 매칭하기 iptables: -m vlan --vlan-tag 10 (xt_vlan 모듈, 드물게 사용) nftables: meta iifname "eth0.10" 또는 vlan id 10 (bridge family) ebtables: --vlan-id 10 (bridge 테이블에서 L2 수준 매칭)
VLAN 서브인터페이스(-i eth0.10)에서는 태그가 이미 제거된 상태이므로, 태그 기반 필터링은 물리 인터페이스 또는 bridge/ebtables에서 해야 합니다.
# nftables에서 VLAN 매칭 (bridge family)
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 10 accept
nft add rule bridge filter forward vlan id 20 drop

# nftables에서 인터페이스 이름으로 매칭 (inet family)
nft add rule inet filter input iifname "eth0.10" tcp dport 80 accept

# ebtables에서 VLAN 태그 매칭
ebtables -A FORWARD --vlan-id 10 -j ACCEPT
ebtables -A FORWARD --vlan-id 99 -j DROP

# bridge-nf-call-iptables와의 상호작용
# bridge에서 포워딩되는 패킷이 iptables를 통과할지 결정
sysctl -w net.bridge.bridge-nf-call-iptables=1   # 기본 1 (Docker 환경)
# 주의: vlan_filtering=1 + bridge-nf-call-iptables=1 동시 사용 시
#   bridge 내부에서 VLAN 기반 iptables 필터링이 복잡해질 수 있음
VLAN + Netfilter 혼동 포인트:
  • 태그 strip 타이밍: NIC RX 오프로드가 켜져 있으면, PREROUTING 훅에 도달하기 전에 이미 태그가 skb->vlan_tci로 분리됩니다. -i eth0에서 태그를 보려면 ethtool -K eth0 rx-vlan-offload off가 필요합니다.
  • FORWARD 체인에서의 VLAN: VLAN 서브인터페이스 간 라우팅 트래픽은 -i eth0.10 -o eth0.20처럼 인터페이스 이름으로 매칭합니다. 태그 자체를 볼 필요는 없습니다.
  • bridge 내부: bridge 포트 간 L2 포워딩은 iptables의 FORWARD 체인을 통과하지 않습니다 (bridge-nf-call-iptables=0인 경우). ebtables 또는 nftables bridge family를 사용하세요.

VLAN 스케일링과 VXLAN 비교

802.1Q VLAN은 12비트 VID(최대 4094개)라는 구조적 확장 한계가 있습니다. 대규모 데이터센터와 멀티 테넌트 환경에서는 이 제한이 병목이 되어 VXLAN(24비트, 약 1600만 ID)이나 GENEVE 같은 오버레이 기술이 대안으로 사용됩니다.

특성802.1Q VLANQ-in-Q (802.1ad)VXLAN
ID 공간 12비트 (4,094) 12+12비트 (4,094 × 4,094) 24비트 (16,777,216)
캡슐화 L2 태그 (4B) 이중 L2 태그 (8B) UDP + VXLAN 헤더 (50B)
L2/L3 범위 단일 L2 도메인 단일 L2 도메인 L3 오버레이 (IP 라우팅 가능)
스패닝 트리 필요 필요 불필요 (IP 라우팅 사용)
MTU 오버헤드 4B 8B 50B (IP+UDP+VXLAN)
HW 오프로드 거의 모든 NIC 일부 NIC 고급 NIC (ConnectX, E810 등)
주 용도 캠퍼스/엔터프라이즈 LAN 서비스 프로바이더 WAN 데이터센터 오버레이, K8s
Linux 구현 8021q 모듈 8021q (protocol 802.1ad) vxlan 모듈
언제 무엇을 선택할까:
  • 802.1Q VLAN: 테넌트 수가 수십~수백이고 단일 L2 도메인이면 가장 단순하고 오버헤드가 적습니다.
  • Q-in-Q: ISP가 고객 VLAN을 투명하게 전달해야 하는 WAN 구간에서 사용합니다.
  • VXLAN/GENEVE: 수천 테넌트, 멀티 사이트, L3 기반 fabric이 필요한 대규모 데이터센터에서 필수입니다. Kubernetes의 Calico, Cilium, Flannel 등이 VXLAN을 기본 백엔드로 사용합니다.
  • 혼합 사용: 물리 인프라에서 VLAN으로 관리 평면을 분리하고, 오버레이로 VXLAN을 사용하는 것이 일반적인 데이터센터 아키텍처입니다.

참고자료