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