VLAN 장애를 조사하다 보면 모순처럼 보이는 결과를 만납니다. Switch port는 tagged VLAN인데 server의 tcpdump에는 802.1Q가 보이지 않습니다. VLAN interface의 MTU도 여전히 1500입니다.
태그는 실제로 붙은 것일까요? VLAN을 사용하면 MTU를 1496으로 낮춰야 할까요?
이 문제는 IP보다 한 단계 아래인 Ethernet frame에서 풀어야 합니다. IEEE 802.1Q-2022의 범위와 Linux v6.16 source를 기준으로 frame을 직접 구성하고 tcpdump decode와 Linux VLAN interface를 확인했습니다.
태그 없는 frame과 VLAN 42 frame을 만들었다
Apple M1에서 Python 3.14.6으로 최소 Ethernet frame 두 개를 PCAP에 저장했습니다. 공통 payload는 46바이트이고, 두 번째 frame에만 VLAN tag를 넣었습니다.
dst = bytes.fromhex("020000000002")
src = bytes.fromhex("020000000001")
payload = bytes(range(46))
untagged = dst + src + bytes.fromhex("0800") + payload
tagged = dst + src + bytes.fromhex("8100b02a0800") + payload
생성 결과는 태그 없음 60바이트, 태그 있음 64바이트였습니다.
untagged: captured_bytes=60
vlan42: captured_bytes=64
Tcpdump 4.99.1은 두 번째 frame을 다음처럼 해석했습니다.
02:00:00:00:00:01 > 02:00:00:00:00:02,
ethertype 802.1Q (0x8100), length 64:
vlan 42, p 5, DEI, ethertype IPv4 (0x0800)
Header 경계만 보면 차이가 분명합니다.
태그 없음: ... 0001 0800 ...
VLAN 42: ... 0001 8100 b02a 0800 ...
TPID TCI inner EtherType
Frame에 정확히 4바이트가 추가됐고 inner EtherType 0x0800은 tag 뒤로 이동했습니다.
4바이트 안에는 무엇이 있나
Linux v6.16의 if_vlan.h는 VLAN_HLEN을 4로 정의합니다. Reader 관점에서는 원래 EtherType 위치부터 다음처럼 읽을 수 있습니다.
TPID 2바이트 | TCI 2바이트 | inner EtherType 2바이트
일반적인 802.1Q C-tag의 TPID는 0x8100입니다. TCI는 다시 나뉩니다.
PCP 3비트 | DEI 1비트 | VLAN ID 12비트
실험은 PCP 5, DEI 1, VLAN ID 42를 사용했습니다.
TCI = (5 << 13) | (1 << 12) | 42 = 0xb02a
Tcpdump도 이를 vlan 42, p 5, DEI로 decode했습니다. 이는 단지 문서의 bit diagram을 옮긴 것이 아니라 실제 byte와 decoder 결과를 대조한 값입니다.
1514와 1518, FCS까지 포함하면
Linux if_ether.h는 Ethernet payload 1500, 일반 frame 1514, FCS 4바이트를 정의합니다.
태그 없는 Ethernet II frame의 FCS 제외 최대 길이는 다음과 같습니다.
목적지 6 + 출발지 6 + EtherType 2 + payload 1500 = 1514
단일 tag가 있으면 4바이트가 추가됩니다.
목적지 6 + 출발지 6 + tag 4 + EtherType 2 + payload 1500 = 1518
정리하면 다음과 같습니다.
| Frame | FCS 제외 | FCS 포함 |
|---|---|---|
| Untagged Ethernet | 1514 | 1518 |
| 단일 802.1Q tag | 1518 | 1522 |
PCAP과 NIC capture에는 FCS가 없는 경우가 많습니다. Preamble과 SFD도 위 숫자에 포함하지 않았습니다. “Wire size”와 “capture length”를 같은 값으로 쓰면 안 됩니다.
VLAN을 쓰면 MTU가 1496이 되나
결론은 “항상 그렇지 않다”입니다.
IP MTU 1500은 Ethernet payload에 들어가는 IP packet 크기입니다. 802.1Q tag는 payload 4바이트를 빼서 넣는 것이 아니라 L2 header에 추가됩니다. Link와 장비가 tagged frame의 추가 길이를 처리하면 IP MTU 1500을 유지할 수 있습니다.
Alpine 3.22.5 arm64 container에서 iproute2 6.15.0으로 interface를 만들었습니다.
ip link add veth0 type veth peer name veth1
ip link add link veth0 name veth0.42 type vlan id 42 reorder_hdr on
ip -d -o link show veth0.42
veth0.42@veth0 ... mtu 1500 ...
vlan protocol 802.1Q id 42 <REORDER_HDR>
양쪽 VLAN interface 사이 ping도 3회 모두 성공했습니다.
Linux vlan_dev_change_mtu()는 lower device가 VLAN 때문에 MTU를 줄이는 경우에만 VLAN_HLEN을 뺍니다. 따라서 “VLAN이면 무조건 1496”은 잘못된 규칙입니다.
반대 방향의 단정도 위험합니다. VLAN interface에 1500이 표시돼도 physical switch, virtual switch, tunnel, NIC가 모두 1522바이트 wire frame을 처리한다는 보장은 없습니다. Path 전체를 확인해야 합니다.
tcpdump에서 tag가 안 보이는 이유
ip-link(8)에 따르면 VLAN의 reorder_hdr 기본값은 on입니다. 수신 packet은 VLAN logical interface에 전달되기 전에 untagged 상태가 될 수 있습니다.
Hardware offload가 적용되면 tag가 packet byte가 아니라 skb metadata에 존재할 수도 있습니다. 이 경우 eth0.42 capture에 0x8100이 없다는 사실만으로 upstream이 untagged frame을 보냈다고 결론 내릴 수 없습니다.
다음 정보를 함께 확인해야 합니다.
ip -d link
ethtool -k <lower-interface>
가능하면 logical VLAN과 lower interface capture를 비교하고, wire-level 증거가 필요하면 offload와 외부 capture 위치를 통제해야 합니다.
이번 veth lab에서도 parent와 VLAN capture를 wire 증거로 재현하지 못했습니다. VLAN acceleration metadata가 parent capture에 영향을 줬기 때문입니다. 이 실패는 “capture 한 곳만 보고 wire를 단정하지 말라”는 한계를 강화하지만, physical NIC 동작을 증명하지는 않습니다.
결론
802.1Q tag는 TPID와 TCI로 구성된 정확히 4바이트이며, 원래 EtherType을 뒤로 이동시킵니다. 최대 frame은 FCS 제외 1514에서 1518바이트로 늘지만 IP MTU가 자동으로 1496이 되지는 않습니다.
동시에 tcpdump는 관측 지점의 결과일 뿐입니다. Linux의 header reorder와 VLAN offload는 tag를 제거하거나 metadata로 옮길 수 있습니다. “Tag가 보이지 않는다”와 “wire에 tag가 없었다”는 같은 문장이 아닙니다.
VLAN 장애에서는 frame 길이, IP MTU, lower device, offload, capture 위치를 함께 기록해야 정확한 결론에 도달할 수 있습니다.