]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
network-generator: support BOOTIF= and rd.bootif=0 options 41028/head
authorNick Rosbrook <enr0n@ubuntu.com>
Tue, 24 Feb 2026 19:05:05 +0000 (14:05 -0500)
committerNick Rosbrook <enr0n@ubuntu.com>
Mon, 30 Mar 2026 14:48:55 +0000 (10:48 -0400)
The network generator currently supports many of the options described
by dracut.cmdline(7), but not everything.

This commit adds support for the BOOTIF= option (and the related
rd.bootif= option) used in PXE setups.

This is implemented by treating BOOTIF as a special name/placeholder
when used as an interface name, and expecting a MAC address to be set in
the BOOTIF= parameter. The resulting .network file then uses MACAddress=
in the [Match] section, instead of Name=.

man/systemd-network-generator.service.xml
src/network/generator/network-generator-main.c
src/network/generator/network-generator.c
src/network/generator/network-generator.h
src/network/generator/test-network-generator.c

index ccdb57b62b2705c31d015a3421676a05f5ed67ba..7b20f8516d0b569e44877a3ae536df81c98ed449 100644 (file)
         </listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><varname>BOOTIF=</varname></term>
+        <term><varname>rd.bootif=</varname></term>
+        <listitem>
+          <para>When <varname>BOOTIF</varname> is specified in the interface field of <varname>ip=</varname>, it is treated
+          as a special placeholder rather than a real interface name. Then, in combination with the MAC address provided
+          by <varname>BOOTIF=</varname>, this is translated into a
+          <citerefentry><refentrytitle>systemd.network</refentrytitle><manvolnum>5</manvolnum></citerefentry> file
+          which matches on the the provided MAC address. When <varname>rd.bootif=0</varname> is passed, this functionality
+          is disabled, and the <varname>BOOTIF=</varname> option is ignored.</para>
+
+          <para>See <citerefentry project='man-pages'><refentrytitle>dracut.cmdline</refentrytitle><manvolnum>7</manvolnum></citerefentry>
+          for details on the usage of these options.</para>
+
+          <xi:include href="version-info.xml" xpointer="v261"/>
+        </listitem>
+      </varlistentry>
+
       <!-- unsupported:
            team=<teammaster>:<teamslaves>
            bootdev=
-           BOOTIF=
            bootdev=
            bootdev=
            bootdev=
index c449a6d305bafb1d8cb8e6e4a4ae89f9dab4228f..d64f65bc44a5500ed8528b6d3b68ba0d046226d6 100644 (file)
@@ -233,6 +233,8 @@ static int run(int argc, char *argv[]) {
                 }
         }
 
+        context_finalize_bootif(&context);
+
         r = context_merge_networks(&context);
         if (r < 0)
                 return log_warning_errno(r, "Failed to merge multiple command line options: %m");
index d0204366fb6abf968e10091acc49424de8dcf4cc..69356898b1628ea945e1b2d3025e82cacc11bcab 100644 (file)
@@ -1,5 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 
+#include <net/if_arp.h>
 
 #include "alloc-util.h"
 #include "extract-word.h"
@@ -25,6 +26,8 @@
   rd.route=<net>/<netmask>:<gateway>[:<interface>]
   nameserver=<IP> [nameserver=<IP> ...]
   rd.peerdns=0
+  BOOTIF=<MAC>
+  rd.bootif=0 # Causes BOOTIF= to be ignored.
 
   # .link
   ifname=<interface>:<MAC>
@@ -38,8 +41,6 @@
 
   # ignored
   bootdev=<interface>
-  BOOTIF=<MAC>
-  rd.bootif=0
   biosdevname=0
   rd.neednet=1
 */
@@ -514,6 +515,40 @@ static int network_set_mac_address(Context *context, const char *ifname, const c
         return 0;
 }
 
+static int network_set_bootif_mac_address(Context *context, const char *mac) {
+        int r;
+
+        assert(context);
+
+        if (isempty(mac))
+                return 0;
+
+        /* "BOOTIF" is a special placeholder interface name, used to configure the
+         * interface referred to by BOOTIF=. I.e., ip=...:BOOTIF:... is valid if and
+         * only if BOOTIF= is also set. */
+        Network *network;
+        r = network_acquire(context, "BOOTIF", &network);
+        if (r < 0)
+                return log_debug_errno(r, "Failed to acquire network for BOOTIF: %m");
+
+        r = parse_hw_addr(mac, &network->match_mac);
+        if (r < 0) {
+                /* PXE bootloaders may provide the MAC with a "hardware type prefix", e.g.
+                 * 01-12:34:56:78:90:ab, where 01 indicates ethernet. Technically, other
+                 * hardware types are possible, but only ethernet is handled here. */
+                const char *p = startswith(mac, "01-");
+                if (p)
+                        r = parse_hw_addr(p, &network->match_mac);
+        }
+        if (r < 0)
+                return log_debug_errno(r, "Invalid MAC address '%s' for BOOTIF", mac);
+
+        if (!hw_addr_is_valid(&network->match_mac, ARPHRD_ETHER))
+                return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid MAC address '%s' for BOOTIF", mac);
+
+        return 0;
+}
+
 static int network_set_address(
                 Context *context,
                 const char *ifname,
@@ -1250,6 +1285,38 @@ static int parse_cmdline_ifname_policy(Context *context, const char *key, const
         return 0;
 }
 
+static int parse_cmdline_rd_bootif(Context *context, const char *key, const char *value) {
+        int r;
+
+        assert(context);
+        assert(key);
+
+        /* rd.bootif=<bool> */
+
+        if (proc_cmdline_value_missing(key, value))
+                return 0;
+
+        r = value ? parse_boolean(value) : true;
+        if (r < 0)
+                return log_debug_errno(r, "Invalid boolean value '%s'", value);
+
+        /* rd.bootif=0 => skip BOOTIF= parsing */
+        context->skip_bootif = !r;
+        return 0;
+}
+
+static int parse_cmdline_bootif_mac(Context *context, const char *key, const char *value) {
+        assert(context);
+        assert(key);
+
+        /* BOOTIF=<MAC> */
+
+        if (proc_cmdline_value_missing(key, value))
+                return 0;
+
+        return network_set_bootif_mac_address(context, value);
+}
+
 int parse_cmdline_item(const char *key, const char *value, void *data) {
         Context *context = ASSERT_PTR(data);
 
@@ -1273,10 +1340,34 @@ int parse_cmdline_item(const char *key, const char *value, void *data) {
                 return parse_cmdline_ifname(context, key, value);
         if (proc_cmdline_key_streq(key, "net.ifname_policy"))
                 return parse_cmdline_ifname_policy(context, key, value);
+        if (proc_cmdline_key_streq(key, "rd.bootif"))
+                return parse_cmdline_rd_bootif(context, key, value);
+        if (proc_cmdline_key_streq(key, "BOOTIF"))
+                return parse_cmdline_bootif_mac(context, key, value);
 
         return 0;
 }
 
+void context_finalize_bootif(Context *context) {
+        assert(context);
+
+        Network *network = hashmap_get(context->networks_by_name, "BOOTIF");
+        if (!network)
+                return;
+
+        /* rd.bootif=0 disables BOOTIF= handling */
+        if (context->skip_bootif) {
+                network_free(hashmap_remove_value(context->networks_by_name, "BOOTIF", network));
+                return;
+        }
+
+        if (hw_addr_is_null(&network->match_mac)) {
+                log_debug("Expected MAC address for BOOTIF, but BOOTIF= is unset.");
+                network_free(hashmap_remove_value(context->networks_by_name, "BOOTIF", network));
+                return;
+        }
+}
+
 int context_merge_networks(Context *context) {
         Network *all, *network;
         int r;
@@ -1368,6 +1459,8 @@ void network_dump(Network *network, FILE *f) {
                  * physical interfaces. */
                 fputs("Kind=!*\n"
                       "Type=!loopback\n", f);
+        else if (streq(network->ifname, "BOOTIF"))
+                fprintf(f, "MACAddress=%s\n", HW_ADDR_TO_STR(&network->match_mac));
         else
                 fprintf(f, "Name=%s\n", network->ifname);
 
index e40aa42ff7e2763f6fbf24495c60990028132991..95a6379e4be4ef3bed5223d0ab5dabc5cbfe6a45 100644 (file)
@@ -54,6 +54,8 @@ struct Route {
 struct Network {
         /* [Match] */
         char *ifname;
+        /* Parsed from BOOTIF= parameter. */
+        struct hw_addr_data match_mac;
 
         /* [Link] */
         struct ether_addr mac;
@@ -101,11 +103,15 @@ typedef struct Context {
         Hashmap *networks_by_name;
         Hashmap *netdevs_by_name;
         Hashmap *links_by_filename;
+
+        /* If rd.bootif=0, ignore BOOTIF= parsing */
+        bool skip_bootif;
 } Context;
 
 int parse_cmdline_item(const char *key, const char *value, void *data);
 int context_merge_networks(Context *context);
 void context_clear(Context *context);
+void context_finalize_bootif(Context *context);
 
 Network *network_get(Context *context, const char *ifname);
 void network_dump(Network *network, FILE *f);
index ff2187755345b032837b28883a92eea74d32fab9..b62f13cc041b0bc45268080fba28e55dabc1591d 100644 (file)
@@ -28,12 +28,39 @@ static void test_network_two(const char *ifname,
 
         ASSERT_OK(parse_cmdline_item(key1, value1, &context));
         ASSERT_OK(parse_cmdline_item(key2, value2, &context));
+        context_finalize_bootif(&context);
         ASSERT_OK(context_merge_networks(&context));
         ASSERT_NOT_NULL((network = network_get(&context, ifname)));
         ASSERT_OK(network_format(network, &output));
         ASSERT_STREQ(output, expected);
 }
 
+static void test_network_three(const char *ifname,
+                               const char *key1, const char *value1,
+                               const char *key2, const char *value2,
+                               const char *key3, const char *value3,
+                               const char *expected) {
+        _cleanup_(context_clear) Context context = {};
+        _cleanup_free_ char *output = NULL;
+        Network *network;
+
+        log_debug("/* %s(%s=%s, %s=%s, %s=%s) */", __func__, key1, value1, key2, value2, key3, value3);
+
+        ASSERT_OK(parse_cmdline_item(key1, value1, &context));
+        ASSERT_OK(parse_cmdline_item(key2, value2, &context));
+        ASSERT_OK(parse_cmdline_item(key3, value3, &context));
+        context_finalize_bootif(&context);
+        ASSERT_OK(context_merge_networks(&context));
+
+        network = network_get(&context, ifname);
+        if (expected) {
+                ASSERT_NOT_NULL(network);
+                ASSERT_OK(network_format(network, &output));
+                ASSERT_STREQ(output, expected);
+        } else
+                ASSERT_NULL(network);
+}
+
 static void test_netdev_one(const char *ifname, const char *key, const char *value, const char *expected) {
         _cleanup_(context_clear) Context context = {};
         _cleanup_free_ char *output = NULL;
@@ -573,5 +600,61 @@ int main(int argc, char *argv[]) {
                          "Gateway=192.168.0.1\n"
                          );
 
+        test_network_two("BOOTIF",
+                         "ip", "::::hogehoge:BOOTIF:dhcp",
+                         "BOOTIF", "01-00:11:22:33:44:55",
+                         "[Match]\n"
+                         "MACAddress=00:11:22:33:44:55\n"
+                         "\n[Link]\n"
+                         "\n[Network]\n"
+                         "DHCP=ipv4\n"
+                         "\n[DHCP]\n"
+                         "Hostname=hogehoge\n"
+                         );
+
+        test_network_two("BOOTIF",
+                         "ip", "::::hogehoge:BOOTIF:dhcp",
+                         "BOOTIF", "00:11:22:33:44:55",
+                         "[Match]\n"
+                         "MACAddress=00:11:22:33:44:55\n"
+                         "\n[Link]\n"
+                         "\n[Network]\n"
+                         "DHCP=ipv4\n"
+                         "\n[DHCP]\n"
+                         "Hostname=hogehoge\n"
+                         );
+
+        test_network_two("BOOTIF",
+                         "ip", "::::hogehoge:BOOTIF:dhcp",
+                         "BOOTIF", "01-00-11-22-33-44-55",
+                         "[Match]\n"
+                         "MACAddress=00:11:22:33:44:55\n"
+                         "\n[Link]\n"
+                         "\n[Network]\n"
+                         "DHCP=ipv4\n"
+                         "\n[DHCP]\n"
+                         "Hostname=hogehoge\n"
+                         );
+
+        test_network_three("BOOTIF",
+                           "ip", "::::hogehoge:BOOTIF:dhcp",
+                           "BOOTIF", "01-00:11:22:33:44:55",
+                           "rd.bootif", "1",
+                           "[Match]\n"
+                           "MACAddress=00:11:22:33:44:55\n"
+                           "\n[Link]\n"
+                           "\n[Network]\n"
+                           "DHCP=ipv4\n"
+                           "\n[DHCP]\n"
+                           "Hostname=hogehoge\n"
+                           );
+
+        test_network_three("BOOTIF",
+                           "ip", "::::hogehoge:BOOTIF:dhcp",
+                           "BOOTIF", "01-00:11:22:33:44:55",
+                           "rd.bootif", "0",
+                           NULL
+                           );
+
         return 0;
 }