]> git.ozlabs.org Git - ppp.git/commitdiff
dhcpv6relay plugin.
authorJaco Kroon <jaco@uls.co.za>
Thu, 6 Mar 2025 11:49:33 +0000 (13:49 +0200)
committerJaco Kroon <jaco@uls.co.za>
Fri, 2 May 2025 11:07:56 +0000 (13:07 +0200)
After the plugin is loaded, it just needs dhcpv6-server option to
know where to relay to.

dhcpv6-trusted / dhcpv6-untrusted can be used to mark the port as
trusted (will further forward relay-fwd messages) or unstrusted (will
only accept and forward client originated messages.

dhcpv6-metric - metric at which to install IPv6 routes into the kernel
routing table for prefix delegations.

Server needs to support fwd option 135 (aka relay-port) in order to
respond to pppd.  KEA (for one) does.

Signed-off-by: Jaco Kroon <jaco@uls.co.za>
configure.ac
pppd/plugins/Makefile.am
pppd/plugins/dhcpv6relay/Makefile.am [new file with mode: 0644]
pppd/plugins/dhcpv6relay/dhcpv6relay.c [new file with mode: 0644]
pppd/plugins/dhcpv6relay/dhcpv6relay.h [new file with mode: 0644]

index 8f20192cdae1974e3a1e74020720e9192c8c7312..3900dc8be4c33af17c23c9ce69aae7b46ad0326b 100644 (file)
@@ -330,6 +330,7 @@ AC_CONFIG_FILES([
     pppd/plugins/pppoatm/Makefile
     pppd/plugins/pppol2tp/Makefile
     pppd/plugins/radius/Makefile
+    pppd/plugins/dhcpv6relay/Makefile
     pppdump/Makefile
     pppstats/Makefile
     scripts/Makefile
index 9480d51b4543ba97426cfe9b5ee67daeb97eaa38..267d6697d91e6b78d7d5080ba37a273531c25dd1 100644 (file)
@@ -17,5 +17,5 @@ winbind_la_LDFLAGS = $(PLUGIN_LDFLAGS)
 winbind_la_SOURCES = winbind.c
 
 if !SUNOS
-SUBDIRS = pppoe pppoatm pppol2tp radius
+SUBDIRS = pppoe pppoatm pppol2tp radius dhcpv6relay
 endif
diff --git a/pppd/plugins/dhcpv6relay/Makefile.am b/pppd/plugins/dhcpv6relay/Makefile.am
new file mode 100644 (file)
index 0000000..8d9a3e7
--- /dev/null
@@ -0,0 +1,8 @@
+pppd_plugin_LTLIBRARIES = dhcpv6relay.la
+pppd_plugindir = $(PPPD_PLUGIN_DIR)
+
+noinst_HEADERS = dhcpv6relay.h
+
+dhcpv6relay_la_CPPFLAGS = -I${top_srcdir} -DSYSCONFDIR=\"${sysconfdir}\" -DPLUGIN
+dhcpv6relay_la_LDFLAGS = -module -avoid-version
+dhcpv6relay_la_SOURCES = dhcpv6relay.c
diff --git a/pppd/plugins/dhcpv6relay/dhcpv6relay.c b/pppd/plugins/dhcpv6relay/dhcpv6relay.c
new file mode 100644 (file)
index 0000000..3fbf3e4
--- /dev/null
@@ -0,0 +1,799 @@
+/*
+ * dhcpv6relay.c - DHCPv6 relay plugin.
+ *
+ * Copyright (c) 2025 Ultimate Linux Solutions (Pty) Ltd represented by
+ * Jaco Kroon <jaco@uls.co.za>. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * THE AUTHORS OF THIS SOFTWARE DISCLAIM ALL WARRANTIES WITH REGARD TO
+ * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS, IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
+ * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
+ * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
+ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#include "dhcpv6relay.h"
+
+#include <pppd/pppd.h>
+#include <pppd/options.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <ifaddrs.h>
+#include <netdb.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <arpa/inet.h>
+#include <net/if.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+
+char pppd_version[] = PPPD_VERSION;
+
+static int dhcpv6relay_setserver(const char** argv);
+
+static bool dhcpv6relay_trusted = false;
+static unsigned dhcpv6relay_metric = 0;
+
+static struct option options[] = {
+    { "dhcpv6-server", o_special, (void*) &dhcpv6relay_setserver,
+       "DHCPv6 server to proxy DHCPv6 requests to",
+       OPT_PRIV },
+    { "dhcpv6-trusted", o_bool, &dhcpv6relay_trusted,
+       "DHCPv6 trusted interface (allow incoming relay messages)",
+       OPT_PRIO | OPT_PRIV | 1 },
+    { "dhcpv6-untrusted", o_bool, &dhcpv6relay_trusted,
+       "DHCPv6 untrusted interface (discard incoming relay messages)",
+       OPT_PRIOSUB },
+    { "dhcpv6-metric", o_int, &dhcpv6relay_metric,
+      "Metric to use DHCPv6 supplied routes",
+      OPT_PRIV|OPT_LLIMIT, NULL, 0, 0 },
+    { NULL }
+};
+
+static char* dhcpv6relay_server = NULL;
+static int dhcpv6relay_sock_ll = -1;
+static int dhcpv6relay_sock_mc = -1;
+static int dhcpv6relay_upstream = -1;
+static struct sockaddr_storage dhcpv6relay_sa;
+static struct dhcpv6relay_route_entry *dhcpv6relay_delegations = NULL;
+
+static
+const char* dhcpv6_type2string(int msg_type)
+{
+    switch (msg_type) {
+    case DHCPv6_MSGTYPE_SOLICIT:
+       return "solicit";
+    case DHCPv6_MSGTYPE_ADVERTISE:
+       return "advertise";
+    case DHCPv6_MSGTYPE_REQUEST:
+       return "request";
+    case DHCPv6_MSGTYPE_CONFIRM:
+       return "confirm";
+    case DHCPv6_MSGTYPE_RENEW:
+       return "renew";
+    case DHCPv6_MSGTYPE_REBIND:
+       return "rebind";
+    case DHCPv6_MSGTYPE_REPLY:
+       return "reply";
+    case DHCPv6_MSGTYPE_RELEASE:
+       return "release";
+    case DHCPv6_MSGTYPE_DECLINE:
+       return "decline";
+    case DHCPv6_MSGTYPE_RECONFIGURE:
+       return "reconfigure";
+    case DHCPv6_MSGTYPE_INFORMATION_REQUEST:
+       return "information_request";
+    case DHCPv6_MSGTYPE_RELAY_FORW:
+       return "relay-forw";
+    case DHCPv6_MSGTYPE_RELAY_REPL:
+       return "relay-repl";
+    default:
+       return NULL;
+    }
+}
+
+static
+int dhcpv6relay_setserver(const char** argv)
+{
+    int r;
+    struct addrinfo *ai = NULL, *i, hint = {
+       .ai_flags = 0,
+       .ai_family = 0, /* we *prefer* IPv6, but will accept IPv4 */
+       .ai_socktype = SOCK_DGRAM, /* UDP */
+       .ai_protocol = 0,
+       .ai_addrlen = 0,
+       .ai_addr = NULL,
+       .ai_canonname = NULL,
+       .ai_next = NULL,
+    };
+    char bfr_ip[INET6_ADDRSTRLEN];
+    char bfr_port[6];
+
+    free(dhcpv6relay_server);
+    dhcpv6relay_server = NULL;
+
+    if (!*argv || !**argv)
+       return 1;
+
+    r = getaddrinfo(*argv, "dhcpv6-server", &hint, &ai);
+    if (r != 0) {
+       error("DHCPv6 relay: Unable to set server address to %s: %s",
+               *argv, gai_strerror(r));
+       return 0;
+    }
+
+    dhcpv6relay_sa.ss_family = 0;
+    for (i = ai; i && dhcpv6relay_sa.ss_family != AF_INET6; i = i->ai_next) {
+       if (!dhcpv6relay_sa.ss_family || i->ai_family == AF_INET6) {
+           memcpy(&dhcpv6relay_sa, i->ai_addr, i->ai_addrlen);
+           if (dhcpv6relay_sa.ss_family == AF_INET6)
+               break;
+       }
+    }
+
+    freeaddrinfo(ai);
+    if (dhcpv6relay_sa.ss_family) {
+       getnameinfo((struct sockaddr*)&dhcpv6relay_sa, sizeof(dhcpv6relay_sa),
+               bfr_ip, sizeof(bfr_ip),
+               bfr_port, sizeof(bfr_port),
+               NI_NUMERICHOST | NI_NUMERICSERV | NI_DGRAM);
+       notice("DHCPv6 relay: Using server [%s]:%s", bfr_ip, bfr_port);
+
+       dhcpv6relay_server = strdup(*argv);
+    } else {
+       error("DHCPv6 relay: Failed to resolve %s to an IP address.",
+               *argv);
+    }
+
+    return 1;
+}
+
+static
+void routes_remove_all()
+{
+    char in6addr[INET6_ADDRSTRLEN];
+    struct dhcpv6relay_route_entry* c = dhcpv6relay_delegations;
+
+    while (c) {
+       struct dhcpv6relay_route_entry* n = c->next;
+
+       if (!sifdelroute(AF_INET6, &c->prefix, c->len, dhcpv6relay_metric))
+           error("DHCPv6 relay: failed to remove route for %s/%d",
+                   inet_ntop(AF_INET6, &c->prefix, in6addr, sizeof(in6addr)), c->len);
+
+       free(c);
+       c = n;
+    }
+
+    dhcpv6relay_delegations = NULL;
+}
+
+static
+void dhcpv6relay_down(void*, int)
+{
+    routes_remove_all();
+    if (dhcpv6relay_sock_ll >= 0) {
+       remove_fd(dhcpv6relay_sock_ll);
+       close(dhcpv6relay_sock_ll);
+       dhcpv6relay_sock_ll = -1;
+    }
+    if (dhcpv6relay_sock_mc >= 0) {
+       remove_fd(dhcpv6relay_sock_mc);
+       close(dhcpv6relay_sock_mc);
+       dhcpv6relay_sock_mc = -1;
+    }
+    if (dhcpv6relay_upstream >= 0) {
+       remove_fd(dhcpv6relay_upstream);
+       close(dhcpv6relay_upstream);
+       dhcpv6relay_upstream = -1;
+    }
+}
+
+static
+struct dhcpv6relay_route_entry** dhcpv6relay_find_route_entry(const struct in6_addr* addr, uint8_t prefixlen)
+{
+    struct dhcpv6relay_route_entry** s = &dhcpv6relay_delegations;
+    while (*s) {
+       if (memcmp(&(*s)->prefix, addr, sizeof(*addr)) == 0 && (*s)->len == prefixlen)
+           return s;
+       s = &(*s)->next;
+    }
+    return NULL;
+}
+
+static void dhcpv6relay_route_timeout(void* _r);
+
+static
+void dhcpv6relay_real_release_route(struct dhcpv6relay_route_entry** _r)
+{
+    char in6addr[INET6_ADDRSTRLEN];
+    struct dhcpv6relay_route_entry* r = *_r;
+
+    if (!sifdelroute(AF_INET6, &r->prefix, r->len, dhcpv6relay_metric))
+       error("DHCPv6 relay: failed to remove route for %s/%d",
+               inet_ntop(AF_INET6, &r->prefix, in6addr, sizeof(in6addr)), r->len);
+    else
+       notice("DHCPv6 relay: removed route %s/%d",
+               inet_ntop(AF_INET6, &r->prefix, in6addr, sizeof(in6addr)), r->len);
+
+    ppp_untimeout(dhcpv6relay_route_timeout, r);
+    *_r = r->next;
+    free(r);
+}
+
+static
+void dhcpv6relay_route_timeout(void* _r)
+{
+    struct dhcpv6relay_route_entry** s = &dhcpv6relay_delegations;
+    while (*s && *s != _r)
+       s = &(*s)->next;
+
+    if (!*s) {
+       error("DHCPv6 relay: timeout on already released route delegation.");
+    } else {
+       dhcpv6relay_real_release_route(s);
+    }
+}
+
+static
+void dhcpv6relay_release_route(const struct in6_addr* addr, uint8_t prefixlen, uint32_t /* lifetime */)
+{
+    char in6addr[INET6_ADDRSTRLEN];
+
+    struct dhcpv6relay_route_entry** r = dhcpv6relay_find_route_entry(addr, prefixlen);
+
+    if (!r) {
+       error("DHCPv6 relay: Release of route %s/%d that was never delegated.",
+               inet_ntop(AF_INET6, addr, in6addr, sizeof(in6addr)), prefixlen);
+    } else {
+       dhcpv6relay_real_release_route(r);
+    }
+}
+
+static
+void dhcpv6relay_add_route(const struct in6_addr* addr, uint8_t prefixlen, uint32_t lifetime)
+{
+    char in6addr[INET6_ADDRSTRLEN];
+    struct dhcpv6relay_route_entry** _r = dhcpv6relay_find_route_entry(addr, prefixlen);
+    struct dhcpv6relay_route_entry* r = _r ? *_r : NULL;
+    if (r) {
+       /* route is already installed, just update preferred lifetime. */
+       r->valid_until = time(NULL) + lifetime;
+       ppp_untimeout(dhcpv6relay_route_timeout, r);
+       ppp_timeout(dhcpv6relay_route_timeout, r, lifetime, 0);
+       return;
+    }
+
+    if (!sifaddroute(AF_INET6, addr, prefixlen, dhcpv6relay_metric))
+       return error("DHCPv6 relay: failed to install route for %s/%d",
+               inet_ntop(AF_INET6, addr, in6addr, sizeof(in6addr)), prefixlen);
+
+    notice("DHCPv6 relay: installed route %s/%d",
+           inet_ntop(AF_INET6, addr, in6addr, sizeof(in6addr)), prefixlen);
+
+    r = malloc(sizeof(*r));
+    r->next = dhcpv6relay_delegations;
+    r->prefix = *addr;
+    r->len = prefixlen;
+    r->valid_until = time(NULL) + lifetime;
+    ppp_timeout(dhcpv6relay_route_timeout, r, lifetime, 0);
+
+    dhcpv6relay_delegations = r;
+}
+
+
+void dhcpv6relay_process_ia_pd(const unsigned char *bfr, uint16_t len, dhcpv6relay_route_func routefunc)
+{
+    if (len < 12)
+       return; /* IAID, T1, T2, 4 octets each, we don't care */
+    bfr += 12;
+    len -= 12;
+    while (len > 4) {
+       uint16_t opttype = ntohs(*(uint16_t*)bfr);
+       uint16_t optlen = ntohs(*(uint16_t*)(bfr+2));
+       bfr += 4;
+       len -= 4;
+
+       switch (opttype) {
+       case DHCPv6_OPTION_IAPREFIX:
+           /* 4 octets preferred, 4 octets valid lifetime, 1 octet length, 16 octets prefix */
+           routefunc((const struct in6_addr*)(bfr + 9), bfr[8], ntohl(*(const uint32_t*)(bfr+4)));
+           break;
+       default:
+           /* nothing */
+       }
+
+       bfr += optlen;
+       len -= optlen;
+    }
+}
+
+static
+void dhcpv6relay_process_packet_for_routes(const unsigned char *bfr, uint16_t len)
+{
+    if (len < 1)
+       return;
+    uint8_t pkttype = *bfr;
+
+    switch (pkttype) {
+    case DHCPv6_MSGTYPE_RELAY_FORW:
+    case DHCPv6_MSGTYPE_RELAY_REPL:
+       /* these have 34 byte headers, so we can jump over that, then look for the relay message option
+        * and recurse on that as we really don't care about anything else. */
+       if (len < 34)
+           return;
+       bfr += 34;
+       len -= 34;
+       while (len > 4) {
+           uint16_t opttype = ntohs(*(uint16_t*)bfr);
+           uint16_t optlen = ntohs(*(uint16_t*)(bfr+2));
+           bfr += 4;
+           len -= 4;
+           if (optlen > len)
+               return;
+           if (opttype == DHCPv6_OPTION_RELAY_MSG) {
+               dhcpv6relay_process_packet_for_routes(bfr, optlen);
+               return; /* there may be only one */
+           }
+           bfr += optlen;
+           len -= optlen;
+       }
+       break;
+    case DHCPv6_MSGTYPE_REPLY:
+    case DHCPv6_MSGTYPE_RELEASE:
+       dhcpv6relay_route_func func;
+       if (pkttype == DHCPv6_MSGTYPE_RELEASE)
+           func = dhcpv6relay_release_route;
+       else
+           func = dhcpv6relay_add_route;
+
+       /* everything else has a 4 byte header, the packet type (1 octet) and a transaction id (3 octets)
+        * which we don't care about, so just skip ahead to the options that we do care about */
+       if (len < 4)
+           return;
+       bfr += 4;
+       len -= 4;
+       while (len > 4) {
+           uint16_t opttype = ntohs(*(uint16_t*)bfr);
+           uint16_t optlen = ntohs(*(uint16_t*)(bfr+2));
+           bfr += 4;
+           len -= 4;
+           if (optlen > len) /* packet overrun */
+               return;
+
+           switch (opttype) {
+           case DHCPv6_OPTION_IA_PD:
+               dhcpv6relay_process_ia_pd(bfr, optlen, func);
+               break;
+           default:
+           }
+           bfr += optlen;
+
+           len -= optlen;
+       }
+       break;
+    default:
+       /* nothing to do, we don't care about these. */
+       break;
+    }
+}
+
+static
+void dhcpv6relay_server_event(int fd, void*)
+{
+    unsigned char buffer[1024];
+    unsigned char *options = buffer + 34; /* skip fixed header */
+    unsigned char *fwd_packet = NULL;
+    uint16_t fwd_len = 0;
+    char in6addr[INET6_ADDRSTRLEN];
+    struct sockaddr_in6 sa;
+    socklen_t slen = sizeof(sa);
+    bool valid_source = true;
+    int hlim = 0;
+    ssize_t r = recvfrom(fd, buffer, sizeof(buffer), MSG_DONTWAIT,
+           (struct sockaddr*)&sa, &slen);
+    if (r < 0) {
+       error("DHCPv6 relay: Failed to read from %s socket: %s",
+               fd == dhcpv6relay_sock_ll ? "LL" : "MC",
+               strerror(errno));
+       return;
+    }
+    if (r >= sizeof(buffer)) {
+       error("DHCPv6 buffer overrun, recvfrom returned %d, max %u",
+               r, sizeof(buffer));
+       return;
+    }
+
+    /* notice("Received %d bytes from fd=%d (%s), with source [%s]:%d, packet type: %s", r, fd,
+           "UPSTREAM",
+           inet_ntop(sa.sin6_family, sa.sin6_family == AF_INET ?
+               (void*)&((struct sockaddr_in*)&sa)->sin_addr : (void*)&sa.sin6_addr,
+               in6addr, sizeof(in6addr)),
+           ntohs(sa.sin6_port), r ? dhcpv6_type2string(buffer[0]) : "empty"); */
+
+    if (sa.sin6_family != dhcpv6relay_sa.ss_family) {
+       valid_source = false;
+    } else if (sa.sin6_family == AF_INET6) {
+       valid_source = sa.sin6_port == ((struct sockaddr_in6*)&dhcpv6relay_sa)->sin6_port
+
+           && memcmp(&sa.sin6_addr, &((struct sockaddr_in6*)&dhcpv6relay_sa)->sin6_addr,
+                   sizeof(sa.sin6_addr)) == 0;
+    } else if (sa.sin6_family == AF_INET) {
+       valid_source = ((struct sockaddr_in*)&sa)->sin_port ==
+           ((struct sockaddr_in*)&dhcpv6relay_sa)->sin_port
+
+           && ((struct sockaddr_in*)&sa)->sin_addr.s_addr ==
+           ((struct sockaddr_in*)&dhcpv6relay_sa)->sin_addr.s_addr;
+    } else {
+       error("DHCv6 relay: Received non-IP packet on upstream socket.");
+       return;
+    }
+
+    if (!valid_source) {
+       error("DHCPv6 relay: Received packet from unexpected source [%s]:%d on upstream socket.",
+               inet_ntop(sa.sin6_family, sa.sin6_family == AF_INET ?
+                   (void*)&((struct sockaddr_in*)&sa)->sin_addr : (void*)&sa.sin6_addr,
+                   in6addr, sizeof(in6addr)),
+               sa.sin6_family == AF_INET ? ((struct sockaddr_in*)&sa)->sin_port : sa.sin6_port);
+       return;
+    }
+
+    /* relay-repl is at least 34 bytes, without required options,
+     * so blindly discard anything smaller */
+    if (r < 34) {
+       error("DHCPv6 relay: Received packet on upstream socket is too small to be a valid relay-repl.");
+       return;
+    }
+
+    if (buffer[0] != DHCPv6_MSGTYPE_RELAY_REPL) {
+       error("DHCPv6 relay: packet received from upstream server is a %s, expected relay-repl.",
+               dhcpv6_type2string(buffer[0]));
+       return;
+    }
+
+    /* don't partircularly care about the hop-count or linkaddr, we do need
+     * peeraddr, but can recover that later */
+
+    r -= 34;
+    while (r > 4) {
+       /* each option header is a 32-bits, 16-bits type and 16-bit length */
+       uint16_t type = ntohs(*(uint16_t*)options);
+       uint16_t len = ntohs(*(uint16_t*)(options + 2));
+       options += 4;
+       r -= 4;
+       if (len > r) {
+           error("DHCPv6 relay: Error parsing packet from server, option %u specified "
+                   "len=%u but only %d bytes available.", type, len, r);
+           return;
+       }
+       switch (type) {
+       case DHCPv6_OPTION_RELAY_MSG:
+           fwd_packet = options;
+           fwd_len = len;
+           break;
+       /*case DHCPv6_OPTION_RELAY_PORT:
+           notice("Got relay-port: %u", ntohs(*(uint16_t*)options));
+           break; */
+       default:
+           /* notice("DHCPv6 relay: Skipping processing of option %u of length %u.",
+                   type, len); */
+       }
+       options += len;
+       r -= len;
+    }
+
+    if (!fwd_packet) {
+       error("DHCPv6 relay: relay-repl message from server did not contain a relay-msg option.");
+       return;
+    }
+
+    memset(&sa, 0, sizeof(sa));
+    sa.sin6_family = AF_INET6;
+    if (fwd_packet[0] == DHCPv6_MSGTYPE_RELAY_REPL) {
+       /* this should only ever happen towards "trusted" ports, wich is not the default. */
+       /* TODO: Honour option 135 towards downstream, would need to see an example, spec
+        * is unclear and observed behaviour from KEA doesn't make sense. */
+       sa.sin6_port = getservbyname("dhcpv6-server", "udp")->s_port;
+       hlim = 64;
+    } else {
+       sa.sin6_port = getservbyname("dhcpv6-client", "udp")->s_port;
+    }
+    memcpy(&sa.sin6_addr, buffer + 18 /* peer-link address */, sizeof(sa.sin6_addr));
+    sa.sin6_scope_id = if_nametoindex(ppp_ifname());
+
+    setsockopt(dhcpv6relay_sock_ll, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &hlim, sizeof(hlim));
+    r = sendto(dhcpv6relay_sock_ll, fwd_packet, fwd_len, 0,
+           (struct sockaddr*)&sa, sizeof(sa));
+    if (r < 0) {
+       error("DHCPv6 relay: Error transmitting server response to client: %s",
+               strerror(errno));
+    }
+
+    dhcpv6relay_process_packet_for_routes(fwd_packet, fwd_len);
+}
+
+static
+int dhcpv6relay_init_upstream()
+{
+    /* use family from sa so that we can do DHCPv6 / IPv4. */
+    dhcpv6relay_upstream = socket(dhcpv6relay_sa.ss_family, SOCK_DGRAM, 0);
+    if (dhcpv6relay_upstream < 0) {
+       error("DHCPv6 relay: Failed to bind upstream socket: %s",
+               strerror(errno));
+       return 0;
+    }
+    if (connect(dhcpv6relay_upstream, (struct sockaddr*)&dhcpv6relay_sa, sizeof(dhcpv6relay_sa)) < 0) {
+       error("DHCPv6 relay: Failed to connect upstream socket: %s",
+               strerror(errno));
+       close(dhcpv6relay_upstream);
+       dhcpv6relay_upstream = -1;
+       return 0;
+    }
+    add_fd_callback(dhcpv6relay_upstream, dhcpv6relay_server_event, NULL);
+
+    return 1;
+}
+
+static
+void dhcpv6relay_client_event(int fd, void*)
+{
+    unsigned char buffer[1024];
+    unsigned char fwd_head[256];
+    const char* remote_id;
+    const char* subscriber_id;
+    struct iovec v[] = {
+       {
+           .iov_base = fwd_head,
+           .iov_len = 0,
+       },
+       {
+           .iov_base = buffer,
+           .iov_len = 0,
+       },
+    };
+    struct msghdr wv = {
+       .msg_name = &dhcpv6relay_sa,
+       .msg_namelen = sizeof(dhcpv6relay_sa),
+       .msg_iov = v,
+       .msg_iovlen = sizeof(v) / sizeof(*v),
+       .msg_control = NULL,
+       .msg_controllen = 0,
+       .msg_flags = 0,
+    };
+    /* char in6addr[INET6_ADDRSTRLEN] */;
+    struct sockaddr_in6 sa;
+    uint16_t sport;
+    socklen_t slen = sizeof(sa);
+    ssize_t r = recvfrom(fd, buffer, sizeof(buffer), MSG_DONTWAIT,
+           (struct sockaddr*)&sa, &slen);
+    if (r < 0) {
+       if (errno != EAGAIN)
+           error("DHCPv6 relay: Failed to read from %s socket: %s",
+                   fd == dhcpv6relay_sock_ll ? "LL" : "MC",
+                   strerror(errno));
+       return;
+    }
+    if (r >= sizeof(buffer)) {
+       error("DHCPv6 relay: buffer overrun, recvfrom returned %d, max %u (%s socket)",
+               r, sizeof(buffer), fd == dhcpv6relay_sock_ll ? "LL" : "MC");
+       return;
+    }
+    if (r < 4) {
+       error("DHCPv6 relay: buffer underrun, we only got %d bytes from client, need at least 4 to be valid.", r);
+       return;
+    }
+    v[1].iov_len = r;
+
+    /* notice("Received %d bytes from fd=%d (%s), with source [%s]:%d, packet type: %s", r, fd,
+           fd == dhcpv6relay_sock_ll ? "LL" : "MC",
+           inet_ntop(sa.sin6_family, &sa.sin6_addr, in6addr, sizeof(in6addr)),
+           ntohs(sa.sin6_port), r ? dhcpv6_type2string(buffer[0]) : "empty"); */
+
+    /* disallow Reply and Relay-Reply messages */
+    if (buffer[0] == DHCPv6_MSGTYPE_REPLY || buffer[0] == DHCPv6_MSGTYPE_RELAY_REPL) {
+       warn("Discarding DHCPv6 %s message received on PPP interface.",
+               dhcpv6_type2string(buffer[0]));
+       return;
+    }
+
+    /* if the interface is not trusted, also discard Relay-Fwd messages */
+    if (!dhcpv6relay_trusted && buffer[0] == DHCPv6_MSGTYPE_RELAY_FORW) {
+       warn("Discarding DHCPv6 %s message received on untrusted PPP interface.",
+               dhcpv6_type2string(buffer[0]));
+       return;
+    }
+
+    if (dhcpv6relay_upstream < 0 && !dhcpv6relay_init_upstream())
+       return;
+
+    dhcpv6relay_process_packet_for_routes(buffer, r);
+
+    /* populate the forward header */
+    fwd_head[0] = DHCPv6_MSGTYPE_RELAY_FORW; /* msg-type */
+    fwd_head[1] = buffer[0] == DHCPv6_MSGTYPE_RELAY_FORW ? buffer[1] + 1 : 0; /* hop count */
+    memset(&fwd_head[2], 0, 16); /* link-address, unspecified */
+    memcpy(&fwd_head[18], &sa.sin6_addr, 16); /* peer-address */
+    v[0].iov_len = 34;
+
+    slen = sizeof(sa);
+    if (getsockname(dhcpv6relay_upstream, (struct sockaddr*)&sa, &slen) < 0) {
+       error("DHCPv6 relay: Unable to determine local sending port: %s",
+               strerror(errno));
+       return;
+    }
+
+#define push_checkbytes(x) do { if ((x) + v[0].iov_len > sizeof(fwd_head)) { error("DHCPv6 relay: Buffer overlow avoidance pushing %d bytes, need %d.", (x), (x) + v[0].iov_len - sizeof(fwd_head)); return; }} while(0)
+#define push_uint16(val) do { push_checkbytes(2); uint16_t t = (val); fwd_head[v[0].iov_len++] = t >> 8; fwd_head[v[0].iov_len++] = t & 0xFF; } while(0);
+#define push_bytes(ptr, bytes) do { push_checkbytes(bytes); memcpy(&fwd_head[v[0].iov_len], (ptr), (bytes)); v[0].iov_len += (bytes); } while(0)
+
+    /* On Linux at least sin6_port and sin_port would refer the same
+     * data but I can't guarantee that for solaris (and others) */
+    switch (sa.sin6_family) {
+    case AF_INET:
+       sport = ((struct sockaddr_in*)&sa)->sin_port;
+       break;
+    case AF_INET6:
+       sport = sa.sin6_port;
+       break;
+    default:
+       error("DHCPv6 relay: Upstream socket is bound to something other than IP ... can't relay.");
+       return;
+    }
+
+    push_uint16(DHCPv6_OPTION_RELAY_PORT);
+    push_uint16(2);
+    push_uint16(ntohs(sport));
+
+    remote_id = ppp_get_remote_number();
+    if (remote_id) {
+       r = strlen(remote_id);
+       push_uint16(DHCPv6_OPTION_REMOTE_ID);
+       push_uint16(r);
+       push_bytes(remote_id, r);
+    }
+
+    subscriber_id = ppp_peer_authname(NULL, 0);
+    if (subscriber_id) {
+       r = strlen(subscriber_id);
+       push_uint16(DHCPv6_OPTION_SUBSCRIBER_ID);
+       push_uint16(r);
+       push_bytes(subscriber_id, r);
+    }
+
+    /* This *must* be the last option since it refers the the content from v[1] */
+    push_uint16(DHCPv6_OPTION_RELAY_MSG);
+    push_uint16(v[1].iov_len);
+
+#undef push_checkbytes
+#undef push_uint16
+#undef push_bytes
+
+    if (sendmsg(dhcpv6relay_upstream, &wv, 0) < 0) {
+       error("DHCPv6 relay: Failed to transmit proxies request: %s",
+               strerror(errno));
+    }
+}
+
+static
+int dhcpv6relay_populate_ll(struct sockaddr_in6* res)
+{
+    /* can we rather shortcut to get the address directly from ipv6cp? */
+    struct ifaddrs *ifap, *ifa;
+    int r = getifaddrs(&ifap);
+    const struct sockaddr_in6* sa6;
+
+    if (r < 0) {
+       error("DHCPv6 relay: Unable to determine LL address");
+       return 0;
+    }
+
+    for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
+       if (!ifa->ifa_addr || ifa->ifa_addr->sa_family != AF_INET6)
+           continue;
+
+       sa6 = (struct sockaddr_in6*)ifa->ifa_addr;
+       if (!sa6->sin6_scope_id)
+           continue; /* LL has sin6_scope_id set to interface id, != 0 */
+
+       if (strcmp(ifa->ifa_name, ppp_ifname()) != 0)
+           continue; /* wrong interface */
+
+       /* use it */
+       *res = *sa6;
+       freeifaddrs(ifap);
+       return 1;
+    }
+
+    error("DHCPv6 relay: No matching LL addresses available for use.");
+    freeifaddrs(ifap);
+    return 0;
+}
+
+static
+void dhcpv6relay_up(void*, int)
+{
+    struct sockaddr_in6 sa;
+    struct ipv6_mreq mreq;
+    struct servent *se;
+
+    /* no relay configured, so we can't work, simply don't listen
+     * for DHCP solicitations */
+    if (!dhcpv6relay_server)
+       return;
+
+    if (!dhcpv6relay_populate_ll(&sa))
+       return;
+
+    se = getservbyname("dhcpv6-server", "udp");
+    if (!se) {
+       error("DHCPv6 relay: Unable to determine UDP port number for dhcpv6-server: %s",
+               strerror(errno));
+       return;
+    }
+
+    sa.sin6_port = se->s_port;
+
+    dhcpv6relay_sock_ll = socket(AF_INET6, SOCK_DGRAM, 0);
+    if (dhcpv6relay_sock_ll < 0) {
+       error("DHCPv6 relay: Unable to create LL socket: %s", strerror(errno));
+       return dhcpv6relay_down(NULL, 0);
+    }
+    fcntl(dhcpv6relay_sock_ll, F_SETFD, FD_CLOEXEC);
+
+    if (bind(dhcpv6relay_sock_ll, (const struct sockaddr*)&sa, sizeof(sa)) < 0) {
+       error("DHCPv6 relay: Unable to bind LL socket: %s", strerror(errno));
+       return dhcpv6relay_down(NULL, 0);
+    }
+
+    memset(&mreq, 0, sizeof(mreq));
+    if (inet_pton(AF_INET6, "ff02::1:2", &mreq.ipv6mr_multiaddr) < 0) {
+       error("DHCPv6 relay: Error preparing multicast binding: %s", strerror(errno));
+       return dhcpv6relay_down(NULL, 0);
+    }
+
+    mreq.ipv6mr_interface = sa.sin6_scope_id;
+    if (setsockopt(dhcpv6relay_sock_ll, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq, sizeof(mreq)) < 0) {
+       error("DHCPv6 relay: Error joining multicast group: %s", strerror(errno));
+       return dhcpv6relay_down(NULL, 0);
+    }
+
+    dhcpv6relay_sock_mc = socket(AF_INET6, SOCK_DGRAM, 0);
+    if (dhcpv6relay_sock_mc < 0) {
+       error("DHCPv6 relay: Unable to create MC socket: %s", strerror(errno));
+       return dhcpv6relay_down(NULL, 0);
+    }
+    fcntl(dhcpv6relay_sock_mc, F_SETFD, FD_CLOEXEC);
+
+    sa.sin6_addr = mreq.ipv6mr_multiaddr;
+    if (bind(dhcpv6relay_sock_mc, (const struct sockaddr*)&sa, sizeof(sa)) < 0) {
+       error("DHCPv6 relay: Unable to bind MC socket: %s", strerror(errno));
+       return dhcpv6relay_down(NULL, 0);
+    }
+
+    add_fd_callback(dhcpv6relay_sock_ll, dhcpv6relay_client_event, NULL);
+    add_fd_callback(dhcpv6relay_sock_mc, dhcpv6relay_client_event, NULL);
+
+    notice("DHCPv6 relay: ready.");
+}
+
+void
+plugin_init(void)
+{
+    ppp_add_options(options);
+    ppp_add_notify(NF_IPV6_UP, dhcpv6relay_up, NULL);
+    ppp_add_notify(NF_IPV6_DOWN, dhcpv6relay_down, NULL);
+}
diff --git a/pppd/plugins/dhcpv6relay/dhcpv6relay.h b/pppd/plugins/dhcpv6relay/dhcpv6relay.h
new file mode 100644 (file)
index 0000000..cce6251
--- /dev/null
@@ -0,0 +1,203 @@
+/*
+ * dhcpv6relay.h - DHCPv6 relay plugin.
+ *
+ * Copyright (c) 2025 Ultimate Linux Solutions (Pty) Ltd represented by
+ * Jaco Kroon <jaco@uls.co.za>. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * THE AUTHORS OF THIS SOFTWARE DISCLAIM ALL WARRANTIES WITH REGARD TO
+ * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS, IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
+ * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
+ * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
+ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+#ifndef DHCPV6RELAY_H
+#define DHCPV6RELAY_H
+
+#include <time.h>
+#include <stdint.h>
+#include <netinet/in.h>
+
+struct dhcpv6relay_route_entry {
+    struct dhcpv6relay_route_entry *next;
+    struct in6_addr prefix;
+    uint8_t len;
+    time_t valid_until;
+};
+
+typedef void (*dhcpv6relay_route_func)(const struct in6_addr*, uint8_t, uint32_t);
+
+#define DHCPv6_MSGTYPE_SOLICIT              1
+#define DHCPv6_MSGTYPE_ADVERTISE            2
+#define DHCPv6_MSGTYPE_REQUEST              3
+#define DHCPv6_MSGTYPE_CONFIRM              4
+#define DHCPv6_MSGTYPE_RENEW                5
+#define DHCPv6_MSGTYPE_REBIND               6
+#define DHCPv6_MSGTYPE_REPLY                7
+#define DHCPv6_MSGTYPE_RELEASE              8
+#define DHCPv6_MSGTYPE_DECLINE              9
+#define DHCPv6_MSGTYPE_RECONFIGURE         10
+#define DHCPv6_MSGTYPE_INFORMATION_REQUEST 11
+#define DHCPv6_MSGTYPE_RELAY_FORW          12
+#define DHCPv6_MSGTYPE_RELAY_REPL          13
+
+#define DHCPv6_OPTION_CLIENTID                            1
+#define DHCPv6_OPTION_SERVERID                            2
+#define DHCPv6_OPTION_IA_NA                               3
+#define DHCPv6_OPTION_IA_TA                               4
+#define DHCPv6_OPTION_IAADDR                              5
+#define DHCPv6_OPTION_ORO                                 6
+#define DHCPv6_OPTION_PREFERENCE                          7
+#define DHCPv6_OPTION_ELAPSED_TIME                        8
+#define DHCPv6_OPTION_RELAY_MSG                           9
+#define DHCPv6_OPTION_AUTH                               11
+#define DHCPv6_OPTION_UNICAST                            12
+#define DHCPv6_OPTION_STATUS_CODE                        13
+#define DHCPv6_OPTION_RAPID_COMMIT                       14
+#define DHCPv6_OPTION_USER_CLASS                         15
+#define DHCPv6_OPTION_VENDOR_CLASS                       16
+#define DHCPv6_OPTION_VENDOR_OPTS                        17
+#define DHCPv6_OPTION_INTERFACE_ID                       18
+#define DHCPv6_OPTION_RECONF_MSG                         19
+#define DHCPv6_OPTION_RECONF_ACCEPT                      20
+#define DHCPv6_OPTION_SIP_SERVER_D                       21
+#define DHCPv6_OPTION_SIP_SERVER_A                       22
+#define DHCPv6_OPTION_DNS_SERVERS                        23
+#define DHCPv6_OPTION_DOMAIN_LIST                        24
+#define DHCPv6_OPTION_IA_PD                              25
+#define DHCPv6_OPTION_IAPREFIX                           26
+#define DHCPv6_OPTION_NIS_SERVERS                        27
+#define DHCPv6_OPTION_NISP_SERVERS                       28
+#define DHCPv6_OPTION_NIS_DOMAIN_NAME                    29
+#define DHCPv6_OPTION_NISP_DOMAIN_NAME                   30
+#define DHCPv6_OPTION_SNTP_SERVERS                       31
+#define DHCPv6_OPTION_INFORMATION_REFRESH_TIME           32
+#define DHCPv6_OPTION_BCMCS_SERVER_D                     33
+#define DHCPv6_OPTION_BCMCS_SERVER_A                     34
+#define DHCPv6_OPTION_GEOCONF_CIVIC                      36
+#define DHCPv6_OPTION_REMOTE_ID                          37
+#define DHCPv6_OPTION_SUBSCRIBER_ID                      38
+#define DHCPv6_OPTION_CLIENT_FQDN                        39
+#define DHCPv6_OPTION_PANA_AGENT                         40
+#define DHCPv6_OPTION_NEW_POSIX_TIMEZONE                 41
+#define DHCPv6_OPTION_NEW_TZDB_TIMEZONE                  42
+#define DHCPv6_OPTION_ERO                                43
+#define DHCPv6_OPTION_LQ_QUERY                           44
+#define DHCPv6_OPTION_CLIENT_DATA                        45
+#define DHCPv6_OPTION_CLT_TIME                           46
+#define DHCPv6_OPTION_LQ_RELAY_DATA                      47
+#define DHCPv6_OPTION_LQ_CLIENT_LINK                     48
+#define DHCPv6_OPTION_MIP6_HNIDF                         49
+#define DHCPv6_OPTION_MIP6_VDINF                         50
+#define DHCPv6_OPTION_V6_LOST                            51
+#define DHCPv6_OPTION_CAPWAP_AC_V6                       52
+#define DHCPv6_OPTION_RELAY_ID                           53
+#define DHCPv6_OPTION_IPv6_ADDRESS_MOS                   54
+#define DHCPv6_OPTION_IPv6_FQDN_MOS                      55
+#define DHCPv6_OPTION_NTP_SERVER                         56
+#define DHCPv6_OPTION_V6_ACCESS_DOMAIN                   57
+#define DHCPv6_OPTION_SIP_UA_CS_LIST                     58
+#define DHCPv6_OPTION_BOOTFILE_URL                       59
+#define DHCPv6_OPTION_BOOTFILE_PARAM                     60
+#define DHCPv6_OPTION_CLIENT_ARCH_TYPE                   61
+#define DHCPv6_OPTION_NII                                62
+#define DHCPv6_OPTION_GEOLOCATION                        63
+#define DHCPv6_OPTION_AFTR_NAME                          64
+#define DHCPv6_OPTION_ERP_LOCAL_DOMAIN_NAME              65
+#define DHCPv6_OPTION_RSOO                               66
+#define DHCPv6_OPTION_PD_EXCLUDE                         67
+#define DHCPv6_OPTION_VSS                                68
+#define DHCPv6_OPTION_MIP6_IDINF                         69
+#define DHCPv6_OPTION_MIP6_UDINF                         70
+#define DHCPv6_OPTION_MIP6_HNP                           71
+#define DHCPv6_OPTION_MIP6_HAA                           72
+#define DHCPv6_OPTION_MIP6_HAF                           73
+#define DHCPv6_OPTION_RDNSS_SELECTION                    74
+#define DHCPv6_OPTION_KRB_PRINCIPAL_NAME                 75
+#define DHCPv6_OPTION_KRB_REALM_NAME                     76
+#define DHCPv6_OPTION_KRB_DEFAULT_REALM_NAME             77
+#define DHCPv6_OPTION_KRB_KDC                            78
+#define DHCPv6_OPTION_CLIENT_LINKLAYER_ADDR              79
+#define DHCPv6_OPTION_LINK_ADDRESS                       80
+#define DHCPv6_OPTION_RADIUS                             81
+#define DHCPv6_OPTION_SOL_MAX_RT                         82
+#define DHCPv6_OPTION_INF_MAX_RT                         83
+#define DHCPv6_OPTION_ADDRSEL                            84
+#define DHCPv6_OPTION_ADDRSEL_TABLE                      85
+#define DHCPv6_OPTION_V6_PCP_SERVER                      86
+#define DHCPv6_OPTION_DHCPV4_MSG                         87
+#define DHCPv6_OPTION_DHCP4_O_DHCP6_SERVER               88
+#define DHCPv6_OPTION_S46_RULE                           89
+#define DHCPv6_OPTION_S46_BR                             90
+#define DHCPv6_OPTION_S46_DMR                            91
+#define DHCPv6_OPTION_S46_V4V6BIND                       92
+#define DHCPv6_OPTION_S46_PORTPARAMS                     93
+#define DHCPv6_OPTION_S46_CONT_MAPE                      94
+#define DHCPv6_OPTION_S46_CONT_MAPT                      95
+#define DHCPv6_OPTION_S46_CONT_LW                        96
+#define DHCPv6_OPTION_4RD                                97
+#define DHCPv6_OPTION_4RD_MAP_RULE                       98
+#define DHCPv6_OPTION_4RD_NON_MAP_RULE                   99
+#define DHCPv6_OPTION_LQ_BASE_TIME                      100
+#define DHCPv6_OPTION_LQ_START_TIME                     101
+#define DHCPv6_OPTION_LQ_END_TIME                       102
+#define DHCPv6_OPTION_CAPTIVE_PORTAL                    103
+#define DHCPv6_OPTION_MPL_PARAMETERS                    104
+#define DHCPv6_OPTION_ANI_ATT                           105
+#define DHCPv6_OPTION_ANI_NETWORK_NAME                  106
+#define DHCPv6_OPTION_ANI_AP_NAME                       107
+#define DHCPv6_OPTION_ANI_AP_BSSID                      108
+#define DHCPv6_OPTION_ANI_OPERATOR_ID                   109
+#define DHCPv6_OPTION_ANI_OPERATOR_REALM                110
+#define DHCPv6_OPTION_S46_PRIORITY                      111
+#define DHCPv6_OPTION_MUD_URL_V6                        112
+#define DHCPv6_OPTION_V6_PREFIX64                       113
+#define DHCPv6_OPTION_F_BINDING_STATUS                  114
+#define DHCPv6_OPTION_F_CONNECT_FLAGS                   115
+#define DHCPv6_OPTION_F_DNS_REMOVAL_INFO                116
+#define DHCPv6_OPTION_F_DNS_HOST_NAME                   117
+#define DHCPv6_OPTION_F_DNS_ZONE_NAME                   118
+#define DHCPv6_OPTION_F_DNS_FLAGS                       119
+#define DHCPv6_OPTION_F_EXPIRATION_TIME                 120
+#define DHCPv6_OPTION_F_MAX_UNACKED_BNDUPD              121
+#define DHCPv6_OPTION_F_MCLT                            122
+#define DHCPv6_OPTION_F_PARTNER_LIFETIME                123
+#define DHCPv6_OPTION_F_PARTNER_LIFETIME_SENT           124
+#define DHCPv6_OPTION_F_PARTNER_DOWN_TIME               125
+#define DHCPv6_OPTION_F_PARTNER_RAW_CLT_TIME            126
+#define DHCPv6_OPTION_F_PROTOCOL_VERSION                127
+#define DHCPv6_OPTION_F_KEEPALIVE_TIME                  128
+#define DHCPv6_OPTION_F_RECONFIGURE_DATA                129
+#define DHCPv6_OPTION_F_RELATIONSHIP_NAME               130
+#define DHCPv6_OPTION_F_SERVER_FLAGS                    131
+#define DHCPv6_OPTION_F_SERVER_STATE                    132
+#define DHCPv6_OPTION_F_START_TIME_OF_STATE             133
+#define DHCPv6_OPTION_F_STATE_EXPIRATION_TIME           134
+#define DHCPv6_OPTION_RELAY_PORT                        135
+#define DHCPv6_OPTION_V6_SZTP_REDIRECT                  136
+#define DHCPv6_OPTION_S46_BIND_IPV6_PREFIX              137
+#define DHCPv6_OPTION_IA_LL                             138
+#define DHCPv6_OPTION_LLADDR                            139
+#define DHCPv6_OPTION_SLAP_QUAD                         140
+#define DHCPv6_OPTION_V6_DOTS_RI                        141
+#define DHCPv6_OPTION_V6_DOTS_ADDRESS                   142
+#define DHCPv6_OPTION_IPV6_ADDRESS_ANDSF                143
+#define DHCPv6_OPTION_V6_DNR                            144
+#define DHCPv6_OPTION_REGISTERED_DOMAIN                 145
+#define DHCPv6_OPTION_FORWARD_DIST_MANAGER              146
+#define DHCPv6_OPTION_REVERSE_DIST_MANAGER              147
+
+#endif