* [PATCH v4 0/2] [PATCH 0/2] dhcpv6: Add --dhcpv6-opt for custom DHCPv6 options @ 2026-10-06 9:04 Anshu Kumari 2026-10-06 9:04 ` [PATCH v4 1/2] dhcpv6: Add --dhcpv6-opt with option type table and value parser Anshu Kumari 2026-10-06 9:04 ` [PATCH v4 2/2] dhcpv6: Inject custom options into DHCPv6 replies Anshu Kumari 0 siblings, 2 replies; 5+ messages in thread From: Anshu Kumari @ 2026-10-06 9:04 UTC (permalink / raw) To: sbrivio, passt-dev; +Cc: lvivier, anskuma This series adds a --dhcpv6-opt CODE,VALUE command-line option to inject custom options into DHCPv6 replies, complementing the existing --dhcp-opt support for DHCPv4. The primary use case is UEFI HTTP Boot, which requires Vendor Class (option 16) with the correct enterprise-number + length-prefixed wire encoding per RFC 8415 Section 21.16, and Boot File URL (option 59). Value formats are determined automatically from the option code via a type table. Supported types include plain strings, IPv6 addresses (single and list), 8/16/32-bit integers, vendor class (ENTERPRISE:DATA), and length-prefixed string lists. Anshu Kumari (2): dhcpv6: Add --dhcpv6-opt with option type table and value parser dhcpv6: Inject custom options into DHCPv6 replies conf.c | 26 +++- dhcpv6.c | 375 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ dhcpv6.h | 2 + passt.1 | 31 +++++ 4 files changed, 433 insertions(+), 1 deletion(-) -- 2.55.0 ^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH v4 1/2] dhcpv6: Add --dhcpv6-opt with option type table and value parser 2026-10-06 9:04 [PATCH v4 0/2] [PATCH 0/2] dhcpv6: Add --dhcpv6-opt for custom DHCPv6 options Anshu Kumari @ 2026-10-06 9:04 ` Anshu Kumari 2026-10-25 12:01 ` Stefano Brivio 2026-10-06 9:04 ` [PATCH v4 2/2] dhcpv6: Inject custom options into DHCPv6 replies Anshu Kumari 1 sibling, 1 reply; 5+ messages in thread From: Anshu Kumari @ 2026-10-06 9:04 UTC (permalink / raw) To: sbrivio, passt-dev; +Cc: lvivier, anskuma Introduce the --dhcpv6-opt flag that allows setting arbitrary DHCPv6 options from command-line in the form [--dhcpv6-opt CODE,VALUE]. Add a type lookup table mapping option codes to value types (IPv6, IPv6 list, integer, string, vendor class, length-prefixed string list) and dhcpv6_opt_parse() to convert CLI strings to binary wire format. If the same option code is given more than once, the last value wins. Link: https://bugs.passt.top/show_bug.cgi?id=192 Signed-off-by: Anshu Kumari <anskuma@redhat.com> --- v4: - The dhcpv6_opt_code < 1 check was replaced with proper validation: dhcpv6_opt_code == 0 || dhcpv6_opt_code > UINT16_MAX in conf.c - All uint16_t/uint32_t writes to byte buffers now use intermediate variables + memcpy() instead of direct pointer casts. - The DHCPV6_OPT_STR case now uses slen > buf_len instead of slen >= buf_len. - Option 17 (Vendor-specific Info) excluded. - A separate char optbuf[512] was added in conf_print() instead of reusing the small INANY_ADDRSTRLEN-sized buf for option rendering. - inet_ntop() return value checked. - Uses off += strlen(buf + off) instead of off = strlen(buf) in the IPv6 list rendering loop. - Iterator macro added — for_each_in6_off(off, len). - The addr and af variables are declared at function scope in dhcpv6_opt_parse() rather than inside block wrappers. - snprintf() alignment and line formatting. v3: - Use parse_unsigned(), parse_literal(), parse_eoi() from parse.c instead of manual strtoul()/errno/strchr() in dhcpv6_opt_parse() for UINT and VENDOR_CLASS cases. - Store options as pre-parsed binary in a static array indexed by option code, instead of storing raw strings. - Add dhcpv6_opt_to_str() to render binary option values back to printable strings for startup logging in conf_print(). v2: - Renamed custom_v6opts to dhcpv6_opts, MAX_CUSTOM_DHCPV6_OPTS to MAX_DHCPV6_OPTS. - Dropped val/len from ctx struct. - Moved dhcpv6_add_option() to conf.c as static conf_dhcpv6_option(). - Made dhcpv6_opt_parse() non-static, declared in dhcpv6.h - Omitted explicit [256] from dhcpv6_opt_types[]. - Moved chunk declaration into while block. - Removed redundant !slen check in DHCPV6_OPT_STR case. - All errors in dhcpv6_opt_parse() return -1, removed die() calls. --- conf.c | 26 ++++- dhcpv6.c | 339 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ dhcpv6.h | 2 + passt.1 | 31 +++++ 4 files changed, 397 insertions(+), 1 deletion(-) diff --git a/conf.c b/conf.c index faf26814..dba62bf8 100644 --- a/conf.c +++ b/conf.c @@ -47,6 +47,7 @@ #include "lineread.h" #include "isolation.h" #include "log.h" +#include "dhcpv6.h" #include "vhost_user.h" #include "epoll_ctl.h" #include "conf.h" @@ -632,7 +633,8 @@ static void usage(const char *name, FILE *f, int status) " -S, --search LIST Space-separated list, search domains\n" " a single, empty option disables the DNS search list\n" " -H, --hostname NAME Hostname to configure client with\n" - " --fqdn NAME FQDN to configure client with\n"); + " --fqdn NAME FQDN to configure client with\n" + " --dhcpv6-opt CODE,VAL Set DHCPv6 option CODE to VAL\n"); if (strstr(name, "pasta")) FPRINTF(f, " default: don't use any search list\n"); else @@ -800,6 +802,7 @@ enum passt_modes conf_mode(int argc, char *argv[]) static void conf_print(const struct ctx *c) { char buf[INANY_ADDRSTRLEN]; + char optbuf[512]; int i; if (c->fd_control_listen >= 0) @@ -900,6 +903,10 @@ static void conf_print(const struct ctx *c) info(" our link-local: %s", inet_ntop(AF_INET6, &c->ip6.our_tap_ll, buf, sizeof(buf))); + for (i = 1; i < UINT16_MAX; i++) { + if (dhcpv6_opt_to_str(i, optbuf, sizeof(optbuf))) + info(" option %u: %s", i, optbuf); + } dns6: for (i = 0; i < ARRAY_SIZE(c->ip6.dns); i++) { @@ -1348,6 +1355,7 @@ void conf(struct ctx *c, int argc, char **argv) {"stats", required_argument, NULL, 31 }, {"conf-path", required_argument, NULL, 'c' }, {"chroot-fallback", no_argument, NULL, 32 }, + {"dhcpv6-opt", required_argument, NULL, 35 }, { 0 }, }; const char *optstring = "+dqfel:hs:c:F:I:p:P:m:a:n:M:g:i:o:D:S:H:461t:u:T:U:"; @@ -1589,6 +1597,22 @@ void conf(struct ctx *c, int argc, char **argv) case 32: c->chroot_fallback = true; break; + case 35: { + unsigned long dhcpv6_opt_code; + + p = optarg; + + if (!parse_unsigned(&p, 0, &dhcpv6_opt_code) || + !parse_literal(&p, ",")) + die("--dhcpv6-opt requires CODE,VALUE format"); + + if (dhcpv6_opt_code == 0 || + dhcpv6_opt_code > UINT16_MAX) + die("Invalid DHCPv6 option code: %s", optarg); + + dhcpv6_set_opt(dhcpv6_opt_code, p); + break; + } case 'd': c->debug = 1; c->quiet = 0; diff --git a/dhcpv6.c b/dhcpv6.c index 29c7e320..abc45813 100644 --- a/dhcpv6.c +++ b/dhcpv6.c @@ -31,6 +31,8 @@ #include "passt.h" #include "tap.h" #include "log.h" +#include "inany.h" +#include "parse.h" /** * struct opt_hdr - DHCPv6 option header @@ -63,6 +65,11 @@ struct opt_hdr { #define OPT_MAX_SIZE IPV6_MIN_MTU - (sizeof(struct ipv6hdr) + \ UDP_MSG_HDR_SIZE) +#define for_each_in6_off(off, len) \ + for ((off) = 0; \ + (off) + sizeof(struct in6_addr) <= (unsigned int)(len); \ + (off) += sizeof(struct in6_addr)) + /** * struct opt_client_id - DHCPv6 Client Identifier option * @hdr: Option header @@ -278,6 +285,338 @@ static struct resp_not_on_link_t { { 0, }, }; +/** + * enum dhcpv6_opt_type - DHCPv6 option value types + * @DHCPV6_OPT_NONE: Unsupported or unknown option + * @DHCPV6_OPT_STR: Variable-length string + * @DHCPV6_OPT_IPV6: Single IPv6 address + * @DHCPV6_OPT_IPV6_LIST: Multiple IPv6 addresses, comma-separated + * @DHCPV6_OPT_UINT8: Unsigned 8-bit integer + * @DHCPV6_OPT_UINT16: Unsigned 16-bit integer + * @DHCPV6_OPT_UINT32: Unsigned 32-bit integer + * @DHCPV6_OPT_VENDOR_CLASS: Enterprise number + length-prefixed data + * @DHCPV6_OPT_LEN_STR_LIST: Length-prefixed string list + */ +enum dhcpv6_opt_type { + DHCPV6_OPT_NONE, + DHCPV6_OPT_STR, + DHCPV6_OPT_IPV6, + DHCPV6_OPT_IPV6_LIST, + DHCPV6_OPT_UINT8, + DHCPV6_OPT_UINT16, + DHCPV6_OPT_UINT32, + DHCPV6_OPT_VENDOR_CLASS, + DHCPV6_OPT_LEN_STR_LIST, +}; + +/** + * dhcpv6_opt_types - Maps DHCPv6 option code to value type, indexed by code + * RFC 8415 Options: 7, 15, 16, 32, 82, 83 + * RFC 5970 Options: 59, 60 + * RFC 4075 Options: 31 + */ +static const enum dhcpv6_opt_type dhcpv6_opt_types[] = { + [7] = DHCPV6_OPT_UINT8, /* Preference */ + [15] = DHCPV6_OPT_LEN_STR_LIST, /* User Class */ + [16] = DHCPV6_OPT_VENDOR_CLASS, /* Vendor Class */ + [31] = DHCPV6_OPT_IPV6_LIST, /* SNTP Servers */ + [32] = DHCPV6_OPT_UINT32, /* Information Refresh Time */ + [59] = DHCPV6_OPT_STR, /* Boot File URL */ + [60] = DHCPV6_OPT_LEN_STR_LIST, /* Boot File Params */ + [82] = DHCPV6_OPT_UINT32, /* SOL_MAX_RT */ + [83] = DHCPV6_OPT_UINT32, /* INF_MAX_RT */ +}; + +/** + * dhcpv6_opt_parse() - Parse a DHCPv6 option value string into binary + * @code: DHCPv6 option code + * @str: Value string from command line + * @buf: Output buffer for binary value + * @buf_len: Size of output buffer + * + * Return: number of bytes written to @buf, or -1 on error + */ +static int dhcpv6_opt_parse(uint16_t code, const char *str, + uint8_t *buf, size_t buf_len) +{ + enum dhcpv6_opt_type type; + union inany_addr addr; + unsigned long val; + uint16_t slen_net; + sa_family_t af; + uint8_t width; + uint32_t ent; + size_t slen; + int len; + + if (!*str) + return -1; + + if (code >= ARRAY_SIZE(dhcpv6_opt_types)) + return -1; + + type = dhcpv6_opt_types[code]; + + switch (type) { + case DHCPV6_OPT_NONE: + return -1; + case DHCPV6_OPT_IPV6: + case DHCPV6_OPT_IPV6_LIST: + len = 0; + + do { + if (len + sizeof(struct in6_addr) > buf_len) + return -1; + + if (!parse_inany_(&str, &addr, &af) || af != AF_INET6) + return -1; + + memcpy(buf + len, &addr.a6, sizeof(struct in6_addr)); + len += sizeof(struct in6_addr); + + if (type == DHCPV6_OPT_IPV6) + break; + } while (parse_literal(&str, ",")); + + if (!len || !parse_eoi(str)) + return -1; + + return len; + case DHCPV6_OPT_UINT8: + case DHCPV6_OPT_UINT16: + case DHCPV6_OPT_UINT32: + if (type == DHCPV6_OPT_UINT8) + width = 1; + else if (type == DHCPV6_OPT_UINT16) + width = 2; + else + width = 4; + + if (buf_len < width) + return -1; + + if (!parse_unsigned(&str, 0, &val) || !parse_eoi(str) || + val >= (1ULL << (width * 8))) + return -1; + + if (type == DHCPV6_OPT_UINT16) { + uint16_t v16 = htons(val); + memcpy(buf, &v16, sizeof(v16)); + } else if (type == DHCPV6_OPT_UINT32) { + uint32_t v32 = htonl(val); + memcpy(buf, &v32, sizeof(v32)); + } else { + buf[0] = val; + } + + return width; + case DHCPV6_OPT_STR: + slen = strlen(str); + + if (slen > buf_len) + return -1; + + memcpy(buf, str, slen); + + return slen; + case DHCPV6_OPT_VENDOR_CLASS: + if (!parse_unsigned(&str, 0, &val) || + !parse_literal(&str, ":") || val > UINT32_MAX) + return -1; + + slen = strlen(str); + if (!slen) + return -1; + + len = sizeof(uint32_t) + sizeof(uint16_t) + slen; + if ((size_t)len > buf_len) + return -1; + + ent = htonl(val); + memcpy(buf, &ent, sizeof(ent)); + + slen_net = htons(slen); + memcpy(buf + sizeof(uint32_t), &slen_net, sizeof(slen_net)); + + memcpy(buf + sizeof(uint32_t) + sizeof(uint16_t), str, slen); + + return len; + case DHCPV6_OPT_LEN_STR_LIST: + len = 0; + + while (*str) { + slen = strcspn(str, ","); + if (!slen) + return -1; + + if (len + (int)(sizeof(uint16_t) + slen) > (int)buf_len) + return -1; + + slen_net = htons(slen); + memcpy(buf + len, &slen_net, sizeof(slen_net)); + len += sizeof(uint16_t); + + memcpy(buf + len, str, slen); + len += slen; + + str += slen; + if (*str == ',') + str++; + } + + if (!len) + return -1; + + return len; + } + + return -1; +} + +/** + * struct dhcpv6_user_opts - User-specified DHCPv6 options from --dhcpv6-opt + * @val: Binary option value in wire format + * @len: Length of @val in bytes, 0 if not set + */ +static struct { + uint8_t val[UINT16_MAX]; + int len; +} dhcpv6_user_opts[ARRAY_SIZE(dhcpv6_opt_types)]; + +/** + * dhcpv6_opt_to_str() - Render a binary DHCPv6 option value to printable string + * @code: DHCPv6 option code + * @buf: Output string buffer + * @buf_len: Size of output buffer + * + * Return: pointer to @buf if option is set, NULL otherwise + */ +const char *dhcpv6_opt_to_str(uint16_t code, char *buf, size_t buf_len) +{ + enum dhcpv6_opt_type type; + unsigned int i; + uint16_t slen; + int off = 0; + + if (code >= ARRAY_SIZE(dhcpv6_user_opts) || + !dhcpv6_user_opts[code].len) + return NULL; + + type = dhcpv6_opt_types[code]; + + switch (type) { + case DHCPV6_OPT_IPV6: + case DHCPV6_OPT_IPV6_LIST: + for_each_in6_off(i, dhcpv6_user_opts[code].len) { + if (off) { + if (off + 1 >= (int)buf_len) + return NULL; + buf[off++] = ','; + } + if (!inet_ntop(AF_INET6, + dhcpv6_user_opts[code].val + i, + buf + off, buf_len - off)) + return NULL; + off += strlen(buf + off); + } + return buf; + case DHCPV6_OPT_UINT8: + case DHCPV6_OPT_UINT16: + case DHCPV6_OPT_UINT32: { + uint32_t val = 0; + + if (dhcpv6_user_opts[code].len == 1) { + val = dhcpv6_user_opts[code].val[0]; + } else if (dhcpv6_user_opts[code].len == 2) { + uint16_t v16; + memcpy(&v16, dhcpv6_user_opts[code].val, sizeof(v16)); + val = ntohs(v16); + } else if (dhcpv6_user_opts[code].len == 4) { + memcpy(&val, dhcpv6_user_opts[code].val, sizeof(val)); + val = ntohl(val); + } + + (void)snprintf(buf, buf_len, "%u", val); + return buf; + } + case DHCPV6_OPT_STR: + (void)snprintf(buf, buf_len, "%.*s", + dhcpv6_user_opts[code].len, + dhcpv6_user_opts[code].val); + return buf; + case DHCPV6_OPT_VENDOR_CLASS: { + uint32_t ent; + int doff; + + if (dhcpv6_user_opts[code].len < + (int)(sizeof(uint32_t) + sizeof(uint16_t))) + return NULL; + + memcpy(&ent, dhcpv6_user_opts[code].val, sizeof(ent)); + memcpy(&slen, dhcpv6_user_opts[code].val + sizeof(uint32_t), + sizeof(slen)); + doff = sizeof(uint32_t) + sizeof(uint16_t); + + (void)snprintf(buf, buf_len, "%u:%.*s", + ntohl(ent), ntohs(slen), + dhcpv6_user_opts[code].val + doff); + return buf; + } + case DHCPV6_OPT_LEN_STR_LIST: + off = 0; + i = 0; + + while (i + sizeof(uint16_t) <= + (unsigned int)dhcpv6_user_opts[code].len) { + memcpy(&slen, dhcpv6_user_opts[code].val + i, + sizeof(slen)); + slen = ntohs(slen); + i += sizeof(uint16_t); + + if (i + slen > (unsigned int)dhcpv6_user_opts[code].len) + break; + + if (off) { + if (off + 1 >= (int)buf_len) + return NULL; + buf[off++] = ','; + } + if (off + slen >= (int)buf_len) + return NULL; + memcpy(buf + off, dhcpv6_user_opts[code].val + i, + slen); + off += slen; + i += slen; + } + buf[off] = '\0'; + return buf; + default: + return NULL; + } +} + +/** + * dhcpv6_set_opt() - Parse and store a user-specified DHCPv6 option + * @code: DHCPv6 option code + * @val_str: Value string from command line + */ +void dhcpv6_set_opt(uint16_t code, const char *val_str) +{ + int ret; + + if (code >= ARRAY_SIZE(dhcpv6_opt_types) || + dhcpv6_opt_types[code] == DHCPV6_OPT_NONE) + die("DHCPv6 option %u is not supported", code); + + ret = dhcpv6_opt_parse(code, val_str, + dhcpv6_user_opts[code].val, + sizeof(dhcpv6_user_opts[code].val)); + if (ret < 0) + die("Invalid value for DHCPv6 option %u: %s", code, val_str); + + dhcpv6_user_opts[code].len = ret; +} + /** * dhcpv6_opt() - Get option from DHCPv6 message * @data: Buffer with options, set to matching option on return diff --git a/dhcpv6.h b/dhcpv6.h index 1015a1a7..973c477c 100644 --- a/dhcpv6.h +++ b/dhcpv6.h @@ -9,5 +9,7 @@ int dhcpv6(struct ctx *c, struct iov_tail *data, const struct in6_addr *saddr, const struct in6_addr *daddr); void dhcpv6_init(const struct ctx *c); +void dhcpv6_set_opt(uint16_t code, const char *val_str); +const char *dhcpv6_opt_to_str(uint16_t code, char *buf, size_t buf_len); #endif /* DHCPV6_H */ diff --git a/passt.1 b/passt.1 index 53e072a9..249ed5cf 100644 --- a/passt.1 +++ b/passt.1 @@ -440,6 +440,37 @@ Send \fIname\fR as DHCP option 12 (hostname). FQDN to configure the client with. Send \fIname\fR as Client FQDN: DHCP option 81 and DHCPv6 option 39. +.TP +.BR \-\-dhcpv6-opt " " \fICODE\fR,\fIVALUE\fR +Set DHCPv6 option \fICODE\fR to \fIVALUE\fR. The value format depends +on the option type and is determined automatically from the option code. +Multiple IPv6 addresses are comma-separated. +This option can be specified multiple times. If the same option code is +given more than once, the last value wins. +.RS +.TP +.B String options +59 (Boot File URL, RFC 5970) +.TP +.B Length-prefixed string list options (comma-separated entries) +15 (User Class, RFC 8415), 60 (Boot File Params, RFC 5970). +Each comma-separated entry is encoded with a 2-byte length prefix. +Example: \fB\-\-dhcpv6-opt 15,class1,class2\fR. +.TP +.B Vendor class options (ENTERPRISE:DATA format) +16 (Vendor Class, RFC 8415). +VALUE is \fIENTERPRISE\fR:\fIDATA\fR where \fIENTERPRISE\fR is the IANA +Private Enterprise Number and \fIDATA\fR is the vendor class string. +Example: \fB\-\-dhcpv6-opt 16,0:HTTPClient\fR for UEFI HTTP Boot. +.TP +.B IPv6 address list options (comma-separated) +31 (SNTP Servers) +.TP +.B Integer options +7 (Preference, 8-bit), 32 (Information Refresh Time, 32-bit), +82 (SOL_MAX_RT, 32-bit), 83 (INF_MAX_RT, 32-bit) +.RE + .TP .BR \-t ", " \-\-tcp-ports " " \fIspec Configure TCP port forwarding to guest or namespace. \fIspec\fR can be either: -- 2.55.0 ^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH v4 1/2] dhcpv6: Add --dhcpv6-opt with option type table and value parser 2026-10-06 9:04 ` [PATCH v4 1/2] dhcpv6: Add --dhcpv6-opt with option type table and value parser Anshu Kumari @ 2026-10-25 12:01 ` Stefano Brivio 0 siblings, 0 replies; 5+ messages in thread From: Stefano Brivio @ 2026-10-25 12:01 UTC (permalink / raw) To: Anshu Kumari; +Cc: passt-dev, lvivier On Tue, 6 Oct 2026 14:34:52 +0530 Anshu Kumari <anskuma@redhat.com> wrote: > Introduce the --dhcpv6-opt flag that allows setting arbitrary DHCPv6 > options from command-line in the form [--dhcpv6-opt CODE,VALUE]. > > Add a type lookup table mapping option codes to value types (IPv6, > IPv6 list, integer, string, vendor class, length-prefixed string > list) and dhcpv6_opt_parse() to convert CLI strings to binary wire > format. If the same option code is given more than once, the > last value wins. > > Link: https://bugs.passt.top/show_bug.cgi?id=192 > Signed-off-by: Anshu Kumari <anskuma@redhat.com> > --- > v4: > - The dhcpv6_opt_code < 1 check was replaced with proper validation: > dhcpv6_opt_code == 0 || dhcpv6_opt_code > UINT16_MAX in conf.c > - All uint16_t/uint32_t writes to byte buffers now use intermediate > variables + memcpy() instead of direct pointer casts. > - The DHCPV6_OPT_STR case now uses slen > buf_len instead of slen >= buf_len. > - Option 17 (Vendor-specific Info) excluded. > - A separate char optbuf[512] was added in conf_print() instead of > reusing the small INANY_ADDRSTRLEN-sized buf for option rendering. > - inet_ntop() return value checked. > - Uses off += strlen(buf + off) instead of off = strlen(buf) in > the IPv6 list rendering loop. > - Iterator macro added — for_each_in6_off(off, len). > - The addr and af variables are declared at function scope > in dhcpv6_opt_parse() rather than inside block wrappers. > - snprintf() alignment and line formatting. > > v3: > - Use parse_unsigned(), parse_literal(), parse_eoi() from parse.c > instead of manual strtoul()/errno/strchr() in dhcpv6_opt_parse() > for UINT and VENDOR_CLASS cases. > - Store options as pre-parsed binary in a static array indexed by > option code, instead of storing raw strings. > - Add dhcpv6_opt_to_str() to render binary option values back to > printable strings for startup logging in conf_print(). > > v2: > - Renamed custom_v6opts to dhcpv6_opts, MAX_CUSTOM_DHCPV6_OPTS > to MAX_DHCPV6_OPTS. > - Dropped val/len from ctx struct. > - Moved dhcpv6_add_option() to conf.c as static > conf_dhcpv6_option(). > - Made dhcpv6_opt_parse() non-static, declared in dhcpv6.h > - Omitted explicit [256] from dhcpv6_opt_types[]. > - Moved chunk declaration into while block. > - Removed redundant !slen check in DHCPV6_OPT_STR case. > - All errors in dhcpv6_opt_parse() return -1, removed die() > calls. > --- > conf.c | 26 ++++- > dhcpv6.c | 339 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ > dhcpv6.h | 2 + > passt.1 | 31 +++++ > 4 files changed, 397 insertions(+), 1 deletion(-) Note: this will have a trivial conflict with --no-pidns in conf(), will need a rebase. > diff --git a/conf.c b/conf.c > index faf26814..dba62bf8 100644 > --- a/conf.c > +++ b/conf.c > @@ -47,6 +47,7 @@ > #include "lineread.h" > #include "isolation.h" > #include "log.h" > +#include "dhcpv6.h" > #include "vhost_user.h" > #include "epoll_ctl.h" > #include "conf.h" > @@ -632,7 +633,8 @@ static void usage(const char *name, FILE *f, int status) > " -S, --search LIST Space-separated list, search domains\n" > " a single, empty option disables the DNS search list\n" > " -H, --hostname NAME Hostname to configure client with\n" > - " --fqdn NAME FQDN to configure client with\n"); > + " --fqdn NAME FQDN to configure client with\n" > + " --dhcpv6-opt CODE,VAL Set DHCPv6 option CODE to VAL\n"); > if (strstr(name, "pasta")) > FPRINTF(f, " default: don't use any search list\n"); > else > @@ -800,6 +802,7 @@ enum passt_modes conf_mode(int argc, char *argv[]) > static void conf_print(const struct ctx *c) > { > char buf[INANY_ADDRSTRLEN]; > + char optbuf[512]; We could actually pack about 4k IPv6 addresses in a DHCPv6 option, and they need up to 46 bytes (IPv4-mapped address, with separator) to be represented, but having ~ 190 KiB on the stack for that isn't great. This doesn't make any practical sense for the moment by the way, as all we can encode as list of addresses is SNTP servers (and we won't have 12 of them), but maybe in the future we might want to support a larger number of addresses for some other option. So you could do this: use a reasonable multiple of INANY_ADDRSTRLEN here (say, INANY_ADDRSTRLEN * 2, note that it already includes a terminator which you can use as separator), and then: > int i; > > if (c->fd_control_listen >= 0) > @@ -900,6 +903,10 @@ static void conf_print(const struct ctx *c) > info(" our link-local: %s", > inet_ntop(AF_INET6, &c->ip6.our_tap_ll, > buf, sizeof(buf))); > + for (i = 1; i < UINT16_MAX; i++) { > + if (dhcpv6_opt_to_str(i, optbuf, sizeof(optbuf))) ...pass an offset, starting from 0, opaque to the caller, and turn this into a loop, say: while (dhcpv6_opt_to_str(i, buf, sizeof(buf)), &off) and dhcpv6_opt_to_str() will use that offset to go over addresses (see comments there). With that, you would print several "option" lines in case you exceed the buffer, but it's actually better than the alternative, I think. By limiting the buffer to 2 addresses, the output could be even easier to read. > + info(" option %u: %s", i, optbuf); > + } > > dns6: > for (i = 0; i < ARRAY_SIZE(c->ip6.dns); i++) { > @@ -1348,6 +1355,7 @@ void conf(struct ctx *c, int argc, char **argv) > {"stats", required_argument, NULL, 31 }, > {"conf-path", required_argument, NULL, 'c' }, > {"chroot-fallback", no_argument, NULL, 32 }, > + {"dhcpv6-opt", required_argument, NULL, 35 }, At some point it wasn't done consistently anymore, but I think it would be nice to not break this further (and maybe make this all consistent at some point): the 'has_arg' fields were aligned with tabs where possible, that is: {"dhcpv6-opt", required_argument, NULL, 35 }, instead. > { 0 }, > }; > const char *optstring = "+dqfel:hs:c:F:I:p:P:m:a:n:M:g:i:o:D:S:H:461t:u:T:U:"; > @@ -1589,6 +1597,22 @@ void conf(struct ctx *c, int argc, char **argv) > case 32: > c->chroot_fallback = true; > break; > + case 35: { Same comment as for the DHCP patches about extra / artificial code blocks. > + unsigned long dhcpv6_opt_code; > + > + p = optarg; > + > + if (!parse_unsigned(&p, 0, &dhcpv6_opt_code) || > + !parse_literal(&p, ",")) > + die("--dhcpv6-opt requires CODE,VALUE format"); > + > + if (dhcpv6_opt_code == 0 || > + dhcpv6_opt_code > UINT16_MAX) > + die("Invalid DHCPv6 option code: %s", optarg); > + > + dhcpv6_set_opt(dhcpv6_opt_code, p); > + break; > + } > case 'd': > c->debug = 1; > c->quiet = 0; > diff --git a/dhcpv6.c b/dhcpv6.c > index 29c7e320..abc45813 100644 > --- a/dhcpv6.c > +++ b/dhcpv6.c > @@ -31,6 +31,8 @@ > #include "passt.h" > #include "tap.h" > #include "log.h" > +#include "inany.h" > +#include "parse.h" > > /** > * struct opt_hdr - DHCPv6 option header > @@ -63,6 +65,11 @@ struct opt_hdr { > #define OPT_MAX_SIZE IPV6_MIN_MTU - (sizeof(struct ipv6hdr) + \ > UDP_MSG_HDR_SIZE) > > +#define for_each_in6_off(off, len) \ > + for ((off) = 0; \ > + (off) + sizeof(struct in6_addr) <= (unsigned int)(len); \ > + (off) += sizeof(struct in6_addr)) > + > /** > * struct opt_client_id - DHCPv6 Client Identifier option > * @hdr: Option header > @@ -278,6 +285,338 @@ static struct resp_not_on_link_t { > { 0, }, > }; > > +/** > + * enum dhcpv6_opt_type - DHCPv6 option value types > + * @DHCPV6_OPT_NONE: Unsupported or unknown option > + * @DHCPV6_OPT_STR: Variable-length string > + * @DHCPV6_OPT_IPV6: Single IPv6 address > + * @DHCPV6_OPT_IPV6_LIST: Multiple IPv6 addresses, comma-separated > + * @DHCPV6_OPT_UINT8: Unsigned 8-bit integer > + * @DHCPV6_OPT_UINT16: Unsigned 16-bit integer > + * @DHCPV6_OPT_UINT32: Unsigned 32-bit integer > + * @DHCPV6_OPT_VENDOR_CLASS: Enterprise number + length-prefixed data > + * @DHCPV6_OPT_LEN_STR_LIST: Length-prefixed string list > + */ > +enum dhcpv6_opt_type { > + DHCPV6_OPT_NONE, > + DHCPV6_OPT_STR, > + DHCPV6_OPT_IPV6, > + DHCPV6_OPT_IPV6_LIST, > + DHCPV6_OPT_UINT8, > + DHCPV6_OPT_UINT16, > + DHCPV6_OPT_UINT32, > + DHCPV6_OPT_VENDOR_CLASS, > + DHCPV6_OPT_LEN_STR_LIST, > +}; > + > +/** > + * dhcpv6_opt_types - Maps DHCPv6 option code to value type, indexed by code It's an enum, so this should say " * enum ...". And "map" (imperative) instead of "maps" (third-person indicative) as it's the convention we use in all code comments. About the name: I was about to suggest dhcpv6_opt_type, because the fact it's an array makes it plural ('s' -> '[]'), but it conflicts with the enum describing types. What about calling this dhcpv6_opt in a more general sense? Say: * enum dhcpv6_opt - Description of DHCPv6 options (value type), indexed by code ? > + * RFC 8415 Options: 7, 15, 16, 32, 82, 83 I was mentioning it here a while ago: https://archives.passt.top/passt-dev/20260620000930.62f30fee@elisabeth/ ...RFC 8415 was obsoleted by RFC 9915. Option codes remain the same. > + * RFC 5970 Options: 59, 60 > + * RFC 4075 Options: 31 > + */ > +static const enum dhcpv6_opt_type dhcpv6_opt_types[] = { > + [7] = DHCPV6_OPT_UINT8, /* Preference */ > + [15] = DHCPV6_OPT_LEN_STR_LIST, /* User Class */ > + [16] = DHCPV6_OPT_VENDOR_CLASS, /* Vendor Class */ > + [31] = DHCPV6_OPT_IPV6_LIST, /* SNTP Servers */ > + [32] = DHCPV6_OPT_UINT32, /* Information Refresh Time */ > + [59] = DHCPV6_OPT_STR, /* Boot File URL */ > + [60] = DHCPV6_OPT_LEN_STR_LIST, /* Boot File Params */ > + [82] = DHCPV6_OPT_UINT32, /* SOL_MAX_RT */ > + [83] = DHCPV6_OPT_UINT32, /* INF_MAX_RT */ > +}; > + > +/** > + * dhcpv6_opt_parse() - Parse a DHCPv6 option value string into binary > + * @code: DHCPv6 option code > + * @str: Value string from command line > + * @buf: Output buffer for binary value > + * @buf_len: Size of output buffer > + * > + * Return: number of bytes written to @buf, or -1 on error > + */ > +static int dhcpv6_opt_parse(uint16_t code, const char *str, > + uint8_t *buf, size_t buf_len) > +{ > + enum dhcpv6_opt_type type; > + union inany_addr addr; > + unsigned long val; > + uint16_t slen_net; > + sa_family_t af; > + uint8_t width; > + uint32_t ent; > + size_t slen; > + int len; > + > + if (!*str) > + return -1; > + > + if (code >= ARRAY_SIZE(dhcpv6_opt_types)) > + return -1; > + > + type = dhcpv6_opt_types[code]; > + > + switch (type) { > + case DHCPV6_OPT_NONE: > + return -1; > + case DHCPV6_OPT_IPV6: > + case DHCPV6_OPT_IPV6_LIST: > + len = 0; > + > + do { > + if (len + sizeof(struct in6_addr) > buf_len) > + return -1; > + > + if (!parse_inany_(&str, &addr, &af) || af != AF_INET6) > + return -1; > + > + memcpy(buf + len, &addr.a6, sizeof(struct in6_addr)); > + len += sizeof(struct in6_addr); > + > + if (type == DHCPV6_OPT_IPV6) > + break; > + } while (parse_literal(&str, ",")); > + > + if (!len || !parse_eoi(str)) > + return -1; > + > + return len; > + case DHCPV6_OPT_UINT8: > + case DHCPV6_OPT_UINT16: > + case DHCPV6_OPT_UINT32: > + if (type == DHCPV6_OPT_UINT8) > + width = 1; > + else if (type == DHCPV6_OPT_UINT16) > + width = 2; > + else > + width = 4; > + > + if (buf_len < width) > + return -1; > + > + if (!parse_unsigned(&str, 0, &val) || !parse_eoi(str) || > + val >= (1ULL << (width * 8))) > + return -1; > + > + if (type == DHCPV6_OPT_UINT16) { > + uint16_t v16 = htons(val); > + memcpy(buf, &v16, sizeof(v16)); > + } else if (type == DHCPV6_OPT_UINT32) { > + uint32_t v32 = htonl(val); > + memcpy(buf, &v32, sizeof(v32)); > + } else { > + buf[0] = val; > + } > + > + return width; > + case DHCPV6_OPT_STR: > + slen = strlen(str); > + > + if (slen > buf_len) > + return -1; > + > + memcpy(buf, str, slen); > + > + return slen; > + case DHCPV6_OPT_VENDOR_CLASS: > + if (!parse_unsigned(&str, 0, &val) || > + !parse_literal(&str, ":") || val > UINT32_MAX) > + return -1; > + > + slen = strlen(str); > + if (!slen) > + return -1; > + > + len = sizeof(uint32_t) + sizeof(uint16_t) + slen; > + if ((size_t)len > buf_len) > + return -1; > + > + ent = htonl(val); > + memcpy(buf, &ent, sizeof(ent)); > + > + slen_net = htons(slen); > + memcpy(buf + sizeof(uint32_t), &slen_net, sizeof(slen_net)); > + > + memcpy(buf + sizeof(uint32_t) + sizeof(uint16_t), str, slen); > + > + return len; > + case DHCPV6_OPT_LEN_STR_LIST: > + len = 0; > + > + while (*str) { > + slen = strcspn(str, ","); > + if (!slen) > + return -1; > + > + if (len + (int)(sizeof(uint16_t) + slen) > (int)buf_len) > + return -1; > + > + slen_net = htons(slen); > + memcpy(buf + len, &slen_net, sizeof(slen_net)); > + len += sizeof(uint16_t); > + > + memcpy(buf + len, str, slen); > + len += slen; > + > + str += slen; > + if (*str == ',') > + str++; > + } > + > + if (!len) > + return -1; > + > + return len; > + } > + > + return -1; > +} > + > +/** > + * struct dhcpv6_user_opts - User-specified DHCPv6 options from --dhcpv6-opt > + * @val: Binary option value in wire format > + * @len: Length of @val in bytes, 0 if not set > + */ > +static struct { > + uint8_t val[UINT16_MAX]; > + int len; > +} dhcpv6_user_opts[ARRAY_SIZE(dhcpv6_opt_types)]; > + > +/** > + * dhcpv6_opt_to_str() - Render a binary DHCPv6 option value to printable string > + * @code: DHCPv6 option code > + * @buf: Output string buffer > + * @buf_len: Size of output buffer > + * > + * Return: pointer to @buf if option is set, NULL otherwise If you change this to accept an offset that can be used for DHCPV6_OPT_IPV6_LIST, you can either keep returning a pointer to @buf *if this function wrote something* (the comment would need to be updated), or return a number of bytes written, which is perhaps less surprising. Both would work though. > + */ > +const char *dhcpv6_opt_to_str(uint16_t code, char *buf, size_t buf_len) > +{ > + enum dhcpv6_opt_type type; > + unsigned int i; > + uint16_t slen; > + int off = 0; > + > + if (code >= ARRAY_SIZE(dhcpv6_user_opts) || > + !dhcpv6_user_opts[code].len) This fits on one line now. > + return NULL; > + > + type = dhcpv6_opt_types[code]; > + > + switch (type) { > + case DHCPV6_OPT_IPV6: > + case DHCPV6_OPT_IPV6_LIST: > + for_each_in6_off(i, dhcpv6_user_opts[code].len) { ...to implement an opaque offset ("opaque" because the caller doesn't need to know what it is about, and just passes it as a pointer), this function can use that as initial value for 'i', and update it from 'i' before returning. You could call that parameter 'off', and do, before this loop: i = *off; and rename the current 'off' to 'buf_off' or similar. The rest would remain the same, I think. > + if (off) { > + if (off + 1 >= (int)buf_len) > + return NULL; > + buf[off++] = ','; > + } > + if (!inet_ntop(AF_INET6, > + dhcpv6_user_opts[code].val + i, > + buf + off, buf_len - off)) > + return NULL; > + off += strlen(buf + off); > + } And before returning, you would update *off = i;. > + return buf; > + case DHCPV6_OPT_UINT8: > + case DHCPV6_OPT_UINT16: > + case DHCPV6_OPT_UINT32: { Usual comment about extra code blocks (I'd suggest to just add a function-wide 'uint32_t val' and also use it for 'ent' below). > + uint32_t val = 0; > + > + if (dhcpv6_user_opts[code].len == 1) { > + val = dhcpv6_user_opts[code].val[0]; > + } else if (dhcpv6_user_opts[code].len == 2) { > + uint16_t v16; > + memcpy(&v16, dhcpv6_user_opts[code].val, sizeof(v16)); > + val = ntohs(v16); > + } else if (dhcpv6_user_opts[code].len == 4) { > + memcpy(&val, dhcpv6_user_opts[code].val, sizeof(val)); > + val = ntohl(val); > + } > + > + (void)snprintf(buf, buf_len, "%u", val); > + return buf; > + } > + case DHCPV6_OPT_STR: > + (void)snprintf(buf, buf_len, "%.*s", > + dhcpv6_user_opts[code].len, > + dhcpv6_user_opts[code].val); These two lines should be intended with three tabs plus spaces. > + return buf; > + case DHCPV6_OPT_VENDOR_CLASS: { > + uint32_t ent; > + int doff; ...and you could use 'off' for this one instead. It's also an offset. > + > + if (dhcpv6_user_opts[code].len < > + (int)(sizeof(uint32_t) + sizeof(uint16_t))) > + return NULL; > + > + memcpy(&ent, dhcpv6_user_opts[code].val, sizeof(ent)); > + memcpy(&slen, dhcpv6_user_opts[code].val + sizeof(uint32_t), > + sizeof(slen)); > + doff = sizeof(uint32_t) + sizeof(uint16_t); > + > + (void)snprintf(buf, buf_len, "%u:%.*s", > + ntohl(ent), ntohs(slen), > + dhcpv6_user_opts[code].val + doff); Same here, three tabs plus spaces. > + return buf; > + } > + case DHCPV6_OPT_LEN_STR_LIST: > + off = 0; > + i = 0; > + > + while (i + sizeof(uint16_t) <= > + (unsigned int)dhcpv6_user_opts[code].len) { If you use a pointer to refer to dhcpv6_user_opts[code].len (unsigned int *opt_len maybe), this becomes more readable. > + memcpy(&slen, dhcpv6_user_opts[code].val + i, Same for .val, here... > + sizeof(slen)); > + slen = ntohs(slen); > + i += sizeof(uint16_t); > + > + if (i + slen > (unsigned int)dhcpv6_user_opts[code].len) (same here for .len) > + break; > + > + if (off) { > + if (off + 1 >= (int)buf_len) > + return NULL; > + buf[off++] = ','; > + } > + if (off + slen >= (int)buf_len) > + return NULL; > + memcpy(buf + off, dhcpv6_user_opts[code].val + i, ...and here. > + slen); > + off += slen; > + i += slen; > + } > + buf[off] = '\0'; > + return buf; > + default: > + return NULL; > + } > +} > + > +/** > + * dhcpv6_set_opt() - Parse and store a user-specified DHCPv6 option > + * @code: DHCPv6 option code > + * @val_str: Value string from command line > + */ > +void dhcpv6_set_opt(uint16_t code, const char *val_str) > +{ > + int ret; > + > + if (code >= ARRAY_SIZE(dhcpv6_opt_types) || > + dhcpv6_opt_types[code] == DHCPV6_OPT_NONE) > + die("DHCPv6 option %u is not supported", code); > + > + ret = dhcpv6_opt_parse(code, val_str, > + dhcpv6_user_opts[code].val, > + sizeof(dhcpv6_user_opts[code].val)); > + if (ret < 0) > + die("Invalid value for DHCPv6 option %u: %s", code, val_str); > + > + dhcpv6_user_opts[code].len = ret; > +} > + > /** > * dhcpv6_opt() - Get option from DHCPv6 message > * @data: Buffer with options, set to matching option on return > diff --git a/dhcpv6.h b/dhcpv6.h > index 1015a1a7..973c477c 100644 > --- a/dhcpv6.h > +++ b/dhcpv6.h > @@ -9,5 +9,7 @@ > int dhcpv6(struct ctx *c, struct iov_tail *data, > const struct in6_addr *saddr, const struct in6_addr *daddr); > void dhcpv6_init(const struct ctx *c); > +void dhcpv6_set_opt(uint16_t code, const char *val_str); > +const char *dhcpv6_opt_to_str(uint16_t code, char *buf, size_t buf_len); > > #endif /* DHCPV6_H */ > diff --git a/passt.1 b/passt.1 > index 53e072a9..249ed5cf 100644 > --- a/passt.1 > +++ b/passt.1 > @@ -440,6 +440,37 @@ Send \fIname\fR as DHCP option 12 (hostname). > FQDN to configure the client with. > Send \fIname\fR as Client FQDN: DHCP option 81 and DHCPv6 option 39. > > +.TP > +.BR \-\-dhcpv6-opt " " \fICODE\fR,\fIVALUE\fR > +Set DHCPv6 option \fICODE\fR to \fIVALUE\fR. The value format depends > +on the option type and is determined automatically from the option code. > +Multiple IPv6 addresses are comma-separated. > +This option can be specified multiple times. If the same option code is > +given more than once, the last value wins. > +.RS > +.TP > +.B String options > +59 (Boot File URL, RFC 5970) > +.TP > +.B Length-prefixed string list options (comma-separated entries) > +15 (User Class, RFC 8415), 60 (Boot File Params, RFC 5970). Same as above about RFC 8415, here... > +Each comma-separated entry is encoded with a 2-byte length prefix. > +Example: \fB\-\-dhcpv6-opt 15,class1,class2\fR. > +.TP > +.B Vendor class options (ENTERPRISE:DATA format) > +16 (Vendor Class, RFC 8415). and here. > +VALUE is \fIENTERPRISE\fR:\fIDATA\fR where \fIENTERPRISE\fR is the IANA > +Private Enterprise Number and \fIDATA\fR is the vendor class string. > +Example: \fB\-\-dhcpv6-opt 16,0:HTTPClient\fR for UEFI HTTP Boot. > +.TP > +.B IPv6 address list options (comma-separated) > +31 (SNTP Servers) > +.TP > +.B Integer options > +7 (Preference, 8-bit), 32 (Information Refresh Time, 32-bit), > +82 (SOL_MAX_RT, 32-bit), 83 (INF_MAX_RT, 32-bit) > +.RE > + > .TP > .BR \-t ", " \-\-tcp-ports " " \fIspec > Configure TCP port forwarding to guest or namespace. \fIspec\fR can be either: -- Stefano ^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH v4 2/2] dhcpv6: Inject custom options into DHCPv6 replies 2026-10-06 9:04 [PATCH v4 0/2] [PATCH 0/2] dhcpv6: Add --dhcpv6-opt for custom DHCPv6 options Anshu Kumari 2026-10-06 9:04 ` [PATCH v4 1/2] dhcpv6: Add --dhcpv6-opt with option type table and value parser Anshu Kumari @ 2026-10-06 9:04 ` Anshu Kumari 2026-10-25 12:01 ` Stefano Brivio 1 sibling, 1 reply; 5+ messages in thread From: Anshu Kumari @ 2026-10-06 9:04 UTC (permalink / raw) To: sbrivio, passt-dev; +Cc: lvivier, anskuma Append user-specified options from --dhcpv6-opt to DHCPv6 reply messages. Options that exceed available buffer space are skipped with a warning. Link: https://bugs.passt.top/show_bug.cgi?id=192 Signed-off-by: Anshu Kumari <anskuma@redhat.com> --- v4: - warn() instead of debug() when options don't fit in the reply. - doc comment for dhcpv6_user_opts_fill() updated. v3: - rename dhcpv6_custom_opts_fill() to dhcpv6_user_opts_fill(). - Iterate directly over the pre-parsed binary dhcpv6_user_opts[] array instead of re-parsing string values at reply time. v2: - Updated dhcpv6_custom_opts_fill() to parse str at reply time using dhcpv6_opt_parse() instead of copying cached val/len. --- dhcpv6.c | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/dhcpv6.c b/dhcpv6.c index abc45813..a0545406 100644 --- a/dhcpv6.c +++ b/dhcpv6.c @@ -879,6 +879,41 @@ static size_t dhcpv6_client_fqdn_fill(const struct iov_tail *data, return offset + sizeof(struct opt_hdr) + opt_len; } +/** + * dhcpv6_user_opts_fill() - Append user-specified options to reply + * @buf: Response message buffer + * @offset: Current offset in buffer + * + * Return: offset after appended user options + */ +static size_t dhcpv6_user_opts_fill(char *buf, size_t offset) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(dhcpv6_user_opts); i++) { + int vlen = dhcpv6_user_opts[i].len; + struct opt_hdr *hdr; + + if (!vlen) + continue; + + if (offset + sizeof(struct opt_hdr) + vlen > OPT_MAX_SIZE) { + warn("DHCPv6: option %u doesn't fit, skipping", i); + continue; + } + + hdr = (struct opt_hdr *)(buf + offset); + hdr->t = htons(i); + hdr->l = htons(vlen); + offset += sizeof(struct opt_hdr); + + memcpy(buf + offset, dhcpv6_user_opts[i].val, vlen); + offset += vlen; + } + + return offset; +} + /** * dhcpv6() - Check if this is a DHCPv6 message, reply as needed * @c: Execution context @@ -1017,6 +1052,7 @@ int dhcpv6(struct ctx *c, struct iov_tail *data, sizeof(struct opt_hdr) + ntohs(client_id->l); n = dhcpv6_dns_fill(c, (char *)&resp, n); n = dhcpv6_client_fqdn_fill(data, c, (char *)&resp, n); + n = dhcpv6_user_opts_fill((char *)&resp, n); resp.hdr.xid = mh->xid; -- 2.55.0 ^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH v4 2/2] dhcpv6: Inject custom options into DHCPv6 replies 2026-10-06 9:04 ` [PATCH v4 2/2] dhcpv6: Inject custom options into DHCPv6 replies Anshu Kumari @ 2026-10-25 12:01 ` Stefano Brivio 0 siblings, 0 replies; 5+ messages in thread From: Stefano Brivio @ 2026-10-25 12:01 UTC (permalink / raw) To: Anshu Kumari; +Cc: passt-dev, lvivier On Tue, 6 Oct 2026 14:34:53 +0530 Anshu Kumari <anskuma@redhat.com> wrote: > Append user-specified options from --dhcpv6-opt to DHCPv6 reply > messages. Options that exceed available buffer space are skipped > with a warning. > > Link: https://bugs.passt.top/show_bug.cgi?id=192 > Signed-off-by: Anshu Kumari <anskuma@redhat.com> > --- > v4: > - warn() instead of debug() when options don't fit in the reply. > - doc comment for dhcpv6_user_opts_fill() updated. > > v3: > - rename dhcpv6_custom_opts_fill() to dhcpv6_user_opts_fill(). > - Iterate directly over the pre-parsed binary dhcpv6_user_opts[] > array instead of re-parsing string values at reply time. > > v2: > - Updated dhcpv6_custom_opts_fill() to parse str at reply time > using dhcpv6_opt_parse() instead of copying cached val/len. > --- > dhcpv6.c | 36 ++++++++++++++++++++++++++++++++++++ > 1 file changed, 36 insertions(+) > > diff --git a/dhcpv6.c b/dhcpv6.c > index abc45813..a0545406 100644 > --- a/dhcpv6.c > +++ b/dhcpv6.c > @@ -879,6 +879,41 @@ static size_t dhcpv6_client_fqdn_fill(const struct iov_tail *data, > return offset + sizeof(struct opt_hdr) + opt_len; > } > > +/** > + * dhcpv6_user_opts_fill() - Append user-specified options to reply > + * @buf: Response message buffer > + * @offset: Current offset in buffer > + * > + * Return: offset after appended user options > + */ > +static size_t dhcpv6_user_opts_fill(char *buf, size_t offset) > +{ > + unsigned int i; > + > + for (i = 0; i < ARRAY_SIZE(dhcpv6_user_opts); i++) { > + int vlen = dhcpv6_user_opts[i].len; > + struct opt_hdr *hdr; > + > + if (!vlen) > + continue; > + > + if (offset + sizeof(struct opt_hdr) + vlen > OPT_MAX_SIZE) { > + warn("DHCPv6: option %u doesn't fit, skipping", i); I've seen Laurent's comment in: https://archives.passt.top/passt-dev/6fcca48c-a992-436f-a308-061ae0017d4f@redhat.com/ but the issue with a warn() call here is that the guest could try to flood logs that way. You could rate-limit it by passing the timestamp to here via dhcpv6(), it's just one caller to modify, it doesn't look that bad. > + continue; > + } > + > + hdr = (struct opt_hdr *)(buf + offset); > + hdr->t = htons(i); > + hdr->l = htons(vlen); > + offset += sizeof(struct opt_hdr); > + > + memcpy(buf + offset, dhcpv6_user_opts[i].val, vlen); > + offset += vlen; > + } > + > + return offset; > +} > + > /** > * dhcpv6() - Check if this is a DHCPv6 message, reply as needed > * @c: Execution context > @@ -1017,6 +1052,7 @@ int dhcpv6(struct ctx *c, struct iov_tail *data, > sizeof(struct opt_hdr) + ntohs(client_id->l); > n = dhcpv6_dns_fill(c, (char *)&resp, n); > n = dhcpv6_client_fqdn_fill(data, c, (char *)&resp, n); > + n = dhcpv6_user_opts_fill((char *)&resp, n); > > resp.hdr.xid = mh->xid; > -- Stefano ^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-10-25 12:01 UTC | newest] Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2026-10-06 9:04 [PATCH v4 0/2] [PATCH 0/2] dhcpv6: Add --dhcpv6-opt for custom DHCPv6 options Anshu Kumari 2026-10-06 9:04 ` [PATCH v4 1/2] dhcpv6: Add --dhcpv6-opt with option type table and value parser Anshu Kumari 2026-10-25 12:01 ` Stefano Brivio 2026-10-06 9:04 ` [PATCH v4 2/2] dhcpv6: Inject custom options into DHCPv6 replies Anshu Kumari 2026-10-25 12:01 ` Stefano Brivio
Code repositories for project(s) associated with this public inbox https://passt.top/passt This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for IMAP folder(s).