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_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에 도달할 수 있음을 인지해야 합니다.
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-aware Bridge 동작
bridge VLAN filtering 개요
Linux bridge는 vlan_filtering을 활성화하면 물리 스위치와 유사한 VLAN-aware 동작을 합니다.
각 브리지 포트에 허용 VLAN 목록, PVID(Port VLAN ID), untagged 설정을 부여하여
access/trunk 포트 개념을 구현합니다.
# 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 포트 간 직접 통신 불가 |
실전 구성
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 큐는 물리 장치에 있음 |
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