From 4308384748bea969c2abdfc2aed906bcf178cb1e Mon Sep 17 00:00:00 2001 From: Hang Zhou <929513338@qq.com> Date: Sun, 5 Jan 2025 08:09:47 +1100 Subject: [PATCH] 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> --- CMakeLists.txt | 2 +- README | 6 +++ src/config.c | 49 ++++++++++++++++++++++ src/dhcpv6-pxe.c | 104 +++++++++++++++++++++++++++++++++++++++++++++++ src/dhcpv6-pxe.h | 13 ++++++ src/dhcpv6.c | 8 +++- src/dhcpv6.h | 3 ++ 7 files changed, 183 insertions(+), 2 deletions(-) create mode 100644 src/dhcpv6-pxe.c create mode 100644 src/dhcpv6-pxe.h 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 #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 index 0000000..7169602 --- /dev/null +++ b/src/dhcpv6-pxe.c @@ -0,0 +1,104 @@ +#include +#include + +#include + +#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 +#include + +// 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 -- 2.30.2