public inbox for passt-dev@passt.top
 help / color / mirror / code / Atom feed
* [PATCH 0/6] Add --dhcp-boot and --dhcp-opt options
@ 2026-05-18 13:19 Anshu Kumari
  2026-05-18 13:19 ` [PATCH 1/6] conf: Add --dhcp-opt command-line option Anshu Kumari
  2026-05-19  5:30 ` [PATCH 0/6] Add --dhcp-boot and --dhcp-opt options David Gibson
  0 siblings, 2 replies; 13+ messages in thread
From: Anshu Kumari @ 2026-05-18 13:19 UTC (permalink / raw)
  To: anskuma, sbrivio, passt-dev; +Cc: lvivier, jmaloy, david

This series adds support for custom DHCP options in passt, enabling
network boot (PXE/UEFI HTTP Boot) and arbitrary DHCP option injection.

Two new command-line flags are introduced:

  --dhcp-boot URL    Sets the boot file URL (DHCP option 67 and the
                     legacy boot file field)

  --dhcp-opt CODE,VALUE
                     Sets any DHCP option by numeric code, with
                     type-aware parsing per RFC 2132

The DHCP reply path is extended with option overload support (RFC 2132
option 52), allowing options to overflow into the file and sname fields
when the standard options area is full.

*** BLURB HERE ***

Anshu Kumari (6):
  conf: Add --dhcp-opt command-line option
  conf: Add --dhcp-boot command-line option
  dhcp: Add option type table and value parser
  dhcp: Refactor fill_one() to operate on a generic buffer
  dhcp: Add option overload
  doc: Add --dhcp-boot and --dhcp-opt to man page

 conf.c  |  54 ++++++++-
 dhcp.c  | 336 ++++++++++++++++++++++++++++++++++++++++++++++++++++----
 dhcp.h  |  15 +++
 passt.1 |  44 ++++++++
 passt.h |  11 ++
 5 files changed, 438 insertions(+), 22 deletions(-)

-- 
2.54.0


^ permalink raw reply	[flat|nested] 13+ messages in thread

* [PATCH 1/6] conf: Add --dhcp-opt command-line option
  2026-05-18 13:19 [PATCH 0/6] Add --dhcp-boot and --dhcp-opt options Anshu Kumari
@ 2026-05-18 13:19 ` Anshu Kumari
  2026-05-18 13:19   ` [PATCH 2/6] conf: Add --dhcp-boot " Anshu Kumari
  2026-05-19  5:33   ` [PATCH 1/6] conf: Add --dhcp-opt " David Gibson
  2026-05-19  5:30 ` [PATCH 0/6] Add --dhcp-boot and --dhcp-opt options David Gibson
  1 sibling, 2 replies; 13+ messages in thread
From: Anshu Kumari @ 2026-05-18 13:19 UTC (permalink / raw)
  To: anskuma, sbrivio, passt-dev; +Cc: lvivier, jmaloy, david

Introduce the --dhcp-opt flag that allows setting arbitrary DHCP
options from command-line in the form of [Option CODE,VALUE].
This patch adds the option storage in struct ctx and CLI parsing;
the type-aware value parser and DHCP reply injection follow
in subsequent patches.

Link: https://bugs.passt.top/show_bug.cgi?id=192
Signed-off-by: Anshu Kumari <anskuma@redhat.com>
---
 conf.c  | 36 +++++++++++++++++++++++++++++++++++-
 passt.h | 10 ++++++++++
 2 files changed, 45 insertions(+), 1 deletion(-)

diff --git a/conf.c b/conf.c
index 029b9c7..2624e58 100644
--- a/conf.c
+++ b/conf.c
@@ -47,6 +47,7 @@
 #include "lineread.h"
 #include "isolation.h"
 #include "log.h"
+#include "dhcp.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"
+		"  --dhcp-opt CODE,VAL	Set DHCP option by code\n");
 	if (strstr(name, "pasta"))
 		FPRINTF(f, "    default: don't use any search list\n");
 	else
@@ -844,6 +846,10 @@ static void conf_print(const struct ctx *c)
 			info("    router: %s",
 			     inet_ntop(AF_INET, &c->ip4.guest_gw,
 				       buf, sizeof(buf)));
+			for (i = 0; i < c->custom_opts_count; i++)
+				info("    option %u: %s",
+				     c->custom_opts[i].code,
+				     c->custom_opts[i].str);
 		}
 
 		for (i = 0; i < ARRAY_SIZE(c->ip4.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' },
+		{"dhcp-opt", required_argument,		NULL,		33 },
 		{ 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:";
@@ -1465,6 +1472,33 @@ 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 33: {
+			unsigned long code;
+			const char *comma;
+			char *end;
+
+			comma = strchr(optarg, ',');
+			if (!comma)
+				die("--dhcp-opt requires Option CODE,VALUE format");
+
+			code = strtoul(optarg, &end, 0);
+			if (end != comma || code < 1 || code > 254)
+				die("DHCP option code must be 1-254: %s",
+				    optarg);
+
+			if (c->custom_opts_count >= MAX_CUSTOM_DHCP_OPTS)
+				die("Too many --dhcp-opt entries (max %d)",
+				    MAX_CUSTOM_DHCP_OPTS);
+
+			c->custom_opts[c->custom_opts_count].code = code;
+			if (snprintf_check(c->custom_opts[c->custom_opts_count].str,
+					   sizeof(c->custom_opts[0].str),
+					   "%s", comma + 1))
+				die("DHCP option value too long: %s",
+				    comma + 1);
+			c->custom_opts_count++;
+			break;
+		}
 		case 'd':
 			c->debug = 1;
 			c->quiet = 0;
diff --git a/passt.h b/passt.h
index 1726965..acb57dd 100644
--- a/passt.h
+++ b/passt.h
@@ -263,6 +263,16 @@ struct ctx {
 	char hostname[PASST_MAXDNAME];
 	char fqdn[PASST_MAXDNAME];
 
+#define MAX_CUSTOM_DHCP_OPTS	32
+
+	struct {
+		uint8_t code;
+		uint8_t len;
+		uint8_t val[255];
+		char str[256];
+	} custom_opts[MAX_CUSTOM_DHCP_OPTS];
+	int custom_opts_count;
+
 	int ifi6;
 	struct ip6_ctx ip6;
 
-- 
2.54.0


^ permalink raw reply	[flat|nested] 13+ messages in thread

* [PATCH 2/6] conf: Add --dhcp-boot command-line option
  2026-05-18 13:19 ` [PATCH 1/6] conf: Add --dhcp-opt command-line option Anshu Kumari
@ 2026-05-18 13:19   ` Anshu Kumari
  2026-05-18 13:19     ` [PATCH 3/6] dhcp: Add option type table and value parser Anshu Kumari
  2026-05-19  5:35     ` [PATCH 2/6] conf: Add --dhcp-boot command-line option David Gibson
  2026-05-19  5:33   ` [PATCH 1/6] conf: Add --dhcp-opt " David Gibson
  1 sibling, 2 replies; 13+ messages in thread
From: Anshu Kumari @ 2026-05-18 13:19 UTC (permalink / raw)
  To: anskuma, sbrivio, passt-dev; +Cc: lvivier, jmaloy, david

Introduce the --dhcp-boot flag that sets the boot file URL for
network boot specially for ipxe. This patch adds the option
storage and CLI parsing.

Link: https://bugs.passt.top/show_bug.cgi?id=192
Signed-off-by: Anshu Kumari <anskuma@redhat.com>
---
 conf.c  | 9 +++++++++
 passt.h | 1 +
 2 files changed, 10 insertions(+)

diff --git a/conf.c b/conf.c
index 2624e58..61a393f 100644
--- a/conf.c
+++ b/conf.c
@@ -618,6 +618,7 @@ static void usage(const char *name, FILE *f, int status)
 		"    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"
+		"  --dhcp-boot URL	Boot file URL for network boot\n"
 		"  --dhcp-opt CODE,VAL	Set DHCP option by code\n");
 	if (strstr(name, "pasta"))
 		FPRINTF(f, "    default: don't use any search list\n");
@@ -846,6 +847,8 @@ static void conf_print(const struct ctx *c)
 			info("    router: %s",
 			     inet_ntop(AF_INET, &c->ip4.guest_gw,
 				       buf, sizeof(buf)));
+			if (c->dhcp_boot[0])
+				info("    boot file: %s", c->dhcp_boot);
 			for (i = 0; i < c->custom_opts_count; i++)
 				info("    option %u: %s",
 				     c->custom_opts[i].code,
@@ -1239,6 +1242,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' },
+		{"dhcp-boot", required_argument,	NULL,		32 },
 		{"dhcp-opt", required_argument,		NULL,		33 },
 		{ 0 },
 	};
@@ -1472,6 +1476,11 @@ 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 32:
+			if (snprintf_check(c->dhcp_boot, sizeof(c->dhcp_boot),
+					   "%s", optarg))
+				die("Boot file name too long: %s", optarg);
+			break;
 		case 33: {
 			unsigned long code;
 			const char *comma;
diff --git a/passt.h b/passt.h
index acb57dd..2354a0f 100644
--- a/passt.h
+++ b/passt.h
@@ -262,6 +262,7 @@ struct ctx {
 
 	char hostname[PASST_MAXDNAME];
 	char fqdn[PASST_MAXDNAME];
+	char dhcp_boot[PATH_MAX];
 
 #define MAX_CUSTOM_DHCP_OPTS	32
 
-- 
2.54.0


^ permalink raw reply	[flat|nested] 13+ messages in thread

* [PATCH 3/6] dhcp: Add option type table and value parser
  2026-05-18 13:19   ` [PATCH 2/6] conf: Add --dhcp-boot " Anshu Kumari
@ 2026-05-18 13:19     ` Anshu Kumari
  2026-05-18 13:20       ` [PATCH 4/6] dhcp: Refactor fill_one() to operate on a generic buffer Anshu Kumari
  2026-05-19  5:59       ` [PATCH 3/6] dhcp: Add option type table and value parser David Gibson
  2026-05-19  5:35     ` [PATCH 2/6] conf: Add --dhcp-boot command-line option David Gibson
  1 sibling, 2 replies; 13+ messages in thread
From: Anshu Kumari @ 2026-05-18 13:19 UTC (permalink / raw)
  To: anskuma, sbrivio, passt-dev; +Cc: lvivier, jmaloy, david

Add an RFC 2132 type lookup table mapping DHCP option codes to their
expected value formats, and a dhcp_opt_parse() function that converts
CLI string values into their binary wire representation.

Wire dhcp_opt_parse() into the --dhcp-opt handler so that values are
validated and encoded at configuration time.

Link: https://bugs.passt.top/show_bug.cgi?id=192
Signed-off-by: Anshu Kumari <anskuma@redhat.com>
---
 conf.c |   9 +++
 dhcp.c | 227 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 dhcp.h |  15 ++++
 3 files changed, 251 insertions(+)

diff --git a/conf.c b/conf.c
index 61a393f..3ec10ac 100644
--- a/conf.c
+++ b/conf.c
@@ -1485,6 +1485,7 @@ void conf(struct ctx *c, int argc, char **argv)
 			unsigned long code;
 			const char *comma;
 			char *end;
+			int len;
 
 			comma = strchr(optarg, ',');
 			if (!comma)
@@ -1499,7 +1500,15 @@ void conf(struct ctx *c, int argc, char **argv)
 				die("Too many --dhcp-opt entries (max %d)",
 				    MAX_CUSTOM_DHCP_OPTS);
 
+			len = dhcp_opt_parse(code, comma + 1,
+					     c->custom_opts[c->custom_opts_count].val,
+					     sizeof(c->custom_opts[0].val));
+			if (len < 0)
+				die("Invalid value for DHCP option %lu: %s",
+				    code, comma + 1);
+
 			c->custom_opts[c->custom_opts_count].code = code;
+			c->custom_opts[c->custom_opts_count].len = len;
 			if (snprintf_check(c->custom_opts[c->custom_opts_count].str,
 					   sizeof(c->custom_opts[0].str),
 					   "%s", comma + 1))
diff --git a/dhcp.c b/dhcp.c
index 1ff8cba..9220516 100644
--- a/dhcp.c
+++ b/dhcp.c
@@ -33,6 +33,233 @@
 #include "log.h"
 #include "dhcp.h"
 
+/**
+ * struct dhcp_opt_type_entry - Maps option code to RFC 2132 value type
+ * @code:	DHCP option code
+ * @type:	Expected value format
+ */
+static const struct dhcp_opt_type_entry {
+	uint8_t code;
+	enum dhcp_opt_type type;
+} dhcp_opt_types[] = {
+	{  1, DHCP_OPT_IPV4 },		/* Subnet Mask */
+	{  2, DHCP_OPT_UINT32 },	/* Time Offset */
+	{  3, DHCP_OPT_IPV4_LIST },	/* Router */
+	{  4, DHCP_OPT_IPV4_LIST },	/* Time Server */
+	{  5, DHCP_OPT_IPV4_LIST },	/* Name Server */
+
+	{  6, DHCP_OPT_IPV4_LIST },	/* Domain Name Server */
+	{  7, DHCP_OPT_IPV4_LIST },	/* Log Server */
+	{  8, DHCP_OPT_IPV4_LIST },	/* Cookie Server */
+	{  9, DHCP_OPT_IPV4_LIST },	/* LPR Server */
+	{ 10, DHCP_OPT_IPV4_LIST },	/* Impress Server */
+
+	{ 11, DHCP_OPT_IPV4_LIST },	/* Resource Location Server */
+	{ 12, DHCP_OPT_STR },		/* Host Name */
+	{ 13, DHCP_OPT_UINT16 },	/* Boot File Size */
+	{ 15, DHCP_OPT_STR },		/* Domain Name */
+	{ 16, DHCP_OPT_IPV4 },		/* Swap Server */
+
+	{ 17, DHCP_OPT_STR },		/* Root Path */
+	{ 19, DHCP_OPT_UINT8 },	/* IP Forwarding */
+	{ 23, DHCP_OPT_UINT8 },	/* Default IP TTL */
+	{ 26, DHCP_OPT_UINT16 },	/* Interface MTU */
+	{ 28, DHCP_OPT_IPV4 },		/* Broadcast Address */
+
+	{ 33, DHCP_OPT_IPV4_LIST },	/* Static Routes (dest+router pairs) */
+	{ 37, DHCP_OPT_UINT8 },	/* TCP Default TTL */
+	{ 38, DHCP_OPT_UINT32 },	/* TCP Keepalive Interval */
+	{ 40, DHCP_OPT_STR },		/* NIS Domain Name */
+	{ 41, DHCP_OPT_IPV4_LIST },	/* NIS Servers */
+
+	{ 42, DHCP_OPT_IPV4_LIST },	/* NTP Servers */
+	{ 44, DHCP_OPT_IPV4_LIST },	/* NetBIOS Name Server */
+	{ 50, DHCP_OPT_IPV4 },		/* Requested IP Address */
+	{ 51, DHCP_OPT_UINT32 },	/* IP Address Lease Time */
+	{ 53, DHCP_OPT_UINT8 },	/* DHCP Message Type */
+
+	{ 54, DHCP_OPT_IPV4 },		/* Server Identifier */
+	{ 57, DHCP_OPT_UINT16 },	/* Max DHCP Message Size */
+	{ 58, DHCP_OPT_UINT32 },	/* Renewal (T1) Time */
+	{ 59, DHCP_OPT_UINT32 },	/* Rebinding (T2) Time */
+	{ 60, DHCP_OPT_STR },		/* Vendor Class Identifier */
+
+	{ 61, DHCP_OPT_STR },		/* Client Identifier */
+	{ 66, DHCP_OPT_STR },		/* TFTP Server Name */
+	{ 67, DHCP_OPT_STR },		/* Bootfile Name */
+	{ 119, DHCP_OPT_STR },		/* Domain Search List (RFC 3397) */
+	{ 121, DHCP_OPT_ROUTES },	/* Classless Static Routes */
+
+	{ 252, DHCP_OPT_STR },		/* WPAD URL */
+};
+
+/**
+ * dhcp_opt_type_lookup() - Look up the value type for a DHCP option code
+ * @code:	DHCP option code
+ *
+ * Return: type from table
+ */
+static enum dhcp_opt_type dhcp_opt_type_lookup(uint8_t code)
+{
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(dhcp_opt_types); i++) {
+		if (dhcp_opt_types[i].code == code)
+			return dhcp_opt_types[i].type;
+	}
+
+	return DHCP_OPT_NONE;
+}
+
+/**
+ * dhcp_opt_parse() - Parse a DHCP option value
+ * @code:	DHCP option code (determines value type via lookup table)
+ * @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
+ */
+int dhcp_opt_parse(uint8_t code, const char *str, uint8_t *buf, size_t buf_len)
+{
+	enum dhcp_opt_type type = dhcp_opt_type_lookup(code);
+
+	switch (type) {
+	case DHCP_OPT_NONE: {
+		die("Unsupported DHCP option: %u,"
+		    " see passt(1) for supported codes",
+		    code);
+	}
+	case DHCP_OPT_IPV4: {
+		struct in_addr addr;
+
+		if (inet_pton(AF_INET, str, &addr) != 1)
+			return -1;
+		if (buf_len < sizeof(addr))
+			return -1;
+		memcpy(buf, &addr, sizeof(addr));
+		return sizeof(addr);
+	}
+	case DHCP_OPT_IPV4_LIST: {
+		char *tok, *saveptr;
+		char tmp[1024];
+		int len = 0;
+
+		if (snprintf_check(tmp, sizeof(tmp), "%s", str))
+			return -1;
+
+		for (tok = strtok_r(tmp, " ", &saveptr); tok;
+		     tok = strtok_r(NULL, " ", &saveptr)) {
+			struct in_addr addr;
+
+			if (inet_pton(AF_INET, tok, &addr) != 1)
+				return -1;
+			if (len + (int)sizeof(addr) > (int)buf_len)
+				return -1;
+			memcpy(buf + len, &addr, sizeof(addr));
+			len += sizeof(addr);
+		}
+		return len;
+	}
+	case DHCP_OPT_UINT8: {
+		unsigned long val;
+		char *end;
+
+		val = strtoul(str, &end, 0);
+		if (*end || val > 255 || buf_len < 1)
+			return -1;
+		buf[0] = val;
+		return 1;
+	}
+	case DHCP_OPT_UINT16: {
+		unsigned long val;
+		char *end;
+
+		val = strtoul(str, &end, 0);
+		if (*end || val > 65535 || buf_len < 2)
+			return -1;
+		buf[0] = (val >> 8) & 0xff;
+		buf[1] = val & 0xff;
+		return 2;
+	}
+	case DHCP_OPT_UINT32: {
+		unsigned long val;
+		char *end;
+
+		val = strtoul(str, &end, 0);
+		if (*end || buf_len < 4)
+			return -1;
+		buf[0] = (val >> 24) & 0xff;
+		buf[1] = (val >> 16) & 0xff;
+		buf[2] = (val >>  8) & 0xff;
+		buf[3] = val & 0xff;
+		return 4;
+	}
+	case DHCP_OPT_ROUTES: {
+		/* RFC 3442: "CIDR/mask,gateway" entries, space-separated
+		 * Encodes as: mask-width + significant-octets + router
+		 * e.g. "192.168.1.0/24,10.0.0.1 0.0.0.0/0,10.0.0.1"
+		 */
+		char *tok, *saveptr;
+		char tmp[1024];
+		int len = 0;
+
+		if (snprintf_check(tmp, sizeof(tmp), "%s", str))
+			return -1;
+
+		for (tok = strtok_r(tmp, " ", &saveptr); tok;
+		     tok = strtok_r(NULL, " ", &saveptr)) {
+			struct in_addr dest, gw;
+			char *slash, *comma;
+			unsigned long mask;
+			int sig_octets;
+
+			slash = strchr(tok, '/');
+			if (!slash)
+				return -1;
+			*slash = '\0';
+
+			if (inet_pton(AF_INET, tok, &dest) != 1)
+				return -1;
+
+			comma = strchr(slash + 1, ',');
+			if (!comma)
+				return -1;
+			*comma = '\0';
+
+			mask = strtoul(slash + 1, NULL, 10);
+			if (mask > 32)
+				return -1;
+
+			if (inet_pton(AF_INET, comma + 1, &gw) != 1)
+				return -1;
+
+			sig_octets = (mask + 7) / 8;
+
+			if (len + 1 + sig_octets + 4 > (int)buf_len)
+				return -1;
+
+			buf[len++] = mask;
+			memcpy(buf + len, &dest, sig_octets);
+			len += sig_octets;
+			memcpy(buf + len, &gw, 4);
+			len += 4;
+		}
+		return len;
+	}
+	case DHCP_OPT_STR: {
+		size_t len = strlen(str);
+
+		if (!len || len >= buf_len)
+			return -1;
+		strncpy((char *)buf, str, buf_len);
+		return len;
+	}
+	}
+
+	return -1;
+}
+
 /**
  * struct opt - DHCP option
  * @sent:	Convenience flag, set while filling replies
diff --git a/dhcp.h b/dhcp.h
index cd50c99..01b2290 100644
--- a/dhcp.h
+++ b/dhcp.h
@@ -6,7 +6,22 @@
 #ifndef DHCP_H
 #define DHCP_H
 
+/**
+ * enum dhcp_opt_type - DHCP option value types per RFC 2132
+ */
+enum dhcp_opt_type {
+	DHCP_OPT_NONE,
+	DHCP_OPT_STR,
+	DHCP_OPT_IPV4,
+	DHCP_OPT_IPV4_LIST,
+	DHCP_OPT_UINT8,
+	DHCP_OPT_UINT16,
+	DHCP_OPT_UINT32,
+	DHCP_OPT_ROUTES,
+};
+
 int dhcp(const struct ctx *c, struct iov_tail *data);
 void dhcp_init(void);
+int dhcp_opt_parse(uint8_t code, const char *str, uint8_t *buf, size_t buf_len);
 
 #endif /* DHCP_H */
-- 
2.54.0


^ permalink raw reply	[flat|nested] 13+ messages in thread

* [PATCH 4/6] dhcp: Refactor fill_one() to operate on a generic buffer
  2026-05-18 13:19     ` [PATCH 3/6] dhcp: Add option type table and value parser Anshu Kumari
@ 2026-05-18 13:20       ` Anshu Kumari
  2026-05-18 13:20         ` [PATCH 5/6] dhcp: Add option overload Anshu Kumari
  2026-05-19  6:02         ` [PATCH 4/6] dhcp: Refactor fill_one() to operate on a generic buffer David Gibson
  2026-05-19  5:59       ` [PATCH 3/6] dhcp: Add option type table and value parser David Gibson
  1 sibling, 2 replies; 13+ messages in thread
From: Anshu Kumari @ 2026-05-18 13:20 UTC (permalink / raw)
  To: anskuma, sbrivio, passt-dev; +Cc: lvivier, jmaloy, david

Change fill_one() to accept a buffer pointer and capacity instead of
a struct msg pointer.  This is a pure refactor with no behavior change,
preparing for option overload support where fill_one() will also write
into the file and sname fields.

Link: https://bugs.passt.top/show_bug.cgi?id=192
Signed-off-by: Anshu Kumari <anskuma@redhat.com>
---
 dhcp.c | 27 +++++++++++++--------------
 1 file changed, 13 insertions(+), 14 deletions(-)

diff --git a/dhcp.c b/dhcp.c
index 9220516..a966c34 100644
--- a/dhcp.c
+++ b/dhcp.c
@@ -358,28 +358,27 @@ struct msg {
 } __attribute__((__packed__));
 
 /**
- * fill_one() - Fill a single option in message
- * @m:		Message to fill
+ * fill_one() - Fill a single option into a buffer
+ * @buf:	Buffer to write option
+ * @cap:	Usable capacity of @buf (excluding end marker)
  * @o:		Option number
- * @offset:	Current offset within options field, updated on insertion
+ * @offset:	Current offset within @buf, updated on insertion
  *
- * Return: false if m has space to write the option, true otherwise
+ * Return: false if @buf has space to write the option, true otherwise
  */
-static bool fill_one(struct msg *m, int o, int *offset)
+static bool fill_one(uint8_t *buf, size_t cap, int o, int *offset)
 {
 	size_t slen = opts[o].slen;
 
-	/* If we don't have space to write the option, then just skip */
-	if (*offset + 2 /* code and length of option */ + slen > OPT_MAX)
+	if (*offset + 2 + slen > cap)
 		return true;
 
-	m->o[*offset] = o;
-	m->o[*offset + 1] = slen;
+	buf[*offset] = o;
+	buf[*offset + 1] = slen;
 
-	/* Move to option */
 	*offset += 2;
 
-	memcpy(&m->o[*offset], opts[o].s, slen);
+	memcpy(&buf[*offset], opts[o].s, slen);
 
 	opts[o].sent = 1;
 	*offset += slen;
@@ -404,19 +403,19 @@ static int fill(struct msg *m)
 	 * Put it there explicitly, unless requested via option 55.
 	 */
 	if (opts[55].clen > 0 && !memchr(opts[55].c, 53, opts[55].clen))
-		if (fill_one(m, 53, &offset))
+		if (fill_one(m->o, OPT_MAX, 53, &offset))
 			 debug("DHCP: skipping option 53");
 
 	for (i = 0; i < opts[55].clen; i++) {
 		o = opts[55].c[i];
 		if (opts[o].slen != -1)
-			if (fill_one(m, o, &offset))
+			if (fill_one(m->o, OPT_MAX, o, &offset))
 				debug("DHCP: skipping option %i", o);
 	}
 
 	for (o = 0; o < 255; o++) {
 		if (opts[o].slen != -1 && !opts[o].sent)
-			if (fill_one(m, o, &offset))
+			if (fill_one(m->o, OPT_MAX, o, &offset))
 				debug("DHCP: skipping option %i", o);
 	}
 
-- 
2.54.0


^ permalink raw reply	[flat|nested] 13+ messages in thread

* [PATCH 5/6] dhcp: Add option overload
  2026-05-18 13:20       ` [PATCH 4/6] dhcp: Refactor fill_one() to operate on a generic buffer Anshu Kumari
@ 2026-05-18 13:20         ` Anshu Kumari
  2026-05-18 13:20           ` [PATCH 6/6] doc: Add --dhcp-boot and --dhcp-opt to man page Anshu Kumari
  2026-05-19  6:11           ` [PATCH 5/6] dhcp: Add option overload David Gibson
  2026-05-19  6:02         ` [PATCH 4/6] dhcp: Refactor fill_one() to operate on a generic buffer David Gibson
  1 sibling, 2 replies; 13+ messages in thread
From: Anshu Kumari @ 2026-05-18 13:20 UTC (permalink / raw)
  To: anskuma, sbrivio, passt-dev; +Cc: lvivier, jmaloy, david

A user can enter lots of options in command-line which may not fit in
existing buffer, So when the options field is full, overflow remaining
DHCP options into the file and sname fields per RFC 2132 option 52.

Also, when the file field is not used for overload, copy the boot
file URL there directly for legacy PXE clients.

Link: https://bugs.passt.top/show_bug.cgi?id=192
Signed-off-by: Anshu Kumari <anskuma@redhat.com>
---
 dhcp.c | 88 +++++++++++++++++++++++++++++++++++++++++++++++++++-------
 1 file changed, 78 insertions(+), 10 deletions(-)

diff --git a/dhcp.c b/dhcp.c
index a966c34..fde5d57 100644
--- a/dhcp.c
+++ b/dhcp.c
@@ -386,13 +386,53 @@ static bool fill_one(uint8_t *buf, size_t cap, int o, int *offset)
 }
 
 /**
- * fill() - Fill options in message
+ * fill_overflow() - Fill remaining options into file and sname fields
+ * @m:		Message whose file/sname fields may be used for overflow
+ *
+ * Return: option 52 overload value: 0 if no overflow, 1 for file,
+ *         2 for sname, 3 for both
+ */
+static int fill_overflow(struct msg *m)
+{
+	int file_off = 0, sname_off = 0, overload = 0;
+	int o;
+
+	for (o = 0; o < 255; o++) {
+		if (opts[o].slen == -1 || opts[o].sent)
+			continue;
+		fill_one(m->file, sizeof(m->file) - 1, o, &file_off);
+	}
+
+	for (o = 0; o < 255; o++) {
+		if (opts[o].slen == -1 || opts[o].sent)
+			continue;
+		if (fill_one(m->sname, sizeof(m->sname) - 1, o, &sname_off))
+			debug("DHCP: skipping option %i (overload full)", o);
+	}
+
+	if (file_off) {
+		m->file[file_off] = 255;
+		overload |= 1;
+	}
+
+	if (sname_off) {
+		m->sname[sname_off] = 255;
+		overload |= 2;
+	}
+
+	return overload;
+}
+
+/**
+ * fill() - Fill options in message, with overload into file/sname if needed
  * @m:		Message to fill
+ * @overload:	Set to option 52 value (0 if none, 1/2/3 per RFC 2132)
  *
  * Return: current size of options field
  */
-static int fill(struct msg *m)
+static int fill(struct msg *m, int *overload)
 {
+	size_t cap = OPT_MAX - 3;
 	int i, o, offset = 0;
 
 	for (o = 0; o < 255; o++)
@@ -403,20 +443,25 @@ static int fill(struct msg *m)
 	 * Put it there explicitly, unless requested via option 55.
 	 */
 	if (opts[55].clen > 0 && !memchr(opts[55].c, 53, opts[55].clen))
-		if (fill_one(m->o, OPT_MAX, 53, &offset))
-			 debug("DHCP: skipping option 53");
+		fill_one(m->o, cap, 53, &offset);
 
 	for (i = 0; i < opts[55].clen; i++) {
 		o = opts[55].c[i];
 		if (opts[o].slen != -1)
-			if (fill_one(m->o, OPT_MAX, o, &offset))
-				debug("DHCP: skipping option %i", o);
+			fill_one(m->o, cap, o, &offset);
 	}
 
 	for (o = 0; o < 255; o++) {
 		if (opts[o].slen != -1 && !opts[o].sent)
-			if (fill_one(m->o, OPT_MAX, o, &offset))
-				debug("DHCP: skipping option %i", o);
+			fill_one(m->o, cap, o, &offset);
+	}
+
+	*overload = fill_overflow(m);
+
+	if (*overload) {
+		m->o[offset++] = 52;
+		m->o[offset++] = 1;
+		m->o[offset++] = *overload;
 	}
 
 	m->o[offset++] = 255;
@@ -541,6 +586,7 @@ int dhcp(const struct ctx *c, struct iov_tail *data)
 	struct msg const *m;
 	struct msg reply;
 	unsigned int i;
+	int overload;
 
 	eh = IOV_REMOVE_HEADER(data, eh_storage);
 	iph = IOV_PEEK_HEADER(data, iph_storage);
@@ -690,9 +736,31 @@ int dhcp(const struct ctx *c, struct iov_tail *data)
 	}
 
 	if (!c->no_dhcp_dns_search)
-		opt_set_dns_search(c, sizeof(m->o));
+		opt_set_dns_search(c, sizeof(m->o) + sizeof(m->file)
+					       + sizeof(m->sname));
+
+	if (c->dhcp_boot[0]) {
+		size_t boot_len = strlen(c->dhcp_boot);
+
+		if (boot_len <= sizeof(opts[67].s)) {
+			opts[67].slen = boot_len;
+			memcpy(opts[67].s, c->dhcp_boot, boot_len);
+		}
+	}
+
+	for (i = 0; i < (unsigned int)c->custom_opts_count; i++) {
+		uint8_t code = c->custom_opts[i].code;
+
+		opts[code].slen = c->custom_opts[i].len;
+		memcpy(opts[code].s, c->custom_opts[i].val,
+		       c->custom_opts[i].len);
+	}
+
+	dlen = offsetof(struct msg, o) + fill(&reply, &overload);
 
-	dlen = offsetof(struct msg, o) + fill(&reply);
+	if (!(overload & 1) &&
+	    c->dhcp_boot[0] && strlen(c->dhcp_boot) < sizeof(reply.file))
+		memcpy(&reply.file, c->dhcp_boot, strlen(c->dhcp_boot) + 1);
 
 	if (m->flags & FLAG_BROADCAST)
 		dst = in4addr_broadcast;
-- 
2.54.0


^ permalink raw reply	[flat|nested] 13+ messages in thread

* [PATCH 6/6] doc: Add --dhcp-boot and --dhcp-opt to man page
  2026-05-18 13:20         ` [PATCH 5/6] dhcp: Add option overload Anshu Kumari
@ 2026-05-18 13:20           ` Anshu Kumari
  2026-05-19  6:11           ` [PATCH 5/6] dhcp: Add option overload David Gibson
  1 sibling, 0 replies; 13+ messages in thread
From: Anshu Kumari @ 2026-05-18 13:20 UTC (permalink / raw)
  To: anskuma, sbrivio, passt-dev; +Cc: lvivier, jmaloy, david

Document the new --dhcp-boot and --dhcp-opt command-line options in
the passt(1) man page, including supported option codes grouped by
value type and usage examples.

Link: https://bugs.passt.top/show_bug.cgi?id=192
Signed-off-by: Anshu Kumari <anskuma@redhat.com>
---
 passt.1 | 44 ++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 44 insertions(+)

diff --git a/passt.1 b/passt.1
index 908fd4a..c39e5ec 100644
--- a/passt.1
+++ b/passt.1
@@ -430,6 +430,50 @@ 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 \-\-dhcp-boot " " \fIurl
+Boot file URL for network boot.
+Populates the boot file field in DHCP replies. For UEFI HTTP boot,
+also set the vendor class identifier using \-\-dhcp-opt 60,HTTPClient.
+
+.TP
+.BR \-\-dhcp-opt " " \fICODE\fR,\fIVALUE\fR
+Set a DHCP option by numeric code. The value format is determined automatically
+from the option code. Multiple IP addresses are space-separated within quotes.
+This option can be specified multiple times. Options set with \-\-dhcp-opt
+override built-in values and \-\-dhcp-boot settings.
+Only the following option codes are supported (unsupported codes cause an error):
+.RS
+.TP
+.B IPv4 address options
+1 (Subnet Mask), 16 (Swap Server), 28 (Broadcast Address), 50 (Requested IP),
+54 (Server Identifier)
+.TP
+.B IPv4 address list options
+3 (Router), 4 (Time Server), 5 (Name Server), 6 (DNS), 7 (Log Server),
+8 (Cookie Server), 9 (LPR Server), 10 (Impress Server),
+11 (Resource Location Server), 33 (Static Routes), 41 (NIS Servers),
+42 (NTP Servers), 44 (NetBIOS Name Server)
+.TP
+.B Integer options
+2 (Time Offset, 32-bit), 13 (Boot File Size, 16-bit), 19 (IP Forwarding, 8-bit),
+23 (Default IP TTL, 8-bit), 26 (Interface MTU, 16-bit),
+37 (TCP Default TTL, 8-bit), 38 (TCP Keepalive Interval, 32-bit),
+51 (IP Address Lease Time, 32-bit),
+53 (DHCP Message Type, 8-bit), 57 (Max DHCP Message Size, 16-bit),
+58 (Renewal Time, 32-bit), 59 (Rebinding Time, 32-bit)
+.TP
+.B String options
+12 (Host Name), 15 (Domain Name), 17 (Root Path), 40 (NIS Domain Name),
+60 (Vendor Class Identifier), 61 (Client Identifier), 66 (TFTP Server Name),
+67 (Bootfile Name), 119 (Domain Search List), 252 (WPAD URL)
+.TP
+.B Classless static route options (RFC 3442 encoding)
+121 (Classless Static Routes).
+Format: "CIDR/mask,gateway" entries, space-separated.
+Example: \-\-dhcp-opt 121,"10.0.1.0/24,10.0.0.1 0.0.0.0/0,10.0.0.1"
+.RE
+
 .TP
 .BR \-t ", " \-\-tcp-ports " " \fIspec
 Configure TCP port forwarding to guest or namespace. \fIspec\fR can be one of:
-- 
2.54.0


^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH 0/6] Add --dhcp-boot and --dhcp-opt options
  2026-05-18 13:19 [PATCH 0/6] Add --dhcp-boot and --dhcp-opt options Anshu Kumari
  2026-05-18 13:19 ` [PATCH 1/6] conf: Add --dhcp-opt command-line option Anshu Kumari
@ 2026-05-19  5:30 ` David Gibson
  1 sibling, 0 replies; 13+ messages in thread
From: David Gibson @ 2026-05-19  5:30 UTC (permalink / raw)
  To: Anshu Kumari; +Cc: sbrivio, passt-dev, lvivier, jmaloy

[-- Attachment #1: Type: text/plain, Size: 1027 bytes --]

On Mon, May 18, 2026 at 06:49:56PM +0530, Anshu Kumari wrote:
> This series adds support for custom DHCP options in passt, enabling
> network boot (PXE/UEFI HTTP Boot) and arbitrary DHCP option injection.
> 
> Two new command-line flags are introduced:
> 
>   --dhcp-boot URL    Sets the boot file URL (DHCP option 67 and the
>                      legacy boot file field)
> 
>   --dhcp-opt CODE,VALUE
>                      Sets any DHCP option by numeric code, with
>                      type-aware parsing per RFC 2132
> 
> The DHCP reply path is extended with option overload support (RFC 2132
> option 52), allowing options to overflow into the file and sname fields
> when the standard options area is full.
> 
> *** BLURB HERE ***

Nit: remember to remove this boilerplate that git-publish inserts :)

-- 
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] 13+ messages in thread

* Re: [PATCH 1/6] conf: Add --dhcp-opt command-line option
  2026-05-18 13:19 ` [PATCH 1/6] conf: Add --dhcp-opt command-line option Anshu Kumari
  2026-05-18 13:19   ` [PATCH 2/6] conf: Add --dhcp-boot " Anshu Kumari
@ 2026-05-19  5:33   ` David Gibson
  1 sibling, 0 replies; 13+ messages in thread
From: David Gibson @ 2026-05-19  5:33 UTC (permalink / raw)
  To: Anshu Kumari; +Cc: sbrivio, passt-dev, lvivier, jmaloy

[-- Attachment #1: Type: text/plain, Size: 4266 bytes --]

On Mon, May 18, 2026 at 06:49:57PM +0530, Anshu Kumari wrote:
> Introduce the --dhcp-opt flag that allows setting arbitrary DHCP
> options from command-line in the form of [Option CODE,VALUE].
> This patch adds the option storage in struct ctx and CLI parsing;
> the type-aware value parser and DHCP reply injection follow
> in subsequent patches.
> 
> Link: https://bugs.passt.top/show_bug.cgi?id=192
> Signed-off-by: Anshu Kumari <anskuma@redhat.com>
> ---
>  conf.c  | 36 +++++++++++++++++++++++++++++++++++-
>  passt.h | 10 ++++++++++
>  2 files changed, 45 insertions(+), 1 deletion(-)
> 
> diff --git a/conf.c b/conf.c
> index 029b9c7..2624e58 100644
> --- a/conf.c
> +++ b/conf.c
> @@ -47,6 +47,7 @@
>  #include "lineread.h"
>  #include "isolation.h"
>  #include "log.h"
> +#include "dhcp.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"
> +		"  --dhcp-opt CODE,VAL	Set DHCP option by code\n");
>  	if (strstr(name, "pasta"))
>  		FPRINTF(f, "    default: don't use any search list\n");
>  	else
> @@ -844,6 +846,10 @@ static void conf_print(const struct ctx *c)
>  			info("    router: %s",
>  			     inet_ntop(AF_INET, &c->ip4.guest_gw,
>  				       buf, sizeof(buf)));
> +			for (i = 0; i < c->custom_opts_count; i++)
> +				info("    option %u: %s",
> +				     c->custom_opts[i].code,
> +				     c->custom_opts[i].str);
>  		}
>  
>  		for (i = 0; i < ARRAY_SIZE(c->ip4.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' },
> +		{"dhcp-opt", required_argument,		NULL,		33 },
>  		{ 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:";
> @@ -1465,6 +1472,33 @@ 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 33: {
> +			unsigned long code;
> +			const char *comma;
> +			char *end;
> +
> +			comma = strchr(optarg, ',');
> +			if (!comma)
> +				die("--dhcp-opt requires Option CODE,VALUE format");
> +
> +			code = strtoul(optarg, &end, 0);
> +			if (end != comma || code < 1 || code > 254)
> +				die("DHCP option code must be 1-254: %s",
> +				    optarg);
> +
> +			if (c->custom_opts_count >= MAX_CUSTOM_DHCP_OPTS)
> +				die("Too many --dhcp-opt entries (max %d)",
> +				    MAX_CUSTOM_DHCP_OPTS);
> +
> +			c->custom_opts[c->custom_opts_count].code = code;
> +			if (snprintf_check(c->custom_opts[c->custom_opts_count].str,
> +					   sizeof(c->custom_opts[0].str),
> +					   "%s", comma + 1))
> +				die("DHCP option value too long: %s",
> +				    comma + 1);
> +			c->custom_opts_count++;
> +			break;
> +		}
>  		case 'd':
>  			c->debug = 1;
>  			c->quiet = 0;
> diff --git a/passt.h b/passt.h
> index 1726965..acb57dd 100644
> --- a/passt.h
> +++ b/passt.h
> @@ -263,6 +263,16 @@ struct ctx {
>  	char hostname[PASST_MAXDNAME];
>  	char fqdn[PASST_MAXDNAME];
>  
> +#define MAX_CUSTOM_DHCP_OPTS	32
> +
> +	struct {
> +		uint8_t code;
> +		uint8_t len;
> +		uint8_t val[255];

The @len and @val fields are not used so far.  I'm assuming they are
later in the series, but in that case it's generally preferable to add
those fields in the patches that use them.  Possible more comments
there.

> +		char str[256];
> +	} custom_opts[MAX_CUSTOM_DHCP_OPTS];
> +	int custom_opts_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] 13+ messages in thread

* Re: [PATCH 2/6] conf: Add --dhcp-boot command-line option
  2026-05-18 13:19   ` [PATCH 2/6] conf: Add --dhcp-boot " Anshu Kumari
  2026-05-18 13:19     ` [PATCH 3/6] dhcp: Add option type table and value parser Anshu Kumari
@ 2026-05-19  5:35     ` David Gibson
  1 sibling, 0 replies; 13+ messages in thread
From: David Gibson @ 2026-05-19  5:35 UTC (permalink / raw)
  To: Anshu Kumari; +Cc: sbrivio, passt-dev, lvivier, jmaloy

[-- Attachment #1: Type: text/plain, Size: 2978 bytes --]

On Mon, May 18, 2026 at 06:49:58PM +0530, Anshu Kumari wrote:
> Introduce the --dhcp-boot flag that sets the boot file URL for
> network boot specially for ipxe. This patch adds the option
> storage and CLI parsing.
> 
> Link: https://bugs.passt.top/show_bug.cgi?id=192
> Signed-off-by: Anshu Kumari <anskuma@redhat.com>
> ---
>  conf.c  | 9 +++++++++
>  passt.h | 1 +
>  2 files changed, 10 insertions(+)
> 
> diff --git a/conf.c b/conf.c
> index 2624e58..61a393f 100644
> --- a/conf.c
> +++ b/conf.c
> @@ -618,6 +618,7 @@ static void usage(const char *name, FILE *f, int status)
>  		"    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"
> +		"  --dhcp-boot URL	Boot file URL for network boot\n"
>  		"  --dhcp-opt CODE,VAL	Set DHCP option by code\n");
>  	if (strstr(name, "pasta"))
>  		FPRINTF(f, "    default: don't use any search list\n");
> @@ -846,6 +847,8 @@ static void conf_print(const struct ctx *c)
>  			info("    router: %s",
>  			     inet_ntop(AF_INET, &c->ip4.guest_gw,
>  				       buf, sizeof(buf)));
> +			if (c->dhcp_boot[0])
> +				info("    boot file: %s", c->dhcp_boot);
>  			for (i = 0; i < c->custom_opts_count; i++)
>  				info("    option %u: %s",
>  				     c->custom_opts[i].code,
> @@ -1239,6 +1242,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' },
> +		{"dhcp-boot", required_argument,	NULL,		32 },
>  		{"dhcp-opt", required_argument,		NULL,		33 },
>  		{ 0 },
>  	};
> @@ -1472,6 +1476,11 @@ 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 32:
> +			if (snprintf_check(c->dhcp_boot, sizeof(c->dhcp_boot),
> +					   "%s", optarg))
> +				die("Boot file name too long: %s", optarg);
> +			break;

IIUC, --dhcp-boot foo is equivalent to --dhcp-opt 67,foo

Is that right?  If so, it would be preferable to make that more
explicit in the code, using a single way of storing the options and a
common "Add dhcp option helper" that's called from both the
--dhcp-boot and --dhcp-opt handling.


>  		case 33: {
>  			unsigned long code;
>  			const char *comma;
> diff --git a/passt.h b/passt.h
> index acb57dd..2354a0f 100644
> --- a/passt.h
> +++ b/passt.h
> @@ -262,6 +262,7 @@ struct ctx {
>  
>  	char hostname[PASST_MAXDNAME];
>  	char fqdn[PASST_MAXDNAME];
> +	char dhcp_boot[PATH_MAX];
>  
>  #define MAX_CUSTOM_DHCP_OPTS	32
>  
> -- 
> 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] 13+ messages in thread

* Re: [PATCH 3/6] dhcp: Add option type table and value parser
  2026-05-18 13:19     ` [PATCH 3/6] dhcp: Add option type table and value parser Anshu Kumari
  2026-05-18 13:20       ` [PATCH 4/6] dhcp: Refactor fill_one() to operate on a generic buffer Anshu Kumari
@ 2026-05-19  5:59       ` David Gibson
  1 sibling, 0 replies; 13+ messages in thread
From: David Gibson @ 2026-05-19  5:59 UTC (permalink / raw)
  To: Anshu Kumari; +Cc: sbrivio, passt-dev, lvivier, jmaloy

[-- Attachment #1: Type: text/plain, Size: 10791 bytes --]

On Mon, May 18, 2026 at 06:49:59PM +0530, Anshu Kumari wrote:
> Add an RFC 2132 type lookup table mapping DHCP option codes to their
> expected value formats, and a dhcp_opt_parse() function that converts
> CLI string values into their binary wire representation.
> 
> Wire dhcp_opt_parse() into the --dhcp-opt handler so that values are
> validated and encoded at configuration time.
> 
> Link: https://bugs.passt.top/show_bug.cgi?id=192
> Signed-off-by: Anshu Kumari <anskuma@redhat.com>
> ---
>  conf.c |   9 +++
>  dhcp.c | 227 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
>  dhcp.h |  15 ++++
>  3 files changed, 251 insertions(+)
> 
> diff --git a/conf.c b/conf.c
> index 61a393f..3ec10ac 100644
> --- a/conf.c
> +++ b/conf.c
> @@ -1485,6 +1485,7 @@ void conf(struct ctx *c, int argc, char **argv)
>  			unsigned long code;
>  			const char *comma;
>  			char *end;
> +			int len;
>  
>  			comma = strchr(optarg, ',');
>  			if (!comma)
> @@ -1499,7 +1500,15 @@ void conf(struct ctx *c, int argc, char **argv)
>  				die("Too many --dhcp-opt entries (max %d)",
>  				    MAX_CUSTOM_DHCP_OPTS);
>  
> +			len = dhcp_opt_parse(code, comma + 1,
> +					     c->custom_opts[c->custom_opts_count].val,
> +					     sizeof(c->custom_opts[0].val));
> +			if (len < 0)
> +				die("Invalid value for DHCP option %lu: %s",
> +				    code, comma + 1);
> +
>  			c->custom_opts[c->custom_opts_count].code = code;
> +			c->custom_opts[c->custom_opts_count].len = len;

I don't love the fact that .val and .str permanently store redundant
information.  Initially I was going to suggest that you delay
dhcp_opt_parse() until you're actually processing a DHCP message.
However, that's also not great at that point it awkward to handle
parse errors.  Not sure how best to tackle this for now.

>  			if (snprintf_check(c->custom_opts[c->custom_opts_count].str,
>  					   sizeof(c->custom_opts[0].str),
>  					   "%s", comma + 1))
> diff --git a/dhcp.c b/dhcp.c
> index 1ff8cba..9220516 100644
> --- a/dhcp.c
> +++ b/dhcp.c
> @@ -33,6 +33,233 @@
>  #include "log.h"
>  #include "dhcp.h"
>  
> +/**
> + * struct dhcp_opt_type_entry - Maps option code to RFC 2132 value type
> + * @code:	DHCP option code
> + * @type:	Expected value format
> + */
> +static const struct dhcp_opt_type_entry {
> +	uint8_t code;
> +	enum dhcp_opt_type type;
> +} dhcp_opt_types[] = {
> +	{  1, DHCP_OPT_IPV4 },		/* Subnet Mask */
> +	{  2, DHCP_OPT_UINT32 },	/* Time Offset */
> +	{  3, DHCP_OPT_IPV4_LIST },	/* Router */
> +	{  4, DHCP_OPT_IPV4_LIST },	/* Time Server */
> +	{  5, DHCP_OPT_IPV4_LIST },	/* Name Server */
> +
> +	{  6, DHCP_OPT_IPV4_LIST },	/* Domain Name Server */
> +	{  7, DHCP_OPT_IPV4_LIST },	/* Log Server */
> +	{  8, DHCP_OPT_IPV4_LIST },	/* Cookie Server */
> +	{  9, DHCP_OPT_IPV4_LIST },	/* LPR Server */
> +	{ 10, DHCP_OPT_IPV4_LIST },	/* Impress Server */
> +
> +	{ 11, DHCP_OPT_IPV4_LIST },	/* Resource Location Server */
> +	{ 12, DHCP_OPT_STR },		/* Host Name */
> +	{ 13, DHCP_OPT_UINT16 },	/* Boot File Size */
> +	{ 15, DHCP_OPT_STR },		/* Domain Name */
> +	{ 16, DHCP_OPT_IPV4 },		/* Swap Server */
> +
> +	{ 17, DHCP_OPT_STR },		/* Root Path */
> +	{ 19, DHCP_OPT_UINT8 },	/* IP Forwarding */
> +	{ 23, DHCP_OPT_UINT8 },	/* Default IP TTL */
> +	{ 26, DHCP_OPT_UINT16 },	/* Interface MTU */
> +	{ 28, DHCP_OPT_IPV4 },		/* Broadcast Address */
> +
> +	{ 33, DHCP_OPT_IPV4_LIST },	/* Static Routes (dest+router pairs) */
> +	{ 37, DHCP_OPT_UINT8 },	/* TCP Default TTL */
> +	{ 38, DHCP_OPT_UINT32 },	/* TCP Keepalive Interval */
> +	{ 40, DHCP_OPT_STR },		/* NIS Domain Name */
> +	{ 41, DHCP_OPT_IPV4_LIST },	/* NIS Servers */
> +
> +	{ 42, DHCP_OPT_IPV4_LIST },	/* NTP Servers */
> +	{ 44, DHCP_OPT_IPV4_LIST },	/* NetBIOS Name Server */
> +	{ 50, DHCP_OPT_IPV4 },		/* Requested IP Address */
> +	{ 51, DHCP_OPT_UINT32 },	/* IP Address Lease Time */
> +	{ 53, DHCP_OPT_UINT8 },	/* DHCP Message Type */
> +
> +	{ 54, DHCP_OPT_IPV4 },		/* Server Identifier */
> +	{ 57, DHCP_OPT_UINT16 },	/* Max DHCP Message Size */
> +	{ 58, DHCP_OPT_UINT32 },	/* Renewal (T1) Time */
> +	{ 59, DHCP_OPT_UINT32 },	/* Rebinding (T2) Time */
> +	{ 60, DHCP_OPT_STR },		/* Vendor Class Identifier */
> +
> +	{ 61, DHCP_OPT_STR },		/* Client Identifier */
> +	{ 66, DHCP_OPT_STR },		/* TFTP Server Name */
> +	{ 67, DHCP_OPT_STR },		/* Bootfile Name */
> +	{ 119, DHCP_OPT_STR },		/* Domain Search List (RFC 3397) */
> +	{ 121, DHCP_OPT_ROUTES },	/* Classless Static Routes */
> +
> +	{ 252, DHCP_OPT_STR },		/* WPAD URL */

At least initially, I'd suggest we don't permit the user to set
options which passt already manages internally (1, 3, 51, 53, 54, 55,
and 199 from a quick scan).

> +};
> +
> +/**
> + * dhcp_opt_type_lookup() - Look up the value type for a DHCP option code
> + * @code:	DHCP option code
> + *
> + * Return: type from table
> + */
> +static enum dhcp_opt_type dhcp_opt_type_lookup(uint8_t code)
> +{
> +	unsigned int i;
> +
> +	for (i = 0; i < ARRAY_SIZE(dhcp_opt_types); i++) {
> +		if (dhcp_opt_types[i].code == code)
> +			return dhcp_opt_types[i].type;
> +	}
> +
> +	return DHCP_OPT_NONE;
> +}
> +
> +/**
> + * dhcp_opt_parse() - Parse a DHCP option value
> + * @code:	DHCP option code (determines value type via lookup table)
> + * @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
> + */
> +int dhcp_opt_parse(uint8_t code, const char *str, uint8_t *buf, size_t buf_len)
> +{
> +	enum dhcp_opt_type type = dhcp_opt_type_lookup(code);
> +
> +	switch (type) {
> +	case DHCP_OPT_NONE: {
> +		die("Unsupported DHCP option: %u,"
> +		    " see passt(1) for supported codes",
> +		    code);
> +	}
> +	case DHCP_OPT_IPV4: {
> +		struct in_addr addr;
> +
> +		if (inet_pton(AF_INET, str, &addr) != 1)
> +			return -1;
> +		if (buf_len < sizeof(addr))
> +			return -1;
> +		memcpy(buf, &addr, sizeof(addr));
> +		return sizeof(addr);
> +	}
> +	case DHCP_OPT_IPV4_LIST: {
> +		char *tok, *saveptr;
> +		char tmp[1024];

Bare 1024 constant isn't ideal.

> +		int len = 0;
> +
> +		if (snprintf_check(tmp, sizeof(tmp), "%s", str))
> +			return -1;
> +
> +		for (tok = strtok_r(tmp, " ", &saveptr); tok;
> +		     tok = strtok_r(NULL, " ", &saveptr)) {

Apparently strtok_r() is specified by POSIX, so it should be ok.
Might be worth double checking musl has it, though.

" " seems an odd choice of delimiter - it's pretty awkward to use on
the command line.  I guess "," is also problematic since it can be
found within various of the of the option values.


> +			struct in_addr addr;
> +
> +			if (inet_pton(AF_INET, tok, &addr) != 1)
> +				return -1;
> +			if (len + (int)sizeof(addr) > (int)buf_len)
> +				return -1;
> +			memcpy(buf + len, &addr, sizeof(addr));
> +			len += sizeof(addr);
> +		}
> +		return len;
> +	}
> +	case DHCP_OPT_UINT8: {
> +		unsigned long val;
> +		char *end;
> +
> +		val = strtoul(str, &end, 0);
> +		if (*end || val > 255 || buf_len < 1)
> +			return -1;
> +		buf[0] = val;
> +		return 1;
> +	}
> +	case DHCP_OPT_UINT16: {
> +		unsigned long val;
> +		char *end;
> +
> +		val = strtoul(str, &end, 0);
> +		if (*end || val > 65535 || buf_len < 2)
> +			return -1;
> +		buf[0] = (val >> 8) & 0xff;
> +		buf[1] = val & 0xff;
> +		return 2;
> +	}
> +	case DHCP_OPT_UINT32: {
> +		unsigned long val;
> +		char *end;
> +
> +		val = strtoul(str, &end, 0);
> +		if (*end || buf_len < 4)
> +			return -1;
> +		buf[0] = (val >> 24) & 0xff;
> +		buf[1] = (val >> 16) & 0xff;
> +		buf[2] = (val >>  8) & 0xff;
> +		buf[3] = val & 0xff;
> +		return 4;
> +	}

It might be possible to simplify the sections above using a single
DHCP_OPT_INTEGER type, plus an extra field giving the integer width.

> +	case DHCP_OPT_ROUTES: {

I'd consider excluding this one for now.  For one thing it's rather a
lot of code.  But more importantly with the multi-address and route
monitoring changes we're considering, there's a fairly high chance we
might want to manage this one ourselves.

> +		/* RFC 3442: "CIDR/mask,gateway" entries, space-separated
> +		 * Encodes as: mask-width + significant-octets + router
> +		 * e.g. "192.168.1.0/24,10.0.0.1 0.0.0.0/0,10.0.0.1"
> +		 */
> +		char *tok, *saveptr;
> +		char tmp[1024];
> +		int len = 0;
> +
> +		if (snprintf_check(tmp, sizeof(tmp), "%s", str))
> +			return -1;
> +
> +		for (tok = strtok_r(tmp, " ", &saveptr); tok;
> +		     tok = strtok_r(NULL, " ", &saveptr)) {
> +			struct in_addr dest, gw;
> +			char *slash, *comma;
> +			unsigned long mask;
> +			int sig_octets;
> +
> +			slash = strchr(tok, '/');
> +			if (!slash)
> +				return -1;
> +			*slash = '\0';
> +
> +			if (inet_pton(AF_INET, tok, &dest) != 1)
> +				return -1;
> +
> +			comma = strchr(slash + 1, ',');
> +			if (!comma)
> +				return -1;
> +			*comma = '\0';
> +
> +			mask = strtoul(slash + 1, NULL, 10);
> +			if (mask > 32)
> +				return -1;
> +
> +			if (inet_pton(AF_INET, comma + 1, &gw) != 1)
> +				return -1;
> +
> +			sig_octets = (mask + 7) / 8;
> +
> +			if (len + 1 + sig_octets + 4 > (int)buf_len)
> +				return -1;
> +
> +			buf[len++] = mask;
> +			memcpy(buf + len, &dest, sig_octets);
> +			len += sig_octets;
> +			memcpy(buf + len, &gw, 4);
> +			len += 4;
> +		}
> +		return len;
> +	}
> +	case DHCP_OPT_STR: {
> +		size_t len = strlen(str);
> +
> +		if (!len || len >= buf_len)
> +			return -1;
> +		strncpy((char *)buf, str, buf_len);
> +		return len;
> +	}
> +	}
> +
> +	return -1;
> +}
> +
>  /**
>   * struct opt - DHCP option
>   * @sent:	Convenience flag, set while filling replies
> diff --git a/dhcp.h b/dhcp.h
> index cd50c99..01b2290 100644
> --- a/dhcp.h
> +++ b/dhcp.h
> @@ -6,7 +6,22 @@
>  #ifndef DHCP_H
>  #define DHCP_H
>  
> +/**
> + * enum dhcp_opt_type - DHCP option value types per RFC 2132
> + */
> +enum dhcp_opt_type {
> +	DHCP_OPT_NONE,
> +	DHCP_OPT_STR,
> +	DHCP_OPT_IPV4,
> +	DHCP_OPT_IPV4_LIST,
> +	DHCP_OPT_UINT8,
> +	DHCP_OPT_UINT16,
> +	DHCP_OPT_UINT32,
> +	DHCP_OPT_ROUTES,
> +};
> +
>  int dhcp(const struct ctx *c, struct iov_tail *data);
>  void dhcp_init(void);
> +int dhcp_opt_parse(uint8_t code, const char *str, uint8_t *buf, size_t buf_len);
>  
>  #endif /* DHCP_H */
> -- 
> 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] 13+ messages in thread

* Re: [PATCH 4/6] dhcp: Refactor fill_one() to operate on a generic buffer
  2026-05-18 13:20       ` [PATCH 4/6] dhcp: Refactor fill_one() to operate on a generic buffer Anshu Kumari
  2026-05-18 13:20         ` [PATCH 5/6] dhcp: Add option overload Anshu Kumari
@ 2026-05-19  6:02         ` David Gibson
  1 sibling, 0 replies; 13+ messages in thread
From: David Gibson @ 2026-05-19  6:02 UTC (permalink / raw)
  To: Anshu Kumari; +Cc: sbrivio, passt-dev, lvivier, jmaloy

[-- Attachment #1: Type: text/plain, Size: 3191 bytes --]

On Mon, May 18, 2026 at 06:50:00PM +0530, Anshu Kumari wrote:
> Change fill_one() to accept a buffer pointer and capacity instead of
> a struct msg pointer.  This is a pure refactor with no behavior change,
> preparing for option overload support where fill_one() will also write
> into the file and sname fields.
> 
> Link: https://bugs.passt.top/show_bug.cgi?id=192
> Signed-off-by: Anshu Kumari <anskuma@redhat.com>

Reviewed-by: David Gibson <david@gibson.dropbear.id.au>

Though one cosmetic flaw noted below.

> ---
>  dhcp.c | 27 +++++++++++++--------------
>  1 file changed, 13 insertions(+), 14 deletions(-)
> 
> diff --git a/dhcp.c b/dhcp.c
> index 9220516..a966c34 100644
> --- a/dhcp.c
> +++ b/dhcp.c
> @@ -358,28 +358,27 @@ struct msg {
>  } __attribute__((__packed__));
>  
>  /**
> - * fill_one() - Fill a single option in message
> - * @m:		Message to fill
> + * fill_one() - Fill a single option into a buffer
> + * @buf:	Buffer to write option
> + * @cap:	Usable capacity of @buf (excluding end marker)

Nit: I'd suggest "size" or "len". "cap" more commonly means
"capability" in the codebase, rather than "capacity".

>   * @o:		Option number
> - * @offset:	Current offset within options field, updated on insertion
> + * @offset:	Current offset within @buf, updated on insertion
>   *
> - * Return: false if m has space to write the option, true otherwise
> + * Return: false if @buf has space to write the option, true otherwise
>   */
> -static bool fill_one(struct msg *m, int o, int *offset)
> +static bool fill_one(uint8_t *buf, size_t cap, int o, int *offset)
>  {
>  	size_t slen = opts[o].slen;
>  
> -	/* If we don't have space to write the option, then just skip */
> -	if (*offset + 2 /* code and length of option */ + slen > OPT_MAX)
> +	if (*offset + 2 + slen > cap)
>  		return true;
>  
> -	m->o[*offset] = o;
> -	m->o[*offset + 1] = slen;
> +	buf[*offset] = o;
> +	buf[*offset + 1] = slen;
>  
> -	/* Move to option */
>  	*offset += 2;
>  
> -	memcpy(&m->o[*offset], opts[o].s, slen);
> +	memcpy(&buf[*offset], opts[o].s, slen);
>  
>  	opts[o].sent = 1;
>  	*offset += slen;
> @@ -404,19 +403,19 @@ static int fill(struct msg *m)
>  	 * Put it there explicitly, unless requested via option 55.
>  	 */
>  	if (opts[55].clen > 0 && !memchr(opts[55].c, 53, opts[55].clen))
> -		if (fill_one(m, 53, &offset))
> +		if (fill_one(m->o, OPT_MAX, 53, &offset))
>  			 debug("DHCP: skipping option 53");
>  
>  	for (i = 0; i < opts[55].clen; i++) {
>  		o = opts[55].c[i];
>  		if (opts[o].slen != -1)
> -			if (fill_one(m, o, &offset))
> +			if (fill_one(m->o, OPT_MAX, o, &offset))
>  				debug("DHCP: skipping option %i", o);
>  	}
>  
>  	for (o = 0; o < 255; o++) {
>  		if (opts[o].slen != -1 && !opts[o].sent)
> -			if (fill_one(m, o, &offset))
> +			if (fill_one(m->o, OPT_MAX, o, &offset))
>  				debug("DHCP: skipping option %i", o);
>  	}
>  
> -- 
> 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] 13+ messages in thread

* Re: [PATCH 5/6] dhcp: Add option overload
  2026-05-18 13:20         ` [PATCH 5/6] dhcp: Add option overload Anshu Kumari
  2026-05-18 13:20           ` [PATCH 6/6] doc: Add --dhcp-boot and --dhcp-opt to man page Anshu Kumari
@ 2026-05-19  6:11           ` David Gibson
  1 sibling, 0 replies; 13+ messages in thread
From: David Gibson @ 2026-05-19  6:11 UTC (permalink / raw)
  To: Anshu Kumari; +Cc: sbrivio, passt-dev, lvivier, jmaloy

[-- Attachment #1: Type: text/plain, Size: 5275 bytes --]

On Mon, May 18, 2026 at 06:50:01PM +0530, Anshu Kumari wrote:
> A user can enter lots of options in command-line which may not fit in
> existing buffer, So when the options field is full, overflow remaining
> DHCP options into the file and sname fields per RFC 2132 option 52.
> 
> Also, when the file field is not used for overload, copy the boot
> file URL there directly for legacy PXE clients.
> 
> Link: https://bugs.passt.top/show_bug.cgi?id=192
> Signed-off-by: Anshu Kumari <anskuma@redhat.com>
> ---
>  dhcp.c | 88 +++++++++++++++++++++++++++++++++++++++++++++++++++-------
>  1 file changed, 78 insertions(+), 10 deletions(-)
> 
> diff --git a/dhcp.c b/dhcp.c
> index a966c34..fde5d57 100644
> --- a/dhcp.c
> +++ b/dhcp.c
> @@ -386,13 +386,53 @@ static bool fill_one(uint8_t *buf, size_t cap, int o, int *offset)
>  }
>  
>  /**
> - * fill() - Fill options in message
> + * fill_overflow() - Fill remaining options into file and sname fields
> + * @m:		Message whose file/sname fields may be used for overflow
> + *
> + * Return: option 52 overload value: 0 if no overflow, 1 for file,
> + *         2 for sname, 3 for both
> + */
> +static int fill_overflow(struct msg *m)
> +{
> +	int file_off = 0, sname_off = 0, overload = 0;
> +	int o;
> +
> +	for (o = 0; o < 255; o++) {
> +		if (opts[o].slen == -1 || opts[o].sent)
> +			continue;
> +		fill_one(m->file, sizeof(m->file) - 1, o, &file_off);
> +	}
> +
> +	for (o = 0; o < 255; o++) {
> +		if (opts[o].slen == -1 || opts[o].sent)
> +			continue;
> +		if (fill_one(m->sname, sizeof(m->sname) - 1, o, &sname_off))
> +			debug("DHCP: skipping option %i (overload full)", o);
> +	}
> +
> +	if (file_off) {
> +		m->file[file_off] = 255;
> +		overload |= 1;

Some #defined constants for the overload bits would probably be a good idea.

> +	}
> +
> +	if (sname_off) {
> +		m->sname[sname_off] = 255;
> +		overload |= 2;
> +	}
> +
> +	return overload;
> +}
> +
> +/**
> + * fill() - Fill options in message, with overload into file/sname if needed
>   * @m:		Message to fill
> + * @overload:	Set to option 52 value (0 if none, 1/2/3 per RFC 2132)
>   *
>   * Return: current size of options field
>   */
> -static int fill(struct msg *m)
> +static int fill(struct msg *m, int *overload)
>  {
> +	size_t cap = OPT_MAX - 3;
>  	int i, o, offset = 0;
>  
>  	for (o = 0; o < 255; o++)
> @@ -403,20 +443,25 @@ static int fill(struct msg *m)
>  	 * Put it there explicitly, unless requested via option 55.
>  	 */
>  	if (opts[55].clen > 0 && !memchr(opts[55].c, 53, opts[55].clen))
> -		if (fill_one(m->o, OPT_MAX, 53, &offset))
> -			 debug("DHCP: skipping option 53");
> +		fill_one(m->o, cap, 53, &offset);
>  
>  	for (i = 0; i < opts[55].clen; i++) {
>  		o = opts[55].c[i];
>  		if (opts[o].slen != -1)
> -			if (fill_one(m->o, OPT_MAX, o, &offset))
> -				debug("DHCP: skipping option %i", o);
> +			fill_one(m->o, cap, o, &offset);
>  	}
>  
>  	for (o = 0; o < 255; o++) {
>  		if (opts[o].slen != -1 && !opts[o].sent)
> -			if (fill_one(m->o, OPT_MAX, o, &offset))
> -				debug("DHCP: skipping option %i", o);
> +			fill_one(m->o, cap, o, &offset);
> +	}
> +
> +	*overload = fill_overflow(m);
> +
> +	if (*overload) {
> +		m->o[offset++] = 52;
> +		m->o[offset++] = 1;
> +		m->o[offset++] = *overload;

If we reach this path then we've near-filled the normal option area.
What guarantees we'll have space for option 52 itself?

>  	}
>  
>  	m->o[offset++] = 255;
> @@ -541,6 +586,7 @@ int dhcp(const struct ctx *c, struct iov_tail *data)
>  	struct msg const *m;
>  	struct msg reply;
>  	unsigned int i;
> +	int overload;
>  
>  	eh = IOV_REMOVE_HEADER(data, eh_storage);
>  	iph = IOV_PEEK_HEADER(data, iph_storage);
> @@ -690,9 +736,31 @@ int dhcp(const struct ctx *c, struct iov_tail *data)
>  	}
>  
>  	if (!c->no_dhcp_dns_search)
> -		opt_set_dns_search(c, sizeof(m->o));
> +		opt_set_dns_search(c, sizeof(m->o) + sizeof(m->file)
> +					       + sizeof(m->sname));

Does passing the combined length here actually make sense?  IIUC each
single option still needs to fit within one of the buffer areas.

> +
> +	if (c->dhcp_boot[0]) {
> +		size_t boot_len = strlen(c->dhcp_boot);
> +
> +		if (boot_len <= sizeof(opts[67].s)) {
> +			opts[67].slen = boot_len;
> +			memcpy(opts[67].s, c->dhcp_boot, boot_len);
> +		}
> +	}
> +
> +	for (i = 0; i < (unsigned int)c->custom_opts_count; i++) {
> +		uint8_t code = c->custom_opts[i].code;
> +
> +		opts[code].slen = c->custom_opts[i].len;
> +		memcpy(opts[code].s, c->custom_opts[i].val,
> +		       c->custom_opts[i].len);
> +	}
> +
> +	dlen = offsetof(struct msg, o) + fill(&reply, &overload);
>  
> -	dlen = offsetof(struct msg, o) + fill(&reply);
> +	if (!(overload & 1) &&
> +	    c->dhcp_boot[0] && strlen(c->dhcp_boot) < sizeof(reply.file))
> +		memcpy(&reply.file, c->dhcp_boot, strlen(c->dhcp_boot) + 1);
>  
>  	if (m->flags & FLAG_BROADCAST)
>  		dst = in4addr_broadcast;
> -- 
> 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] 13+ messages in thread

end of thread, other threads:[~2026-05-19  6:12 UTC | newest]

Thread overview: 13+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-05-18 13:19 [PATCH 0/6] Add --dhcp-boot and --dhcp-opt options Anshu Kumari
2026-05-18 13:19 ` [PATCH 1/6] conf: Add --dhcp-opt command-line option Anshu Kumari
2026-05-18 13:19   ` [PATCH 2/6] conf: Add --dhcp-boot " Anshu Kumari
2026-05-18 13:19     ` [PATCH 3/6] dhcp: Add option type table and value parser Anshu Kumari
2026-05-18 13:20       ` [PATCH 4/6] dhcp: Refactor fill_one() to operate on a generic buffer Anshu Kumari
2026-05-18 13:20         ` [PATCH 5/6] dhcp: Add option overload Anshu Kumari
2026-05-18 13:20           ` [PATCH 6/6] doc: Add --dhcp-boot and --dhcp-opt to man page Anshu Kumari
2026-05-19  6:11           ` [PATCH 5/6] dhcp: Add option overload David Gibson
2026-05-19  6:02         ` [PATCH 4/6] dhcp: Refactor fill_one() to operate on a generic buffer David Gibson
2026-05-19  5:59       ` [PATCH 3/6] dhcp: Add option type table and value parser David Gibson
2026-05-19  5:35     ` [PATCH 2/6] conf: Add --dhcp-boot command-line option David Gibson
2026-05-19  5:33   ` [PATCH 1/6] conf: Add --dhcp-opt " David Gibson
2026-05-19  5:30 ` [PATCH 0/6] Add --dhcp-boot and --dhcp-opt options David Gibson

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).