Linux VLAN Tagging with ip link: 802.1Q Sub-Interfaces - 夜莺博客

Linux VLAN Tagging with ip link: 802.1Q Sub-Interfaces

Linux hosts connected to a switch trunk can carry multiple VLANs over one physical NIC by creating 802.1Q sub-interfaces with the ip command. Each sub-interface behaves like a normal Ethernet device to the network stack, but only frames tagged with its VLAN ID reach it. This is how hypervisors, routers-on-a-stick and firewall appliances segment traffic without extra hardware. This guide covers creating, addressing and persisting VLAN interfaces using iproute2 and the kernel 8021q module.

The model is worth stating plainly before any command: the parent NIC transmits and receives ordinary Ethernet frames. When a VLAN sub-interface sends a frame, the kernel inserts a four-byte 802.1Q tag after the source MAC address containing the VLAN ID, then hands the frame to the parent. On receive, the kernel inspects the tag and delivers the payload to the sub-interface with the matching ID. Untagged frames go to the parent as usual. Everything above that — ARP, IP, routing, firewalling — treats the sub-interface as an independent interface, which is why a trunk can carry several subnets that know nothing about each other.

Create a VLAN Sub-Interface

# Create VLAN 100 on top of eth0
ip link add link eth0 name eth0.100 type vlan id 100
# Bring it up and assign an IP
ip link set eth0.100 up
ip addr add 10.100.0.1/24 dev eth0.100

The parent physical interface stays untagged (or carries whatever the trunk needs) and typically has no IP of its own when it is a pure trunk. The .100 suffix is only a naming convention — the VLAN ID comes from id 100, so names like eth0_100 or vlan100 work identically.

The kernel module 8021q is normally loaded automatically the first time a VLAN device is created. On minimal installations or hardened kernels it may not be, and the failure mode is a clear error:

modprobe 8021q
lsmod | grep 8021q
# make it permanent
echo 8021q > /etc/modules-load.d/8021q.conf

Multiple VLANs on One Trunk

ip link add link eth0 name eth0.100 type vlan id 100   # management
ip link add link eth0 name eth0.200 type vlan id 200   # production
ip link add link eth0 name eth0.300 type vlan id 300   # storage
ip link set eth0.100 up && ip addr add 10.100.0.1/24 dev eth0.100
ip link set eth0.200 up && ip addr add 10.200.0.1/24 dev eth0.200
ip link set eth0.300 up && ip addr add 10.300.0.1/24 dev eth0.300

Each VLAN now has its own broadcast domain, routing table entry and, if needed, firewall zone — exactly the segmentation a switch would provide for separate servers.

Verify the Configuration

ip -d link show eth0.100
ip link show eth0.100
cat /proc/net/vlan/eth0.100

ip -d link show prints vlan protocol 802.1Q id 100 <REORDER_HDR> confirming the tag; /proc/net/vlan/ exposes per-VLAN counters and device details for troubleshooting. A typical output looks like this:

eth0.100@eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP
    link/ether 52:54:00:11:22:33 brd ff:ff:ff:ff:ff:ff
    vlan protocol 802.1Q id 100 <REORDER_HDR>

The @eth0 suffix is the link to the parent. If it is missing, the device was not created as a VLAN but as a plain dummy or the parent link has since disappeared. When the parent goes down, every sub-interface drops with it — that cascading state is visible at a glance with ip link show master-style listings or simply by checking the parent first whenever multiple VLANs fail at once.

Interface Attributes Worth Setting

A VLAN sub-interface is a full interface, so the usual attributes apply. The ones that matter in production are MTU, the tag handling flags and the egress QoS mapping:

ip link set eth0.100 mtu 9000                    # jumbo frames for storage VLAN
ip link add link eth0 name eth0.400 type vlan id 400 \
    reorder_hdr off \
    loose_binding on \
    egress-qos-map 0:0 1:1 2:2 3:3 4:4 5:5 6:6 7:7
ip link add link eth0 name eth0.500 type vlan id 500 mtu 9000
  • reorder_hdr — on by default, this lets the kernel insert and strip the tag itself and reorder headers when needed. Turning it off is required for bridged setups that need the exact original frame layout, at the cost of requiring the NIC to support hardware acceleration for the operations.
  • loose_binding — keeps the VLAN device operational even when the underlying physical link has no carrier, which helps in bridges and in labs where the parent flaps.
  • egress-qos-map — maps the socket's priority to the VLAN PCP field so 802.1p marking survives onto the wire, which is what an upstream switch needs to honour QoS classes.

Note that the MTU you set is the IP-level MTU excluding the four-byte tag; the frame on the wire is four bytes larger. Trunk ports and any intermediate links must accept that extra size, which at 1500 MTU means the switch must not be enforcing a strict 1518-byte limit.

VLAN-Aware Bridges and Hypervisor Trunks

The pattern that confuses newcomers most is a VLAN-aware bridge. Instead of one bridge per VLAN, a single bridge configured with vlan_filtering 1 carries tagged and untagged traffic and lets each port declare which VLANs it is allowed to pass:

ip link add name br0 type bridge vlan_filtering 1
ip link set br0 up
ip link set eth0 master br0
bridge vlan add dev eth0 vid 100
bridge vlan add dev eth0 vid 200
bridge vlan add dev br0 vid 100 self
bridge vlan show

That is the model KVM, Proxmox and Open vSwitch expose to virtual machines: define the permitted VLANs once, then attach VM interfaces with a vlan or tag attribute and let the bridge do the tagging. Compare it with the ESXi approach of port groups on a vSwitch if you work across both platforms, and remember that the two are not configured the same way even though they deliver the same segmentation.

Persistence with systemd-networkd

Commands issued by hand disappear on reboot. With systemd-networkd, declare the VLAN in a .netdev file and address it in a .network file:

# /etc/systemd/network/20-eth0.100.netdev
[NetDev]
Name=eth0.100
Kind=vlan

[VLAN]
Id=100
# /etc/systemd/network/20-eth0.100.network
[Match]
Name=eth0.100

[Network]
Address=10.100.0.1/24

NetworkManager keeps VLANs in connection profiles (nmcli connection add type vlan ifname eth0.100 dev eth0 id 100), and Debian/Ubuntu ifupdown uses VLAN=yes plus a stanza in /etc/network/interfaces.

nmcli connection add type vlan con-name vlan100 dev eth0 id 100 \
    ipv4.method manual ipv4.addresses 10.100.0.1/24
nmcli connection up vlan100
# /etc/network/interfaces (ifupdown)
auto eth0.100
iface eth0.100 inet static
    address 10.100.0.1
    netmask 255.255.255.0
    vlan-raw-device eth0

Whichever tool owns the configuration, verify after a reboot that the interface came back with the right ID rather than assuming the tool did what you meant: ip -d link show eth0.100 | grep vlan.

VLAN IDs: Ranges, Reserved Values and What to Avoid

The 802.1Q tag carries a 12-bit VLAN identifier, so the usable range is 1 through 4094. A handful of values carry special meaning and cause trouble in the field:

  • ID 0 — means "no VLAN" or priority-tagged only. It is not a usable VLAN and cannot be assigned with ip link add ... id 0.
  • ID 1 — the default VLAN on nearly every switch. Using it as a data VLAN mixes your traffic with every unconfigured port, which is why most designs keep VLAN 1 for management or shut it down.
  • IDs 1002–1005 — reserved by Cisco for legacy Token Ring and FDDI default VLANs. Avoid them for compatibility, even on Linux where nothing enforces the rule.
  • ID 4095 — reserved and rejected by the kernel.

Keep a documented numbering plan. When the same VLAN ID means "storage" on one site and "guest Wi-Fi" on another, troubleshooting becomes guesswork — and VLAN IDs travel across trunks far beyond the switch they were invented on.

Removing and Recreating VLAN Interfaces

The ip command is symmetric, and cleaning up matters when you script lab rebuilds:

ip link set eth0.100 down
ip addr flush dev eth0.100
ip link delete eth0.100

Deleting the sub-interface also removes the 802.1Q receive rule from the parent, so the parent stops accepting frames for that ID. A re-create with a different id is a clean way to re-tag without touching the IP configuration of the parent. Note that a VLAN device cannot be renamed while it is up in the same way a physical interface can; delete and recreate instead, and adjust any firewall or routing references that named the old interface.

Performance and Hardware Offload

Tag insertion and removal are cheap, but not free. On 10G and faster links, checking whether the NIC accelerates them is worthwhile:

ethtool -k eth0 | grep vlan
ethtool -S eth0 | grep -i vlan

With rx-vlan-offload and tx-vlan-offload enabled, the NIC handles the tag in hardware and the CPU cost of a VLAN is close to zero. Disable them only when debugging a driver that mishandles tags, and remember that turning offload off can cost throughput on busy hosts. On a host doing serious trunk work, also confirm that the interrupt and queue settings still match the workload — VLAN sub-interfaces share the parent's queues, so tuning is done on the parent, not on the sub-interface.

Troubleshooting

  • No traffic on the sub-interface: the switch port must be a trunk allowing that VLAN; frames arrive tagged or the kernel drops them. Confirm with a capture on the parent: tcpdump -i eth0 -e vlan 100 should show tagged frames arriving.
  • Interface renamed: udev can auto-rename to rename1; add a matching udev rule or use a descriptive fixed name.
  • Ping works locally but not to the far side: the peer's VLAN ID or native VLAN differs; check the switch configuration on both ends.
  • Parent had an IP before: remove it — an IP on the untagged parent plus the same subnet on a VLAN sub-interface causes routing confusion.
  • Tagged frames vanish entirely: some NICs and drivers strip or reject tags without the right offload settings. Check ethtool -k eth0 for rx-vlan-offload and tx-vlan-offload.
  • Only one VLAN works after reboot: the .netdev files were dropped in the wrong directory or the .network file matches on the wrong name. Verify with networkctl status eth0.100.

VLAN sub-interfaces are the Linux half of a trunk; the switch half is covered in our Junos VLAN access/trunk examples and Huawei S5700 VLAN partition articles. For bonding multiple NICs under the trunk, see Linux network bonding configuration, and for a declarative host-side alternative to the commands above, see systemd-networkd VLAN, bond and bridge configuration.

原文链接:https://kx.cloudingenium.com/en/vlan-configuration-linux-iproute2