802.1Q VLAN 심화
802.1Q는 이더넷 프레임에 VLAN 태그를 넣어 L2 도메인을 분리하는 IEEE 표준입니다. 이 문서는 Linux 커널의 8021q 모듈 내부 구현, vlan_dev 구조체, VLAN 필터링, Q-in-Q(802.1ad), bridge VLAN filtering, 하드웨어 오프로드, 네트워크 네임스페이스 연동, 디버깅까지 실전 운영에 필요한 전 영역을 깊이 있게 다룹니다.
핵심 요약
- 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 오프로드 플래그
단계별 이해
- 프레임 수신
NIC가 VLAN 태그를 하드웨어 또는 소프트웨어로 해석합니다. - VID 기반 분류
bridge/VLAN 서브인터페이스 정책에 따라 포워딩 대상을 결정합니다. - 출력 정책 적용
포트별 tagged/untagged 설정에 따라 태그를 유지하거나 제거합니다. - 모니터링
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바이트로 증가합니다.
TPID(Tag Protocol Identifier)
TPID는 프레임에 VLAN 태그가 존재함을 수신 측에 알려주는 2바이트 식별자입니다.
표준 802.1Q VLAN의 TPID 값은 0x8100이며, 이 값은 원래 EtherType 필드 위치에 놓입니다.
수신 NIC이나 커널 스택은 EtherType 위치에서 0x8100을 발견하면 다음 2바이트를 TCI로 해석하고,
그 뒤의 2바이트를 실제 EtherType(또는 Length)로 처리합니다.
| TPID 값 | 표준 | 용도 |
|---|---|---|
0x8100 | IEEE 802.1Q | 일반 C-VLAN 태그 (Customer Tag) |
0x88a8 | IEEE 802.1ad | S-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 값 | 약어 | 트래픽 유형 | 설명 |
|---|---|---|---|
| 0 | BE | Best Effort | 기본 트래픽 (태그 없는 프레임의 기본값) |
| 1 | BK | Background | 백그라운드 전송 (낮은 우선순위) |
| 2 | EE | Excellent Effort | 중요하지 않은 비즈니스 트래픽 |
| 3 | CA | Critical Applications | SAN, ERP 등 업무 핵심 트래픽 |
| 4 | VI | Video | 비디오 스트리밍 (지터 민감) |
| 5 | VO | Voice | VoIP 음성 (최소 지연 요구) |
| 6 | IC | Internetwork Control | 라우팅 프로토콜 (OSPF, BGP 등) |
| 7 | NC | Network Control | STP BPDU 등 네트워크 제어 (최고 우선순위) |
태그 삽입/제거 커널 경로
커널의 VLAN 태그 처리는 NIC 오프로드 지원 여부에 따라 크게 두 갈래로 나뉩니다.
- 하드웨어 오프로드(HW VLAN): NIC이 RX 시 태그를 프레임에서 분리해
skb->vlan_tci에 저장하고, TX 시skb->vlan_tci를 읽어 프레임에 태그를 삽입합니다. CPU에 도달하는 패킷에는 VLAN 태그가 없어 파싱 비용이 절감됩니다. - 소프트웨어 처리: NIC이 오프로드를 지원하지 않으면 커널이
__vlan_insert_tag()/__vlan_hwaccel_put_tag()로 직접 태그를 추가/제거합니다.
/* 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;
}
skb->vlan_tci에 메타데이터만 기록되므로,
커널이 실제 이더넷 헤더를 수정(memmove)할 필요가 없습니다. 10 Gbps 이상 환경에서는 이 차이가 수십만 pps 처리량 차이를 만듭니다.
VLAN RX/TX 커널 처리 경로
수신(RX) 경로: VLAN 태그 해석
패킷이 NIC에 도착하면, VLAN 태그는 두 가지 경로로 처리됩니다:
하드웨어 VLAN 스트리핑(NIC가 태그를 제거하고 메타데이터로 전달)과
소프트웨어 처리(커널이 직접 태그 파싱). 대부분의 최신 NIC는
NETIF_F_HW_VLAN_CTAG_RX 기능으로 하드웨어 스트리핑을 지원합니다.
핵심 커널 함수 상세
/* 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) | 없음 | 0 | NIC가 DMA 전 태그 제거 |
| SW RX 파싱 | 약간 | ~50ns | skb 포인터 조작 |
HW TX 삽입 (NETIF_F_HW_VLAN_CTAG_TX) | 없음 | 0 | NIC가 DMA 시 태그 삽입 |
| SW TX 삽입 | 약간 | ~100ns | skb_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-map과 ingress-qos-map으로
skb 우선순위(SO_PRIORITY)와 PCP 값을 매핑합니다.
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 set의ingress-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()를 호출합니다.
이 함수는 다음 단계를 수행합니다.
real_dev의vlan_info에서vlan_group을 가져오거나 새로 생성- VID 중복 검사 수행
vlan_group에 새 VLAN 장치를 VID 슬롯에 등록real_dev의ndo_vlan_rx_add_vid콜백 호출 (하드웨어 VLAN 필터 등록)- 새
net_device를register_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 전송되었으므로 하드웨어 필터링에 비해 비효율적입니다.
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을 생성하면 인접 스위치로 자동 전파됩니다.
| 특성 | GVRP | MVRP |
|---|---|---|
| 표준 | IEEE 802.1Q (1998) | IEEE 802.1ak (2007) |
| 기반 프로토콜 | GARP (Generic Attribute Registration Protocol) | MRP (Multiple Registration Protocol) |
| 수렴 속도 | 느림 (Join/Leave 타이머) | 빠름 (in-order delivery) |
| 확장성 | 4094 VLANs | 4094 VLANs + MST 통합 |
| Ethertype | 0x8100 (GARP PDU) | 0x88F5 |
| 멀티캐스트 주소 | 01:80:C2:00:00:21 | 01: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에 대해 세 가지 등록 상태를 가집니다:
- Normal Registration: GVRP로 학습한 VLAN 자동 등록 (기본값)
- Fixed Registration: 수동 설정된 VLAN, GVRP로 제거 불가
- Forbidden Registration: 해당 VLAN 등록 거부
GARP 메시지 타입은 다음과 같습니다:
- JoinIn/JoinEmpty: VLAN 멤버십 선언/참여
- LeaveIn/LeaveEmpty: VLAN 멤버십 탈퇴
- LeaveAll: 모든 등록 갱신 요청 (주기적 타이머)
Q-in-Q (802.1ad) 이중 태깅
S-VLAN과 C-VLAN 개념
Q-in-Q(802.1ad)는 기존 802.1Q 태그 위에 추가 VLAN 태그를 중첩하는 기술입니다. 서비스 프로바이더가 고객별 VLAN을 통째로 캡슐화하여 자신의 망 내에서 단일 S-VLAN으로 전달합니다.
- S-VLAN (Service VLAN): 외부 태그. TPID
0x88a8. 프로바이더가 관리하는 VLAN ID. - C-VLAN (Customer VLAN): 내부 태그. TPID
0x8100. 고객이 기존에 사용하던 VLAN ID.
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를 구성하는 것은 가능하지만 권장되지 않습니다.
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
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 허용 목록 |
커널 내부: 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 10bridge vlan add dev eth2 vid 20 |
VID 10, 20 tagged 프레임만 수신/송신 허용 |
| native VLAN trunk | bridge vlan add dev eth3 vid 10 pvid untaggedbridge 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 Spoofing과 Double Tagging 공격입니다.
리눅스 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 ...
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 지원 | 리눅스 커널 지원 |
|---|---|---|---|
| STP | IEEE 802.1D | 단일 인스턴스 | bridge STP (기본) |
| RSTP | IEEE 802.1w | 단일 인스턴스 (빠른 수렴) | bridge STP + userspace |
| MSTP | IEEE 802.1s | VLAN-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
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 기능은 하위 물리 장치의 기능을 상속받되, 몇 가지 제약이 있습니다.
- GRO와 VLAN: NIC의 RX VLAN 오프로드가 활성화되면, GRO는
skb->vlan_tci를 기준으로 같은 플로우의 패킷을 병합합니다. 서로 다른 VID의 패킷은 병합되지 않습니다. - GSO와 VLAN: VLAN 장치의 GSO 기능은
real_dev의 GSO 기능에서 파생됩니다. TSO(TCP Segmentation Offload)도 VLAN 오프로드와 함께 작동합니다. - Feature 상속:
vlan_dev_init()에서 하위 장치의 features를netdev_intersect_features()로 교차하여 VLAN 장치 features를 결정합니다.
/* 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로 표현되므로, 표준 ip 및 bridge 명령으로
VLAN 설정을 그대로 적용할 수 있습니다. DSA 태그 프로토콜은 CPU 포트와 스위치 ASIC 사이에서
포트 식별과 VLAN 메타데이터를 전달합니다.
drivers/net/dsa/ 아래 개별 드라이버로 구현됩니다.
DSA VLAN 오프로드 아키텍처
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 flower | TCAM 엔트리 | cls_flower 소프트웨어 분류 |
| 포트 미러링 | 모니터 포트 레지스터 | tc mirred 리다이렉트 |
| CPU 부하 | 거의 없음 | 모든 프레임 CPU 처리 |
VLAN과 네트워크 네임스페이스
VLAN vs macvlan vs ipvlan 비교
컨테이너 네트워킹에서 L2 격리를 구현하는 방법은 여러 가지가 있습니다. VLAN, macvlan, ipvlan은 각각 다른 방식으로 격리와 연결성을 제공합니다.
| 항목 | 802.1Q VLAN | macvlan | ipvlan |
|---|---|---|---|
| 격리 단위 | 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
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 showip -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 서브인터페이스로 올라간 패킷에서는 이미 태그가 제거된 상태라는 것입니다.
# 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 필터링이 복잡해질 수 있음
- 태그 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 VLAN | Q-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을 사용하는 것이 일반적인 데이터센터 아키텍처입니다.