PXE booting on a tagged VLAN

I finally got enlisting to work over a VLAN. I had to re-enable the network config in cloud-init, but modify cloud-init to have vlan as an option from the klibc config. As ipconfig klibc format, did not have this support by default, I just made up my own way of defining it.

/scripts/functions

--- ../main/scripts/functions   2024-08-23 11:50:09.362269934 -0500
+++ functions   2024-08-26 06:53:46.625845257 -0500
@@ -235,6 +235,25 @@
        fi
 }
 
+_handle_vlan_vs_ip()
+{
+       # If the ip= parameter is present and is a colon-separated list,
+       # then:
+       # - If it specifies a device, use that in preference to any
+       #   device name we already have
+       # - Otherwise, substitute in any device name we already have
+       local IFS=:
+       set -f
+       # shellcheck disable=SC2086
+       set -- ${IP}
+       set +f
+       if [ $# -ge 2 ] && [ -n "${vname}" ] && [[ "$DEVICE" == "$vlink" ]]; then
+               IP="$1:$2:$3:$4:$5:${vname}"
+               shift 6 || shift $#
+               IP="${IP}:$*"
+       fi
+}
+
 run_dhclient() {
         local timeout conffile pidfile pid
 
@@ -288,6 +307,7 @@
 
 configure_networking()
 {
+       BOOTIFDEVICE=""
        if [ -n "${BOOTIF}" ]; then
                # pxelinux sets BOOTIF to a value based on the mac address of the
                # network card used to PXE boot, so use this value for DEVICE rather
@@ -319,6 +339,7 @@
                                if [ "$bootif_mac" = "$current_mac" ]; then
                                        DEVICE=${device##*/}
                                        DEVICE6=${device##*/}
+                                       BOOTIFDEVICE=${device##*/}
                                        break
                                fi
                        fi
@@ -329,6 +350,9 @@
 
        for v in $VLAN; do
                vlink=${v##*:}
+               if [[ "$vlink" == "BOOTIF" ]]; then
+                       vlink=$BOOTIFDEVICE
+               fi
                VLAN_LINK="$VLAN_LINK $vlink"
                VLAN_NAMES="$VLAN_NAMES ${v%:*}"
        done
@@ -349,12 +373,22 @@
                esac
        done
 
+       TYPE="physical"
        for v in $VLAN; do
                vlink=${v##*:}
+               if [[ "$vlink" == "BOOTIF" ]]; then
+                       vlink=$BOOTIFDEVICE
+               fi
+               if [[ "$vlink" == "$DEVICE" ]]; then
+                       TYPE="vlan"
+               fi
                vname=${v%:*}
                vid=${vname#*.}
                ip link set up dev "$vlink"
                ip link add name "$vname" link "$vlink" type vlan id "$vid"
+
+               # Update device in IP= to the vlan name if the vlink matches device.
+               _handle_vlan_vs_ip
        done
 
        if [ -n "${DEVICE}" ]; then
@@ -453,11 +487,22 @@
        # but no IPv4 conf files exist.
        for conf in /run/"net-$DEVICE.conf" /run/net-*.conf; do
                if [ -e "$conf" ]; then
+                       echo "TYPE=$TYPE" >> "$conf"
+                       if [ -n "${BOOTIFDEVICE}" ]; then
+                               echo "BOOTIFDEVICE=$BOOTIFDEVICE" >> "$conf"
+                       fi
                        # source specific bootdevice
                        . "$conf"
                        break
                fi
        done
+
+       # Create a boot interface config, if this is a vlan it will not exist.
+       conf="/run/net-$BOOTIFDEVICE.conf"
+       if [ -n "${BOOTIFDEVICE}" ] && [ ! -e $conf ]; then
+               echo "DEVICE=$BOOTIFDEVICE" > $conf
+               echo "PROTO=none" >> $conf
+       fi
 
        netinfo_to_resolv_conf /etc/resolv.conf \
                /run/"net-${DEVICE}.conf" /run/net-*.conf /run/net6-*.conf
@@ -660,6 +705,7 @@
                unset DEVICE DEVICE6 PROTO IPV6PROTO
                unset IPV6ADDR IPV6NETMASK IPV6GATEWAY
                unset IPV4ADDR IPV4NETMASK IPV4GATEWAY
+               unset VLINK VID
                . "$f" || { echo "WARN: failed '. \"$f\"'" 1>&2; return 1; }
                local name=""
                name=${DEVICE:-${DEVICE6}}
@@ -681,9 +727,16 @@
                {
                for v in $VLAN; do
                        vlink=${v##*:}
+                       if [[ "$vlink" == "BOOTIF" ]]; then
+                               vlink=$BOOTIFDEVICE
+                       fi
                        vname=${v%:*}
                        vid=${vname#*.}
                        if [ "$name" = "$vname" ]; then
+                               if [ -z "$VLINK" ]; then
+                                       echo "VLINK=$vlink" >> $f
+                                       echo "VID=$vid" >> $f
+                               fi
                                echo "vlink=$vlink"
                                echo "vname=$vname"
                                echo "vid=$vid"

/scripts/init-bottom/cloud-initramfs-dyn-netconf

--- ../main/scripts/init-bottom/cloud-initramfs-dyn-netconf     2020-08-17 11:00:58.000000000 -0500
+++ cloud-initramfs-dyn-netconf 2024-08-26 04:35:49.492437237 -0500
@@ -177,6 +177,20 @@
                fi
        fi
 
+       # Add vlan link device.
+       for v in $VLAN; do
+               vlink=${v##*:}
+               if [[ "$vlink" == "BOOTIF" ]]; then
+                       vlink=$BOOTIFDEVICE
+               fi
+               vname=${v%:*}
+               vid=${vname#*.}
+               if [ "$DEVICE" = "$vname" ]; then
+                       printf "\t%s\n" "vlan-raw-device $vlink"
+                       break
+               fi
+       done
+
        nsline=""
        if [ -n "$IPV4DNS0" -a "$IPV4DNS0" != "0.0.0.0" ]; then
                nsline="${IPV4DNS0}"

/usr/lib/python3/dist-packages/cloudinit/net/cmdline.py

--- ../ephemeral/usr/lib/python3/dist-packages/cloudinit/net/cmdline.py 2024-03-27 08:14:04.000000000 -0500
+++ cmdline.py  2024-08-26 06:56:02.921289498 -0500
@@ -117,6 +117,7 @@
         name = data["DEVICE"] if "DEVICE" in data else data["DEVICE6"]
     except KeyError as e:
         raise ValueError("no 'DEVICE' or 'DEVICE6' entry in data") from e
+    iftype = data.get("TYPE", "physical")
 
     # ipconfig on precise does not write PROTO
     # IPv6 config gives us IPV6PROTO, not PROTO.
@@ -131,11 +132,15 @@
         raise ValueError("Unexpected value for PROTO: %s" % proto)
 
     iface = {
-        "type": "physical",
+        "type": iftype,
         "name": name,
         "subnets": [],
     }
 
+    if iftype == "vlan":
+        iface["vlan_link"] = data.get("VLINK")
+        iface["vlan_id"] = int(data.get("VID"))
+
     if name in mac_addrs:
         iface["mac_address"] = mac_addrs[name]

After applying these patches to cloud-init and the initramfs scripts, I can just add vlan=vlan.22:BOOTIF to the kernel parameters alone and it boots and enlists. I haven’t tested commissioning or provisioning yet. But hope to get some testing done today.

1 Like