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 dhcp_add_option() 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
---
v3:
- Replaced DHCP_OPT_INTEGER with separate DHCP_OPT_INT8/INT16/INT32
enums, removed dhcp_opt_int_width[] array.
- Shared logic between DHCP_OPT_IPV4 and DHCP_OPT_IPV4_LIST — parse
both as list, error if >1 in single case.
- Added errno = 0 before strtoul() and check after.
- Fixed range check: 1ULL << (width * 8) for all widths including
width==4.
- strncpy → memcpy for DHCP_OPT_STR.
- Moved enum to dhcp.c since not used in other files.
- Removed options 55, 61 (client-only), 119 (DNS compression, use
--dhcp-search instead), 33 (IP pairs not supported).
- DHCP_OPT_PARSE_BUF 1024 → char tmp[256].
- Upgraded dhcp_add_option() to call dhcp_opt_parse() and populate
val[]/len.
- Aligned array entries for readability.
- Added tab after @DHCP_OPT_IPV4_LIST: in kerneldoc.
- Reject empty value strings before parsing
- Reject leading/trailing/consecutive commas in IP list values.
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.
---
dhcp.c | 180 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-
passt.h | 4 ++
2 files changed, 181 insertions(+), 3 deletions(-)
diff --git a/dhcp.c b/dhcp.c
index c5fbf37..07a42b9 100644
--- a/dhcp.c
+++ b/dhcp.c
@@ -23,6 +23,7 @@
#include
#include
#include
+#include
#include "util.h"
#include "ip.h"
@@ -33,6 +34,170 @@
#include "log.h"
#include "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_INT8: Unsigned 8-bit integer
+ * @DHCP_OPT_INT16: Unsigned 16-bit integer
+ * @DHCP_OPT_INT32: Unsigned 32-bit integer
+ */
+enum dhcp_opt_type {
+ DHCP_OPT_NONE,
+ DHCP_OPT_STR,
+ DHCP_OPT_IPV4,
+ DHCP_OPT_IPV4_LIST,
+ DHCP_OPT_INT8,
+ DHCP_OPT_INT16,
+ DHCP_OPT_INT32,
+};
+
+/**
+ * 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_INT32, /* 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_INT16, /* Boot File Size */
+ [15] = DHCP_OPT_STR, /* Domain Name */
+ [16] = DHCP_OPT_IPV4, /* Swap Server */
+ [17] = DHCP_OPT_STR, /* Root Path */
+ [19] = DHCP_OPT_INT8, /* IP Forwarding */
+ [23] = DHCP_OPT_INT8, /* Default IP TTL */
+ [26] = DHCP_OPT_INT16, /* Interface MTU */
+ [28] = DHCP_OPT_IPV4, /* Broadcast Address */
+ [37] = DHCP_OPT_INT8, /* TCP Default TTL */
+ [38] = DHCP_OPT_INT32, /* 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_INT32, /* IP Address Lease Time */
+ [53] = DHCP_OPT_INT8, /* DHCP Message Type */
+ [54] = DHCP_OPT_IPV4, /* Server Identifier */
+ [57] = DHCP_OPT_INT16, /* Max DHCP Message Size */
+ [58] = DHCP_OPT_INT32, /* Renewal (T1) Time */
+ [59] = DHCP_OPT_INT32, /* Rebinding (T2) Time */
+ [60] = DHCP_OPT_STR, /* Vendor Class Identifier */
+ [66] = DHCP_OPT_STR, /* TFTP Server Name */
+ [67] = DHCP_OPT_STR, /* Bootfile Name */
+ [252] = DHCP_OPT_STR, /* WPAD URL */
+};
+
+/**
+ * dhcp_opt_parse() - Parse a DHCP option value
+ * @code: DHCP option code
+ * @str: Value string from command line
+ * @buf: Output buffer for binary value
+ * @buf_len: Size of output buffer
+ *
+ * Return: number of bytes written to @buf, or -1 on error
+ */
+static int 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 *tok, *saveptr, *end;
+ struct in_addr addr;
+ unsigned long val;
+ unsigned int i;
+ uint8_t width;
+ char tmp[256];
+ size_t slen;
+ int len;
+
+ if (!*str)
+ die("Empty value for DHCP option %u", code);
+
+ switch (type) {
+ case DHCP_OPT_NONE:
+ die("Unsupported DHCP option: %u,"
+ " see passt(1) for supported codes", code);
+ case DHCP_OPT_IPV4:
+ case DHCP_OPT_IPV4_LIST:
+ len = 0;
+
+ /* Reject empty, leading/trailing, or consecutive commas */
+ if (!*str || *str == ',' || str[strlen(str) - 1] == ',' ||
+ strstr(str, ",,"))
+ return -1;
+
+ 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);
+
+ if (type == DHCP_OPT_IPV4)
+ break;
+ }
+
+ if (type == DHCP_OPT_IPV4 && strtok_r(NULL, ",", &saveptr))
+ return -1;
+
+ return len;
+ case DHCP_OPT_INT8:
+ case DHCP_OPT_INT16:
+ case DHCP_OPT_INT32:
+ if (type == DHCP_OPT_INT8)
+ width = 1;
+ else if (type == DHCP_OPT_INT16)
+ width = 2;
+ else
+ width = 4;
+
+ errno = 0;
+ val = strtoul(str, &end, 0);
+
+ if (*end || errno)
+ return -1;
+
+ if (buf_len < width)
+ return -1;
+
+ if (val >= (1ULL << (width * 8)))
+ return -1;
+
+ for (i = width; i > 0; i--) {
+ buf[i - 1] = val & 0xff;
+ val >>= 8;
+ }
+
+ return width;
+ case DHCP_OPT_STR:
+ slen = strlen(str);
+
+ if (!slen || slen >= buf_len)
+ return -1;
+
+ memcpy(buf, str, slen);
+
+ return slen;
+ }
+
+ return -1;
+}
/**
* dhcp_add_option() - Add or update a custom DHCP option
@@ -40,14 +205,15 @@
* @code: DHCP option code
* @val_str: Value string from command line
*
- * If @code was already added, the previous value is overwritten.
- * Calls die() on any error.
+ * Parses @val_str according to the type registered for @code in
+ * dhcp_opt_types[]. If @code was already added, the previous value
+ * is overwritten. Calls die() on any error.
*
* Return: 0 on success
*/
int dhcp_add_option(struct ctx *c, uint8_t code, const char *val_str)
{
- int idx;
+ int idx, ret;
for (idx = 0; idx < c->custom_opts_count; idx++) {
if (c->custom_opts[idx].code == code)
@@ -61,7 +227,15 @@ int dhcp_add_option(struct ctx *c, uint8_t code, const char *val_str)
c->custom_opts_count++;
}
+ ret = dhcp_opt_parse(code, val_str,
+ c->custom_opts[idx].val,
+ sizeof(c->custom_opts[0].val));
+ if (ret < 0)
+ die("Invalid value for DHCP option %u: %s",
+ code, val_str);
+
c->custom_opts[idx].code = code;
+ c->custom_opts[idx].len = ret;
if (snprintf_check(c->custom_opts[idx].str,
sizeof(c->custom_opts[0].str),
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