[Dnsmasq-discuss] [PATCH] dhcp6: implement vendor class support

Luiz Angelo Daros de Luca luizluca at gmail.com
Wed Jun 10 17:48:45 UTC 2026


Implement the OT_DHCP6_VENDOR option type to properly handle the
DHCPv6 Vendor Class option (code 16) according to RFC 3315.

Previously, this option was marked as OT_INTERNAL, causing dnsmasq
to fail immediately if configured by name. Bypassing this block by
using the numerical option format (option6:16) caused the payload
to be formatted with an unintended string-length prefix. This broke
compliance because the RFC requires a fixed 4-byte Enterprise ID
at the beginning of the option, followed by data blocks.

This byte misalignment broke features like UEFI HTTP IPv6 Boot

This patch removes the OT_INTERNAL restriction and moves the formatting
logic to the parser phase, correctly assembling the wire-format layout
(4-byte ID + 2-byte chunk length + string) so it can be injected
directly into the network buffer.

Signed-off-by: Luiz Angelo Daros de Luca <luizluca at gmail.com>
---
 src/dhcp-common.c | 33 ++++++++++++++++++++++++++++++++-
 src/dnsmasq.h     |  1 +
 src/option.c      | 45 +++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 78 insertions(+), 1 deletion(-)

diff --git a/src/dhcp-common.c b/src/dhcp-common.c
index 6b8824a..fa92381 100644
--- a/src/dhcp-common.c
+++ b/src/dhcp-common.c
@@ -734,7 +734,7 @@ static const struct opttab_t opttab6[] = {
   { "status", 13, OT_INTERNAL },
   { "rapid-commit", 14, OT_INTERNAL },
   { "user-class", 15, OT_INTERNAL | OT_CSTRING },
-  { "vendor-class", 16, OT_INTERNAL | OT_CSTRING },
+  { "vendor-class", 16, OT_DHCP6_VENDOR },
   { "vendor-opts", 17, OT_INTERNAL },
   { "sip-server-domain", 21,  OT_RFC1035_NAME },
   { "sip-server", 22, OT_ADDR_LIST },
@@ -909,6 +909,37 @@ char *option_string(int prot, unsigned int opt, unsigned char *val, int opt_len,
 		      buf[j++] = ',';
 		  }
 	      }
+	    else if ((ot[o].size & OT_DHCP6_VENDOR))
+	      {
+		unsigned int enterprise;
+		unsigned char *p = &val[0];
+
+		if (opt_len >= 4)
+		  {
+		    GETLONG(enterprise, p);
+		    snprintf(buf, buf_len, "%u", enterprise);
+		    j = strlen(buf);
+		    i = 4;
+		    while (i + 2 <= opt_len)
+		      {
+			int k, len;
+			p = &val[i];
+			GETSHORT(len, p);
+			if (i + 2 + len > opt_len)
+			  break;
+			if (j < buf_len - 1)
+			  buf[j++] = ',';
+			for (k = 0; k < len && j < buf_len - 1; k++)
+			  {
+			    char c = *p++;
+			    if (isprint((unsigned char)c))
+			      buf[j++] = c;
+			  }
+			buf[j] = 0;
+			i += len + 2;
+		      }
+		  }
+	      }
 #endif
 	    else if ((ot[o].size & (OT_DEC | OT_TIME)) && opt_len != 0)
 	      {
diff --git a/src/dnsmasq.h b/src/dnsmasq.h
index ebb8d15..88171f9 100644
--- a/src/dnsmasq.h
+++ b/src/dnsmasq.h
@@ -834,6 +834,7 @@ struct frec {
 #define OT_CSTRING      0x0800
 #define OT_DEC          0x0400 
 #define OT_TIME         0x0200
+#define OT_DHCP6_VENDOR 0x0100
 
 /* actions in the daemon->helper RPC */
 #define ACTION_DEL           1
diff --git a/src/option.c b/src/option.c
index 274732f..a11a341 100644
--- a/src/option.c
+++ b/src/option.c
@@ -1902,6 +1902,51 @@ static int parse_dhcp_opt(char *errstr, char *arg, int flags)
 		  comma = split(arg);
 		}
 
+	      new->val = newp;
+	      new->len = p - newp;
+	    }
+	  else if (comma && (opt_len & OT_DHCP6_VENDOR))
+	    {
+	      /* First arg is Enterprise ID (4 bytes)
+		 subsequent are length fields (2 bytes each) + string */
+	      int i, commas = 1;
+	      unsigned char *p, *newp;
+
+	      for (i = 0; comma[i]; i++)
+		if (comma[i] == ',')
+		  commas++;
+
+	      newp = opt_malloc(strlen(comma)+(2*commas)+4);
+	      p = newp;
+	      arg = comma;
+	      comma = split(arg);
+
+	      if (arg && *arg)
+	      {
+	      unsigned int enterprise = atoi(arg);
+	      PUTLONG(enterprise, p);
+	      }
+	      else
+	      goto_err(_("missing enterprise ID in dhcp-option"));
+
+	      arg = comma;
+	      comma = split(arg);
+
+	      if (!arg || !*arg)
+	      goto_err(_("missing vendor class in dhcp-option"));
+
+	      while (arg && *arg)
+		{
+		  u16 len = strlen(arg);
+		  unhide_metas(arg);
+		  PUTSHORT(len, p);
+		  memcpy(p, arg, len);
+		  p += len;
+
+		  arg = comma;
+		  comma = split(arg);
+		}
+
 	      new->val = newp;
 	      new->len = p - newp;
 	    }
-- 
2.54.0




More information about the Dnsmasq-discuss mailing list