Skip to content

Instantly share code, notes, and snippets.

@aleks-mariusz
Last active January 22, 2026 18:12
Show Gist options
  • Select an option

  • Save aleks-mariusz/7e80ebbb13f90293374045215c7abc67 to your computer and use it in GitHub Desktop.

Select an option

Save aleks-mariusz/7e80ebbb13f90293374045215c7abc67 to your computer and use it in GitHub Desktop.
VLAN Support for Debian debootstrap-based Installer

VLAN Support for Debian debootstrap-based Installer

Overview

This solution (re-)adds VLAN (802.1Q) support to Debian installer's network configuration (netcfg), enabling automated installations on VLAN-tagged networks using both DHCP and static IP configurations.

Key Features:

  • ✅ DHCP on VLAN interfaces
  • ✅ Static IP on VLAN interfaces
  • ✅ Works with standard Debian installer (bookworm/stable)
  • ✅ Minimal overhead (~1.3KB addon initrd)
  • ✅ No modifications to upstream Debian installer required

Why This Is Needed

The Problem

Debian's netcfg component apparantly still lacks native VLAN support: • The official Debian netcfg repository master branch has NO vlan.c file • The debian/netcfg-common.templates file has NO netcfg/use_vlan or netcfg/vlan_id templates

When you try to configure networking on a VLAN interface during installation:

  1. VLAN interface doesn't exist - The installer doesn't create VLAN interfaces from kernel parameters
  2. Parent interface not brought up - Even if you manually create the VLAN interface, netcfg brings down all interfaces during initialization and doesn't properly bring up the parent interface when configuring the VLAN interface
  3. DHCP fails - The udhcpc DHCP client is called without ensuring interfaces are UP
  4. Static IP fails - The ip command is used to configure static IPs, but the parent interface isn't brought up first

Historical Context

VLAN support was proposed for Debian's netcfg in 2012 (commit 63d72aa9 by YunQiang Su, staged by Philipp Kern), but was never merged into the master branch. The patch exists only on the origin/people/pkern/vlan branch in the official Debian repository.

Why it was never merged: The commit message states: "I seems that 8021q module is not included in d-i (Linux).". This blocker is no longer relevant - and as such the 8021q module is now included in modern Debian installer kernels. However, the patch was never revisited or merged after the module became available, and remains only on the origin/people/pkern/vlan branch."

Ubuntu's approach: Ubuntu cherry-picked this patch and included it in Ubuntu 16.04-18.04. However, Ubuntu 20.04+ removed debian-installer entirely, replacing it with subiquity.

Current state: The netcfg/use_vlan and netcfg/vlan_id parameters never worked in official Debian releases (and no longer work in modern Ubuntu).

References:

How It Works

This solution uses a small addon initrd (~1.3KB) that patches the Debian installer environment with three wrapper scripts:

1. S29vlan-setup (Startup Script)

Purpose: Creates the VLAN interface early in the boot process, before netcfg runs.

What it does:

  • Parses the vlan= kernel parameter (format: vlan=<interface>.<vlan_id>:<parent_interface>)
  • Loads the 8021q kernel module for VLAN support
  • Waits for the parent interface to appear
  • Creates the VLAN interface using ip link add

Why it's needed: netcfg doesn't create VLAN interfaces, so we must create them before netcfg runs.

2. udhcpc Wrapper (DHCP Support)

Purpose: Ensures both parent and VLAN interfaces are UP before running DHCP.

What it does:

  • Intercepts calls to udhcpc (BusyBox DHCP client)
  • Detects if the device/interface is a VLAN interface (contains a dot)
  • Brings up the parent interface first
  • Brings up the VLAN interface
  • Passes all arguments unchanged to the real udhcpc

Why it's needed: netcfg brings down all interfaces during initialization and calls udhcpc without bringing them back up. The Linux kernel does NOT automatically bring up the parent interface when you bring up a VLAN interface.

3. ip Wrapper (Static IP Support)

Purpose: Ensures the parent interface is UP before configuring static IP on the VLAN interface.

What it does:

  • Intercepts calls to the ip command
  • Parses arguments looking for dev <interface> patterns
  • If the interface is a VLAN interface (contains a dot), brings up the parent interface first
  • Passes all arguments unchanged to the real ip command

Why it's needed: When netcfg calls netcfg_interface_up() for static IP configuration, it only brings up the VLAN interface itself. The parentif field is NULL in the master branch (only set in the unmerged VLAN patch), so the parent interface is never brought up. Without the parent interface being UP, the VLAN interface cannot communicate.

Important: The wrapper does NOT modify any commands - it only adds a side effect (bringing up the parent interface) before passing the original command through unchanged. This ensures all other uses of the ip command work normally.

Usage

Boot Parameters

For DHCP:

vlan=eth0.100:eth0 interface=eth0.100

For Static IP:

vlan=eth0.100:eth0 interface=eth0.100 netcfg/disable_autoconfig=true netcfg/get_ipaddress=192.168.1.100 netcfg/get_netmask=255.255.255.0 netcfg/get_gateway=192.168.1.1 netcfg/get_nameservers=192.168.1.1 netcfg/confirm_static=true

Parameter explanation:

  • vlan=<interface>.<vlan_id>:<parent> - Tells S29vlan-setup to create the VLAN interface
  • interface=<interface>.<vlan_id> - Tells netcfg which interface to configure
  • Standard netcfg parameters for static IP configuration

iPXE Example

the testing environment utilizes iPXE to load over hte network the linux kernel, as well as the upstream initrd, but it also "stacks" on top of that (concatenate) an add-on initrd, which containers the scripts

#!ipxe
kernel http://deb.debian.org/debian/dists/bookworm/main/installer-amd64/current/images/netboot/debian-installer/amd64/linux
initrd http://deb.debian.org/debian/dists/bookworm/main/installer-amd64/current/images/netboot/debian-installer/amd64/initrd.gz
initrd tftp://local-tftp-server/path-to/debian-installer-vlan-support.gz
imgargs linux initrd=initrd.gz vlan=eth0.100:eth0 interface=eth0.100 auto=true priority=critical
boot

Note: iPXE is used as it supports loading multiple initrd files. They are concatenated, and later files can overwrite earlier files.

Installation

Step 1: Create the Directory Structure

mkdir -p vlan-support-initrd/lib/debian-installer-startup.d
mkdir -p vlan-support-initrd/sbin

Step 2: Create the Scripts

Create the three scripts (see the separate script files in this gist):

  • S29vlan-setup.shvlan-support-initrd/lib/debian-installer-startup.d/S29vlan-setup
  • udhcpc.sh wrapper → vlan-support-initrd/sbin/udhcpc
  • ip.sh wrapper → vlan-support-initrd/sbin/ip
mv S29vlan-setup.sh vlan-support-initrd/lib/debian-installer-startup.d/S29vlan-setup
mv udhcpc.sh vlan-support-initrd/sbin/udhcpc
mv ip.sh vlan-support-initrd/sbin/ip

Make them executable:

chmod +x vlan-support-initrd/lib/debian-installer-startup.d/S29vlan-setup
chmod +x vlan-support-initrd/sbin/udhcpc
chmod +x vlan-support-initrd/sbin/ip

Verify with find vlan-support-initrd/ -type f -ls, you should see:

135584887      4 -rwxr-xr-x   1  user  group      1155 Jan 22 17:08 vlan-support-initrd/lib/debian-installer-startup.d/S29vlan-setup
135584881      4 -rwxr-xr-x   1  user  group       776 Jan 22 17:07 vlan-support-initrd/sbin/udhcpc
135584879      4 -rwxr-xr-x   1  user  group      1338 Jan 22 17:07 vlan-support-initrd/sbin/ip

Step 3: Build the Addon Initrd

cd vlan-support-initrd
find . | cpio -o -H newc | gzip -c > ../debian-installer-vlan-support.gz
cd ..

This creates a ~1.3KB gzipped cpio archive.

Step 4: Use with Debian Installer

Load both the standard Debian installer initrd and your addon initrd. The addon initrd will patch the installer environment.

With iPXE:

initrd http://deb.debian.org/debian/dists/bookworm/main/installer-amd64/current/images/netboot/debian-installer/amd64/initrd.gz
initrd tftp://local-tftp-server/path-to/debian-installer-vlan-support.gz # or use http

How it works: When multiple cpio archives are concatenated, later files overwrite earlier files with the same path. Our wrapper scripts overwrite the BusyBox symlinks in the original initrd.

Technical Details

What netcfg Does (and Doesn't Do)

From netcfg-common.c:

  • netcfg_get_interface() (line 798): Brings down ALL interfaces when building the interface selection list
  • deconfigure_network() (line 1191): Brings down the loopback and specified interface
  • interface_down() (line 1331): Uses ioctl to clear the IFF_UP flag
  • netcfg_interface_up() (line 1347): Would bring up the parent interface IF iface->parentif was set, but it's NULL in master branch (line 1591)

From dhcp.c:

  • start_dhcp_client() (line 142): Directly only executes udhcpc (without bringing the parent interface up first)

From static.c:

  • netcfg_activate_static() (line 348): Calls netcfg_interface_up(interface) before configuring static IP
  • netcfg_interface_up() would bring up the parent interface IF interface->parentif was set
  • But parentif is explicitly set to NULL (line 1591 in netcfg-common.c) in the master branch
  • Only the VLAN interface itself is brought up, not the parent interface (but is fixed by the wrapper for /sbin/ip)

Why Each File is Necessary

  1. S29vlan-setup: Creates the VLAN interface because netcfg doesn't parse the vlan= /proc/cmdline parameter nor create VLAN interfaces

  2. udhcpc wrapper: Brings up both parent and VLAN interfaces because:

    • netcfg brings down all interfaces during initialization
    • netcfg calls udhcpc without bringing interfaces back up
    • The Linux kernel does NOT automatically bring up the parent interface when you bring up a VLAN interface
  3. ip wrapper wrapper: Brings up the parent interface for static IP configuration because:

    • netcfg calls netcfg_interface_up() which only brings up the VLAN interface (not the parent)
    • The parentif field is NULL in the master branch (only set in the unmerged VLAN patch)
    • Without the parent interface being UP, the VLAN interface cannot communicate
    • The wrapper only adds a side effect (bringing up parent) and passes all commands through unchanged

Wrapper Safety

All three wrappers are designed to be safe and non-intrusive:

  • S29vlan-setup: Only runs once at startup, before netcfg runs
  • udhcpc wrapper: Only brings up interfaces, then passes all arguments unchanged to the real udhcpc
  • ip wrapper: Only triggers on dev <vlan_interface>.<parent_interface> patterns, brings up parent as a side effect, then passes all arguments unchanged to the real ip command

The wrappers do NOT modify any commands or arguments - they only add side effects (creating VLAN interface, bringing up interfaces) before executing the original commands normally.

Verified Compatibility

  • ✅ Debian 12 (Bookworm) installer
  • ✅ DHCP configuration on VLAN interfaces
  • ✅ Static IP configuration on VLAN interfaces
  • ✅ All standard ip command uses work normally (wrapper passes commands through unchanged)

License

These scripts are provided as-is for use with Debian installer. They implement functionality that was proposed but never merged into Debian's netcfg.

#!/bin/sh
# ip command wrapper for Debian installer VLAN support
# Ensures parent interface is UP before configuring VLAN interface
# Only triggers on specific commands to avoid breaking other uses
# Check if this is a command that needs VLAN parent interface handling
# We only care about: ip addr add <ip> ... dev <vlan_interface>.<parent_interface>
# and: ip link set up dev <vlan_interface>.<parent_interface>
NEEDS_PARENT_UP=0
VLAN_IFACE=""
# Parse arguments to detect VLAN interface operations
prev_arg=""
for arg in "$@"; do
case "$prev_arg" in
dev)
# This is the interface name after "dev"
case "$arg" in
*.*)
# Contains a dot - might be VLAN interface
VLAN_IFACE="$arg"
PARENT_IFACE="${VLAN_IFACE%.*}"
# Only treat as VLAN if parent != full name
if [ "$PARENT_IFACE" != "$VLAN_IFACE" ]; then
NEEDS_PARENT_UP=1
fi
;;
esac
;;
esac
prev_arg="$arg"
done
# Bring up parent interface if needed
if [ "$NEEDS_PARENT_UP" = "1" ] && [ -n "$VLAN_IFACE" ]; then
/bin/busybox ip link set up dev "$PARENT_IFACE" 2>/dev/null || true
fi
# Run the real ip command
exec /bin/busybox ip "$@"
#!/bin/sh
# VLAN setup for Debian installer
# Parses vlan= kernel parameter and creates VLAN interface
# Format: vlan=<interface>.<vlan_id>:<parent_interface>
# Example: vlan=eth0.2220:eth0
# Parse kernel command line for vlan= parameter
for param in $(cat /proc/cmdline); do
case "$param" in
vlan=*)
VLAN_SPEC="${param#vlan=}"
# Extract components from vlan=interface.vlan_id:parent_interface
VLAN_IFACE="${VLAN_SPEC%:*}" # e.g., eth0.2220
PARENT_IFACE="${VLAN_SPEC#*:}" # e.g., eth0
VLAN_ID="${VLAN_IFACE#*.}" # e.g., 2220
# Load 8021q kernel module for VLAN support
modprobe 8021q 2>/dev/null || true
# Wait for parent interface to appear
for i in 1 2 3 4 5; do
if [ -e "/sys/class/net/$PARENT_IFACE" ]; then
break
fi
sleep 1
done
# Create VLAN interface
ip link add link "$PARENT_IFACE" name "$VLAN_IFACE" type vlan id "$VLAN_ID" 2>/dev/null || true
;;
esac
done
#!/bin/sh
# udhcpc wrapper for Debian installer VLAN support
# Ensures parent and VLAN interfaces are UP before running DHCP
# Extract interface name from arguments
IFACE=""
prev_arg=""
for arg in "$@"; do
case "$prev_arg" in
-i)
IFACE="$arg"
break
;;
esac
prev_arg="$arg"
done
# Bring up interfaces if needed
if [ -n "$IFACE" ]; then
# Get parent interface (strip .VLAN if present)
PARENT="${IFACE%.*}"
# If this is a VLAN interface, bring up parent first
if [ "$PARENT" != "$IFACE" ]; then
ip link set up dev "$PARENT" 2>/dev/null || true
fi
# Bring up the interface
ip link set up dev "$IFACE" 2>/dev/null || true
fi
# Run the real udhcpc
exec /bin/busybox udhcpc "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment