* [PATCH 0/3] [PATCH 0/3] dhcpv6: Add --dhcpv6-opt for custom DHCPv6 options
@ 2026-06-04 10:51 Anshu Kumari
2026-06-04 10:51 ` [PATCH 1/3] conf: Add --dhcpv6-opt command-line option Anshu Kumari
` (3 more replies)
0 siblings, 4 replies; 8+ messages in thread
From: Anshu Kumari @ 2026-06-04 10:51 UTC (permalink / raw)
To: passt-dev, sbrivio, anskuma; +Cc: david, jmaloy, lvivier
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.
Patch 1 adds the CLI flag, storage, and basic man page entry.
Patch 2 adds the type table and binary parser for all supported types.
Patch 3 injects the parsed options into DHCPv6 reply packets.
Anshu Kumari (3):
conf: Add --dhcpv6-opt command-line option
dhcpv6: Add option type table and value parser
dhcpv6: Inject custom options into DHCPv6 replies
conf.c | 26 ++++-
dhcpv6.c | 295 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
dhcpv6.h | 1 +
passt.1 | 30 ++++++
passt.h | 16 +++
5 files changed, 367 insertions(+), 1 deletion(-)
--
2.54.0
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH 1/3] conf: Add --dhcpv6-opt command-line option
2026-06-04 10:51 [PATCH 0/3] [PATCH 0/3] dhcpv6: Add --dhcpv6-opt for custom DHCPv6 options Anshu Kumari
@ 2026-06-04 10:51 ` Anshu Kumari
2026-06-05 0:50 ` David Gibson
2026-06-04 10:51 ` [PATCH 2/3] dhcpv6: Add option type table and value parser Anshu Kumari
` (2 subsequent siblings)
3 siblings, 1 reply; 8+ messages in thread
From: Anshu Kumari @ 2026-06-04 10:51 UTC (permalink / raw)
To: passt-dev, sbrivio, anskuma; +Cc: david, jmaloy, lvivier
Add a --dhcpv6-opt CODE,VALUE option for injecting custom DHCPv6
options into server replies. This patch adds the CLI parsing in conf.c,
storage in struct ctx, and a simple dhcpv6_add_option() that stores the
option code and raw string value.
Signed-off-by: Anshu Kumari <anskuma@redhat.com>
---
conf.c | 26 +++++++++++++++++++++++++-
dhcpv6.c | 37 +++++++++++++++++++++++++++++++++++++
dhcpv6.h | 1 +
passt.1 | 6 ++++++
passt.h | 12 ++++++++++++
5 files changed, 81 insertions(+), 1 deletion(-)
diff --git a/conf.c b/conf.c
index 6f86940..ac7e6e5 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"
@@ -616,7 +617,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 by code\n");
if (strstr(name, "pasta"))
FPRINTF(f, " default: don't use any search list\n");
else
@@ -884,6 +886,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 = 0; i < c->custom_v6opts_count; i++)
+ info(" v6 option %u: %s",
+ c->custom_v6opts[i].code,
+ c->custom_v6opts[i].str);
dns6:
for (i = 0; i < ARRAY_SIZE(c->ip6.dns); i++) {
@@ -1233,6 +1239,7 @@ void conf(struct ctx *c, int argc, char **argv)
{"migrate-no-linger", no_argument, NULL, 30 },
{"stats", required_argument, NULL, 31 },
{"conf-path", required_argument, NULL, 'c' },
+ {"dhcpv6-opt", required_argument, NULL, 34 },
{ 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:";
@@ -1248,10 +1255,13 @@ void conf(struct ctx *c, int argc, char **argv)
uint8_t prefix_len_from_opt = 0;
unsigned int ifi4 = 0, ifi6 = 0;
const char *logfile = NULL;
+ unsigned long v6optcode;
char *runas = NULL;
size_t logsize = 0;
+ const char *comma;
long fd_tap_opt;
int name, ret;
+ char *end;
uid_t uid;
gid_t gid;
@@ -1465,6 +1475,20 @@ void conf(struct ctx *c, int argc, char **argv)
die("Can't display statistics if not running in foreground");
c->stats = strtol(optarg, NULL, 0);
break;
+ case 34:
+ comma = strchr(optarg, ',');
+ if (!comma)
+ die("--dhcpv6-opt requires CODE,VALUE format");
+
+ errno = 0;
+ v6optcode = strtoul(optarg, &end, 0);
+ if (end != comma || errno ||
+ v6optcode < 1 || v6optcode > 255)
+ die("DHCPv6 option code must be 1-255: %s",
+ optarg);
+
+ dhcpv6_add_option(c, v6optcode, comma + 1);
+ break;
case 'd':
c->debug = 1;
c->quiet = 0;
diff --git a/dhcpv6.c b/dhcpv6.c
index 97c04e2..a0fb77c 100644
--- a/dhcpv6.c
+++ b/dhcpv6.c
@@ -32,6 +32,43 @@
#include "tap.h"
#include "log.h"
+/**
+ * dhcpv6_add_option() - Add or update a custom DHCPv6 option
+ * @c: Execution context
+ * @code: DHCPv6 option code
+ * @val_str: Value string from command line
+ *
+ * Stores the option code and raw string value. Binary encoding and
+ * injection into DHCPv6 replies are handled by later patches.
+ *
+ * Return: 0 on success
+ */
+int dhcpv6_add_option(struct ctx *c, uint16_t code, const char *val_str)
+{
+ int idx;
+
+ for (idx = 0; idx < c->custom_v6opts_count; idx++) {
+ if (c->custom_v6opts[idx].code == code)
+ break;
+ }
+
+ if (idx == c->custom_v6opts_count) {
+ if (c->custom_v6opts_count >= MAX_CUSTOM_DHCPV6_OPTS)
+ die("Too many --dhcpv6-opt entries (max %d)",
+ MAX_CUSTOM_DHCPV6_OPTS);
+ c->custom_v6opts_count++;
+ }
+
+ c->custom_v6opts[idx].code = code;
+
+ if (snprintf_check(c->custom_v6opts[idx].str,
+ sizeof(c->custom_v6opts[0].str),
+ "%s", val_str))
+ die("DHCPv6 option value too long: %s", val_str);
+
+ return 0;
+}
+
/**
* struct opt_hdr - DHCPv6 option header
* @t: Option type
diff --git a/dhcpv6.h b/dhcpv6.h
index c706dfd..c01bc36 100644
--- a/dhcpv6.h
+++ b/dhcpv6.h
@@ -9,5 +9,6 @@
int dhcpv6(struct ctx *c, struct iov_tail *data,
struct in6_addr *saddr, struct in6_addr *daddr);
void dhcpv6_init(const struct ctx *c);
+int dhcpv6_add_option(struct ctx *c, uint16_t code, const char *val_str);
#endif /* DHCPV6_H */
diff --git a/passt.1 b/passt.1
index 908fd4a..9c25214 100644
--- a/passt.1
+++ b/passt.1
@@ -430,6 +430,12 @@ 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 a DHCPv6 option by numeric code. The value format is determined
+automatically from the option code. This option can be specified multiple
+times. If the same option code is given more than once, the last value wins.
+
.TP
.BR \-t ", " \-\-tcp-ports " " \fIspec
Configure TCP port forwarding to guest or namespace. \fIspec\fR can be one of:
diff --git a/passt.h b/passt.h
index 1726965..91509df 100644
--- a/passt.h
+++ b/passt.h
@@ -182,6 +182,10 @@ struct ip6_ctx {
* @dns_search: DNS search list
* @hostname: Guest hostname
* @fqdn: Guest FQDN
+ * @custom_v6opts: User-specified DHCPv6 options from --dhcpv6-opt
+ * @custom_v6opts.code: DHCPv6 option code
+ * @custom_v6opts.str: Original string value from command line
+ * @custom_v6opts_count:Number of entries in @custom_v6opts
* @ifi6: Template interface for IPv6, -1: none, 0: IPv6 disabled
* @ip6: IPv6 configuration
* @pasta_ifn: Name of namespace interface for pasta
@@ -263,6 +267,14 @@ struct ctx {
char hostname[PASST_MAXDNAME];
char fqdn[PASST_MAXDNAME];
+#define MAX_CUSTOM_DHCPV6_OPTS 32
+
+ struct {
+ uint16_t code;
+ char str[256];
+ } custom_v6opts[MAX_CUSTOM_DHCPV6_OPTS];
+ int custom_v6opts_count;
+
int ifi6;
struct ip6_ctx ip6;
--
2.54.0
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH 2/3] dhcpv6: Add option type table and value parser
2026-06-04 10:51 [PATCH 0/3] [PATCH 0/3] dhcpv6: Add --dhcpv6-opt for custom DHCPv6 options Anshu Kumari
2026-06-04 10:51 ` [PATCH 1/3] conf: Add --dhcpv6-opt command-line option Anshu Kumari
@ 2026-06-04 10:51 ` Anshu Kumari
2026-06-05 1:08 ` David Gibson
2026-06-04 10:51 ` [PATCH 3/3] dhcpv6: Inject custom options into DHCPv6 replies Anshu Kumari
2026-06-04 11:00 ` [PATCH 0/3] [PATCH 0/3] dhcpv6: Add --dhcpv6-opt for custom DHCPv6 options Anshu Kumari
3 siblings, 1 reply; 8+ messages in thread
From: Anshu Kumari @ 2026-06-04 10:51 UTC (permalink / raw)
To: passt-dev, sbrivio, anskuma; +Cc: david, jmaloy, lvivier
Add a type table mapping DHCPv6 option codes to value types,
and a parser that encodes CLI strings into the correct binary wire
format for each type.
Supported types: plain string, IPv6 address, IPv6 address list,
8/16/32-bit integers, vendor class (enterprise number + length-prefixed
data, per RFC 8415 Section 21.16), status code (uint16 + message),
and length-prefixed string lists.
Modify dhcpv6_add_option() to call the parser for validation and binary
encoding at configuration time, storing both the raw string and the
encoded value.
Signed-off-by: Anshu Kumari <anskuma@redhat.com>
---
dhcpv6.c | 228 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-
passt.1 | 28 ++++++-
passt.h | 4 +
3 files changed, 255 insertions(+), 5 deletions(-)
diff --git a/dhcpv6.c b/dhcpv6.c
index a0fb77c..85e2926 100644
--- a/dhcpv6.c
+++ b/dhcpv6.c
@@ -25,6 +25,7 @@
#include <string.h>
#include <time.h>
#include <limits.h>
+#include <errno.h>
#include "packet.h"
#include "util.h"
@@ -32,20 +33,233 @@
#include "tap.h"
#include "log.h"
+/**
+ * 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_INT8: Unsigned 8-bit integer
+ * @DHCPV6_OPT_INT16: Unsigned 16-bit integer
+ * @DHCPV6_OPT_INT32: 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_INT8,
+ DHCPV6_OPT_INT16,
+ DHCPV6_OPT_INT32,
+ 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, 17, 32, 82, 83
+ * RFC 5970 Options: 59, 60
+ * RFC 4075 Options: 31
+ */
+static const enum dhcpv6_opt_type dhcpv6_opt_types[256] = {
+ [7] = DHCPV6_OPT_INT8, /* Preference */
+ [15] = DHCPV6_OPT_LEN_STR_LIST, /* User Class */
+ [16] = DHCPV6_OPT_VENDOR_CLASS, /* Vendor Class */
+ [17] = DHCPV6_OPT_VENDOR_CLASS, /* Vendor Opts */
+ [31] = DHCPV6_OPT_IPV6_LIST, /* SNTP Servers */
+ [32] = DHCPV6_OPT_INT32, /* Information Refresh Time */
+ [59] = DHCPV6_OPT_STR, /* Boot File URL */
+ [60] = DHCPV6_OPT_LEN_STR_LIST, /* Boot File Params */
+ [82] = DHCPV6_OPT_INT32, /* SOL_MAX_RT */
+ [83] = DHCPV6_OPT_INT32, /* 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)
+{
+ char chunk[INET6_ADDRSTRLEN];
+ enum dhcpv6_opt_type type;
+ unsigned long val;
+ const char *colon;
+ unsigned int i;
+ uint8_t width;
+ size_t slen;
+ char *end;
+ int len;
+
+ if (!*str)
+ die("Empty value for DHCPv6 option %u", code);
+
+ if (code >= ARRAY_SIZE(dhcpv6_opt_types))
+ die("Unsupported DHCPv6 option: %u,"
+ " see passt(1) for supported codes", code);
+
+ type = dhcpv6_opt_types[code];
+
+ switch (type) {
+ case DHCPV6_OPT_NONE:
+ die("Unsupported DHCPv6 option: %u,"
+ " see passt(1) for supported codes", code);
+ case DHCPV6_OPT_IPV6:
+ case DHCPV6_OPT_IPV6_LIST:
+ len = 0;
+
+ while (*str) {
+ slen = strcspn(str, ",");
+ if (!slen || slen >= sizeof(chunk))
+ return -1;
+
+ if (len + (int)sizeof(struct in6_addr) > (int)buf_len)
+ return -1;
+
+ memcpy(chunk, str, slen);
+ chunk[slen] = '\0';
+
+ if (inet_pton(AF_INET6, chunk,
+ buf + len) != 1)
+ return -1;
+
+ len += sizeof(struct in6_addr);
+
+ if (type == DHCPV6_OPT_IPV6) {
+ if (str[slen] == ',')
+ return -1;
+ break;
+ }
+
+ str += slen;
+ if (*str == ',')
+ str++;
+ }
+
+ if (!len)
+ return -1;
+
+ return len;
+ case DHCPV6_OPT_INT8:
+ case DHCPV6_OPT_INT16:
+ case DHCPV6_OPT_INT32:
+ if (type == DHCPV6_OPT_INT8)
+ width = 1;
+ else if (type == DHCPV6_OPT_INT16)
+ width = 2;
+ else
+ width = 4;
+
+ errno = 0;
+ val = strtoul(str, &end, 0);
+
+ if (*end || errno)
+ return -1;
+
+ if (buf_len < width)
+ return -1;
+
+ if (val >= (1ULL << (width * 8)))
+ return -1;
+
+ for (i = width; i > 0; i--) {
+ buf[i - 1] = val & 0xff;
+ val >>= 8;
+ }
+
+ return width;
+ case DHCPV6_OPT_STR:
+ slen = strlen(str);
+
+ if (!slen || slen >= buf_len)
+ return -1;
+
+ memcpy(buf, str, slen);
+
+ return slen;
+ case DHCPV6_OPT_VENDOR_CLASS:
+ colon = strchr(str, ':');
+ if (!colon)
+ return -1;
+
+ errno = 0;
+ val = strtoul(str, &end, 0);
+ if (end != colon || errno || val > UINT32_MAX)
+ return -1;
+
+ slen = strlen(colon + 1);
+ if (!slen)
+ return -1;
+
+ len = sizeof(uint32_t) + sizeof(uint16_t) + slen;
+ if ((size_t)len > buf_len)
+ return -1;
+
+ uint32_t ent = htonl(val);
+
+ memcpy(buf, &ent, sizeof(ent));
+
+ buf[4] = slen >> 8;
+ buf[5] = slen & 0xff;
+
+ memcpy(buf + sizeof(uint32_t) + sizeof(uint16_t),
+ colon + 1, 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;
+
+ buf[len] = slen >> 8;
+ buf[len + 1] = slen & 0xff;
+ len += sizeof(uint16_t);
+
+ memcpy(buf + len, str, slen);
+ len += slen;
+
+ str += slen;
+ if (*str == ',')
+ str++;
+ }
+
+ if (!len)
+ return -1;
+
+ return len;
+ }
+
+ return -1;
+}
+
/**
* dhcpv6_add_option() - Add or update a custom DHCPv6 option
* @c: Execution context
* @code: DHCPv6 option code
* @val_str: Value string from command line
*
- * Stores the option code and raw string value. Binary encoding and
- * injection into DHCPv6 replies are handled by later patches.
+ * Parses @val_str according to the type registered for @code in
+ * dhcpv6_opt_types[]. If @code was already added, the previous value
+ * is overwritten.
*
* Return: 0 on success
*/
int dhcpv6_add_option(struct ctx *c, uint16_t code, const char *val_str)
{
- int idx;
+ int idx, ret;
for (idx = 0; idx < c->custom_v6opts_count; idx++) {
if (c->custom_v6opts[idx].code == code)
@@ -59,7 +273,15 @@ int dhcpv6_add_option(struct ctx *c, uint16_t code, const char *val_str)
c->custom_v6opts_count++;
}
+ ret = dhcpv6_opt_parse(code, val_str,
+ c->custom_v6opts[idx].val,
+ sizeof(c->custom_v6opts[0].val));
+ if (ret < 0)
+ die("Invalid value for DHCPv6 option %u: %s",
+ code, val_str);
+
c->custom_v6opts[idx].code = code;
+ c->custom_v6opts[idx].len = ret;
if (snprintf_check(c->custom_v6opts[idx].str,
sizeof(c->custom_v6opts[0].str),
diff --git a/passt.1 b/passt.1
index 9c25214..3c4f3ac 100644
--- a/passt.1
+++ b/passt.1
@@ -433,8 +433,32 @@ Send \fIname\fR as Client FQDN: DHCP option 81 and DHCPv6 option 39.
.TP
.BR \-\-dhcpv6-opt " " \fICODE\fR,\fIVALUE\fR
Set a DHCPv6 option by numeric code. The value format is determined
-automatically from the option code. This option can be specified multiple
-times. If the same option code is given more than once, the last value wins.
+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), 17 (Vendor-specific Info, 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
diff --git a/passt.h b/passt.h
index 91509df..21f43d8 100644
--- a/passt.h
+++ b/passt.h
@@ -184,6 +184,8 @@ struct ip6_ctx {
* @fqdn: Guest FQDN
* @custom_v6opts: User-specified DHCPv6 options from --dhcpv6-opt
* @custom_v6opts.code: DHCPv6 option code
+ * @custom_v6opts.len: Length of binary value in @val
+ * @custom_v6opts.val: Binary-encoded option value
* @custom_v6opts.str: Original string value from command line
* @custom_v6opts_count:Number of entries in @custom_v6opts
* @ifi6: Template interface for IPv6, -1: none, 0: IPv6 disabled
@@ -271,6 +273,8 @@ struct ctx {
struct {
uint16_t code;
+ uint16_t len;
+ uint8_t val[255];
char str[256];
} custom_v6opts[MAX_CUSTOM_DHCPV6_OPTS];
int custom_v6opts_count;
--
2.54.0
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH 3/3] dhcpv6: Inject custom options into DHCPv6 replies
2026-06-04 10:51 [PATCH 0/3] [PATCH 0/3] dhcpv6: Add --dhcpv6-opt for custom DHCPv6 options Anshu Kumari
2026-06-04 10:51 ` [PATCH 1/3] conf: Add --dhcpv6-opt command-line option Anshu Kumari
2026-06-04 10:51 ` [PATCH 2/3] dhcpv6: Add option type table and value parser Anshu Kumari
@ 2026-06-04 10:51 ` Anshu Kumari
2026-06-05 1:16 ` David Gibson
2026-06-04 11:00 ` [PATCH 0/3] [PATCH 0/3] dhcpv6: Add --dhcpv6-opt for custom DHCPv6 options Anshu Kumari
3 siblings, 1 reply; 8+ messages in thread
From: Anshu Kumari @ 2026-06-04 10:51 UTC (permalink / raw)
To: passt-dev, sbrivio, anskuma; +Cc: david, jmaloy, lvivier
Add dhcpv6_custom_opts_fill() which appends user-specified custom
options to DHCPv6 response packets, and call it from the main dhcpv6()
handler after the built-in DNS and FQDN options.
Each custom option is written with its DHCPv6 option header (code +
length) followed by the binary-encoded value from dhcpv6_opt_parse().
Options that would exceed the IPv6 minimum MTU are skipped with a
debug message.
Signed-off-by: Anshu Kumari <anskuma@redhat.com>
---
dhcpv6.c | 36 ++++++++++++++++++++++++++++++++++++
1 file changed, 36 insertions(+)
diff --git a/dhcpv6.c b/dhcpv6.c
index 85e2926..590c40d 100644
--- a/dhcpv6.c
+++ b/dhcpv6.c
@@ -798,6 +798,41 @@ static size_t dhcpv6_client_fqdn_fill(const struct iov_tail *data,
return offset + sizeof(struct opt_hdr) + opt_len;
}
+/**
+ * dhcpv6_custom_opts_fill() - Append user-specified custom options to reply
+ * @c: Execution context
+ * @buf: Response message buffer
+ * @offset: Current offset in buffer
+ *
+ * Return: updated offset after appending custom options
+ */
+static size_t dhcpv6_custom_opts_fill(const struct ctx *c,
+ char *buf, int offset)
+{
+ int i;
+
+ for (i = 0; i < c->custom_v6opts_count; i++) {
+ struct opt_hdr *hdr;
+ uint16_t len = c->custom_v6opts[i].len;
+
+ if ((size_t)offset + sizeof(struct opt_hdr) + len > OPT_MAX_SIZE) {
+ debug("DHCPv6: custom option %u doesn't fit, skipping",
+ c->custom_v6opts[i].code);
+ continue;
+ }
+
+ hdr = (struct opt_hdr *)(buf + offset);
+ hdr->t = htons(c->custom_v6opts[i].code);
+ hdr->l = htons(len);
+ offset += sizeof(struct opt_hdr);
+
+ memcpy(buf + offset, c->custom_v6opts[i].val, len);
+ offset += len;
+ }
+
+ return offset;
+}
+
/**
* dhcpv6() - Check if this is a DHCPv6 message, reply as needed
* @c: Execution context
@@ -936,6 +971,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_custom_opts_fill(c, (char *)&resp, n);
resp.hdr.xid = mh->xid;
--
2.54.0
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH 0/3] [PATCH 0/3] dhcpv6: Add --dhcpv6-opt for custom DHCPv6 options
2026-06-04 10:51 [PATCH 0/3] [PATCH 0/3] dhcpv6: Add --dhcpv6-opt for custom DHCPv6 options Anshu Kumari
` (2 preceding siblings ...)
2026-06-04 10:51 ` [PATCH 3/3] dhcpv6: Inject custom options into DHCPv6 replies Anshu Kumari
@ 2026-06-04 11:00 ` Anshu Kumari
3 siblings, 0 replies; 8+ messages in thread
From: Anshu Kumari @ 2026-06-04 11:00 UTC (permalink / raw)
To: passt-dev, sbrivio, anskuma; +Cc: david, jmaloy, lvivier
[-- Attachment #1: Type: text/plain, Size: 1652 bytes --]
Following the current changes, future work will involve adding support for
options 138 and 139. I have not included these in this patch series because
the parsing for both requires significant code changes, which I believe
would be too much to review at once.
Regards,
Anshu
On Thu, Jun 4, 2026 at 4:22 PM Anshu Kumari <anskuma@redhat.com> wrote:
> 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.
>
> Patch 1 adds the CLI flag, storage, and basic man page entry.
> Patch 2 adds the type table and binary parser for all supported types.
> Patch 3 injects the parsed options into DHCPv6 reply packets.
>
> Anshu Kumari (3):
> conf: Add --dhcpv6-opt command-line option
> dhcpv6: Add option type table and value parser
> dhcpv6: Inject custom options into DHCPv6 replies
>
> conf.c | 26 ++++-
> dhcpv6.c | 295 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
> dhcpv6.h | 1 +
> passt.1 | 30 ++++++
> passt.h | 16 +++
> 5 files changed, 367 insertions(+), 1 deletion(-)
>
> --
> 2.54.0
>
>
[-- Attachment #2: Type: text/html, Size: 2165 bytes --]
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH 1/3] conf: Add --dhcpv6-opt command-line option
2026-06-04 10:51 ` [PATCH 1/3] conf: Add --dhcpv6-opt command-line option Anshu Kumari
@ 2026-06-05 0:50 ` David Gibson
0 siblings, 0 replies; 8+ messages in thread
From: David Gibson @ 2026-06-05 0:50 UTC (permalink / raw)
To: Anshu Kumari; +Cc: passt-dev, sbrivio, jmaloy, lvivier
[-- Attachment #1: Type: text/plain, Size: 7046 bytes --]
On Thu, Jun 04, 2026 at 04:21:48PM +0530, Anshu Kumari wrote:
> Add a --dhcpv6-opt CODE,VALUE option for injecting custom DHCPv6
> options into server replies. This patch adds the CLI parsing in conf.c,
> storage in struct ctx, and a simple dhcpv6_add_option() that stores the
> option code and raw string value.
>
> Signed-off-by: Anshu Kumari <anskuma@redhat.com>
Reviewed-by: David Gibson <david@gibson.dropbear.id.au>
> ---
> conf.c | 26 +++++++++++++++++++++++++-
> dhcpv6.c | 37 +++++++++++++++++++++++++++++++++++++
> dhcpv6.h | 1 +
> passt.1 | 6 ++++++
> passt.h | 12 ++++++++++++
> 5 files changed, 81 insertions(+), 1 deletion(-)
>
> diff --git a/conf.c b/conf.c
> index 6f86940..ac7e6e5 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"
> @@ -616,7 +617,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 by code\n");
> if (strstr(name, "pasta"))
> FPRINTF(f, " default: don't use any search list\n");
> else
> @@ -884,6 +886,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 = 0; i < c->custom_v6opts_count; i++)
> + info(" v6 option %u: %s",
> + c->custom_v6opts[i].code,
> + c->custom_v6opts[i].str);
>
> dns6:
> for (i = 0; i < ARRAY_SIZE(c->ip6.dns); i++) {
> @@ -1233,6 +1239,7 @@ void conf(struct ctx *c, int argc, char **argv)
> {"migrate-no-linger", no_argument, NULL, 30 },
> {"stats", required_argument, NULL, 31 },
> {"conf-path", required_argument, NULL, 'c' },
> + {"dhcpv6-opt", required_argument, NULL, 34 },
> { 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:";
> @@ -1248,10 +1255,13 @@ void conf(struct ctx *c, int argc, char **argv)
> uint8_t prefix_len_from_opt = 0;
> unsigned int ifi4 = 0, ifi6 = 0;
> const char *logfile = NULL;
> + unsigned long v6optcode;
> char *runas = NULL;
> size_t logsize = 0;
> + const char *comma;
> long fd_tap_opt;
> int name, ret;
> + char *end;
> uid_t uid;
> gid_t gid;
>
> @@ -1465,6 +1475,20 @@ void conf(struct ctx *c, int argc, char **argv)
> die("Can't display statistics if not running in foreground");
> c->stats = strtol(optarg, NULL, 0);
> break;
> + case 34:
> + comma = strchr(optarg, ',');
> + if (!comma)
> + die("--dhcpv6-opt requires CODE,VALUE format");
> +
> + errno = 0;
> + v6optcode = strtoul(optarg, &end, 0);
> + if (end != comma || errno ||
> + v6optcode < 1 || v6optcode > 255)
> + die("DHCPv6 option code must be 1-255: %s",
> + optarg);
> +
> + dhcpv6_add_option(c, v6optcode, comma + 1);
> + break;
> case 'd':
> c->debug = 1;
> c->quiet = 0;
> diff --git a/dhcpv6.c b/dhcpv6.c
> index 97c04e2..a0fb77c 100644
> --- a/dhcpv6.c
> +++ b/dhcpv6.c
> @@ -32,6 +32,43 @@
> #include "tap.h"
> #include "log.h"
>
> +/**
> + * dhcpv6_add_option() - Add or update a custom DHCPv6 option
> + * @c: Execution context
> + * @code: DHCPv6 option code
> + * @val_str: Value string from command line
> + *
> + * Stores the option code and raw string value. Binary encoding and
> + * injection into DHCPv6 replies are handled by later patches.
> + *
> + * Return: 0 on success
> + */
> +int dhcpv6_add_option(struct ctx *c, uint16_t code, const char *val_str)
> +{
> + int idx;
> +
> + for (idx = 0; idx < c->custom_v6opts_count; idx++) {
> + if (c->custom_v6opts[idx].code == code)
> + break;
> + }
> +
> + if (idx == c->custom_v6opts_count) {
> + if (c->custom_v6opts_count >= MAX_CUSTOM_DHCPV6_OPTS)
> + die("Too many --dhcpv6-opt entries (max %d)",
> + MAX_CUSTOM_DHCPV6_OPTS);
> + c->custom_v6opts_count++;
> + }
> +
> + c->custom_v6opts[idx].code = code;
> +
> + if (snprintf_check(c->custom_v6opts[idx].str,
> + sizeof(c->custom_v6opts[0].str),
> + "%s", val_str))
> + die("DHCPv6 option value too long: %s", val_str);
> +
> + return 0;
> +}
> +
> /**
> * struct opt_hdr - DHCPv6 option header
> * @t: Option type
> diff --git a/dhcpv6.h b/dhcpv6.h
> index c706dfd..c01bc36 100644
> --- a/dhcpv6.h
> +++ b/dhcpv6.h
> @@ -9,5 +9,6 @@
> int dhcpv6(struct ctx *c, struct iov_tail *data,
> struct in6_addr *saddr, struct in6_addr *daddr);
> void dhcpv6_init(const struct ctx *c);
> +int dhcpv6_add_option(struct ctx *c, uint16_t code, const char *val_str);
>
> #endif /* DHCPV6_H */
> diff --git a/passt.1 b/passt.1
> index 908fd4a..9c25214 100644
> --- a/passt.1
> +++ b/passt.1
> @@ -430,6 +430,12 @@ 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 a DHCPv6 option by numeric code. The value format is determined
> +automatically from the option code. This option can be specified multiple
> +times. If the same option code is given more than once, the last value wins.
> +
> .TP
> .BR \-t ", " \-\-tcp-ports " " \fIspec
> Configure TCP port forwarding to guest or namespace. \fIspec\fR can be one of:
> diff --git a/passt.h b/passt.h
> index 1726965..91509df 100644
> --- a/passt.h
> +++ b/passt.h
> @@ -182,6 +182,10 @@ struct ip6_ctx {
> * @dns_search: DNS search list
> * @hostname: Guest hostname
> * @fqdn: Guest FQDN
> + * @custom_v6opts: User-specified DHCPv6 options from --dhcpv6-opt
> + * @custom_v6opts.code: DHCPv6 option code
> + * @custom_v6opts.str: Original string value from command line
> + * @custom_v6opts_count:Number of entries in @custom_v6opts
> * @ifi6: Template interface for IPv6, -1: none, 0: IPv6 disabled
> * @ip6: IPv6 configuration
> * @pasta_ifn: Name of namespace interface for pasta
> @@ -263,6 +267,14 @@ struct ctx {
> char hostname[PASST_MAXDNAME];
> char fqdn[PASST_MAXDNAME];
>
> +#define MAX_CUSTOM_DHCPV6_OPTS 32
> +
> + struct {
> + uint16_t code;
> + char str[256];
> + } custom_v6opts[MAX_CUSTOM_DHCPV6_OPTS];
> + int custom_v6opts_count;
> +
> int ifi6;
> struct ip6_ctx ip6;
>
> --
> 2.54.0
>
--
David Gibson (he or they) | I'll have my music baroque, and my code
david AT gibson.dropbear.id.au | minimalist, thank you, not the other way
| around.
http://www.ozlabs.org/~dgibson
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH 2/3] dhcpv6: Add option type table and value parser
2026-06-04 10:51 ` [PATCH 2/3] dhcpv6: Add option type table and value parser Anshu Kumari
@ 2026-06-05 1:08 ` David Gibson
0 siblings, 0 replies; 8+ messages in thread
From: David Gibson @ 2026-06-05 1:08 UTC (permalink / raw)
To: Anshu Kumari; +Cc: passt-dev, sbrivio, jmaloy, lvivier
[-- Attachment #1: Type: text/plain, Size: 11977 bytes --]
On Thu, Jun 04, 2026 at 04:21:49PM +0530, Anshu Kumari wrote:
> Add a type table mapping DHCPv6 option codes to value types,
> and a parser that encodes CLI strings into the correct binary wire
> format for each type.
>
> Supported types: plain string, IPv6 address, IPv6 address list,
> 8/16/32-bit integers, vendor class (enterprise number + length-prefixed
> data, per RFC 8415 Section 21.16), status code (uint16 + message),
> and length-prefixed string lists.
>
> Modify dhcpv6_add_option() to call the parser for validation and binary
> encoding at configuration time, storing both the raw string and the
> encoded value.
>
> Signed-off-by: Anshu Kumari <anskuma@redhat.com>
Many comments below, but all of them minor.
> ---
> dhcpv6.c | 228 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-
> passt.1 | 28 ++++++-
> passt.h | 4 +
> 3 files changed, 255 insertions(+), 5 deletions(-)
>
> diff --git a/dhcpv6.c b/dhcpv6.c
> index a0fb77c..85e2926 100644
> --- a/dhcpv6.c
> +++ b/dhcpv6.c
> @@ -25,6 +25,7 @@
> #include <string.h>
> #include <time.h>
> #include <limits.h>
> +#include <errno.h>
>
> #include "packet.h"
> #include "util.h"
> @@ -32,20 +33,233 @@
> #include "tap.h"
> #include "log.h"
>
> +/**
> + * 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_INT8: Unsigned 8-bit integer
> + * @DHCPV6_OPT_INT16: Unsigned 16-bit integer
> + * @DHCPV6_OPT_INT32: 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_INT8,
> + DHCPV6_OPT_INT16,
> + DHCPV6_OPT_INT32,
> + 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, 17, 32, 82, 83
> + * RFC 5970 Options: 59, 60
> + * RFC 4075 Options: 31
> + */
> +static const enum dhcpv6_opt_type dhcpv6_opt_types[256] = {
Nit: AFAICT when you look up a value here, you already check against
the array size. So, I think you could not specify the array size
explicitly here, and the compiler should infer it from initializer,
omitting all the empty space past option 83.
> + [7] = DHCPV6_OPT_INT8, /* Preference */
> + [15] = DHCPV6_OPT_LEN_STR_LIST, /* User Class */
> + [16] = DHCPV6_OPT_VENDOR_CLASS, /* Vendor Class */
> + [17] = DHCPV6_OPT_VENDOR_CLASS, /* Vendor Opts */
> + [31] = DHCPV6_OPT_IPV6_LIST, /* SNTP Servers */
> + [32] = DHCPV6_OPT_INT32, /* Information Refresh Time */
> + [59] = DHCPV6_OPT_STR, /* Boot File URL */
> + [60] = DHCPV6_OPT_LEN_STR_LIST, /* Boot File Params */
> + [82] = DHCPV6_OPT_INT32, /* SOL_MAX_RT */
> + [83] = DHCPV6_OPT_INT32, /* 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)
> +{
> + char chunk[INET6_ADDRSTRLEN];
> + enum dhcpv6_opt_type type;
> + unsigned long val;
> + const char *colon;
> + unsigned int i;
> + uint8_t width;
> + size_t slen;
> + char *end;
> + int len;
> +
> + if (!*str)
> + die("Empty value for DHCPv6 option %u", code);
Nit: I can imagine an option for which an empty string is a valid and
meaningful value, though I don't know if there exist any in practice.
> +
> + if (code >= ARRAY_SIZE(dhcpv6_opt_types))
> + die("Unsupported DHCPv6 option: %u,"
> + " see passt(1) for supported codes", code);
> +
> + type = dhcpv6_opt_types[code];
> +
> + switch (type) {
> + case DHCPV6_OPT_NONE:
> + die("Unsupported DHCPv6 option: %u,"
> + " see passt(1) for supported codes", code);
> + case DHCPV6_OPT_IPV6:
> + case DHCPV6_OPT_IPV6_LIST:
> + len = 0;
> +
> + while (*str) {
Nit: 'chunk' can be moved into this while block.
> + slen = strcspn(str, ",");
> + if (!slen || slen >= sizeof(chunk))
Nit: I don't think you need the explicit !slen check - inet_pton()
should fail on an empty string anyway.
> + return -1;
> +
> + if (len + (int)sizeof(struct in6_addr) > (int)buf_len)
> + return -1;
> +
> + memcpy(chunk, str, slen);
> + chunk[slen] = '\0';
> +
> + if (inet_pton(AF_INET6, chunk,
> + buf + len) != 1)
> + return -1;
> +
> + len += sizeof(struct in6_addr);
> +
> + if (type == DHCPV6_OPT_IPV6) {
> + if (str[slen] == ',')
> + return -1;
> + break;
> + }
> +
> + str += slen;
> + if (*str == ',')
> + str++;
> + }
> +
> + if (!len)
> + return -1;
> +
> + return len;
> + case DHCPV6_OPT_INT8:
> + case DHCPV6_OPT_INT16:
> + case DHCPV6_OPT_INT32:
> + if (type == DHCPV6_OPT_INT8)
> + width = 1;
> + else if (type == DHCPV6_OPT_INT16)
> + width = 2;
> + else
> + width = 4;
> +
> + errno = 0;
> + val = strtoul(str, &end, 0);
> +
> + if (*end || errno)
> + return -1;
> +
> + if (buf_len < width)
> + return -1;
> +
> + if (val >= (1ULL << (width * 8)))
> + return -1;
> +
> + for (i = width; i > 0; i--) {
> + buf[i - 1] = val & 0xff;
> + val >>= 8;
> + }
> +
> + return width;
> + case DHCPV6_OPT_STR:
> + slen = strlen(str);
> +
> + if (!slen || slen >= buf_len)
Nit: Again, I'm not sure an empty string option is necessarily
impossible, and in any case you already checked for that case.
> + return -1;
> +
> + memcpy(buf, str, slen);
> +
> + return slen;
> + case DHCPV6_OPT_VENDOR_CLASS:
> + colon = strchr(str, ':');
> + if (!colon)
> + return -1;
> +
> + errno = 0;
> + val = strtoul(str, &end, 0);
> + if (end != colon || errno || val > UINT32_MAX)
> + return -1;
> +
> + slen = strlen(colon + 1);
> + if (!slen)
> + return -1;
> +
> + len = sizeof(uint32_t) + sizeof(uint16_t) + slen;
> + if ((size_t)len > buf_len)
> + return -1;
> +
> + uint32_t ent = htonl(val);
Nit: Although they are permitted in C11, by convention we avoid inline
declarations in passt.
> +
> + memcpy(buf, &ent, sizeof(ent));
> +
> + buf[4] = slen >> 8;
> + buf[5] = slen & 0xff;
> +
> + memcpy(buf + sizeof(uint32_t) + sizeof(uint16_t),
> + colon + 1, slen);
For this type of structured option value, it might be nicer to declare
a structure and cast the buf pointer into that. This approach is also
fine, though.
> +
> + 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;
> +
> + buf[len] = slen >> 8;
> + buf[len + 1] = slen & 0xff;
> + len += sizeof(uint16_t);
> +
> + memcpy(buf + len, str, slen);
> + len += slen;
> +
> + str += slen;
> + if (*str == ',')
> + str++;
> + }
> +
> + if (!len)
> + return -1;
> +
> + return len;
> + }
> +
> + return -1;
> +}
> +
> /**
> * dhcpv6_add_option() - Add or update a custom DHCPv6 option
> * @c: Execution context
> * @code: DHCPv6 option code
> * @val_str: Value string from command line
> *
> - * Stores the option code and raw string value. Binary encoding and
> - * injection into DHCPv6 replies are handled by later patches.
> + * Parses @val_str according to the type registered for @code in
> + * dhcpv6_opt_types[]. If @code was already added, the previous value
> + * is overwritten.
> *
> * Return: 0 on success
> */
> int dhcpv6_add_option(struct ctx *c, uint16_t code, const char *val_str)
> {
> - int idx;
> + int idx, ret;
>
> for (idx = 0; idx < c->custom_v6opts_count; idx++) {
> if (c->custom_v6opts[idx].code == code)
> @@ -59,7 +273,15 @@ int dhcpv6_add_option(struct ctx *c, uint16_t code, const char *val_str)
> c->custom_v6opts_count++;
> }
>
> + ret = dhcpv6_opt_parse(code, val_str,
> + c->custom_v6opts[idx].val,
> + sizeof(c->custom_v6opts[0].val));
Unlike IPV4 DHCP, we don't appear to have an existing array of DHCP
option values in the DHCPv6 code we could reuse. Nonetheless, it
seems like it would be nicer to have that table of values in something
local to the DHCPv6 code, rather than in the global context structure.
> + if (ret < 0)
> + die("Invalid value for DHCPv6 option %u: %s",
> + code, val_str);
> +
> c->custom_v6opts[idx].code = code;
> + c->custom_v6opts[idx].len = ret;
>
> if (snprintf_check(c->custom_v6opts[idx].str,
> sizeof(c->custom_v6opts[0].str),
> diff --git a/passt.1 b/passt.1
> index 9c25214..3c4f3ac 100644
> --- a/passt.1
> +++ b/passt.1
> @@ -433,8 +433,32 @@ Send \fIname\fR as Client FQDN: DHCP option 81 and DHCPv6 option 39.
> .TP
> .BR \-\-dhcpv6-opt " " \fICODE\fR,\fIVALUE\fR
> Set a DHCPv6 option by numeric code. The value format is determined
> -automatically from the option code. This option can be specified multiple
> -times. If the same option code is given more than once, the last value wins.
> +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), 17 (Vendor-specific Info, 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
> diff --git a/passt.h b/passt.h
> index 91509df..21f43d8 100644
> --- a/passt.h
> +++ b/passt.h
> @@ -184,6 +184,8 @@ struct ip6_ctx {
> * @fqdn: Guest FQDN
> * @custom_v6opts: User-specified DHCPv6 options from --dhcpv6-opt
> * @custom_v6opts.code: DHCPv6 option code
> + * @custom_v6opts.len: Length of binary value in @val
> + * @custom_v6opts.val: Binary-encoded option value
> * @custom_v6opts.str: Original string value from command line
> * @custom_v6opts_count:Number of entries in @custom_v6opts
> * @ifi6: Template interface for IPv6, -1: none, 0: IPv6 disabled
> @@ -271,6 +273,8 @@ struct ctx {
>
> struct {
> uint16_t code;
> + uint16_t len;
> + uint8_t val[255];
> char str[256];
> } custom_v6opts[MAX_CUSTOM_DHCPV6_OPTS];
> int custom_v6opts_count;
> --
> 2.54.0
>
--
David Gibson (he or they) | I'll have my music baroque, and my code
david AT gibson.dropbear.id.au | minimalist, thank you, not the other way
| around.
http://www.ozlabs.org/~dgibson
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH 3/3] dhcpv6: Inject custom options into DHCPv6 replies
2026-06-04 10:51 ` [PATCH 3/3] dhcpv6: Inject custom options into DHCPv6 replies Anshu Kumari
@ 2026-06-05 1:16 ` David Gibson
0 siblings, 0 replies; 8+ messages in thread
From: David Gibson @ 2026-06-05 1:16 UTC (permalink / raw)
To: Anshu Kumari; +Cc: passt-dev, sbrivio, jmaloy, lvivier
[-- Attachment #1: Type: text/plain, Size: 2651 bytes --]
On Thu, Jun 04, 2026 at 04:21:50PM +0530, Anshu Kumari wrote:
> Add dhcpv6_custom_opts_fill() which appends user-specified custom
> options to DHCPv6 response packets, and call it from the main dhcpv6()
> handler after the built-in DNS and FQDN options.
>
> Each custom option is written with its DHCPv6 option header (code +
> length) followed by the binary-encoded value from dhcpv6_opt_parse().
> Options that would exceed the IPv6 minimum MTU are skipped with a
> debug message.
>
> Signed-off-by: Anshu Kumari <anskuma@redhat.com>
Reviewed-by: David Gibson <david@gibson.dropbear.id.au>
> ---
> dhcpv6.c | 36 ++++++++++++++++++++++++++++++++++++
> 1 file changed, 36 insertions(+)
>
> diff --git a/dhcpv6.c b/dhcpv6.c
> index 85e2926..590c40d 100644
> --- a/dhcpv6.c
> +++ b/dhcpv6.c
> @@ -798,6 +798,41 @@ static size_t dhcpv6_client_fqdn_fill(const struct iov_tail *data,
> return offset + sizeof(struct opt_hdr) + opt_len;
> }
>
> +/**
> + * dhcpv6_custom_opts_fill() - Append user-specified custom options to reply
> + * @c: Execution context
> + * @buf: Response message buffer
> + * @offset: Current offset in buffer
> + *
> + * Return: updated offset after appending custom options
> + */
> +static size_t dhcpv6_custom_opts_fill(const struct ctx *c,
> + char *buf, int offset)
> +{
> + int i;
> +
> + for (i = 0; i < c->custom_v6opts_count; i++) {
> + struct opt_hdr *hdr;
> + uint16_t len = c->custom_v6opts[i].len;
> +
> + if ((size_t)offset + sizeof(struct opt_hdr) + len > OPT_MAX_SIZE) {
> + debug("DHCPv6: custom option %u doesn't fit, skipping",
> + c->custom_v6opts[i].code);
> + continue;
> + }
> +
> + hdr = (struct opt_hdr *)(buf + offset);
> + hdr->t = htons(c->custom_v6opts[i].code);
> + hdr->l = htons(len);
> + offset += sizeof(struct opt_hdr);
> +
> + memcpy(buf + offset, c->custom_v6opts[i].val, len);
> + offset += len;
> + }
> +
> + return offset;
> +}
> +
> /**
> * dhcpv6() - Check if this is a DHCPv6 message, reply as needed
> * @c: Execution context
> @@ -936,6 +971,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_custom_opts_fill(c, (char *)&resp, n);
>
> resp.hdr.xid = mh->xid;
>
> --
> 2.54.0
>
--
David Gibson (he or they) | I'll have my music baroque, and my code
david AT gibson.dropbear.id.au | minimalist, thank you, not the other way
| around.
http://www.ozlabs.org/~dgibson
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2026-06-05 1:23 UTC | newest]
Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-06-04 10:51 [PATCH 0/3] [PATCH 0/3] dhcpv6: Add --dhcpv6-opt for custom DHCPv6 options Anshu Kumari
2026-06-04 10:51 ` [PATCH 1/3] conf: Add --dhcpv6-opt command-line option Anshu Kumari
2026-06-05 0:50 ` David Gibson
2026-06-04 10:51 ` [PATCH 2/3] dhcpv6: Add option type table and value parser Anshu Kumari
2026-06-05 1:08 ` David Gibson
2026-06-04 10:51 ` [PATCH 3/3] dhcpv6: Inject custom options into DHCPv6 replies Anshu Kumari
2026-06-05 1:16 ` David Gibson
2026-06-04 11:00 ` [PATCH 0/3] [PATCH 0/3] dhcpv6: Add --dhcpv6-opt for custom DHCPv6 options Anshu Kumari
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).