From b4a066365483f21de50ec0913fc64f1b0814eb8c Mon Sep 17 00:00:00 2001 From: Simon Kelley Date: Tue, 17 Mar 2026 12:12:59 +0000 Subject: [PATCH] Accept DHCPv6 vendorclasses with any enterprise number in --dhcp-vendorclass if not enterprise number is specified. Also accept and match on enterprise number only. --- man/dnsmasq.8 | 8 ++++--- src/option.c | 58 ++++++++++++++++++++++++++------------------------- src/rfc3315.c | 21 +++++++++++++++---- 3 files changed, 52 insertions(+), 35 deletions(-) diff --git a/man/dnsmasq.8 b/man/dnsmasq.8 index 780d6d5..d4068c5 100644 --- a/man/dnsmasq.8 +++ b/man/dnsmasq.8 @@ -1515,7 +1515,7 @@ The normal default DNS server (the same address as the DHCP server) will not be appropriate when there is no route bewteen the two, so this will have to be explicitly configured. .TP -.B \-U, --dhcp-vendorclass=set:,[enterprise:,] +.B \-U, --dhcp-vendorclass=set:,[enterprise:,][] Map from a vendor-class string to a tag. Most DHCP clients provide a "vendor class" which represents, in some sense, the type of host. This option maps vendor classes to tags, so that DHCP options may be selectively delivered @@ -1529,9 +1529,11 @@ allow fuzzy matching. The set: prefix is optional but allowed for consistency. Note that in IPv6 only, vendorclasses are namespaced with an -IANA-allocated enterprise number. This is given with enterprise: +IANA-allocated enterprise number. This may be given with an enterprise: keyword and specifies that only vendorclasses matching the specified -number should be searched. +number should be searched. Similarly, an enterprise number may be given +with no vendor-class string. This always matches vendorclasses with +the given enterprise number. .TP .B \-j, --dhcp-userclass=set:, Map from a user-class string to a tag (with substring diff --git a/src/option.c b/src/option.c index 051555a..add460e 100644 --- a/src/option.c +++ b/src/option.c @@ -4580,39 +4580,41 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma only allowed for agent-options. */ arg = comma; - if ((comma = split(arg))) + if (option == 'U' && strstr(arg, "enterprise:") == arg) { - if (option != 'U' || strstr(arg, "enterprise:") != arg) + comma = split(arg); + new->enterprise = atoi(arg+11); + arg = comma; + } + + if (arg) + { + for (dig = 0, colon = 0, p = (unsigned char *)arg; *p; p++) + if (isxdigit(*p)) + dig = 1; + else if (*p == ':') + colon = 1; + else + break; + + unhide_metas(arg); + if (option == 'U' || option == 'j' || *p || !dig || !colon) { - free(new->netid.net); - ret_err_free(gen_err, new); + new->len = strlen(arg); + new->data = opt_malloc(new->len); + memcpy(new->data, arg, new->len); } else - new->enterprise = atoi(arg+11); + { + new->len = parse_hex(comma, (unsigned char *)arg, strlen(arg), NULL, NULL); + new->data = opt_malloc(new->len); + memcpy(new->data, arg, new->len); + } } - else - comma = arg; - - for (dig = 0, colon = 0, p = (unsigned char *)comma; *p; p++) - if (isxdigit(*p)) - dig = 1; - else if (*p == ':') - colon = 1; - else - break; - - unhide_metas(comma); - if (option == 'U' || option == 'j' || *p || !dig || !colon) + else if (option != 'U' || new->enterprise == 0) { - new->len = strlen(comma); - new->data = opt_malloc(new->len); - memcpy(new->data, comma, new->len); - } - else - { - new->len = parse_hex(comma, (unsigned char *)comma, strlen(comma), NULL, NULL); - new->data = opt_malloc(new->len); - memcpy(new->data, comma, new->len); + free(new->netid.net); + ret_err_free(gen_err, new); } switch (option) @@ -4635,7 +4637,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma } new->next = daemon->dhcp_vendors; daemon->dhcp_vendors = new; - + break; } diff --git a/src/rfc3315.c b/src/rfc3315.c index 31a88b0..cc2510f 100644 --- a/src/rfc3315.c +++ b/src/rfc3315.c @@ -392,13 +392,26 @@ static int dhcp6_no_relay(struct state *state, int msg_type, unsigned char *inbu if (opt6_len(opt) < 4) continue; - if (vendor->enterprise != opt6_uint(opt, 0, 4)) + if (vendor->enterprise != 0 && vendor->enterprise != opt6_uint(opt, 0, 4)) continue; - + + /* matching enterprise, no string match. */ + if (vendor->enterprise != 0 && vendor->len == 0) + { + vendor->netid.next = state->tags; + state->tags = &vendor->netid; + break; + } + offset = 4; + + /* If we're going to search the strings below, there must be at least one empty string to search + I think a vendor_class option with just the enterprise number is valid. */ + if (opt6_len(opt) < 6) + continue; } - - /* Note that format if user/vendor classes is different to DHCP options - no option types. */ + + /* Note that format if user/vendor classes is different to DHCP options - no option types. */ for (enc_opt = opt6_ptr(opt, offset); enc_opt; enc_opt = opt6_user_vendor_next(enc_opt, enc_end)) for (i = 0; i <= (opt6_user_vendor_len(enc_opt) - vendor->len); i++) if (memcmp(vendor->data, opt6_user_vendor_ptr(enc_opt, i), vendor->len) == 0)