summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHang Zhou2025-01-04 21:09:47 +0000
committerdedeckeh2025-01-19 17:34:08 +0000
commit4308384748bea969c2abdfc2aed906bcf178cb1e (patch)
treeedcfae43784379e123432ef9822cd02a949c75e1
parent5585b969c18f84bd3bb7d02a0034c19ebae79e79 (diff)
downloadodhcpd-4308384748bea969c2abdfc2aed906bcf178cb1e.tar.gz
dhcpv6: add ipv6 pxe support
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 <929513338@qq.com>
-rw-r--r--CMakeLists.txt2
-rw-r--r--README6
-rw-r--r--src/config.c49
-rw-r--r--src/dhcpv6-pxe.c104
-rw-r--r--src/dhcpv6-pxe.h13
-rw-r--r--src/dhcpv6.c8
-rw-r--r--src/dhcpv6.h3
7 files changed, 183 insertions, 2 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 828fa71..1588637 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -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 392ad2f..80e8a45 100644
--- 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.
diff --git a/src/config.c b/src/config.c
index 4e1493f..1a2d392 100644
--- a/src/config.c
+++ b/src/config.c
@@ -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 };
@@ -44,6 +45,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,
IFACE_ATTR_NETWORKID,
@@ -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
index 0000000..7169602
--- /dev/null
+++ b/src/dhcpv6-pxe.c
@@ -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
index 0000000..0e3c227
--- /dev/null
+++ b/src/dhcpv6-pxe.h
@@ -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);
diff --git a/src/dhcpv6.c b/src/dhcpv6.c
index aea6ac2..3c0289d 100644
--- a/src/dhcpv6.c
+++ b/src/dhcpv6.c
@@ -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);
diff --git a/src/dhcpv6.h b/src/dhcpv6.h
index 2356340..a394453 100644
--- a/src/dhcpv6.h
+++ b/src/dhcpv6.h
@@ -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