* [PATCH v2 1/6] conf: Add --dhcp-opt command-line option
2026-05-26 12:31 [PATCH v2 0/6] Add --dhcp-boot and --dhcp-opt options Anshu Kumari
@ 2026-05-26 12:31 ` Anshu Kumari
2026-05-26 13:19 ` Laurent Vivier
` (2 more replies)
2026-05-26 12:31 ` [PATCH v2 2/6] conf: Add --dhcp-boot " Anshu Kumari
` (4 subsequent siblings)
5 siblings, 3 replies; 27+ messages in thread
From: Anshu Kumari @ 2026-05-26 12:31 UTC (permalink / raw)
To: anskuma, passt-dev, sbrivio; +Cc: jmaloy, lvivier, 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>
---
v2:
- Added kerneldoc for @custom_opts, @custom_opts.code, @custom_opts.str, and @custom_opts_count in struct ctx
- Removed len and val[255] fields from struct (moved to patch 3)
- Removed braces from case 33, moved declarations (optcode, comma, end) to function scope
- Renamed code → optcode to follow function-scope convention
---
conf.c | 34 +++++++++++++++++++++++++++++++++-
passt.h | 12 ++++++++++++
2 files changed, 45 insertions(+), 1 deletion(-)
diff --git a/conf.c b/conf.c
index 029b9c7..89d2127 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:";
@@ -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 optcode;
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,28 @@ 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:
+ comma = strchr(optarg, ',');
+ if (!comma)
+ die("--dhcp-opt requires Option CODE,VALUE format");
+
+ optcode = strtoul(optarg, &end, 0);
+ if (end != comma || optcode < 1 || optcode > 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 = optcode;
+ 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..3a0816f 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_opts: User-specified DHCP options from --dhcp-opt
+ * @custom_opts.code: DHCP option code
+ * @custom_opts.str: Original string value from command line
+ * @custom_opts_count: Number of entries in @custom_opts
* @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_DHCP_OPTS 32
+
+ struct {
+ uint8_t code;
+ 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] 27+ messages in thread* Re: [PATCH v2 1/6] conf: Add --dhcp-opt command-line option
2026-05-26 12:31 ` [PATCH v2 1/6] conf: Add --dhcp-opt command-line option Anshu Kumari
@ 2026-05-26 13:19 ` Laurent Vivier
2026-05-27 2:51 ` David Gibson
2026-05-27 7:23 ` Laurent Vivier
2 siblings, 0 replies; 27+ messages in thread
From: Laurent Vivier @ 2026-05-26 13:19 UTC (permalink / raw)
To: Anshu Kumari, passt-dev, sbrivio; +Cc: jmaloy, david
On 5/26/26 14:31, 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>
Reviewed-by: Laurent Vivier <lvivier@redhat.com>
one comment below
> ---
> v2:
> - Added kerneldoc for @custom_opts, @custom_opts.code, @custom_opts.str, and @custom_opts_count in struct ctx
> - Removed len and val[255] fields from struct (moved to patch 3)
> - Removed braces from case 33, moved declarations (optcode, comma, end) to function scope
> - Renamed code → optcode to follow function-scope convention
> ---
> conf.c | 34 +++++++++++++++++++++++++++++++++-
> passt.h | 12 ++++++++++++
> 2 files changed, 45 insertions(+), 1 deletion(-)
>
> diff --git a/conf.c b/conf.c
> index 029b9c7..89d2127 100644
> --- a/conf.c
> +++ b/conf.c
> @@ -47,6 +47,7 @@
> #include "lineread.h"
> #include "isolation.h"
> #include "log.h"
> +#include "dhcp.h"
I think this include is relevant to changes introduced in patch 3.
Thanks,
Laurent
> #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:";
> @@ -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 optcode;
> 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,28 @@ 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:
> + comma = strchr(optarg, ',');
> + if (!comma)
> + die("--dhcp-opt requires Option CODE,VALUE format");
> +
> + optcode = strtoul(optarg, &end, 0);
> + if (end != comma || optcode < 1 || optcode > 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 = optcode;
> + 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..3a0816f 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_opts: User-specified DHCP options from --dhcp-opt
> + * @custom_opts.code: DHCP option code
> + * @custom_opts.str: Original string value from command line
> + * @custom_opts_count: Number of entries in @custom_opts
> * @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_DHCP_OPTS 32
> +
> + struct {
> + uint8_t code;
> + char str[256];
> + } custom_opts[MAX_CUSTOM_DHCP_OPTS];
> + int custom_opts_count;
> +
> int ifi6;
> struct ip6_ctx ip6;
>
^ permalink raw reply [flat|nested] 27+ messages in thread* Re: [PATCH v2 1/6] conf: Add --dhcp-opt command-line option
2026-05-26 12:31 ` [PATCH v2 1/6] conf: Add --dhcp-opt command-line option Anshu Kumari
2026-05-26 13:19 ` Laurent Vivier
@ 2026-05-27 2:51 ` David Gibson
2026-05-28 5:02 ` Anshu Kumari
2026-05-27 7:23 ` Laurent Vivier
2 siblings, 1 reply; 27+ messages in thread
From: David Gibson @ 2026-05-27 2:51 UTC (permalink / raw)
To: Anshu Kumari; +Cc: passt-dev, sbrivio, jmaloy, lvivier
[-- Attachment #1: Type: text/plain, Size: 5571 bytes --]
On Tue, May 26, 2026 at 06:01:08PM +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>
Mostly LGTM, but one query below.
> ---
> v2:
> - Added kerneldoc for @custom_opts, @custom_opts.code, @custom_opts.str, and @custom_opts_count in struct ctx
> - Removed len and val[255] fields from struct (moved to patch 3)
> - Removed braces from case 33, moved declarations (optcode, comma, end) to function scope
> - Renamed code → optcode to follow function-scope convention
> ---
> conf.c | 34 +++++++++++++++++++++++++++++++++-
> passt.h | 12 ++++++++++++
> 2 files changed, 45 insertions(+), 1 deletion(-)
>
> diff --git a/conf.c b/conf.c
> index 029b9c7..89d2127 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:";
> @@ -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 optcode;
> 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,28 @@ 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:
> + comma = strchr(optarg, ',');
> + if (!comma)
> + die("--dhcp-opt requires Option CODE,VALUE format");
> +
> + optcode = strtoul(optarg, &end, 0);
> + if (end != comma || optcode < 1 || optcode > 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 = optcode;
What happens if the user specifies the same DHCP option code multiple
times?
I don't know off-hand if DHCP allows the same option to be presented
multiple times; I'm guessing not. In which case we probably want to
check for already-specified options and overwrite them, instead of
adding them to the array twice.
> + 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..3a0816f 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_opts: User-specified DHCP options from --dhcp-opt
> + * @custom_opts.code: DHCP option code
> + * @custom_opts.str: Original string value from command line
> + * @custom_opts_count: Number of entries in @custom_opts
> * @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_DHCP_OPTS 32
> +
> + struct {
> + uint8_t code;
> + 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] 27+ messages in thread* Re: [PATCH v2 1/6] conf: Add --dhcp-opt command-line option
2026-05-27 2:51 ` David Gibson
@ 2026-05-28 5:02 ` Anshu Kumari
2026-05-28 5:22 ` David Gibson
0 siblings, 1 reply; 27+ messages in thread
From: Anshu Kumari @ 2026-05-28 5:02 UTC (permalink / raw)
To: David Gibson; +Cc: passt-dev, sbrivio, jmaloy, lvivier
[-- Attachment #1: Type: text/plain, Size: 7547 bytes --]
On Wed, May 27, 2026 at 8:56 AM David Gibson <david@gibson.dropbear.id.au>
wrote:
> On Tue, May 26, 2026 at 06:01:08PM +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>
>
> Mostly LGTM, but one query below.
>
> > ---
> > v2:
> > - Added kerneldoc for @custom_opts, @custom_opts.code,
> @custom_opts.str, and @custom_opts_count in struct ctx
> > - Removed len and val[255] fields from struct (moved to patch 3)
> > - Removed braces from case 33, moved declarations (optcode, comma,
> end) to function scope
> > - Renamed code → optcode to follow function-scope convention
> > ---
> > conf.c | 34 +++++++++++++++++++++++++++++++++-
> > passt.h | 12 ++++++++++++
> > 2 files changed, 45 insertions(+), 1 deletion(-)
> >
> > diff --git a/conf.c b/conf.c
> > index 029b9c7..89d2127 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:";
> > @@ -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 optcode;
> > 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,28 @@ 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:
> > + comma = strchr(optarg, ',');
> > + if (!comma)
> > + die("--dhcp-opt requires Option CODE,VALUE
> format");
> > +
> > + optcode = strtoul(optarg, &end, 0);
> > + if (end != comma || optcode < 1 || optcode > 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 =
> optcode;
>
> What happens if the user specifies the same DHCP option code multiple
> times?
>
> I don't know off-hand if DHCP allows the same option to be presented
> multiple times; I'm guessing not. In which case we probably want to
> check for already-specified options and overwrite them, instead of
> adding them to the array twice.
>
If a user specifies the same DHCP option code multiple times, the option
value is overwritten.
The last specified value is considered, like if we run below command
./passt -f --dhcp-opt 6,8.8.8.8,8.8.4.4 --dhcp-opt 6,1.1.1.1,9.9.9.9
then passt DHCP option stores the last value
DHCP:
assign: 192.168.1.9
mask: 255.255.255.0
router: 192.168.1.1
option 6: 1.1.1.1,9.9.9.9
>
> > + 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..3a0816f 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_opts: User-specified DHCP options from --dhcp-opt
> > + * @custom_opts.code: DHCP option code
> > + * @custom_opts.str: Original string value from command line
> > + * @custom_opts_count: Number of entries in @custom_opts
> > * @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_DHCP_OPTS 32
> > +
> > + struct {
> > + uint8_t code;
> > + 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: Type: text/html, Size: 10297 bytes --]
^ permalink raw reply [flat|nested] 27+ messages in thread* Re: [PATCH v2 1/6] conf: Add --dhcp-opt command-line option
2026-05-28 5:02 ` Anshu Kumari
@ 2026-05-28 5:22 ` David Gibson
0 siblings, 0 replies; 27+ messages in thread
From: David Gibson @ 2026-05-28 5:22 UTC (permalink / raw)
To: Anshu Kumari; +Cc: passt-dev, sbrivio, jmaloy, lvivier
[-- Attachment #1: Type: text/plain, Size: 8364 bytes --]
On Thu, May 28, 2026 at 10:32:54AM +0530, Anshu Kumari wrote:
> On Wed, May 27, 2026 at 8:56 AM David Gibson <david@gibson.dropbear.id.au>
> wrote:
>
> > On Tue, May 26, 2026 at 06:01:08PM +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>
> >
> > Mostly LGTM, but one query below.
> >
> > > ---
> > > v2:
> > > - Added kerneldoc for @custom_opts, @custom_opts.code,
> > @custom_opts.str, and @custom_opts_count in struct ctx
> > > - Removed len and val[255] fields from struct (moved to patch 3)
> > > - Removed braces from case 33, moved declarations (optcode, comma,
> > end) to function scope
> > > - Renamed code → optcode to follow function-scope convention
> > > ---
> > > conf.c | 34 +++++++++++++++++++++++++++++++++-
> > > passt.h | 12 ++++++++++++
> > > 2 files changed, 45 insertions(+), 1 deletion(-)
> > >
> > > diff --git a/conf.c b/conf.c
> > > index 029b9c7..89d2127 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:";
> > > @@ -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 optcode;
> > > 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,28 @@ 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:
> > > + comma = strchr(optarg, ',');
> > > + if (!comma)
> > > + die("--dhcp-opt requires Option CODE,VALUE
> > format");
> > > +
> > > + optcode = strtoul(optarg, &end, 0);
> > > + if (end != comma || optcode < 1 || optcode > 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 =
> > optcode;
> >
> > What happens if the user specifies the same DHCP option code multiple
> > times?
> >
> > I don't know off-hand if DHCP allows the same option to be presented
> > multiple times; I'm guessing not. In which case we probably want to
> > check for already-specified options and overwrite them, instead of
> > adding them to the array twice.
> >
>
> If a user specifies the same DHCP option code multiple times, the option
> value is overwritten.
> The last specified value is considered, like if we run below command
>
> ./passt -f --dhcp-opt 6,8.8.8.8,8.8.4.4 --dhcp-opt 6,1.1.1.1,9.9.9.9
>
> then passt DHCP option stores the last value
> DHCP:
> assign: 192.168.1.9
> mask: 255.255.255.0
> router: 192.168.1.1
> option 6: 1.1.1.1,9.9.9.9
Ok, that's good. It's not clear to me where that behaviour is
implemented: the option parsing logic here seems to just add things to
the list, even if they're the same option.
> >
> > > + 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..3a0816f 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_opts: User-specified DHCP options from --dhcp-opt
> > > + * @custom_opts.code: DHCP option code
> > > + * @custom_opts.str: Original string value from command line
> > > + * @custom_opts_count: Number of entries in @custom_opts
> > > * @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_DHCP_OPTS 32
> > > +
> > > + struct {
> > > + uint8_t code;
> > > + 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
> >
--
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] 27+ messages in thread
* Re: [PATCH v2 1/6] conf: Add --dhcp-opt command-line option
2026-05-26 12:31 ` [PATCH v2 1/6] conf: Add --dhcp-opt command-line option Anshu Kumari
2026-05-26 13:19 ` Laurent Vivier
2026-05-27 2:51 ` David Gibson
@ 2026-05-27 7:23 ` Laurent Vivier
2 siblings, 0 replies; 27+ messages in thread
From: Laurent Vivier @ 2026-05-27 7:23 UTC (permalink / raw)
To: Anshu Kumari, passt-dev, sbrivio; +Cc: jmaloy, david
On 5/26/26 14:31, 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>
Tested-by: Laurent Vivier <lvivier@redhat.com>
UEFI With:
--dhcp-boot
http://dl-cdn.alpinelinux.org/alpine/v3.23/releases/x86_64/alpine-virt-3.23.2-x86_64.iso
--dhcp-opt 60,HTTPClient
IPv4 only, full series applied.
> ---
> v2:
> - Added kerneldoc for @custom_opts, @custom_opts.code, @custom_opts.str, and @custom_opts_count in struct ctx
> - Removed len and val[255] fields from struct (moved to patch 3)
> - Removed braces from case 33, moved declarations (optcode, comma, end) to function scope
> - Renamed code → optcode to follow function-scope convention
> ---
> conf.c | 34 +++++++++++++++++++++++++++++++++-
> passt.h | 12 ++++++++++++
> 2 files changed, 45 insertions(+), 1 deletion(-)
>
> diff --git a/conf.c b/conf.c
> index 029b9c7..89d2127 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:";
> @@ -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 optcode;
> 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,28 @@ 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:
> + comma = strchr(optarg, ',');
> + if (!comma)
> + die("--dhcp-opt requires Option CODE,VALUE format");
> +
> + optcode = strtoul(optarg, &end, 0);
> + if (end != comma || optcode < 1 || optcode > 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 = optcode;
> + 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..3a0816f 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_opts: User-specified DHCP options from --dhcp-opt
> + * @custom_opts.code: DHCP option code
> + * @custom_opts.str: Original string value from command line
> + * @custom_opts_count: Number of entries in @custom_opts
> * @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_DHCP_OPTS 32
> +
> + struct {
> + uint8_t code;
> + char str[256];
> + } custom_opts[MAX_CUSTOM_DHCP_OPTS];
> + int custom_opts_count;
> +
> int ifi6;
> struct ip6_ctx ip6;
>
^ permalink raw reply [flat|nested] 27+ messages in thread
* [PATCH v2 2/6] conf: Add --dhcp-boot command-line option
2026-05-26 12:31 [PATCH v2 0/6] Add --dhcp-boot and --dhcp-opt options Anshu Kumari
2026-05-26 12:31 ` [PATCH v2 1/6] conf: Add --dhcp-opt command-line option Anshu Kumari
@ 2026-05-26 12:31 ` Anshu Kumari
2026-05-26 13:25 ` Laurent Vivier
` (2 more replies)
2026-05-26 12:31 ` [PATCH v2 3/6] dhcp: Add option type table and value parser Anshu Kumari
` (3 subsequent siblings)
5 siblings, 3 replies; 27+ messages in thread
From: Anshu Kumari @ 2026-05-26 12:31 UTC (permalink / raw)
To: anskuma, passt-dev, sbrivio; +Cc: jmaloy, lvivier, 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>
---
v2:
- Removed separate dhcp_boot[PATH_MAX] field — --dhcp-boot foo now stores into custom_opts[] as code 67 (same as --dhcp-opt 67,foo)
---
conf.c | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/conf.c b/conf.c
index 89d2127..ae8ee26 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");
@@ -1239,6 +1240,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 },
};
@@ -1475,6 +1477,18 @@ 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 (c->custom_opts_count >= MAX_CUSTOM_DHCP_OPTS)
+ die("Too many DHCP options (max %d)",
+ MAX_CUSTOM_DHCP_OPTS);
+
+ c->custom_opts[c->custom_opts_count].code = 67;
+ if (snprintf_check(c->custom_opts[c->custom_opts_count].str,
+ sizeof(c->custom_opts[0].str),
+ "%s", optarg))
+ die("Boot file name too long: %s", optarg);
+ c->custom_opts_count++;
+ break;
case 33:
comma = strchr(optarg, ',');
if (!comma)
--
2.54.0
^ permalink raw reply [flat|nested] 27+ messages in thread* Re: [PATCH v2 2/6] conf: Add --dhcp-boot command-line option
2026-05-26 12:31 ` [PATCH v2 2/6] conf: Add --dhcp-boot " Anshu Kumari
@ 2026-05-26 13:25 ` Laurent Vivier
2026-05-27 2:52 ` David Gibson
2026-05-27 6:59 ` Laurent Vivier
2 siblings, 0 replies; 27+ messages in thread
From: Laurent Vivier @ 2026-05-26 13:25 UTC (permalink / raw)
To: Anshu Kumari, passt-dev, sbrivio; +Cc: jmaloy, david
On 5/26/26 14:31, 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>
Reviewed-by: Laurent Vivier <lvivier@redhat.com>
> ---
> v2:
> - Removed separate dhcp_boot[PATH_MAX] field — --dhcp-boot foo now stores into custom_opts[] as code 67 (same as --dhcp-opt 67,foo)
>
> ---
> conf.c | 14 ++++++++++++++
> 1 file changed, 14 insertions(+)
>
> diff --git a/conf.c b/conf.c
> index 89d2127..ae8ee26 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");
> @@ -1239,6 +1240,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 },
> };
> @@ -1475,6 +1477,18 @@ 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 (c->custom_opts_count >= MAX_CUSTOM_DHCP_OPTS)
> + die("Too many DHCP options (max %d)",
> + MAX_CUSTOM_DHCP_OPTS);
> +
> + c->custom_opts[c->custom_opts_count].code = 67;
> + if (snprintf_check(c->custom_opts[c->custom_opts_count].str,
> + sizeof(c->custom_opts[0].str),
> + "%s", optarg))
> + die("Boot file name too long: %s", optarg);
> + c->custom_opts_count++;
> + break;
> case 33:
> comma = strchr(optarg, ',');
> if (!comma)
^ permalink raw reply [flat|nested] 27+ messages in thread* Re: [PATCH v2 2/6] conf: Add --dhcp-boot command-line option
2026-05-26 12:31 ` [PATCH v2 2/6] conf: Add --dhcp-boot " Anshu Kumari
2026-05-26 13:25 ` Laurent Vivier
@ 2026-05-27 2:52 ` David Gibson
2026-05-28 5:14 ` Anshu Kumari
2026-05-27 6:59 ` Laurent Vivier
2 siblings, 1 reply; 27+ messages in thread
From: David Gibson @ 2026-05-27 2:52 UTC (permalink / raw)
To: Anshu Kumari; +Cc: passt-dev, sbrivio, jmaloy, lvivier
[-- Attachment #1: Type: text/plain, Size: 2552 bytes --]
On Tue, May 26, 2026 at 06:01:09PM +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>
> ---
> v2:
> - Removed separate dhcp_boot[PATH_MAX] field — --dhcp-boot foo now stores into custom_opts[] as code 67 (same as --dhcp-opt 67,foo)
>
> ---
> conf.c | 14 ++++++++++++++
> 1 file changed, 14 insertions(+)
>
> diff --git a/conf.c b/conf.c
> index 89d2127..ae8ee26 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");
> @@ -1239,6 +1240,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 },
> };
> @@ -1475,6 +1477,18 @@ 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 (c->custom_opts_count >= MAX_CUSTOM_DHCP_OPTS)
> + die("Too many DHCP options (max %d)",
> + MAX_CUSTOM_DHCP_OPTS);
> +
> + c->custom_opts[c->custom_opts_count].code = 67;
Similar to my query with --dhcp-opt, what happens if the user
specifies both --dhcp-boot and --dhcp-opt 67,XXX?
> + if (snprintf_check(c->custom_opts[c->custom_opts_count].str,
> + sizeof(c->custom_opts[0].str),
> + "%s", optarg))
> + die("Boot file name too long: %s", optarg);
> + c->custom_opts_count++;
> + break;
> case 33:
> comma = strchr(optarg, ',');
> if (!comma)
> --
> 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] 27+ messages in thread* Re: [PATCH v2 2/6] conf: Add --dhcp-boot command-line option
2026-05-27 2:52 ` David Gibson
@ 2026-05-28 5:14 ` Anshu Kumari
0 siblings, 0 replies; 27+ messages in thread
From: Anshu Kumari @ 2026-05-28 5:14 UTC (permalink / raw)
To: David Gibson; +Cc: passt-dev, sbrivio, jmaloy, lvivier
[-- Attachment #1: Type: text/plain, Size: 3474 bytes --]
On Wed, May 27, 2026 at 8:56 AM David Gibson <david@gibson.dropbear.id.au>
wrote:
> On Tue, May 26, 2026 at 06:01:09PM +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>
> > ---
> > v2:
> > - Removed separate dhcp_boot[PATH_MAX] field — --dhcp-boot foo now
> stores into custom_opts[] as code 67 (same as --dhcp-opt 67,foo)
> >
> > ---
> > conf.c | 14 ++++++++++++++
> > 1 file changed, 14 insertions(+)
> >
> > diff --git a/conf.c b/conf.c
> > index 89d2127..ae8ee26 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");
> > @@ -1239,6 +1240,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 },
> > };
> > @@ -1475,6 +1477,18 @@ 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 (c->custom_opts_count >= MAX_CUSTOM_DHCP_OPTS)
> > + die("Too many DHCP options (max %d)",
> > + MAX_CUSTOM_DHCP_OPTS);
> > +
> > + c->custom_opts[c->custom_opts_count].code = 67;
>
> Similar to my query with --dhcp-opt, what happens if the user
> specifies both --dhcp-boot and --dhcp-opt 67,XXX?
>
option value gets overwritten similar to --dhcp-opt flag.
>
>
> > + if
> (snprintf_check(c->custom_opts[c->custom_opts_count].str,
> > + sizeof(c->custom_opts[0].str),
> > + "%s", optarg))
> > + die("Boot file name too long: %s", optarg);
> > + c->custom_opts_count++;
> > + break;
> > case 33:
> > comma = strchr(optarg, ',');
> > if (!comma)
> > --
> > 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: Type: text/html, Size: 5188 bytes --]
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH v2 2/6] conf: Add --dhcp-boot command-line option
2026-05-26 12:31 ` [PATCH v2 2/6] conf: Add --dhcp-boot " Anshu Kumari
2026-05-26 13:25 ` Laurent Vivier
2026-05-27 2:52 ` David Gibson
@ 2026-05-27 6:59 ` Laurent Vivier
2 siblings, 0 replies; 27+ messages in thread
From: Laurent Vivier @ 2026-05-27 6:59 UTC (permalink / raw)
To: Anshu Kumari, passt-dev, sbrivio; +Cc: jmaloy, david
On 5/26/26 14:31, 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>
Tested-by: Laurent Vivier <lvivier@redhat.com>
> ---
> v2:
> - Removed separate dhcp_boot[PATH_MAX] field — --dhcp-boot foo now stores into custom_opts[] as code 67 (same as --dhcp-opt 67,foo)
>
> ---
> conf.c | 14 ++++++++++++++
> 1 file changed, 14 insertions(+)
>
> diff --git a/conf.c b/conf.c
> index 89d2127..ae8ee26 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");
> @@ -1239,6 +1240,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 },
> };
> @@ -1475,6 +1477,18 @@ 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 (c->custom_opts_count >= MAX_CUSTOM_DHCP_OPTS)
> + die("Too many DHCP options (max %d)",
> + MAX_CUSTOM_DHCP_OPTS);
> +
> + c->custom_opts[c->custom_opts_count].code = 67;
> + if (snprintf_check(c->custom_opts[c->custom_opts_count].str,
> + sizeof(c->custom_opts[0].str),
> + "%s", optarg))
> + die("Boot file name too long: %s", optarg);
> + c->custom_opts_count++;
> + break;
> case 33:
> comma = strchr(optarg, ',');
> if (!comma)
^ permalink raw reply [flat|nested] 27+ messages in thread
* [PATCH v2 3/6] dhcp: Add option type table and value parser
2026-05-26 12:31 [PATCH v2 0/6] Add --dhcp-boot and --dhcp-opt options Anshu Kumari
2026-05-26 12:31 ` [PATCH v2 1/6] conf: Add --dhcp-opt command-line option Anshu Kumari
2026-05-26 12:31 ` [PATCH v2 2/6] conf: Add --dhcp-boot " Anshu Kumari
@ 2026-05-26 12:31 ` Anshu Kumari
2026-05-26 16:05 ` Laurent Vivier
2026-05-27 3:26 ` David Gibson
2026-05-26 12:31 ` [PATCH v2 4/6] dhcp: Refactor fill_one() to operate on a generic buffer Anshu Kumari
` (2 subsequent siblings)
5 siblings, 2 replies; 27+ messages in thread
From: Anshu Kumari @ 2026-05-26 12:31 UTC (permalink / raw)
To: anskuma, passt-dev, sbrivio; +Cc: jmaloy, lvivier, 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>
---
v2:
- Replaced struct lookup table + dhcp_opt_type_lookup() function with flat dhcp_opt_types[256] array indexed by code.
- Consolidated DHCP_OPT_UINT8/UINT16/UINT32 into single DHCP_OPT_INTEGER with dhcp_opt_int_width[256] table.
- Dropped DHCP_OPT_ROUTES / option 121 entirely.
- Added kerneldoc for enum dhcp_opt_type values.
- Removed curly braces from switch cases, declarations before switch.
- Added newlines before return statements.
- Changed IP list delimiter from space to comma (--dhcp-opt 6,1.1.1.1,8.8.8.8).
- Defined DHCP_OPT_PARSE_BUF constant for bare 1024.
- Added len and val[255] fields to struct here (moved from patch 1).
- Added kerneldoc for @custom_opts.len and @custom_opts.val.
- Wired dhcp_opt_parse() into case 32 (--dhcp-boot) to populate val/len.
---
conf.c | 16 ++++++
dhcp.c | 148 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
dhcp.h | 17 +++++++
passt.h | 4 ++
4 files changed, 185 insertions(+)
diff --git a/conf.c b/conf.c
index ae8ee26..3a5f45d 100644
--- a/conf.c
+++ b/conf.c
@@ -1266,6 +1266,7 @@ void conf(struct ctx *c, int argc, char **argv)
char *end;
uid_t uid;
gid_t gid;
+ int len;
if (c->mode == MODE_PASTA)
@@ -1482,7 +1483,14 @@ void conf(struct ctx *c, int argc, char **argv)
die("Too many DHCP options (max %d)",
MAX_CUSTOM_DHCP_OPTS);
+ ret = dhcp_opt_parse(67, optarg,
+ c->custom_opts[c->custom_opts_count].val,
+ sizeof(c->custom_opts[0].val));
+ if (ret < 0)
+ die("Invalid boot file value: %s", optarg);
+
c->custom_opts[c->custom_opts_count].code = 67;
+ c->custom_opts[c->custom_opts_count].len = ret;
if (snprintf_check(c->custom_opts[c->custom_opts_count].str,
sizeof(c->custom_opts[0].str),
"%s", optarg))
@@ -1503,7 +1511,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(optcode, 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",
+ optcode, comma + 1);
+
c->custom_opts[c->custom_opts_count].code = optcode;
+ 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..e1c95ad 100644
--- a/dhcp.c
+++ b/dhcp.c
@@ -33,6 +33,154 @@
#include "log.h"
#include "dhcp.h"
+/**
+ * dhcp_opt_types - Maps option code to RFC 2132 value type, indexed by code
+ */
+static const enum dhcp_opt_type dhcp_opt_types[256] = {
+ [1] = DHCP_OPT_IPV4, /* Subnet Mask */
+ [2] = DHCP_OPT_INTEGER, /* 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_INTEGER, /* Boot File Size */
+ [15] = DHCP_OPT_STR, /* Domain Name */
+ [16] = DHCP_OPT_IPV4, /* Swap Server */
+ [17] = DHCP_OPT_STR, /* Root Path */
+ [19] = DHCP_OPT_INTEGER, /* IP Forwarding */
+ [23] = DHCP_OPT_INTEGER, /* Default IP TTL */
+ [26] = DHCP_OPT_INTEGER, /* Interface MTU */
+ [28] = DHCP_OPT_IPV4, /* Broadcast Address */
+ [33] = DHCP_OPT_IPV4_LIST, /* Static Routes */
+ [37] = DHCP_OPT_INTEGER, /* TCP Default TTL */
+ [38] = DHCP_OPT_INTEGER, /* 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_INTEGER, /* IP Address Lease Time */
+ [53] = DHCP_OPT_INTEGER, /* DHCP Message Type */
+ [54] = DHCP_OPT_IPV4, /* Server Identifier */
+ [55] = DHCP_OPT_STR, /* Parameter Request List */
+ [57] = DHCP_OPT_INTEGER, /* Max DHCP Message Size */
+ [58] = DHCP_OPT_INTEGER, /* Renewal (T1) Time */
+ [59] = DHCP_OPT_INTEGER, /* 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 */
+ [252] = DHCP_OPT_STR, /* WPAD URL */
+};
+
+/**
+ * dhcp_opt_int_width - Integer width in bytes for INTEGER-typed options
+ */
+static const uint8_t dhcp_opt_int_width[256] = {
+ [2] = 4, /* Time Offset */
+ [13] = 2, /* Boot File Size */
+ [19] = 1, /* IP Forwarding */
+ [23] = 1, /* Default IP TTL */
+ [26] = 2, /* Interface MTU */
+ [37] = 1, /* TCP Default TTL */
+ [38] = 4, /* TCP Keepalive Interval */
+ [51] = 4, /* IP Address Lease Time */
+ [53] = 1, /* DHCP Message Type */
+ [57] = 2, /* Max DHCP Message Size */
+ [58] = 4, /* Renewal (T1) Time */
+ [59] = 4, /* Rebinding (T2) Time */
+};
+
+#define DHCP_OPT_PARSE_BUF 1024
+
+/**
+ * dhcp_opt_parse() - Parse a DHCP option value
+ * @code: DHCP option code
+ * @str: DHCP Value string from command line
+ * @buf: Output buffer
+ * @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_types[code];
+ char tmp[DHCP_OPT_PARSE_BUF];
+ char *tok, *saveptr, *end;
+ struct in_addr addr;
+ unsigned long val;
+ unsigned int i;
+ uint8_t width;
+ size_t slen;
+ int len;
+
+ switch (type) {
+ case DHCP_OPT_NONE:
+ die("Unsupported DHCP option: %u,"
+ " see passt(1) for supported codes", code);
+ case DHCP_OPT_IPV4:
+ 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:
+ len = 0;
+
+ if (snprintf_check(tmp, sizeof(tmp), "%s", str))
+ return -1;
+
+ for (tok = strtok_r(tmp, ",", &saveptr); tok;
+ tok = strtok_r(NULL, ",", &saveptr)) {
+ 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_INTEGER:
+ width = dhcp_opt_int_width[code];
+ val = strtoul(str, &end, 0);
+
+ if (*end || buf_len < width)
+ return -1;
+
+ if (width < 4 && val >= (1UL << (width * 8)))
+ return -1;
+
+ for (i = 0; i < width; i++)
+ buf[i] = (val >> ((width - 1 - i) * 8)) & 0xff;
+
+ return width;
+ case DHCP_OPT_STR:
+ slen = strlen(str);
+
+ if (!slen || slen >= buf_len)
+ return -1;
+
+ strncpy((char *)buf, str, buf_len);
+
+ return slen;
+ }
+
+ return -1;
+}
+
/**
* struct opt - DHCP option
* @sent: Convenience flag, set while filling replies
diff --git a/dhcp.h b/dhcp.h
index cd50c99..3da571c 100644
--- a/dhcp.h
+++ b/dhcp.h
@@ -6,7 +6,24 @@
#ifndef DHCP_H
#define DHCP_H
+/**
+ * enum dhcp_opt_type - DHCP option value types per RFC 2132
+ * @DHCP_OPT_NONE: Unsupported or unknown option
+ * @DHCP_OPT_STR: Variable-length string
+ * @DHCP_OPT_IPV4: Single IPv4 address
+ * @DHCP_OPT_IPV4_LIST:Multiple IPv4 addresses, comma-separated
+ * @DHCP_OPT_INTEGER: Unsigned integer (1, 2, or 4 bytes)
+ */
+enum dhcp_opt_type {
+ DHCP_OPT_NONE,
+ DHCP_OPT_STR,
+ DHCP_OPT_IPV4,
+ DHCP_OPT_IPV4_LIST,
+ DHCP_OPT_INTEGER,
+};
+
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 */
diff --git a/passt.h b/passt.h
index 3a0816f..751fee3 100644
--- a/passt.h
+++ b/passt.h
@@ -184,6 +184,8 @@ struct ip6_ctx {
* @fqdn: Guest FQDN
* @custom_opts: User-specified DHCP options from --dhcp-opt
* @custom_opts.code: DHCP option code
+ * @custom_opts.len: Length of binary value in @val
+ * @custom_opts.val: Binary-encoded option value
* @custom_opts.str: Original string value from command line
* @custom_opts_count: Number of entries in @custom_opts
* @ifi6: Template interface for IPv6, -1: none, 0: IPv6 disabled
@@ -271,6 +273,8 @@ struct ctx {
struct {
uint8_t code;
+ uint8_t len;
+ uint8_t val[255];
char str[256];
} custom_opts[MAX_CUSTOM_DHCP_OPTS];
int custom_opts_count;
--
2.54.0
^ permalink raw reply [flat|nested] 27+ messages in thread* Re: [PATCH v2 3/6] dhcp: Add option type table and value parser
2026-05-26 12:31 ` [PATCH v2 3/6] dhcp: Add option type table and value parser Anshu Kumari
@ 2026-05-26 16:05 ` Laurent Vivier
2026-05-27 3:00 ` David Gibson
2026-05-28 5:39 ` Anshu Kumari
2026-05-27 3:26 ` David Gibson
1 sibling, 2 replies; 27+ messages in thread
From: Laurent Vivier @ 2026-05-26 16:05 UTC (permalink / raw)
To: Anshu Kumari, passt-dev, sbrivio; +Cc: jmaloy, david
On 5/26/26 14:31, 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>
> ---
> v2:
> - Replaced struct lookup table + dhcp_opt_type_lookup() function with flat dhcp_opt_types[256] array indexed by code.
> - Consolidated DHCP_OPT_UINT8/UINT16/UINT32 into single DHCP_OPT_INTEGER with dhcp_opt_int_width[256] table.
> - Dropped DHCP_OPT_ROUTES / option 121 entirely.
> - Added kerneldoc for enum dhcp_opt_type values.
> - Removed curly braces from switch cases, declarations before switch.
> - Added newlines before return statements.
> - Changed IP list delimiter from space to comma (--dhcp-opt 6,1.1.1.1,8.8.8.8).
> - Defined DHCP_OPT_PARSE_BUF constant for bare 1024.
> - Added len and val[255] fields to struct here (moved from patch 1).
> - Added kerneldoc for @custom_opts.len and @custom_opts.val.
> - Wired dhcp_opt_parse() into case 32 (--dhcp-boot) to populate val/len.
> ---
> conf.c | 16 ++++++
> dhcp.c | 148 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
> dhcp.h | 17 +++++++
> passt.h | 4 ++
> 4 files changed, 185 insertions(+)
>
> diff --git a/conf.c b/conf.c
> index ae8ee26..3a5f45d 100644
> --- a/conf.c
> +++ b/conf.c
> @@ -1266,6 +1266,7 @@ void conf(struct ctx *c, int argc, char **argv)
> char *end;
> uid_t uid;
> gid_t gid;
> + int len;
I think you don't need to introduce len for --dhcp-opt as you already use ret for --dhcp-boot
>
>
> if (c->mode == MODE_PASTA)
> @@ -1482,7 +1483,14 @@ void conf(struct ctx *c, int argc, char **argv)
> die("Too many DHCP options (max %d)",
> MAX_CUSTOM_DHCP_OPTS);
>
> + ret = dhcp_opt_parse(67, optarg,
> + c->custom_opts[c->custom_opts_count].val,
> + sizeof(c->custom_opts[0].val));
> + if (ret < 0)
> + die("Invalid boot file value: %s", optarg);
> +
> c->custom_opts[c->custom_opts_count].code = 67;
> + c->custom_opts[c->custom_opts_count].len = ret;
> if (snprintf_check(c->custom_opts[c->custom_opts_count].str,
> sizeof(c->custom_opts[0].str),
> "%s", optarg))
> @@ -1503,7 +1511,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(optcode, comma + 1,
> + c->custom_opts[c->custom_opts_count].val,
> + sizeof(c->custom_opts[0].val));
you can use "ret" here too
> + if (len < 0)
> + die("Invalid value for DHCP option %lu: %s",
> + optcode, comma + 1);
> +
> c->custom_opts[c->custom_opts_count].code = optcode;
> + 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..e1c95ad 100644
> --- a/dhcp.c
> +++ b/dhcp.c
> @@ -33,6 +33,154 @@
> #include "log.h"
> #include "dhcp.h"
>
> +/**
> + * dhcp_opt_types - Maps option code to RFC 2132 value type, indexed by code
> + */
> +static const enum dhcp_opt_type dhcp_opt_types[256] = {
> + [1] = DHCP_OPT_IPV4, /* Subnet Mask */
> + [2] = DHCP_OPT_INTEGER, /* 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_INTEGER, /* Boot File Size */
> + [15] = DHCP_OPT_STR, /* Domain Name */
> + [16] = DHCP_OPT_IPV4, /* Swap Server */
> + [17] = DHCP_OPT_STR, /* Root Path */
> + [19] = DHCP_OPT_INTEGER, /* IP Forwarding */
> + [23] = DHCP_OPT_INTEGER, /* Default IP TTL */
> + [26] = DHCP_OPT_INTEGER, /* Interface MTU */
> + [28] = DHCP_OPT_IPV4, /* Broadcast Address */
> + [33] = DHCP_OPT_IPV4_LIST, /* Static Routes */
> + [37] = DHCP_OPT_INTEGER, /* TCP Default TTL */
> + [38] = DHCP_OPT_INTEGER, /* 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_INTEGER, /* IP Address Lease Time */
> + [53] = DHCP_OPT_INTEGER, /* DHCP Message Type */
> + [54] = DHCP_OPT_IPV4, /* Server Identifier */
> + [55] = DHCP_OPT_STR, /* Parameter Request List */
This is a client option, I don't think we need to manage it.
> + [57] = DHCP_OPT_INTEGER, /* Max DHCP Message Size */
> + [58] = DHCP_OPT_INTEGER, /* Renewal (T1) Time */
> + [59] = DHCP_OPT_INTEGER, /* Rebinding (T2) Time */
> + [60] = DHCP_OPT_STR, /* Vendor Class Identifier */
> + [61] = DHCP_OPT_STR, /* Client Identifier */
This is also a client option.
> + [66] = DHCP_OPT_STR, /* TFTP Server Name */
> + [67] = DHCP_OPT_STR, /* Bootfile Name */
> + [119] = DHCP_OPT_STR, /* Domain Search List */
This is not really a string as this uses the message compression described in RFC 1035,
4.1.4. (See also 3. Example in rfc3397). I'm not sure we can encode this manually on the
command line. And RFC 3396 defines how to encode search string for more than 255
characters length
> + [252] = DHCP_OPT_STR, /* WPAD URL */
> +};
> +
> +/**
> + * dhcp_opt_int_width - Integer width in bytes for INTEGER-typed options
> + */
> +static const uint8_t dhcp_opt_int_width[256] = {
> + [2] = 4, /* Time Offset */
> + [13] = 2, /* Boot File Size */
> + [19] = 1, /* IP Forwarding */
> + [23] = 1, /* Default IP TTL */
> + [26] = 2, /* Interface MTU */
> + [37] = 1, /* TCP Default TTL */
> + [38] = 4, /* TCP Keepalive Interval */
> + [51] = 4, /* IP Address Lease Time */
> + [53] = 1, /* DHCP Message Type */
> + [57] = 2, /* Max DHCP Message Size */
> + [58] = 4, /* Renewal (T1) Time */
> + [59] = 4, /* Rebinding (T2) Time */
> +};
> +
> +#define DHCP_OPT_PARSE_BUF 1024
Do we need 1024 bytes, all options are bounded to 256 bytes.
> +
> +/**
> + * dhcp_opt_parse() - Parse a DHCP option value
> + * @code: DHCP option code
> + * @str: DHCP Value string from command line
> + * @buf: Output buffer
> + * @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_types[code];
> + char tmp[DHCP_OPT_PARSE_BUF];
> + char *tok, *saveptr, *end;
> + struct in_addr addr;
> + unsigned long val;
> + unsigned int i;
> + uint8_t width;
> + size_t slen;
> + int len;
> +
> + switch (type) {
> + case DHCP_OPT_NONE:
> + die("Unsupported DHCP option: %u,"
> + " see passt(1) for supported codes", code);
> + case DHCP_OPT_IPV4:
> + 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:
> + len = 0;
> +
> + if (snprintf_check(tmp, sizeof(tmp), "%s", str))
> + return -1;
> +
> + for (tok = strtok_r(tmp, ",", &saveptr); tok;
> + tok = strtok_r(NULL, ",", &saveptr)) {
> + 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_INTEGER:
> + width = dhcp_opt_int_width[code];
> + val = strtoul(str, &end, 0);
> +
> + if (*end || buf_len < width)
> + return -1;
> +
> + if (width < 4 && val >= (1UL << (width * 8)))
> + return -1;
> +
> + for (i = 0; i < width; i++)
> + buf[i] = (val >> ((width - 1 - i) * 8)) & 0xff;
> +
> + return width;
> + case DHCP_OPT_STR:
> + slen = strlen(str);
> +
> + if (!slen || slen >= buf_len)
> + return -1;
> +
> + strncpy((char *)buf, str, buf_len);
> +
> + return slen;
> + }
> +
> + return -1;
> +}
> +
> /**
> * struct opt - DHCP option
> * @sent: Convenience flag, set while filling replies
> diff --git a/dhcp.h b/dhcp.h
> index cd50c99..3da571c 100644
> --- a/dhcp.h
> +++ b/dhcp.h
> @@ -6,7 +6,24 @@
> #ifndef DHCP_H
> #define DHCP_H
>
> +/**
> + * enum dhcp_opt_type - DHCP option value types per RFC 2132
> + * @DHCP_OPT_NONE: Unsupported or unknown option
> + * @DHCP_OPT_STR: Variable-length string
> + * @DHCP_OPT_IPV4: Single IPv4 address
> + * @DHCP_OPT_IPV4_LIST:Multiple IPv4 addresses, comma-separated
> + * @DHCP_OPT_INTEGER: Unsigned integer (1, 2, or 4 bytes)
> + */
> +enum dhcp_opt_type {
> + DHCP_OPT_NONE,
> + DHCP_OPT_STR,
> + DHCP_OPT_IPV4,
> + DHCP_OPT_IPV4_LIST,
> + DHCP_OPT_INTEGER,
> +};
> +
> 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 */
> diff --git a/passt.h b/passt.h
> index 3a0816f..751fee3 100644
> --- a/passt.h
> +++ b/passt.h
> @@ -184,6 +184,8 @@ struct ip6_ctx {
> * @fqdn: Guest FQDN
> * @custom_opts: User-specified DHCP options from --dhcp-opt
> * @custom_opts.code: DHCP option code
> + * @custom_opts.len: Length of binary value in @val
> + * @custom_opts.val: Binary-encoded option value
> * @custom_opts.str: Original string value from command line
> * @custom_opts_count: Number of entries in @custom_opts
> * @ifi6: Template interface for IPv6, -1: none, 0: IPv6 disabled
> @@ -271,6 +273,8 @@ struct ctx {
>
> struct {
> uint8_t code;
> + uint8_t len;
> + uint8_t val[255];
> char str[256];
Why do we need to keep str[] once the value is encoded into val[]?
> } custom_opts[MAX_CUSTOM_DHCP_OPTS];
> int custom_opts_count;
Thanks,
Laurent
^ permalink raw reply [flat|nested] 27+ messages in thread* Re: [PATCH v2 3/6] dhcp: Add option type table and value parser
2026-05-26 16:05 ` Laurent Vivier
@ 2026-05-27 3:00 ` David Gibson
2026-05-28 5:39 ` Anshu Kumari
1 sibling, 0 replies; 27+ messages in thread
From: David Gibson @ 2026-05-27 3:00 UTC (permalink / raw)
To: Laurent Vivier; +Cc: Anshu Kumari, passt-dev, sbrivio, jmaloy
[-- Attachment #1: Type: text/plain, Size: 12217 bytes --]
On Tue, May 26, 2026 at 06:05:04PM +0200, Laurent Vivier wrote:
> On 5/26/26 14:31, 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>
> > ---
> > v2:
> > - Replaced struct lookup table + dhcp_opt_type_lookup() function with flat dhcp_opt_types[256] array indexed by code.
> > - Consolidated DHCP_OPT_UINT8/UINT16/UINT32 into single DHCP_OPT_INTEGER with dhcp_opt_int_width[256] table.
> > - Dropped DHCP_OPT_ROUTES / option 121 entirely.
> > - Added kerneldoc for enum dhcp_opt_type values.
> > - Removed curly braces from switch cases, declarations before switch.
> > - Added newlines before return statements.
> > - Changed IP list delimiter from space to comma (--dhcp-opt 6,1.1.1.1,8.8.8.8).
> > - Defined DHCP_OPT_PARSE_BUF constant for bare 1024.
> > - Added len and val[255] fields to struct here (moved from patch 1).
> > - Added kerneldoc for @custom_opts.len and @custom_opts.val.
> > - Wired dhcp_opt_parse() into case 32 (--dhcp-boot) to populate val/len.
> > ---
> > conf.c | 16 ++++++
> > dhcp.c | 148 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
> > dhcp.h | 17 +++++++
> > passt.h | 4 ++
> > 4 files changed, 185 insertions(+)
> >
> > diff --git a/conf.c b/conf.c
> > index ae8ee26..3a5f45d 100644
> > --- a/conf.c
> > +++ b/conf.c
> > @@ -1266,6 +1266,7 @@ void conf(struct ctx *c, int argc, char **argv)
> > char *end;
> > uid_t uid;
> > gid_t gid;
> > + int len;
>
> I think you don't need to introduce len for --dhcp-opt as you already use ret for --dhcp-boot
Also the inline code here in conf.c is becoming fairly bulky, and
there's a little duplication between the --dhcp-boot and --dhcp-opt
paths. I think it might be worth making an dhcp_add_option() helper
allowing more of this logic, including this local to be moved to a
function in dhcp.c.
That will probably also make life easier for handling repeated options.
> >
> > if (c->mode == MODE_PASTA)
> > @@ -1482,7 +1483,14 @@ void conf(struct ctx *c, int argc, char **argv)
> > die("Too many DHCP options (max %d)",
> > MAX_CUSTOM_DHCP_OPTS);
> > + ret = dhcp_opt_parse(67, optarg,
> > + c->custom_opts[c->custom_opts_count].val,
> > + sizeof(c->custom_opts[0].val));
> > + if (ret < 0)
> > + die("Invalid boot file value: %s", optarg);
> > +
> > c->custom_opts[c->custom_opts_count].code = 67;
> > + c->custom_opts[c->custom_opts_count].len = ret;
> > if (snprintf_check(c->custom_opts[c->custom_opts_count].str,
> > sizeof(c->custom_opts[0].str),
> > "%s", optarg))
> > @@ -1503,7 +1511,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(optcode, comma + 1,
> > + c->custom_opts[c->custom_opts_count].val,
> > + sizeof(c->custom_opts[0].val));
>
> you can use "ret" here too
>
> > + if (len < 0)
> > + die("Invalid value for DHCP option %lu: %s",
> > + optcode, comma + 1);
> > +
> > c->custom_opts[c->custom_opts_count].code = optcode;
> > + 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..e1c95ad 100644
> > --- a/dhcp.c
> > +++ b/dhcp.c
> > @@ -33,6 +33,154 @@
> > #include "log.h"
> > #include "dhcp.h"
> > +/**
> > + * dhcp_opt_types - Maps option code to RFC 2132 value type, indexed by code
> > + */
> > +static const enum dhcp_opt_type dhcp_opt_types[256] = {
> > + [1] = DHCP_OPT_IPV4, /* Subnet Mask */
> > + [2] = DHCP_OPT_INTEGER, /* 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_INTEGER, /* Boot File Size */
> > + [15] = DHCP_OPT_STR, /* Domain Name */
> > + [16] = DHCP_OPT_IPV4, /* Swap Server */
> > + [17] = DHCP_OPT_STR, /* Root Path */
> > + [19] = DHCP_OPT_INTEGER, /* IP Forwarding */
> > + [23] = DHCP_OPT_INTEGER, /* Default IP TTL */
> > + [26] = DHCP_OPT_INTEGER, /* Interface MTU */
> > + [28] = DHCP_OPT_IPV4, /* Broadcast Address */
> > + [33] = DHCP_OPT_IPV4_LIST, /* Static Routes */
> > + [37] = DHCP_OPT_INTEGER, /* TCP Default TTL */
> > + [38] = DHCP_OPT_INTEGER, /* 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_INTEGER, /* IP Address Lease Time */
> > + [53] = DHCP_OPT_INTEGER, /* DHCP Message Type */
> > + [54] = DHCP_OPT_IPV4, /* Server Identifier */
> > + [55] = DHCP_OPT_STR, /* Parameter Request List */
>
> This is a client option, I don't think we need to manage it.
>
> > + [57] = DHCP_OPT_INTEGER, /* Max DHCP Message Size */
> > + [58] = DHCP_OPT_INTEGER, /* Renewal (T1) Time */
> > + [59] = DHCP_OPT_INTEGER, /* Rebinding (T2) Time */
> > + [60] = DHCP_OPT_STR, /* Vendor Class Identifier */
> > + [61] = DHCP_OPT_STR, /* Client Identifier */
>
> This is also a client option.
>
> > + [66] = DHCP_OPT_STR, /* TFTP Server Name */
> > + [67] = DHCP_OPT_STR, /* Bootfile Name */
> > + [119] = DHCP_OPT_STR, /* Domain Search List */
>
> This is not really a string as this uses the message compression described
> in RFC 1035, 4.1.4. (See also 3. Example in rfc3397). I'm not sure we can
> encode this manually on the command line. And RFC 3396 defines how to encode
> search string for more than 255 characters length
It's also already possible to set this via --dhcp-search. For now I
think we want to exclude any options we already set ourselves (which
includes 119). If we discover someone needs this in future, then we
can figure out how the generic and specific ways of specifying it
should interact.
>
> > + [252] = DHCP_OPT_STR, /* WPAD URL */
> > +};
> > +
> > +/**
> > + * dhcp_opt_int_width - Integer width in bytes for INTEGER-typed options
> > + */
> > +static const uint8_t dhcp_opt_int_width[256] = {
> > + [2] = 4, /* Time Offset */
> > + [13] = 2, /* Boot File Size */
> > + [19] = 1, /* IP Forwarding */
> > + [23] = 1, /* Default IP TTL */
> > + [26] = 2, /* Interface MTU */
> > + [37] = 1, /* TCP Default TTL */
> > + [38] = 4, /* TCP Keepalive Interval */
> > + [51] = 4, /* IP Address Lease Time */
> > + [53] = 1, /* DHCP Message Type */
> > + [57] = 2, /* Max DHCP Message Size */
> > + [58] = 4, /* Renewal (T1) Time */
> > + [59] = 4, /* Rebinding (T2) Time */
> > +};
> > +
> > +#define DHCP_OPT_PARSE_BUF 1024
>
> Do we need 1024 bytes, all options are bounded to 256 bytes.
> > +
> > +/**
> > + * dhcp_opt_parse() - Parse a DHCP option value
> > + * @code: DHCP option code
> > + * @str: DHCP Value string from command line
> > + * @buf: Output buffer
> > + * @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_types[code];
> > + char tmp[DHCP_OPT_PARSE_BUF];
> > + char *tok, *saveptr, *end;
> > + struct in_addr addr;
> > + unsigned long val;
> > + unsigned int i;
> > + uint8_t width;
> > + size_t slen;
> > + int len;
> > +
> > + switch (type) {
> > + case DHCP_OPT_NONE:
> > + die("Unsupported DHCP option: %u,"
> > + " see passt(1) for supported codes", code);
> > + case DHCP_OPT_IPV4:
> > + 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:
> > + len = 0;
> > +
> > + if (snprintf_check(tmp, sizeof(tmp), "%s", str))
> > + return -1;
> > +
> > + for (tok = strtok_r(tmp, ",", &saveptr); tok;
> > + tok = strtok_r(NULL, ",", &saveptr)) {
> > + 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_INTEGER:
> > + width = dhcp_opt_int_width[code];
> > + val = strtoul(str, &end, 0);
> > +
> > + if (*end || buf_len < width)
> > + return -1;
> > +
> > + if (width < 4 && val >= (1UL << (width * 8)))
> > + return -1;
> > +
> > + for (i = 0; i < width; i++)
> > + buf[i] = (val >> ((width - 1 - i) * 8)) & 0xff;
> > +
> > + return width;
> > + case DHCP_OPT_STR:
> > + slen = strlen(str);
> > +
> > + if (!slen || slen >= buf_len)
> > + return -1;
> > +
> > + strncpy((char *)buf, str, buf_len);
> > +
> > + return slen;
> > + }
> > +
> > + return -1;
> > +}
> > +
> > /**
> > * struct opt - DHCP option
> > * @sent: Convenience flag, set while filling replies
> > diff --git a/dhcp.h b/dhcp.h
> > index cd50c99..3da571c 100644
> > --- a/dhcp.h
> > +++ b/dhcp.h
> > @@ -6,7 +6,24 @@
> > #ifndef DHCP_H
> > #define DHCP_H
> > +/**
> > + * enum dhcp_opt_type - DHCP option value types per RFC 2132
> > + * @DHCP_OPT_NONE: Unsupported or unknown option
> > + * @DHCP_OPT_STR: Variable-length string
> > + * @DHCP_OPT_IPV4: Single IPv4 address
> > + * @DHCP_OPT_IPV4_LIST:Multiple IPv4 addresses, comma-separated
> > + * @DHCP_OPT_INTEGER: Unsigned integer (1, 2, or 4 bytes)
> > + */
> > +enum dhcp_opt_type {
> > + DHCP_OPT_NONE,
> > + DHCP_OPT_STR,
> > + DHCP_OPT_IPV4,
> > + DHCP_OPT_IPV4_LIST,
> > + DHCP_OPT_INTEGER,
> > +};
> > +
> > 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 */
> > diff --git a/passt.h b/passt.h
> > index 3a0816f..751fee3 100644
> > --- a/passt.h
> > +++ b/passt.h
> > @@ -184,6 +184,8 @@ struct ip6_ctx {
> > * @fqdn: Guest FQDN
> > * @custom_opts: User-specified DHCP options from --dhcp-opt
> > * @custom_opts.code: DHCP option code
> > + * @custom_opts.len: Length of binary value in @val
> > + * @custom_opts.val: Binary-encoded option value
> > * @custom_opts.str: Original string value from command line
> > * @custom_opts_count: Number of entries in @custom_opts
> > * @ifi6: Template interface for IPv6, -1: none, 0: IPv6 disabled
> > @@ -271,6 +273,8 @@ struct ctx {
> > struct {
> > uint8_t code;
> > + uint8_t len;
> > + uint8_t val[255];
> > char str[256];
>
> Why do we need to keep str[] once the value is encoded into val[]?
>
> > } custom_opts[MAX_CUSTOM_DHCP_OPTS];
> > int custom_opts_count;
>
> Thanks,
> Laurent
>
--
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] 27+ messages in thread* Re: [PATCH v2 3/6] dhcp: Add option type table and value parser
2026-05-26 16:05 ` Laurent Vivier
2026-05-27 3:00 ` David Gibson
@ 2026-05-28 5:39 ` Anshu Kumari
2026-05-28 5:45 ` David Gibson
1 sibling, 1 reply; 27+ messages in thread
From: Anshu Kumari @ 2026-05-28 5:39 UTC (permalink / raw)
To: Laurent Vivier; +Cc: passt-dev, sbrivio, jmaloy, david
[-- Attachment #1: Type: text/plain, Size: 14161 bytes --]
On Tue, May 26, 2026 at 9:35 PM Laurent Vivier <lvivier@redhat.com> wrote:
> On 5/26/26 14:31, 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>
> > ---
> > v2:
> > - Replaced struct lookup table + dhcp_opt_type_lookup() function with
> flat dhcp_opt_types[256] array indexed by code.
> > - Consolidated DHCP_OPT_UINT8/UINT16/UINT32 into single
> DHCP_OPT_INTEGER with dhcp_opt_int_width[256] table.
> > - Dropped DHCP_OPT_ROUTES / option 121 entirely.
> > - Added kerneldoc for enum dhcp_opt_type values.
> > - Removed curly braces from switch cases, declarations before switch.
> > - Added newlines before return statements.
> > - Changed IP list delimiter from space to comma (--dhcp-opt
> 6,1.1.1.1,8.8.8.8).
> > - Defined DHCP_OPT_PARSE_BUF constant for bare 1024.
> > - Added len and val[255] fields to struct here (moved from patch 1).
> > - Added kerneldoc for @custom_opts.len and @custom_opts.val.
> > - Wired dhcp_opt_parse() into case 32 (--dhcp-boot) to populate
> val/len.
> > ---
> > conf.c | 16 ++++++
> > dhcp.c | 148 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
> > dhcp.h | 17 +++++++
> > passt.h | 4 ++
> > 4 files changed, 185 insertions(+)
> >
> > diff --git a/conf.c b/conf.c
> > index ae8ee26..3a5f45d 100644
> > --- a/conf.c
> > +++ b/conf.c
> > @@ -1266,6 +1266,7 @@ void conf(struct ctx *c, int argc, char **argv)
> > char *end;
> > uid_t uid;
> > gid_t gid;
> > + int len;
>
> I think you don't need to introduce len for --dhcp-opt as you already use
> ret for --dhcp-boot
> >
> >
> > if (c->mode == MODE_PASTA)
> > @@ -1482,7 +1483,14 @@ void conf(struct ctx *c, int argc, char **argv)
> > die("Too many DHCP options (max %d)",
> > MAX_CUSTOM_DHCP_OPTS);
> >
> > + ret = dhcp_opt_parse(67, optarg,
> > +
> c->custom_opts[c->custom_opts_count].val,
> > +
> sizeof(c->custom_opts[0].val));
> > + if (ret < 0)
> > + die("Invalid boot file value: %s", optarg);
> > +
> > c->custom_opts[c->custom_opts_count].code = 67;
> > + c->custom_opts[c->custom_opts_count].len = ret;
> > if
> (snprintf_check(c->custom_opts[c->custom_opts_count].str,
> > sizeof(c->custom_opts[0].str),
> > "%s", optarg))
> > @@ -1503,7 +1511,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(optcode, comma + 1,
> > +
> c->custom_opts[c->custom_opts_count].val,
> > +
> sizeof(c->custom_opts[0].val));
>
> you can use "ret" here too
>
> > + if (len < 0)
> > + die("Invalid value for DHCP option %lu:
> %s",
> > + optcode, comma + 1);
> > +
> > c->custom_opts[c->custom_opts_count].code =
> optcode;
> > + 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..e1c95ad 100644
> > --- a/dhcp.c
> > +++ b/dhcp.c
> > @@ -33,6 +33,154 @@
> > #include "log.h"
> > #include "dhcp.h"
> >
> > +/**
> > + * dhcp_opt_types - Maps option code to RFC 2132 value type, indexed by
> code
> > + */
> > +static const enum dhcp_opt_type dhcp_opt_types[256] = {
> > + [1] = DHCP_OPT_IPV4, /* Subnet Mask */
> > + [2] = DHCP_OPT_INTEGER, /* 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_INTEGER, /* Boot File Size */
> > + [15] = DHCP_OPT_STR, /* Domain Name */
> > + [16] = DHCP_OPT_IPV4, /* Swap Server */
> > + [17] = DHCP_OPT_STR, /* Root Path */
> > + [19] = DHCP_OPT_INTEGER, /* IP Forwarding */
> > + [23] = DHCP_OPT_INTEGER, /* Default IP TTL */
> > + [26] = DHCP_OPT_INTEGER, /* Interface MTU */
> > + [28] = DHCP_OPT_IPV4, /* Broadcast Address */
> > + [33] = DHCP_OPT_IPV4_LIST, /* Static Routes */
> > + [37] = DHCP_OPT_INTEGER, /* TCP Default TTL */
> > + [38] = DHCP_OPT_INTEGER, /* 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_INTEGER, /* IP Address Lease Time */
> > + [53] = DHCP_OPT_INTEGER, /* DHCP Message Type */
> > + [54] = DHCP_OPT_IPV4, /* Server Identifier */
> > + [55] = DHCP_OPT_STR, /* Parameter Request List */
>
> This is a client option, I don't think we need to manage it.
>
> > + [57] = DHCP_OPT_INTEGER, /* Max DHCP Message Size */
> > + [58] = DHCP_OPT_INTEGER, /* Renewal (T1) Time */
> > + [59] = DHCP_OPT_INTEGER, /* Rebinding (T2) Time */
> > + [60] = DHCP_OPT_STR, /* Vendor Class Identifier */
> > + [61] = DHCP_OPT_STR, /* Client Identifier */
>
> This is also a client option.
>
> > + [66] = DHCP_OPT_STR, /* TFTP Server Name */
> > + [67] = DHCP_OPT_STR, /* Bootfile Name */
> > + [119] = DHCP_OPT_STR, /* Domain Search List */
>
> This is not really a string as this uses the message compression described
> in RFC 1035,
> 4.1.4. (See also 3. Example in rfc3397). I'm not sure we can encode this
> manually on the
> command line. And RFC 3396 defines how to encode search string for more
> than 255
> characters length
>
> > + [252] = DHCP_OPT_STR, /* WPAD URL */
> > +};
> > +
> > +/**
> > + * dhcp_opt_int_width - Integer width in bytes for INTEGER-typed options
> > + */
> > +static const uint8_t dhcp_opt_int_width[256] = {
> > + [2] = 4, /* Time Offset */
> > + [13] = 2, /* Boot File Size */
> > + [19] = 1, /* IP Forwarding */
> > + [23] = 1, /* Default IP TTL */
> > + [26] = 2, /* Interface MTU */
> > + [37] = 1, /* TCP Default TTL */
> > + [38] = 4, /* TCP Keepalive Interval */
> > + [51] = 4, /* IP Address Lease Time */
> > + [53] = 1, /* DHCP Message Type */
> > + [57] = 2, /* Max DHCP Message Size */
> > + [58] = 4, /* Renewal (T1) Time */
> > + [59] = 4, /* Rebinding (T2) Time */
> > +};
> > +
> > +#define DHCP_OPT_PARSE_BUF 1024
>
> Do we need 1024 bytes, all options are bounded to 256 bytes.
> > +
> > +/**
> > + * dhcp_opt_parse() - Parse a DHCP option value
> > + * @code: DHCP option code
> > + * @str: DHCP Value string from command line
> > + * @buf: Output buffer
> > + * @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_types[code];
> > + char tmp[DHCP_OPT_PARSE_BUF];
> > + char *tok, *saveptr, *end;
> > + struct in_addr addr;
> > + unsigned long val;
> > + unsigned int i;
> > + uint8_t width;
> > + size_t slen;
> > + int len;
> > +
> > + switch (type) {
> > + case DHCP_OPT_NONE:
> > + die("Unsupported DHCP option: %u,"
> > + " see passt(1) for supported codes", code);
> > + case DHCP_OPT_IPV4:
> > + 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:
> > + len = 0;
> > +
> > + if (snprintf_check(tmp, sizeof(tmp), "%s", str))
> > + return -1;
> > +
> > + for (tok = strtok_r(tmp, ",", &saveptr); tok;
> > + tok = strtok_r(NULL, ",", &saveptr)) {
> > + 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_INTEGER:
> > + width = dhcp_opt_int_width[code];
> > + val = strtoul(str, &end, 0);
> > +
> > + if (*end || buf_len < width)
> > + return -1;
> > +
> > + if (width < 4 && val >= (1UL << (width * 8)))
> > + return -1;
> > +
> > + for (i = 0; i < width; i++)
> > + buf[i] = (val >> ((width - 1 - i) * 8)) & 0xff;
> > +
> > + return width;
> > + case DHCP_OPT_STR:
> > + slen = strlen(str);
> > +
> > + if (!slen || slen >= buf_len)
> > + return -1;
> > +
> > + strncpy((char *)buf, str, buf_len);
> > +
> > + return slen;
> > + }
> > +
> > + return -1;
> > +}
> > +
> > /**
> > * struct opt - DHCP option
> > * @sent: Convenience flag, set while filling replies
> > diff --git a/dhcp.h b/dhcp.h
> > index cd50c99..3da571c 100644
> > --- a/dhcp.h
> > +++ b/dhcp.h
> > @@ -6,7 +6,24 @@
> > #ifndef DHCP_H
> > #define DHCP_H
> >
> > +/**
> > + * enum dhcp_opt_type - DHCP option value types per RFC 2132
> > + * @DHCP_OPT_NONE: Unsupported or unknown option
> > + * @DHCP_OPT_STR: Variable-length string
> > + * @DHCP_OPT_IPV4: Single IPv4 address
> > + * @DHCP_OPT_IPV4_LIST:Multiple IPv4 addresses, comma-separated
> > + * @DHCP_OPT_INTEGER: Unsigned integer (1, 2, or 4 bytes)
> > + */
> > +enum dhcp_opt_type {
> > + DHCP_OPT_NONE,
> > + DHCP_OPT_STR,
> > + DHCP_OPT_IPV4,
> > + DHCP_OPT_IPV4_LIST,
> > + DHCP_OPT_INTEGER,
> > +};
> > +
> > 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 */
> > diff --git a/passt.h b/passt.h
> > index 3a0816f..751fee3 100644
> > --- a/passt.h
> > +++ b/passt.h
> > @@ -184,6 +184,8 @@ struct ip6_ctx {
> > * @fqdn: Guest FQDN
> > * @custom_opts: User-specified DHCP options from --dhcp-opt
> > * @custom_opts.code: DHCP option code
> > + * @custom_opts.len: Length of binary value in @val
> > + * @custom_opts.val: Binary-encoded option value
> > * @custom_opts.str: Original string value from command line
> > * @custom_opts_count: Number of entries in @custom_opts
> > * @ifi6: Template interface for IPv6, -1: none, 0: IPv6
> disabled
> > @@ -271,6 +273,8 @@ struct ctx {
> >
> > struct {
> > uint8_t code;
> > + uint8_t len;
> > + uint8_t val[255];
> > char str[256];
>
> Why do we need to keep str[] once the value is encoded into val[]?
>
To print the option value as entered by user.
anskuma@anskuma-thinkpadp1gen7:~/Documents/passt$ ./passt -f --dhcp-opt
60,HTTPClient --dhcp-opt 67,http://192.168.1.1:8080/alpine.iso --dhcp-opt
12,testhost --dhcp-opt 15,test.example.com --dhcp-opt 6,8.8.8.8,8.8.4.4
--dhcp-opt 26,1400 --dhcp-opt 42,192.168.1.1 --dhcp-opt 51,360
--dhcp-opt 6,1.1.1.1,9.9.9.9
UNIX domain socket bound at /tmp/passt_1.socket
No IPv6 nameserver available for NDP/DHCPv6
Template interface: wlp9s0f0 (IPv4), wlp9s0f0 (IPv6)
MAC:
host: 9a:55:9a:55:9a:55
NAT to host 127.0.0.1: 192.168.1.1
DHCP:
assign: 192.168.1.9
mask: 255.255.255.0
router: 192.168.1.1
*option 60: HTTPClient option 67: http://192.168.1.1:8080/alpine.iso
<http://192.168.1.1:8080/alpine.iso> option 12: testhost option 15:
test.example.com <http://test.example.com> option 6: 1.1.1.1,9.9.9.9
option 26: 1400 option 42: 192.168.1.1 option 51: 360*
>
> > } custom_opts[MAX_CUSTOM_DHCP_OPTS];
> > int custom_opts_count;
>
> Thanks,
> Laurent
>
>
[-- Attachment #2: Type: text/html, Size: 18291 bytes --]
^ permalink raw reply [flat|nested] 27+ messages in thread* Re: [PATCH v2 3/6] dhcp: Add option type table and value parser
2026-05-28 5:39 ` Anshu Kumari
@ 2026-05-28 5:45 ` David Gibson
0 siblings, 0 replies; 27+ messages in thread
From: David Gibson @ 2026-05-28 5:45 UTC (permalink / raw)
To: Anshu Kumari; +Cc: Laurent Vivier, passt-dev, sbrivio, jmaloy
[-- Attachment #1: Type: text/plain, Size: 15289 bytes --]
On Thu, May 28, 2026 at 11:09:06AM +0530, Anshu Kumari wrote:
> On Tue, May 26, 2026 at 9:35 PM Laurent Vivier <lvivier@redhat.com> wrote:
>
> > On 5/26/26 14:31, 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>
> > > ---
> > > v2:
> > > - Replaced struct lookup table + dhcp_opt_type_lookup() function with
> > flat dhcp_opt_types[256] array indexed by code.
> > > - Consolidated DHCP_OPT_UINT8/UINT16/UINT32 into single
> > DHCP_OPT_INTEGER with dhcp_opt_int_width[256] table.
> > > - Dropped DHCP_OPT_ROUTES / option 121 entirely.
> > > - Added kerneldoc for enum dhcp_opt_type values.
> > > - Removed curly braces from switch cases, declarations before switch.
> > > - Added newlines before return statements.
> > > - Changed IP list delimiter from space to comma (--dhcp-opt
> > 6,1.1.1.1,8.8.8.8).
> > > - Defined DHCP_OPT_PARSE_BUF constant for bare 1024.
> > > - Added len and val[255] fields to struct here (moved from patch 1).
> > > - Added kerneldoc for @custom_opts.len and @custom_opts.val.
> > > - Wired dhcp_opt_parse() into case 32 (--dhcp-boot) to populate
> > val/len.
> > > ---
> > > conf.c | 16 ++++++
> > > dhcp.c | 148 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
> > > dhcp.h | 17 +++++++
> > > passt.h | 4 ++
> > > 4 files changed, 185 insertions(+)
> > >
> > > diff --git a/conf.c b/conf.c
> > > index ae8ee26..3a5f45d 100644
> > > --- a/conf.c
> > > +++ b/conf.c
> > > @@ -1266,6 +1266,7 @@ void conf(struct ctx *c, int argc, char **argv)
> > > char *end;
> > > uid_t uid;
> > > gid_t gid;
> > > + int len;
> >
> > I think you don't need to introduce len for --dhcp-opt as you already use
> > ret for --dhcp-boot
> > >
> > >
> > > if (c->mode == MODE_PASTA)
> > > @@ -1482,7 +1483,14 @@ void conf(struct ctx *c, int argc, char **argv)
> > > die("Too many DHCP options (max %d)",
> > > MAX_CUSTOM_DHCP_OPTS);
> > >
> > > + ret = dhcp_opt_parse(67, optarg,
> > > +
> > c->custom_opts[c->custom_opts_count].val,
> > > +
> > sizeof(c->custom_opts[0].val));
> > > + if (ret < 0)
> > > + die("Invalid boot file value: %s", optarg);
> > > +
> > > c->custom_opts[c->custom_opts_count].code = 67;
> > > + c->custom_opts[c->custom_opts_count].len = ret;
> > > if
> > (snprintf_check(c->custom_opts[c->custom_opts_count].str,
> > > sizeof(c->custom_opts[0].str),
> > > "%s", optarg))
> > > @@ -1503,7 +1511,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(optcode, comma + 1,
> > > +
> > c->custom_opts[c->custom_opts_count].val,
> > > +
> > sizeof(c->custom_opts[0].val));
> >
> > you can use "ret" here too
> >
> > > + if (len < 0)
> > > + die("Invalid value for DHCP option %lu:
> > %s",
> > > + optcode, comma + 1);
> > > +
> > > c->custom_opts[c->custom_opts_count].code =
> > optcode;
> > > + 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..e1c95ad 100644
> > > --- a/dhcp.c
> > > +++ b/dhcp.c
> > > @@ -33,6 +33,154 @@
> > > #include "log.h"
> > > #include "dhcp.h"
> > >
> > > +/**
> > > + * dhcp_opt_types - Maps option code to RFC 2132 value type, indexed by
> > code
> > > + */
> > > +static const enum dhcp_opt_type dhcp_opt_types[256] = {
> > > + [1] = DHCP_OPT_IPV4, /* Subnet Mask */
> > > + [2] = DHCP_OPT_INTEGER, /* 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_INTEGER, /* Boot File Size */
> > > + [15] = DHCP_OPT_STR, /* Domain Name */
> > > + [16] = DHCP_OPT_IPV4, /* Swap Server */
> > > + [17] = DHCP_OPT_STR, /* Root Path */
> > > + [19] = DHCP_OPT_INTEGER, /* IP Forwarding */
> > > + [23] = DHCP_OPT_INTEGER, /* Default IP TTL */
> > > + [26] = DHCP_OPT_INTEGER, /* Interface MTU */
> > > + [28] = DHCP_OPT_IPV4, /* Broadcast Address */
> > > + [33] = DHCP_OPT_IPV4_LIST, /* Static Routes */
> > > + [37] = DHCP_OPT_INTEGER, /* TCP Default TTL */
> > > + [38] = DHCP_OPT_INTEGER, /* 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_INTEGER, /* IP Address Lease Time */
> > > + [53] = DHCP_OPT_INTEGER, /* DHCP Message Type */
> > > + [54] = DHCP_OPT_IPV4, /* Server Identifier */
> > > + [55] = DHCP_OPT_STR, /* Parameter Request List */
> >
> > This is a client option, I don't think we need to manage it.
> >
> > > + [57] = DHCP_OPT_INTEGER, /* Max DHCP Message Size */
> > > + [58] = DHCP_OPT_INTEGER, /* Renewal (T1) Time */
> > > + [59] = DHCP_OPT_INTEGER, /* Rebinding (T2) Time */
> > > + [60] = DHCP_OPT_STR, /* Vendor Class Identifier */
> > > + [61] = DHCP_OPT_STR, /* Client Identifier */
> >
> > This is also a client option.
> >
> > > + [66] = DHCP_OPT_STR, /* TFTP Server Name */
> > > + [67] = DHCP_OPT_STR, /* Bootfile Name */
> > > + [119] = DHCP_OPT_STR, /* Domain Search List */
> >
> > This is not really a string as this uses the message compression described
> > in RFC 1035,
> > 4.1.4. (See also 3. Example in rfc3397). I'm not sure we can encode this
> > manually on the
> > command line. And RFC 3396 defines how to encode search string for more
> > than 255
> > characters length
> >
> > > + [252] = DHCP_OPT_STR, /* WPAD URL */
> > > +};
> > > +
> > > +/**
> > > + * dhcp_opt_int_width - Integer width in bytes for INTEGER-typed options
> > > + */
> > > +static const uint8_t dhcp_opt_int_width[256] = {
> > > + [2] = 4, /* Time Offset */
> > > + [13] = 2, /* Boot File Size */
> > > + [19] = 1, /* IP Forwarding */
> > > + [23] = 1, /* Default IP TTL */
> > > + [26] = 2, /* Interface MTU */
> > > + [37] = 1, /* TCP Default TTL */
> > > + [38] = 4, /* TCP Keepalive Interval */
> > > + [51] = 4, /* IP Address Lease Time */
> > > + [53] = 1, /* DHCP Message Type */
> > > + [57] = 2, /* Max DHCP Message Size */
> > > + [58] = 4, /* Renewal (T1) Time */
> > > + [59] = 4, /* Rebinding (T2) Time */
> > > +};
> > > +
> > > +#define DHCP_OPT_PARSE_BUF 1024
> >
> > Do we need 1024 bytes, all options are bounded to 256 bytes.
> > > +
> > > +/**
> > > + * dhcp_opt_parse() - Parse a DHCP option value
> > > + * @code: DHCP option code
> > > + * @str: DHCP Value string from command line
> > > + * @buf: Output buffer
> > > + * @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_types[code];
> > > + char tmp[DHCP_OPT_PARSE_BUF];
> > > + char *tok, *saveptr, *end;
> > > + struct in_addr addr;
> > > + unsigned long val;
> > > + unsigned int i;
> > > + uint8_t width;
> > > + size_t slen;
> > > + int len;
> > > +
> > > + switch (type) {
> > > + case DHCP_OPT_NONE:
> > > + die("Unsupported DHCP option: %u,"
> > > + " see passt(1) for supported codes", code);
> > > + case DHCP_OPT_IPV4:
> > > + 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:
> > > + len = 0;
> > > +
> > > + if (snprintf_check(tmp, sizeof(tmp), "%s", str))
> > > + return -1;
> > > +
> > > + for (tok = strtok_r(tmp, ",", &saveptr); tok;
> > > + tok = strtok_r(NULL, ",", &saveptr)) {
> > > + 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_INTEGER:
> > > + width = dhcp_opt_int_width[code];
> > > + val = strtoul(str, &end, 0);
> > > +
> > > + if (*end || buf_len < width)
> > > + return -1;
> > > +
> > > + if (width < 4 && val >= (1UL << (width * 8)))
> > > + return -1;
> > > +
> > > + for (i = 0; i < width; i++)
> > > + buf[i] = (val >> ((width - 1 - i) * 8)) & 0xff;
> > > +
> > > + return width;
> > > + case DHCP_OPT_STR:
> > > + slen = strlen(str);
> > > +
> > > + if (!slen || slen >= buf_len)
> > > + return -1;
> > > +
> > > + strncpy((char *)buf, str, buf_len);
> > > +
> > > + return slen;
> > > + }
> > > +
> > > + return -1;
> > > +}
> > > +
> > > /**
> > > * struct opt - DHCP option
> > > * @sent: Convenience flag, set while filling replies
> > > diff --git a/dhcp.h b/dhcp.h
> > > index cd50c99..3da571c 100644
> > > --- a/dhcp.h
> > > +++ b/dhcp.h
> > > @@ -6,7 +6,24 @@
> > > #ifndef DHCP_H
> > > #define DHCP_H
> > >
> > > +/**
> > > + * enum dhcp_opt_type - DHCP option value types per RFC 2132
> > > + * @DHCP_OPT_NONE: Unsupported or unknown option
> > > + * @DHCP_OPT_STR: Variable-length string
> > > + * @DHCP_OPT_IPV4: Single IPv4 address
> > > + * @DHCP_OPT_IPV4_LIST:Multiple IPv4 addresses, comma-separated
> > > + * @DHCP_OPT_INTEGER: Unsigned integer (1, 2, or 4 bytes)
> > > + */
> > > +enum dhcp_opt_type {
> > > + DHCP_OPT_NONE,
> > > + DHCP_OPT_STR,
> > > + DHCP_OPT_IPV4,
> > > + DHCP_OPT_IPV4_LIST,
> > > + DHCP_OPT_INTEGER,
> > > +};
> > > +
> > > 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 */
> > > diff --git a/passt.h b/passt.h
> > > index 3a0816f..751fee3 100644
> > > --- a/passt.h
> > > +++ b/passt.h
> > > @@ -184,6 +184,8 @@ struct ip6_ctx {
> > > * @fqdn: Guest FQDN
> > > * @custom_opts: User-specified DHCP options from --dhcp-opt
> > > * @custom_opts.code: DHCP option code
> > > + * @custom_opts.len: Length of binary value in @val
> > > + * @custom_opts.val: Binary-encoded option value
> > > * @custom_opts.str: Original string value from command line
> > > * @custom_opts_count: Number of entries in @custom_opts
> > > * @ifi6: Template interface for IPv6, -1: none, 0: IPv6
> > disabled
> > > @@ -271,6 +273,8 @@ struct ctx {
> > >
> > > struct {
> > > uint8_t code;
> > > + uint8_t len;
> > > + uint8_t val[255];
> > > char str[256];
> >
> > Why do we need to keep str[] once the value is encoded into val[]?
> >
>
> To print the option value as entered by user.
Right, but I think Laurent's point is that it's unfortunate to keep
str[] around indefinitely for something that's used once at startup.
>
> anskuma@anskuma-thinkpadp1gen7:~/Documents/passt$ ./passt -f --dhcp-opt
> 60,HTTPClient --dhcp-opt 67,http://192.168.1.1:8080/alpine.iso --dhcp-opt
> 12,testhost --dhcp-opt 15,test.example.com --dhcp-opt 6,8.8.8.8,8.8.4.4
> --dhcp-opt 26,1400 --dhcp-opt 42,192.168.1.1 --dhcp-opt 51,360
> --dhcp-opt 6,1.1.1.1,9.9.9.9
> UNIX domain socket bound at /tmp/passt_1.socket
> No IPv6 nameserver available for NDP/DHCPv6
> Template interface: wlp9s0f0 (IPv4), wlp9s0f0 (IPv6)
> MAC:
> host: 9a:55:9a:55:9a:55
> NAT to host 127.0.0.1: 192.168.1.1
> DHCP:
> assign: 192.168.1.9
> mask: 255.255.255.0
> router: 192.168.1.1
>
>
>
>
>
>
>
> *option 60: HTTPClient option 67: http://192.168.1.1:8080/alpine.iso
> <http://192.168.1.1:8080/alpine.iso> option 12: testhost option 15:
> test.example.com <http://test.example.com> option 6: 1.1.1.1,9.9.9.9
> option 26: 1400 option 42: 192.168.1.1 option 51: 360*
>
>
> >
> > > } custom_opts[MAX_CUSTOM_DHCP_OPTS];
> > > int custom_opts_count;
> >
> > Thanks,
> > Laurent
> >
> >
--
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] 27+ messages in thread
* Re: [PATCH v2 3/6] dhcp: Add option type table and value parser
2026-05-26 12:31 ` [PATCH v2 3/6] dhcp: Add option type table and value parser Anshu Kumari
2026-05-26 16:05 ` Laurent Vivier
@ 2026-05-27 3:26 ` David Gibson
1 sibling, 0 replies; 27+ messages in thread
From: David Gibson @ 2026-05-27 3:26 UTC (permalink / raw)
To: Anshu Kumari; +Cc: passt-dev, sbrivio, jmaloy, lvivier
[-- Attachment #1: Type: text/plain, Size: 11209 bytes --]
On Tue, May 26, 2026 at 06:01:10PM +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>
Laurent's comments seconded, a few other ones here.
> ---
> v2:
> - Replaced struct lookup table + dhcp_opt_type_lookup() function with flat dhcp_opt_types[256] array indexed by code.
> - Consolidated DHCP_OPT_UINT8/UINT16/UINT32 into single DHCP_OPT_INTEGER with dhcp_opt_int_width[256] table.
> - Dropped DHCP_OPT_ROUTES / option 121 entirely.
> - Added kerneldoc for enum dhcp_opt_type values.
> - Removed curly braces from switch cases, declarations before switch.
> - Added newlines before return statements.
> - Changed IP list delimiter from space to comma (--dhcp-opt 6,1.1.1.1,8.8.8.8).
> - Defined DHCP_OPT_PARSE_BUF constant for bare 1024.
> - Added len and val[255] fields to struct here (moved from patch 1).
> - Added kerneldoc for @custom_opts.len and @custom_opts.val.
> - Wired dhcp_opt_parse() into case 32 (--dhcp-boot) to populate val/len.
> ---
> conf.c | 16 ++++++
> dhcp.c | 148 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
> dhcp.h | 17 +++++++
> passt.h | 4 ++
> 4 files changed, 185 insertions(+)
>
> diff --git a/conf.c b/conf.c
> index ae8ee26..3a5f45d 100644
> --- a/conf.c
> +++ b/conf.c
> @@ -1266,6 +1266,7 @@ void conf(struct ctx *c, int argc, char **argv)
> char *end;
> uid_t uid;
> gid_t gid;
> + int len;
>
>
> if (c->mode == MODE_PASTA)
> @@ -1482,7 +1483,14 @@ void conf(struct ctx *c, int argc, char **argv)
> die("Too many DHCP options (max %d)",
> MAX_CUSTOM_DHCP_OPTS);
>
> + ret = dhcp_opt_parse(67, optarg,
> + c->custom_opts[c->custom_opts_count].val,
> + sizeof(c->custom_opts[0].val));
> + if (ret < 0)
> + die("Invalid boot file value: %s", optarg);
> +
> c->custom_opts[c->custom_opts_count].code = 67;
> + c->custom_opts[c->custom_opts_count].len = ret;
> if (snprintf_check(c->custom_opts[c->custom_opts_count].str,
> sizeof(c->custom_opts[0].str),
> "%s", optarg))
> @@ -1503,7 +1511,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(optcode, 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",
> + optcode, comma + 1);
> +
> c->custom_opts[c->custom_opts_count].code = optcode;
> + 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..e1c95ad 100644
> --- a/dhcp.c
> +++ b/dhcp.c
> @@ -33,6 +33,154 @@
> #include "log.h"
> #include "dhcp.h"
>
> +/**
> + * dhcp_opt_types - Maps option code to RFC 2132 value type, indexed by code
> + */
> +static const enum dhcp_opt_type dhcp_opt_types[256] = {
> + [1] = DHCP_OPT_IPV4, /* Subnet Mask */
> + [2] = DHCP_OPT_INTEGER, /* 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_INTEGER, /* Boot File Size */
> + [15] = DHCP_OPT_STR, /* Domain Name */
> + [16] = DHCP_OPT_IPV4, /* Swap Server */
> + [17] = DHCP_OPT_STR, /* Root Path */
> + [19] = DHCP_OPT_INTEGER, /* IP Forwarding */
> + [23] = DHCP_OPT_INTEGER, /* Default IP TTL */
> + [26] = DHCP_OPT_INTEGER, /* Interface MTU */
> + [28] = DHCP_OPT_IPV4, /* Broadcast Address */
> + [33] = DHCP_OPT_IPV4_LIST, /* Static Routes */
> + [37] = DHCP_OPT_INTEGER, /* TCP Default TTL */
> + [38] = DHCP_OPT_INTEGER, /* 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_INTEGER, /* IP Address Lease Time */
> + [53] = DHCP_OPT_INTEGER, /* DHCP Message Type */
> + [54] = DHCP_OPT_IPV4, /* Server Identifier */
> + [55] = DHCP_OPT_STR, /* Parameter Request List */
> + [57] = DHCP_OPT_INTEGER, /* Max DHCP Message Size */
> + [58] = DHCP_OPT_INTEGER, /* Renewal (T1) Time */
> + [59] = DHCP_OPT_INTEGER, /* 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 */
> + [252] = DHCP_OPT_STR, /* WPAD URL */
> +};
> +
> +/**
> + * dhcp_opt_int_width - Integer width in bytes for INTEGER-typed options
> + */
> +static const uint8_t dhcp_opt_int_width[256] = {
I know I suggested this approach, but now seeing a whole array[256] it
seems unpleasantly bulky. I'd suggest instead encoding the width into
some bits of the DHCP_OPT_* constants.
> + [2] = 4, /* Time Offset */
> + [13] = 2, /* Boot File Size */
> + [19] = 1, /* IP Forwarding */
> + [23] = 1, /* Default IP TTL */
> + [26] = 2, /* Interface MTU */
> + [37] = 1, /* TCP Default TTL */
> + [38] = 4, /* TCP Keepalive Interval */
> + [51] = 4, /* IP Address Lease Time */
> + [53] = 1, /* DHCP Message Type */
> + [57] = 2, /* Max DHCP Message Size */
> + [58] = 4, /* Renewal (T1) Time */
> + [59] = 4, /* Rebinding (T2) Time */
> +};
> +
> +#define DHCP_OPT_PARSE_BUF 1024
> +
> +/**
> + * dhcp_opt_parse() - Parse a DHCP option value
> + * @code: DHCP option code
> + * @str: DHCP Value string from command line
> + * @buf: Output buffer
> + * @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_types[code];
> + char tmp[DHCP_OPT_PARSE_BUF];
> + char *tok, *saveptr, *end;
> + struct in_addr addr;
> + unsigned long val;
> + unsigned int i;
> + uint8_t width;
> + size_t slen;
> + int len;
> +
> + switch (type) {
> + case DHCP_OPT_NONE:
> + die("Unsupported DHCP option: %u,"
> + " see passt(1) for supported codes", code);
> + case DHCP_OPT_IPV4:
> + 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:
I think it should be possible to share more logic between the single
IPv4 and IPv4 list options (parse them both as a list, but error if
there's >1 in the single case).
> + len = 0;
> +
> + if (snprintf_check(tmp, sizeof(tmp), "%s", str))
> + return -1;
> +
> + for (tok = strtok_r(tmp, ",", &saveptr); tok;
> + tok = strtok_r(NULL, ",", &saveptr)) {
> + 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_INTEGER:
> + width = dhcp_opt_int_width[code];
> + val = strtoul(str, &end, 0);
Most strtoul() errors can be discerned by checking end, but not all.
You also need to explicitly set errno to 0 beforehand and check it
again afterwards (clunky, but that's the interface we have).
> +
> + if (*end || buf_len < width)
> + return -1;
> +
> + if (width < 4 && val >= (1UL << (width * 8)))
strtoul() will parse 64-bit values on most current platforms, so you
need this range enforcement for width==4 as well.
> + return -1;
> +
> + for (i = 0; i < width; i++)
> + buf[i] = (val >> ((width - 1 - i) * 8)) & 0xff;
Might be easier to follow as:
for (...) {
buf[i] = val & 0xff;
val >>= 8;
}
> +
> + return width;
> + case DHCP_OPT_STR:
> + slen = strlen(str);
> +
> + if (!slen || slen >= buf_len)
> + return -1;
> +
> + strncpy((char *)buf, str, buf_len);
You've already know the length so you can make this a memcpy().
> +
> + return slen;
> + }
> +
> + return -1;
> +}
> +
> /**
> * struct opt - DHCP option
> * @sent: Convenience flag, set while filling replies
> diff --git a/dhcp.h b/dhcp.h
> index cd50c99..3da571c 100644
> --- a/dhcp.h
> +++ b/dhcp.h
> @@ -6,7 +6,24 @@
> #ifndef DHCP_H
> #define DHCP_H
>
> +/**
> + * enum dhcp_opt_type - DHCP option value types per RFC 2132
> + * @DHCP_OPT_NONE: Unsupported or unknown option
> + * @DHCP_OPT_STR: Variable-length string
> + * @DHCP_OPT_IPV4: Single IPv4 address
> + * @DHCP_OPT_IPV4_LIST:Multiple IPv4 addresses, comma-separated
> + * @DHCP_OPT_INTEGER: Unsigned integer (1, 2, or 4 bytes)
> + */
> +enum dhcp_opt_type {
> + DHCP_OPT_NONE,
> + DHCP_OPT_STR,
> + DHCP_OPT_IPV4,
> + DHCP_OPT_IPV4_LIST,
> + DHCP_OPT_INTEGER,
> +};
This can move to dhcp.c, since it's not used in any other files.
> +
> 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 */
> diff --git a/passt.h b/passt.h
> index 3a0816f..751fee3 100644
> --- a/passt.h
> +++ b/passt.h
> @@ -184,6 +184,8 @@ struct ip6_ctx {
> * @fqdn: Guest FQDN
> * @custom_opts: User-specified DHCP options from --dhcp-opt
> * @custom_opts.code: DHCP option code
> + * @custom_opts.len: Length of binary value in @val
> + * @custom_opts.val: Binary-encoded option value
> * @custom_opts.str: Original string value from command line
> * @custom_opts_count: Number of entries in @custom_opts
> * @ifi6: Template interface for IPv6, -1: none, 0: IPv6 disabled
> @@ -271,6 +273,8 @@ struct ctx {
>
> struct {
> uint8_t code;
> + uint8_t len;
> + uint8_t val[255];
> char str[256];
> } custom_opts[MAX_CUSTOM_DHCP_OPTS];
> int custom_opts_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] 27+ messages in thread
* [PATCH v2 4/6] dhcp: Refactor fill_one() to operate on a generic buffer
2026-05-26 12:31 [PATCH v2 0/6] Add --dhcp-boot and --dhcp-opt options Anshu Kumari
` (2 preceding siblings ...)
2026-05-26 12:31 ` [PATCH v2 3/6] dhcp: Add option type table and value parser Anshu Kumari
@ 2026-05-26 12:31 ` Anshu Kumari
2026-05-26 16:13 ` Laurent Vivier
2026-05-27 3:46 ` David Gibson
2026-05-26 12:31 ` [PATCH v2 5/6] dhcp: Add option overload Anshu Kumari
2026-05-26 12:31 ` [PATCH v2 6/6] doc: Add --dhcp-boot and --dhcp-opt to man page Anshu Kumari
5 siblings, 2 replies; 27+ messages in thread
From: Anshu Kumari @ 2026-05-26 12:31 UTC (permalink / raw)
To: anskuma, passt-dev, sbrivio; +Cc: jmaloy, lvivier, 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>
---
v2:
- Renamed parameter cap → size.
---
dhcp.c | 27 +++++++++++++--------------
1 file changed, 13 insertions(+), 14 deletions(-)
diff --git a/dhcp.c b/dhcp.c
index e1c95ad..e126063 100644
--- a/dhcp.c
+++ b/dhcp.c
@@ -279,28 +279,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
+ * @size: Usable size 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 size, 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 > size)
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;
@@ -325,19 +324,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] 27+ messages in thread* Re: [PATCH v2 4/6] dhcp: Refactor fill_one() to operate on a generic buffer
2026-05-26 12:31 ` [PATCH v2 4/6] dhcp: Refactor fill_one() to operate on a generic buffer Anshu Kumari
@ 2026-05-26 16:13 ` Laurent Vivier
2026-05-27 3:46 ` David Gibson
1 sibling, 0 replies; 27+ messages in thread
From: Laurent Vivier @ 2026-05-26 16:13 UTC (permalink / raw)
To: Anshu Kumari, passt-dev, sbrivio; +Cc: jmaloy, david
On 5/26/26 14:31, 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>
> ---
> v2:
> - Renamed parameter cap → size.
> ---
> dhcp.c | 27 +++++++++++++--------------
> 1 file changed, 13 insertions(+), 14 deletions(-)
>
> diff --git a/dhcp.c b/dhcp.c
> index e1c95ad..e126063 100644
> --- a/dhcp.c
> +++ b/dhcp.c
> @@ -279,28 +279,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
> + * @size: Usable size 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 size, 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)
Perhaps we can keep the comments?
> + if (*offset + 2 + slen > size)
> return true;
>
> - m->o[*offset] = o;
> - m->o[*offset + 1] = slen;
> + buf[*offset] = o;
> + buf[*offset + 1] = slen;
>
> - /* Move to option */
ditto
> *offset += 2;
>
> - memcpy(&m->o[*offset], opts[o].s, slen);
> + memcpy(&buf[*offset], opts[o].s, slen);
>
> opts[o].sent = 1;
> *offset += slen;
> @@ -325,19 +324,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);
> }
>
Reviewed-by: Laurent Vivier <lvivier@redhat.com>
^ permalink raw reply [flat|nested] 27+ messages in thread* Re: [PATCH v2 4/6] dhcp: Refactor fill_one() to operate on a generic buffer
2026-05-26 12:31 ` [PATCH v2 4/6] dhcp: Refactor fill_one() to operate on a generic buffer Anshu Kumari
2026-05-26 16:13 ` Laurent Vivier
@ 2026-05-27 3:46 ` David Gibson
1 sibling, 0 replies; 27+ messages in thread
From: David Gibson @ 2026-05-27 3:46 UTC (permalink / raw)
To: Anshu Kumari; +Cc: passt-dev, sbrivio, jmaloy, lvivier
[-- Attachment #1: Type: text/plain, Size: 3438 bytes --]
On Tue, May 26, 2026 at 06:01:11PM +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>
One pre-existing oddity noted. Might be nice to address (as a
separate patch) if you have the chance, but it's not a big deal.
> ---
> v2:
> - Renamed parameter cap → size.
> ---
> dhcp.c | 27 +++++++++++++--------------
> 1 file changed, 13 insertions(+), 14 deletions(-)
>
> diff --git a/dhcp.c b/dhcp.c
> index e1c95ad..e126063 100644
> --- a/dhcp.c
> +++ b/dhcp.c
> @@ -279,28 +279,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
> + * @size: Usable size 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
Pre-existing wart: It's very unusual for bool-valued functions to
report failure with 'true' even though though it *is* usual for
int-valued functions to report failure with negative =~ non-zero =~
"trueish" values.
> */
> -static bool fill_one(struct msg *m, int o, int *offset)
> +static bool fill_one(uint8_t *buf, size_t size, 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 > size)
> 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;
> @@ -325,19 +324,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] 27+ messages in thread
* [PATCH v2 5/6] dhcp: Add option overload
2026-05-26 12:31 [PATCH v2 0/6] Add --dhcp-boot and --dhcp-opt options Anshu Kumari
` (3 preceding siblings ...)
2026-05-26 12:31 ` [PATCH v2 4/6] dhcp: Refactor fill_one() to operate on a generic buffer Anshu Kumari
@ 2026-05-26 12:31 ` Anshu Kumari
2026-05-26 17:42 ` Laurent Vivier
2026-05-27 4:03 ` David Gibson
2026-05-26 12:31 ` [PATCH v2 6/6] doc: Add --dhcp-boot and --dhcp-opt to man page Anshu Kumari
5 siblings, 2 replies; 27+ messages in thread
From: Anshu Kumari @ 2026-05-26 12:31 UTC (permalink / raw)
To: anskuma, passt-dev, sbrivio; +Cc: jmaloy, lvivier, 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>
---
v2:
- Added #define DHCP_OVERLOAD_FILE and #define DHCP_OVERLOAD_SNAME constants
- Added comment documenting space reservation: /* Reserve 3 bytes for option 52 */
- Fixed DNS search length: sizeof(m->o) only, not combined with file+sname
- Removed dhcp_boot references — reply.file copy now reads from opts[67]
- Used DHCP_OVERLOAD_FILE constant in reply.file guard
---
dhcp.c | 84 +++++++++++++++++++++++++++++++++++++++++++++++++++-------
1 file changed, 75 insertions(+), 9 deletions(-)
diff --git a/dhcp.c b/dhcp.c
index e126063..a49a05a 100644
--- a/dhcp.c
+++ b/dhcp.c
@@ -306,14 +306,59 @@ static bool fill_one(uint8_t *buf, size_t size, int o, int *offset)
return false;
}
+#define DHCP_OVERLOAD_FILE 1
+#define DHCP_OVERLOAD_SNAME 2
+
+/**
+ * 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,
+ * DHCP_OVERLOAD_FILE for file, DHCP_OVERLOAD_SNAME for sname,
+ * or both OR'd together
+ */
+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 |= DHCP_OVERLOAD_FILE;
+ }
+
+ if (sname_off) {
+ m->sname[sname_off] = 255;
+ overload |= DHCP_OVERLOAD_SNAME;
+ }
+
+ return overload;
+}
+
/**
- * fill() - Fill options in message
+ * 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)
{
+ /* Reserve 3 bytes for option 52 (overload) if needed */
+ size_t size = OPT_MAX - 3;
int i, o, offset = 0;
for (o = 0; o < 255; o++)
@@ -324,20 +369,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, size, 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, size, 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, size, o, &offset);
+ }
+
+ *overload = fill_overflow(m);
+
+ if (*overload) {
+ m->o[offset++] = 52;
+ m->o[offset++] = 1;
+ m->o[offset++] = *overload;
}
m->o[offset++] = 255;
@@ -462,6 +512,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);
@@ -613,7 +664,22 @@ int dhcp(const struct ctx *c, struct iov_tail *data)
if (!c->no_dhcp_dns_search)
opt_set_dns_search(c, sizeof(m->o));
- dlen = offsetof(struct msg, o) + fill(&reply);
+ 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);
+
+ /* Copy boot file name into the file field for legacy PXE clients,
+ * unless the file field is already used for option overload.
+ */
+ if (!(overload & DHCP_OVERLOAD_FILE) &&
+ opts[67].slen > 0 && (size_t)opts[67].slen < sizeof(reply.file))
+ memcpy(&reply.file, opts[67].s, opts[67].slen + 1);
if (m->flags & FLAG_BROADCAST)
dst = in4addr_broadcast;
--
2.54.0
^ permalink raw reply [flat|nested] 27+ messages in thread* Re: [PATCH v2 5/6] dhcp: Add option overload
2026-05-26 12:31 ` [PATCH v2 5/6] dhcp: Add option overload Anshu Kumari
@ 2026-05-26 17:42 ` Laurent Vivier
2026-05-27 4:03 ` David Gibson
1 sibling, 0 replies; 27+ messages in thread
From: Laurent Vivier @ 2026-05-26 17:42 UTC (permalink / raw)
To: Anshu Kumari, passt-dev, sbrivio; +Cc: jmaloy, david
On 5/26/26 14:31, 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.
This patch not only adds the overload, it also copies the options from the command line
(custom_opts) to the actual opts.
>
> Link: https://bugs.passt.top/show_bug.cgi?id=192
> Signed-off-by: Anshu Kumari <anskuma@redhat.com>
> ---
> v2:
> - Added #define DHCP_OVERLOAD_FILE and #define DHCP_OVERLOAD_SNAME constants
> - Added comment documenting space reservation: /* Reserve 3 bytes for option 52 */
> - Fixed DNS search length: sizeof(m->o) only, not combined with file+sname
> - Removed dhcp_boot references — reply.file copy now reads from opts[67]
> - Used DHCP_OVERLOAD_FILE constant in reply.file guard
> ---
> dhcp.c | 84 +++++++++++++++++++++++++++++++++++++++++++++++++++-------
> 1 file changed, 75 insertions(+), 9 deletions(-)
>
> diff --git a/dhcp.c b/dhcp.c
> index e126063..a49a05a 100644
> --- a/dhcp.c
> +++ b/dhcp.c
> @@ -306,14 +306,59 @@ static bool fill_one(uint8_t *buf, size_t size, int o, int *offset)
> return false;
> }
>
> +#define DHCP_OVERLOAD_FILE 1
> +#define DHCP_OVERLOAD_SNAME 2
> +
> +/**
> + * 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,
> + * DHCP_OVERLOAD_FILE for file, DHCP_OVERLOAD_SNAME for sname,
> + * or both OR'd together
> + */
> +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 |= DHCP_OVERLOAD_FILE;
> + }
> +
> + if (sname_off) {
> + m->sname[sname_off] = 255;
> + overload |= DHCP_OVERLOAD_SNAME;
> + }
> +
> + return overload;
> +}
> +
> /**
> - * fill() - Fill options in message
> + * 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)
> {
> + /* Reserve 3 bytes for option 52 (overload) if needed */
> + size_t size = OPT_MAX - 3;
> int i, o, offset = 0;
>
> for (o = 0; o < 255; o++)
> @@ -324,20 +369,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, size, 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, size, 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, size, o, &offset);
> + }
> +
> + *overload = fill_overflow(m);
> +
> + if (*overload) {
> + m->o[offset++] = 52;
> + m->o[offset++] = 1;
> + m->o[offset++] = *overload;
> }
>
> m->o[offset++] = 255;
> @@ -462,6 +512,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);
> @@ -613,7 +664,22 @@ 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() manages option 119 but we have also it in custom_opts (incorrectly
set as it doesn't manage compression). I think we overwrite it with incorrect values.
Moreover I think this function is broken, because max_len is computed using opts[].slen,
but opts[].slen is updated below (and max_len should be OPT_MAX - 3, not sizeof(m->o))
>
> - dlen = offsetof(struct msg, o) + fill(&reply);
> + 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);
> +
> + /* Copy boot file name into the file field for legacy PXE clients,
> + * unless the file field is already used for option overload.
> + */
> + if (!(overload & DHCP_OVERLOAD_FILE) &&
> + opts[67].slen > 0 && (size_t)opts[67].slen < sizeof(reply.file))
> + memcpy(&reply.file, opts[67].s, opts[67].slen + 1);
No need for "&" in "&reply.size".
the "+ 1" is wrong as reply.file has been cleared by a memset() (whereas opts[67].s[slen]
can be not NUL).
>
> if (m->flags & FLAG_BROADCAST)
> dst = in4addr_broadcast;
Thanks,
Laurent
^ permalink raw reply [flat|nested] 27+ messages in thread* Re: [PATCH v2 5/6] dhcp: Add option overload
2026-05-26 12:31 ` [PATCH v2 5/6] dhcp: Add option overload Anshu Kumari
2026-05-26 17:42 ` Laurent Vivier
@ 2026-05-27 4:03 ` David Gibson
2026-05-28 7:18 ` Anshu Kumari
1 sibling, 1 reply; 27+ messages in thread
From: David Gibson @ 2026-05-27 4:03 UTC (permalink / raw)
To: Anshu Kumari; +Cc: passt-dev, sbrivio, jmaloy, lvivier
[-- Attachment #1: Type: text/plain, Size: 5797 bytes --]
On Tue, May 26, 2026 at 06:01:12PM +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>
> ---
> v2:
> - Added #define DHCP_OVERLOAD_FILE and #define DHCP_OVERLOAD_SNAME constants
> - Added comment documenting space reservation: /* Reserve 3 bytes for option 52 */
> - Fixed DNS search length: sizeof(m->o) only, not combined with file+sname
> - Removed dhcp_boot references — reply.file copy now reads from opts[67]
> - Used DHCP_OVERLOAD_FILE constant in reply.file guard
> ---
> dhcp.c | 84 +++++++++++++++++++++++++++++++++++++++++++++++++++-------
> 1 file changed, 75 insertions(+), 9 deletions(-)
>
> diff --git a/dhcp.c b/dhcp.c
> index e126063..a49a05a 100644
> --- a/dhcp.c
> +++ b/dhcp.c
> @@ -306,14 +306,59 @@ static bool fill_one(uint8_t *buf, size_t size, int o, int *offset)
> return false;
> }
>
> +#define DHCP_OVERLOAD_FILE 1
> +#define DHCP_OVERLOAD_SNAME 2
IIUC, these values are from the RFC - might be worth referencing the
relevant section in a comment to make it clear they can't be changed
arbitrarily.
> +
> +/**
> + * 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,
> + * DHCP_OVERLOAD_FILE for file, DHCP_OVERLOAD_SNAME for sname,
> + * or both OR'd together
> + */
> +static int fill_overflow(struct msg *m)
> +{
> + int file_off = 0, sname_off = 0, overload = 0;
> + int o;
> +
> + for (o = 0; o < 255; o++) {
You could use ARRAY_size(opts) instead of raw 255, yes?
> + 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 |= DHCP_OVERLOAD_FILE;
> + }
> +
> + if (sname_off) {
> + m->sname[sname_off] = 255;
> + overload |= DHCP_OVERLOAD_SNAME;
> + }
> +
> + return overload;
> +}
> +
> /**
> - * fill() - Fill options in message
> + * 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)
> {
> + /* Reserve 3 bytes for option 52 (overload) if needed */
> + size_t size = OPT_MAX - 3;
> int i, o, offset = 0;
>
> for (o = 0; o < 255; o++)
> @@ -324,20 +369,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, size, 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, size, 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, size, o, &offset);
> + }
> +
> + *overload = fill_overflow(m);
> +
> + if (*overload) {
> + m->o[offset++] = 52;
> + m->o[offset++] = 1;
> + m->o[offset++] = *overload;
> }
>
> m->o[offset++] = 255;
> @@ -462,6 +512,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);
> @@ -613,7 +664,22 @@ int dhcp(const struct ctx *c, struct iov_tail *data)
> if (!c->no_dhcp_dns_search)
> opt_set_dns_search(c, sizeof(m->o));
>
> - dlen = offsetof(struct msg, o) + fill(&reply);
> + 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);
> +
> + /* Copy boot file name into the file field for legacy PXE clients,
> + * unless the file field is already used for option overload.
Given we do this, would it make more sense to use the slen field in
preference to the file field for overloads? The logic above appears
to do it the other way around,
> + */
> + if (!(overload & DHCP_OVERLOAD_FILE) &&
> + opts[67].slen > 0 && (size_t)opts[67].slen < sizeof(reply.file))
> + memcpy(&reply.file, opts[67].s, opts[67].slen + 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] 27+ messages in thread* Re: [PATCH v2 5/6] dhcp: Add option overload
2026-05-27 4:03 ` David Gibson
@ 2026-05-28 7:18 ` Anshu Kumari
0 siblings, 0 replies; 27+ messages in thread
From: Anshu Kumari @ 2026-05-28 7:18 UTC (permalink / raw)
To: David Gibson; +Cc: passt-dev, sbrivio, jmaloy, lvivier
[-- Attachment #1: Type: text/plain, Size: 7051 bytes --]
On Wed, May 27, 2026 at 9:46 AM David Gibson <david@gibson.dropbear.id.au>
wrote:
> On Tue, May 26, 2026 at 06:01:12PM +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>
> > ---
> > v2:
> > - Added #define DHCP_OVERLOAD_FILE and #define DHCP_OVERLOAD_SNAME
> constants
> > - Added comment documenting space reservation: /* Reserve 3 bytes for
> option 52 */
> > - Fixed DNS search length: sizeof(m->o) only, not combined with
> file+sname
> > - Removed dhcp_boot references — reply.file copy now reads from
> opts[67]
> > - Used DHCP_OVERLOAD_FILE constant in reply.file guard
> > ---
> > dhcp.c | 84 +++++++++++++++++++++++++++++++++++++++++++++++++++-------
> > 1 file changed, 75 insertions(+), 9 deletions(-)
> >
> > diff --git a/dhcp.c b/dhcp.c
> > index e126063..a49a05a 100644
> > --- a/dhcp.c
> > +++ b/dhcp.c
> > @@ -306,14 +306,59 @@ static bool fill_one(uint8_t *buf, size_t size,
> int o, int *offset)
> > return false;
> > }
> >
> > +#define DHCP_OVERLOAD_FILE 1
> > +#define DHCP_OVERLOAD_SNAME 2
>
> IIUC, these values are from the RFC - might be worth referencing the
> relevant section in a comment to make it clear they can't be changed
> arbitrarily.
>
> > +
> > +/**
> > + * 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,
> > + * DHCP_OVERLOAD_FILE for file, DHCP_OVERLOAD_SNAME for sname,
> > + * or both OR'd together
> > + */
> > +static int fill_overflow(struct msg *m)
> > +{
> > + int file_off = 0, sname_off = 0, overload = 0;
> > + int o;
> > +
> > + for (o = 0; o < 255; o++) {
>
> You could use ARRAY_size(opts) instead of raw 255, yes?
>
> > + 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 |= DHCP_OVERLOAD_FILE;
> > + }
> > +
> > + if (sname_off) {
> > + m->sname[sname_off] = 255;
> > + overload |= DHCP_OVERLOAD_SNAME;
> > + }
> > +
> > + return overload;
> > +}
> > +
> > /**
> > - * fill() - Fill options in message
> > + * 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)
> > {
> > + /* Reserve 3 bytes for option 52 (overload) if needed */
> > + size_t size = OPT_MAX - 3;
> > int i, o, offset = 0;
> >
> > for (o = 0; o < 255; o++)
> > @@ -324,20 +369,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, size, 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, size, 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, size, o, &offset);
> > + }
> > +
> > + *overload = fill_overflow(m);
> > +
> > + if (*overload) {
> > + m->o[offset++] = 52;
> > + m->o[offset++] = 1;
> > + m->o[offset++] = *overload;
> > }
> >
> > m->o[offset++] = 255;
> > @@ -462,6 +512,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);
> > @@ -613,7 +664,22 @@ int dhcp(const struct ctx *c, struct iov_tail *data)
> > if (!c->no_dhcp_dns_search)
> > opt_set_dns_search(c, sizeof(m->o));
> >
> > - dlen = offsetof(struct msg, o) + fill(&reply);
> > + 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);
> > +
> > + /* Copy boot file name into the file field for legacy PXE clients,
> > + * unless the file field is already used for option overload.
>
> Given we do this, would it make more sense to use the slen field in
> preference to the file field for overloads? The logic above appears
> to do it the other way around,
>
are you referring to the sname field here? I am not sure how to use the
slen field instead of the file field.
>
> > + */
> > + if (!(overload & DHCP_OVERLOAD_FILE) &&
> > + opts[67].slen > 0 && (size_t)opts[67].slen <
> sizeof(reply.file))
> > + memcpy(&reply.file, opts[67].s, opts[67].slen + 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: Type: text/html, Size: 9405 bytes --]
^ permalink raw reply [flat|nested] 27+ messages in thread
* [PATCH v2 6/6] doc: Add --dhcp-boot and --dhcp-opt to man page
2026-05-26 12:31 [PATCH v2 0/6] Add --dhcp-boot and --dhcp-opt options Anshu Kumari
` (4 preceding siblings ...)
2026-05-26 12:31 ` [PATCH v2 5/6] dhcp: Add option overload Anshu Kumari
@ 2026-05-26 12:31 ` Anshu Kumari
2026-05-27 4:16 ` David Gibson
5 siblings, 1 reply; 27+ messages in thread
From: Anshu Kumari @ 2026-05-26 12:31 UTC (permalink / raw)
To: anskuma, passt-dev, sbrivio; +Cc: jmaloy, lvivier, 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>
---
v2:
- Updated --dhcp-boot description.
- Highlighted cross-referenced options with \fB...\fR.
- Updated IP list format from "space-separated within quotes" to "comma-separated".
- option 121 dropped.
- Added option 55 to string options list.
- Removed --dhcp-boot override reference from --dhcp-opt description.
---
passt.1 | 41 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 41 insertions(+)
diff --git a/passt.1 b/passt.1
index 908fd4a..199172f 100644
--- a/passt.1
+++ b/passt.1
@@ -430,6 +430,47 @@ 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
+Convenience shorthand for \fB\-\-dhcp-opt\fR 67,\fIurl\fR.
+Sets the boot file name (DHCP option 67) for network boot.
+For UEFI HTTP boot, also set the vendor class identifier using
+\fB\-\-dhcp-opt\fR 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 IPv4 addresses are comma-separated.
+This option can be specified multiple times. Options set with \fB\-\-dhcp-opt\fR
+override built-in values.
+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 (comma-separated)
+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),
+55 (Parameter Request List),
+60 (Vendor Class Identifier), 61 (Client Identifier), 66 (TFTP Server Name),
+67 (Bootfile Name), 119 (Domain Search List), 252 (WPAD URL)
+.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] 27+ messages in thread* Re: [PATCH v2 6/6] doc: Add --dhcp-boot and --dhcp-opt to man page
2026-05-26 12:31 ` [PATCH v2 6/6] doc: Add --dhcp-boot and --dhcp-opt to man page Anshu Kumari
@ 2026-05-27 4:16 ` David Gibson
0 siblings, 0 replies; 27+ messages in thread
From: David Gibson @ 2026-05-27 4:16 UTC (permalink / raw)
To: Anshu Kumari; +Cc: passt-dev, sbrivio, jmaloy, lvivier
[-- Attachment #1: Type: text/plain, Size: 3507 bytes --]
On Tue, May 26, 2026 at 06:01:13PM +0530, Anshu Kumari wrote:
> 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>
Reviewed-by: David Gibson <david@gibson.dropbear.id.au>
Not worth a respin, but as a general rule, I'd include man page
updates in the same patch adding the functionality described.
> ---
> v2:
> - Updated --dhcp-boot description.
> - Highlighted cross-referenced options with \fB...\fR.
> - Updated IP list format from "space-separated within quotes" to "comma-separated".
> - option 121 dropped.
> - Added option 55 to string options list.
> - Removed --dhcp-boot override reference from --dhcp-opt description.
> ---
> passt.1 | 41 +++++++++++++++++++++++++++++++++++++++++
> 1 file changed, 41 insertions(+)
>
> diff --git a/passt.1 b/passt.1
> index 908fd4a..199172f 100644
> --- a/passt.1
> +++ b/passt.1
> @@ -430,6 +430,47 @@ 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
> +Convenience shorthand for \fB\-\-dhcp-opt\fR 67,\fIurl\fR.
> +Sets the boot file name (DHCP option 67) for network boot.
> +For UEFI HTTP boot, also set the vendor class identifier using
> +\fB\-\-dhcp-opt\fR 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 IPv4 addresses are comma-separated.
> +This option can be specified multiple times. Options set with \fB\-\-dhcp-opt\fR
> +override built-in values.
> +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 (comma-separated)
> +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),
> +55 (Parameter Request List),
> +60 (Vendor Class Identifier), 61 (Client Identifier), 66 (TFTP Server Name),
> +67 (Bootfile Name), 119 (Domain Search List), 252 (WPAD URL)
> +.RE
> +
> .TP
> .BR \-t ", " \-\-tcp-ports " " \fIspec
> Configure TCP port forwarding to guest or namespace. \fIspec\fR can be one of:
> --
> 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] 27+ messages in thread