dhcp6: implement vendor class support

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@gmail.com>
This commit is contained in:
Luiz Angelo Daros de Luca authored and Simon Kelley committed 2026-06-11 15:29:35 +01:00
1 parent c310853907
commit 1af6ee6468
4 files changed
+83 -4

No files matched your search

+32 -1
View File
@@ -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)
{
+1
View File
@@ -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
+45
View File
@@ -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;
}