dhcpv6: add ipv6 pxe support
authorHang Zhou <[email protected]>
Sat, 4 Jan 2025 21:09:47 +0000 (08:09 +1100)
committerdedeckeh <[email protected]>
Sun, 19 Jan 2025 17:34:08 +0000 (18:34 +0100)
1. Implement PxE in separate files where possible
2. Update README
3. User can add IPv6 PxE entries in `/etc/config/dhcp`.
4. The new section type is "boot6".
5. The compulsory "url" string specifies the URL to the bootable image.
6. The optional "arch" integer specifies the expected client machine type.
7. The last "boot6" section without "arch" defines the default bootable image.

Signed-off-by: Hang Zhou <[email protected]>
CMakeLists.txt
README
src/config.c
src/dhcpv6-pxe.c [new file with mode: 0644]
src/dhcpv6-pxe.h [new file with mode: 0644]
src/dhcpv6.c
src/dhcpv6.h

index 828fa71d13e57504614305612531cae0e25abb7c..1588637e781a937a45c513b0e114c931adb69293 100644 (file)
@@ -36,7 +36,7 @@ if(${DHCPV4_SUPPORT})
        set(EXT_SRC ${EXT_SRC} src/dhcpv4.c)
 endif(${DHCPV4_SUPPORT})
 
-add_executable(odhcpd src/odhcpd.c src/config.c src/router.c src/dhcpv6.c src/ndp.c src/dhcpv6-ia.c src/netlink.c ${EXT_SRC})
+add_executable(odhcpd src/odhcpd.c src/config.c src/router.c src/dhcpv6.c src/ndp.c src/dhcpv6-ia.c src/dhcpv6-pxe.c src/netlink.c ${EXT_SRC})
 target_link_libraries(odhcpd resolv ubox uci ${libnl} ${EXT_LINK})
 
 # Installation
diff --git a/README b/README
index 392ad2f5085de54dae47f78ffd650dd89a3d2d23..80e8a450bc4f5e25145497c67a78ce4650af9bab 100644 (file)
--- a/README
+++ b/README
@@ -40,6 +40,7 @@ prefix delegation and can be used to relay RA, DHCPv6 and NDP between routed
       and only serving NDP for DAD and for traffic to the router itself
       [Warning: you should provide additional firewall rules for security]
 
+5. IPv6 PxE Support.
 
 ** Compiling **
 
@@ -168,3 +169,8 @@ hostid              string                          IPv6 host identifier
 name           string                          Hostname
 leasetime      string                          DHCPv4/v6 leasetime
 
+Sections of type boot6
+Option         Type    Required                Description
+url            string  yes             e.g. tftp://[fd11::1]/pxe.efi
+arch           integer no              the arch code. 07 is EFI.
+                                               If not present, this boot6 will be the default.
index 4e1493f350670e9826d9862ebac17a4f9e394d62..1a2d392b2a33c117c0bef6fb8385d5e96387b062 100644 (file)
@@ -18,6 +18,7 @@
 #include <libubox/vlist.h>
 
 #include "odhcpd.h"
+#include "dhcpv6-pxe.h"
 
 static struct blob_buf b;
 static int reload_pipe[2] = { -1, -1 };
@@ -43,6 +44,22 @@ struct config config = {.legacy = false, .main_dhcpv4 = false,
 
 #define OAF_DHCPV6     (OAF_DHCPV6_NA | OAF_DHCPV6_PD)
 
+enum {
+       IPV6_PXE_URL,
+       IPV6_PXE_ARCH,
+       IPV6_PXE_MAX
+};
+
+static const struct blobmsg_policy ipv6_pxe_attrs[IPV6_PXE_MAX] = {
+       [IPV6_PXE_URL] = {.name = "url", .type = BLOBMSG_TYPE_STRING },
+       [IPV6_PXE_ARCH] = {.name = "arch", .type = BLOBMSG_TYPE_INT32 },
+};
+
+const struct uci_blob_param_list ipv6_pxe_attr_list = {
+       .n_params = IPV6_PXE_MAX,
+       .params = ipv6_pxe_attrs,
+};
+
 enum {
        IFACE_ATTR_INTERFACE,
        IFACE_ATTR_IFNAME,
@@ -1623,6 +1640,29 @@ void reload_services(struct interface *iface)
        }
 }
 
+static int ipv6_pxe_from_uci(struct uci_section* s)
+{
+       blob_buf_init(&b, 0);
+       uci_to_blob(&b, s, &ipv6_pxe_attr_list);
+
+       void* data = blob_data(b.head);
+       size_t len = blob_len(b.head);
+
+       struct blob_attr* tb[IFACE_ATTR_MAX];
+       blobmsg_parse(ipv6_pxe_attrs, IPV6_PXE_MAX, tb, data, len);
+
+       if (!tb[IPV6_PXE_URL])
+               return -1;
+
+       const char* url = blobmsg_get_string(tb[IPV6_PXE_URL]);
+
+       uint32_t arch = 0xFFFFFFFF;
+       if (tb[IPV6_PXE_ARCH])
+               arch = blobmsg_get_u32(tb[IPV6_PXE_ARCH]);
+
+       return ipv6_pxe_entry_new(arch, url) ? -1 : 0;
+}
+
 void odhcpd_reload(void)
 {
        struct uci_context *uci = uci_alloc_context();
@@ -1659,6 +1699,15 @@ void odhcpd_reload(void)
                        if (!strcmp(s->type, "host"))
                                set_lease_from_uci(s);
                }
+
+               /* 4. IPv6 PxE */
+               ipv6_pxe_clear();
+               uci_foreach_element(&dhcp->sections, e) {
+                       struct uci_section* s = uci_to_section(e);
+                       if (!strcmp(s->type, "boot6"))
+                               ipv6_pxe_from_uci(s);
+               }
+               ipv6_pxe_dump();
        }
 
        if (config.dhcp_statefile) {
diff --git a/src/dhcpv6-pxe.c b/src/dhcpv6-pxe.c
new file mode 100644 (file)
index 0000000..7169602
--- /dev/null
@@ -0,0 +1,104 @@
+#include <unistd.h>
+#include <stddef.h>
+
+#include <libubox/list.h>
+
+#include "dhcpv6.h"
+#include "dhcpv6-pxe.h"
+
+struct ipv6_pxe_entry {
+       struct list_head list;  // List head for linking
+       uint32_t arch;
+
+       // Ready to send
+       struct __attribute__((packed)) {
+               uint16_t type;          // In network endianess
+               uint16_t len;           // In network endianess, without /0
+               char payload[];         // Null-terminated here
+       } bootfile_url;
+};
+
+static struct ipv6_pxe_entry* ipv6_pxe_default = NULL;
+LIST_HEAD(ipv6_pxe_list);
+
+const struct ipv6_pxe_entry* ipv6_pxe_entry_new(uint32_t arch, const char* url) {
+       size_t url_len = strlen(url);
+       struct ipv6_pxe_entry* ipe = malloc(sizeof(struct ipv6_pxe_entry) + url_len + 1);
+       if (!ipe)
+               return NULL;
+
+       memcpy(ipe->bootfile_url.payload, url, url_len + 1);
+       ipe->bootfile_url.len = htons(url_len);
+       ipe->bootfile_url.type = htons(DHCPV6_OPT_BOOTFILE_URL);
+
+       if (arch == 0xFFFFFFFF) {
+               ipv6_pxe_default = ipe;
+       }
+       else {
+               ipe->arch = arch;
+               list_add(&ipe->list, &ipv6_pxe_list);
+       }
+
+       return ipe;
+}
+
+const struct ipv6_pxe_entry* ipv6_pxe_of_arch(uint16_t arch) {
+       struct ipv6_pxe_entry* entry;
+       list_for_each_entry(entry, &ipv6_pxe_list, list) {
+               if (arch == entry->arch)
+                       return entry;
+       }
+
+       return ipv6_pxe_default;
+}
+
+void ipv6_pxe_serve_boot_url(uint16_t arch, struct iovec* iov) {
+       const struct ipv6_pxe_entry* entry = ipv6_pxe_of_arch(arch);
+
+       if (entry == NULL) {
+               // No IPv6 PxE bootfile defined
+               iov->iov_base = NULL;
+               iov->iov_len = 0;
+       }
+       else {
+               iov->iov_base = (void*)&(entry->bootfile_url);
+               iov->iov_len = 4 + ntohs(entry->bootfile_url.len);
+               syslog(LOG_INFO, "Serve IPv6 PxE, arch = %d, url = %s", arch, entry->bootfile_url.payload);
+       }
+}
+
+void ipv6_pxe_dump(void) {
+       struct ipv6_pxe_entry* entry;
+       int count = 0;
+
+       if (ipv6_pxe_default)
+               count++;
+
+       list_for_each_entry(entry, &ipv6_pxe_list, list) {
+               count++;
+       }
+
+       if (count) {
+               syslog(LOG_INFO, "IPv6 PxE URLs:\n");
+
+               list_for_each_entry(entry, &ipv6_pxe_list, list) {
+                       syslog(LOG_INFO, "    arch %04d = %s\n", entry->arch, entry->bootfile_url.payload);
+               }
+
+               if (ipv6_pxe_default)
+                       syslog(LOG_INFO, "    Default   = %s\n", ipv6_pxe_default->bootfile_url.payload);
+       }
+}
+
+void ipv6_pxe_clear(void) {
+       struct ipv6_pxe_entry* entry, * temp;
+       list_for_each_entry_safe(entry, temp, &ipv6_pxe_list, list) {
+               list_del(&entry->list);
+               free(entry);
+       }
+
+       if (ipv6_pxe_default) {
+               free(ipv6_pxe_default);
+               ipv6_pxe_default = NULL;
+       }
+}
diff --git a/src/dhcpv6-pxe.h b/src/dhcpv6-pxe.h
new file mode 100644 (file)
index 0000000..0e3c227
--- /dev/null
@@ -0,0 +1,13 @@
+#pragma once
+
+#include <unistd.h>
+#include <stddef.h>
+
+// The detail is hidden except for dhcpv6-pxe.c
+struct ipv6_pxe_entry;
+
+const struct ipv6_pxe_entry* ipv6_pxe_entry_new(uint32_t arch, const char* url);
+const struct ipv6_pxe_entry* ipv6_pxe_of_arch(uint16_t arch);
+void ipv6_pxe_serve_boot_url(uint16_t arch, struct iovec* iov);
+void ipv6_pxe_dump(void);
+void ipv6_pxe_clear(void);
index aea6ac256d762ed42169dfbdc767190dce7e0974..3c0289d14a67f31ceca440bd1f6f97120389e4ab 100644 (file)
@@ -25,6 +25,7 @@
 
 #include "odhcpd.h"
 #include "dhcpv6.h"
+#include "dhcpv6-pxe.h"
 #ifdef DHCPV4_SUPPORT
 #include "dhcpv4.h"
 #endif
@@ -183,6 +184,7 @@ enum {
        IOV_RELAY_MSG,
        IOV_DHCPV4O6_SERVER,
        IOV_DNR,
+       IOV_BOOTFILE_URL,
        IOV_TOTAL
 };
 
@@ -558,6 +560,7 @@ static void handle_client_request(void *addr, void *data, size_t len,
                [IOV_DNR] = {dnrs, dnrs_len},
                [IOV_RELAY_MSG] = {NULL, 0},
                [IOV_DHCPV4O6_SERVER] = {&dhcpv4o6_server, 0},
+               [IOV_BOOTFILE_URL] = {NULL, 0}
        };
 
        if (hdr->msg_type == DHCPV6_MSG_RELAY_FORW)
@@ -663,6 +666,9 @@ static void handle_client_request(void *addr, void *data, size_t len,
                                        break;
                                }
                        }
+               } else if (otype == DHCPV6_OPT_CLIENT_ARCH) {
+                       uint16_t arch_code = ntohs(((uint16_t*)odata)[0]);
+                       ipv6_pxe_serve_boot_url(arch_code, &iov[IOV_BOOTFILE_URL]);
                }
        }
 
@@ -732,7 +738,7 @@ static void handle_client_request(void *addr, void *data, size_t len,
                                      iov[IOV_CERID].iov_len + iov[IOV_DHCPV6_RAW].iov_len +
                                      iov[IOV_NTP].iov_len + iov[IOV_NTP_ADDR].iov_len +
                                      iov[IOV_SNTP].iov_len + iov[IOV_SNTP_ADDR].iov_len +
-                                     iov[IOV_DNR].iov_len -
+                                     iov[IOV_DNR].iov_len + iov[IOV_BOOTFILE_URL].iov_len -
                                      (4 + opts_end - opts));
 
        syslog(LOG_DEBUG, "Sending a DHCPv6-%s on %s", iov[IOV_NESTED].iov_len ? "relay-reply" : "reply", iface->name);
index 2356340a39645511b09799fb34a6d988f070b61c..a3944539af02603513f68f13326f68f3e1e72ce2 100644 (file)
@@ -59,6 +59,9 @@
 #define DHCPV6_OPT_INFO_REFRESH 32
 #define DHCPV6_OPT_FQDN 39
 #define DHCPV6_OPT_NTP_SERVERS 56
+#define DHCPV6_OPT_BOOTFILE_URL 59
+#define DHCPV6_OPT_BOOTFILE_PARAM 60
+#define DHCPV6_OPT_CLIENT_ARCH 61
 #define DHCPV6_OPT_SOL_MAX_RT 82
 #define DHCPV6_OPT_INF_MAX_RT 83
 #define DHCPV6_OPT_DHCPV4_MSG 87