From 278bf4da905aa36563951667e18c9a81720946c6 Mon Sep 17 00:00:00 2001 From: Stijn Tintel Date: Thu, 15 Jan 2026 18:27:59 +0200 Subject: [PATCH 1/2] config: add dhcpv6_relay_servers Add a new interface config attribute to allow configuring one or more DHCPv6 server IP addresses to relay the DHCPv6 request to. If the DHCPv6 server is behind another router, forwarding the request to ff05::1:3 will not work, unless multicast routing has been set up. As multicast routing is extremely difficult to set up correctly, and specifying an IP address to forward the request to is very common in other DHCP relay agents, odhcpd should support this. Signed-off-by: Stijn Tintel --- src/config.c | 35 +++++++++++++++++++++++++++++++++++ src/odhcpd.h | 2 ++ 2 files changed, 37 insertions(+) diff --git a/src/config.c b/src/config.c index 29384482..13b8ba84 100644 --- a/src/config.c +++ b/src/config.c @@ -139,6 +139,7 @@ enum { IFACE_ATTR_NTP, IFACE_ATTR_CAPTIVE_PORTAL_URI, IFACE_ATTR_IPV6_ONLY_PREFERRED, + IFACE_ATTR_DHCPV6_RELAY_SERVERS, IFACE_ATTR_MAX }; @@ -193,6 +194,7 @@ static const struct blobmsg_policy iface_attrs[IFACE_ATTR_MAX] = { [IFACE_ATTR_NTP] = { .name = "ntp", .type = BLOBMSG_TYPE_ARRAY }, [IFACE_ATTR_CAPTIVE_PORTAL_URI] = { .name = "captive_portal_uri", .type = BLOBMSG_TYPE_STRING }, [IFACE_ATTR_IPV6_ONLY_PREFERRED] = { .name = "ipv6_only_preferred", .type = BLOBMSG_TYPE_INT32 }, + [IFACE_ATTR_DHCPV6_RELAY_SERVERS] = { .name = "dhcpv6_relay_servers", .type = BLOBMSG_TYPE_ARRAY }, }; const struct uci_blob_param_list interface_attr_list = { @@ -1710,6 +1712,39 @@ int config_parse_interface(void *data, size_t len, const char *name, bool overwr &iface->pio_filter_addr, &iface->pio_filter_length); + if ((c = tb[IFACE_ATTR_DHCPV6_RELAY_SERVERS])) { + struct blob_attr *cur; + unsigned rem; + + blobmsg_for_each_attr(cur, c, rem) { + struct in6_addr addr6, *tmp6; + + if (blobmsg_type(cur) != BLOBMSG_TYPE_STRING || !blobmsg_check_attr(cur, false)) + continue; + + if (inet_pton(AF_INET6, blobmsg_get_string(cur), &addr6) == 1) { + if (IN6_IS_ADDR_UNSPECIFIED(&addr6)) { + error("Invalid %s value configured for interface '%s'", + iface_attrs[IFACE_ATTR_DHCPV6_RELAY_SERVERS].name, iface->name); + continue; + } + + tmp6 = realloc(iface->dhcpv6_relay_server_addrs6, + (iface->dhcpv6_relay_server_addrs6_cnt + 1) * + sizeof(*iface->dhcpv6_relay_server_addrs6)); + + if (!tmp6) + goto err; + + iface->dhcpv6_relay_server_addrs6 = tmp6; + iface->dhcpv6_relay_server_addrs6[iface->dhcpv6_relay_server_addrs6_cnt++] = addr6; + } else { + error("Invalid %s value configured for interface '%s'", + iface_attrs[IFACE_ATTR_DHCPV6_RELAY_SERVERS].name, iface->name); + } + } + } + if (overwrite && (c = tb[IFACE_ATTR_NTP])) { struct blob_attr *cur; unsigned rem; diff --git a/src/odhcpd.h b/src/odhcpd.h index 9298d62a..d59facf1 100644 --- a/src/odhcpd.h +++ b/src/odhcpd.h @@ -468,6 +468,8 @@ struct interface { bool dhcpv6_na; uint32_t dhcpv6_hostid_len; uint32_t dhcpv6_pd_min_len; // minimum delegated prefix length + struct in6_addr *dhcpv6_relay_server_addrs6; + size_t dhcpv6_relay_server_addrs6_cnt; char *upstream; size_t upstream_len; From ecd2d5586532b2f31ca1949c66f6a6c0a2becc8f Mon Sep 17 00:00:00 2001 From: Stijn Tintel Date: Sat, 7 Feb 2026 13:02:51 +0200 Subject: [PATCH 2/2] dhcpv6: relay to dhcpv6_relay_server server(s) If one or more DHCPv6 relay servers are configured for the interface we receive the DHCPv6 Solicit Message on, forward it to those servers. Signed-off-by: Stijn Tintel --- src/dhcpv6.c | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/src/dhcpv6.c b/src/dhcpv6.c index a211b259..483e95b2 100644 --- a/src/dhcpv6.c +++ b/src/dhcpv6.c @@ -28,7 +28,8 @@ #endif static void relay_client_request(struct sockaddr_in6 *source, - const void *data, size_t len, struct interface *iface); + const void *data, size_t len, struct interface *iface, + struct in6_addr *dest); static void relay_server_response(uint8_t *data, size_t len); static void handle_dhcpv6(void *addr, void *data, size_t len, @@ -806,10 +807,15 @@ static void handle_dhcpv6(void *addr, void *data, size_t len, if (iface->dhcpv6 == MODE_SERVER) { handle_client_request(addr, data, len, iface, dest_addr); } else if (iface->dhcpv6 == MODE_RELAY) { - if (iface->master) + if (iface->master) { relay_server_response(data, len); - else - relay_client_request(addr, data, len, iface); + } else if (iface->dhcpv6_relay_server_addrs6_cnt > 0) { + for (size_t i = 0; i < iface->dhcpv6_relay_server_addrs6_cnt; i++) { + relay_client_request(addr, data, len, iface, &iface->dhcpv6_relay_server_addrs6[i]); + } + } else { + relay_client_request(addr, data, len, iface, NULL); + } } } @@ -926,7 +932,8 @@ static struct odhcpd_ipaddr *relay_link_address(struct interface *iface) /* Relay client request (regular DHCPv6-relay) */ static void relay_client_request(struct sockaddr_in6 *source, - const void *data, size_t len, struct interface *iface) + const void *data, size_t len, struct interface *iface, + struct in6_addr *dest) { const struct dhcpv6_relay_header *h = data; /* Construct our forwarding envelope */ @@ -988,7 +995,11 @@ static void relay_client_request(struct sockaddr_in6 *source, memset(&s, 0, sizeof(s)); s.sin6_family = AF_INET6; s.sin6_port = htons(DHCPV6_SERVER_PORT); - inet_pton(AF_INET6, ALL_DHCPV6_SERVERS, &s.sin6_addr); + + if (dest) + s.sin6_addr = *dest; + else + inet_pton(AF_INET6, ALL_DHCPV6_SERVERS, &s.sin6_addr); avl_for_each_element(&interfaces, c, avl) { if (!c->master || c->dhcpv6 != MODE_RELAY)