On Fri, Jul 17, 2026 at 11:26:41PM +0530, Anshu Kumari wrote:
Introduce the --dhcp-opt flag that allows setting arbitrary DHCP options from command-line in the form [--dhcp-opt CODE,VALUE].
Add a type lookup table mapping option codes to RFC 2132 value types (IPv4, IPv4 list, integer, string) and dhcp_opt_parse() to convert CLI strings to binary wire format. Parsed options are stored in struct opts[] and injected into DHCP replies. If the same option code is given more than once, the last value wins.
Link: https://bugs.passt.top/show_bug.cgi?id=192 Signed-off-by: Anshu Kumari
--- v5: - Rename enum values: INT8/INT16/INT32/SINT32 → UINT8/UINT16/UINT32/INT32 - Change int len to size_t len in dhcp_opt_parse(), remove int casts - Make dhcp_opt_parse() static, add dhcp_set_opt() to store options directly in opts[] at startup instead of double-parsing via ctx->dhcp_opts - Add OPT_USER to enum opt_state to protect user options from being overwritten by built-in defaults - Add dhcp_opt_to_str() for conf_print() display - Remove dhcp_opts[] from struct ctx in passt.h - Remove conf_dhcp_option() wrapper from conf.c v4: - Renamed custom_opts to dhcp_opts, 256 entries indexed by option code, removed MAX_CUSTOM_DHCP_OPTS and count field. - Changed str buffer from 256 to 255 bytes. - Moved function to conf.c as static conf_dhcp_option(), renamed from dhcp_add_option(). - Made dhcp_opt_parse() non-static, declared in dhcp.h - Dropped val/len from ctx struct; conf_dhcp_option() validates with temp buffer, dhcp() parses str directly into opts[] at reply time. - Replaced strtok_r() + 256-byte buffer with strcspn() + INET_ADDRSTRLEN buffer. - Added DHCP_OPT_SINT32 for option 2 (Time Offset), uses strtol() per RFC 2132 Section 8.2. - All errors in dhcp_opt_parse() return -1, removed die() calls; caller handles error message consistently. - Removed redundant !slen check in DHCP_OPT_STR case. - Omitted explicit array size for dhcp_opt_types[], arraydded bounds check before lookup. - Added errno = 0 + errno check for strtoul() in case 34. - Fixed usage text: "Set DHCP option CODE to VAL". - Improved man page: added format description and examples
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.
--- conf.c | 25 ++++- dhcp.c | 338 +++++++++++++++++++++++++++++++++++++++++++++++++++----- dhcp.h | 2 + passt.1 | 42 +++++++ 4 files changed, 381 insertions(+), 26 deletions(-)
diff --git a/conf.c b/conf.c index 5b6cc2b..e7f3ea0 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" @@ -615,7 +616,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 CODE to VAL\n"); if (strstr(name, "pasta")) FPRINTF(f, " default: don't use any search list\n"); else @@ -843,6 +845,9 @@ static void conf_print(const struct ctx *c) info(" router: %s", inet_ntop(AF_INET, &c->ip4.guest_gw, buf, sizeof(buf))); + for (i = 1; i < 255; i++) + if (dhcp_opt_to_str(i, buf, sizeof(buf))) + info(" option %u: %s", i, buf); }
for (i = 0; i < ARRAY_SIZE(c->ip4.dns); i++) { @@ -1316,6 +1321,7 @@ void conf(struct ctx *c, int argc, char **argv) {"stats", required_argument, NULL, 31 }, {"conf-path", required_argument, NULL, 'c' }, {"chroot-fallback", no_argument, NULL, 32 }, + {"dhcp-opt", required_argument, NULL, 34 }, { 0 }, }; const char *optstring = "+dqfel:hs:c:F:I:p:P:m:a:n:M:g:i:o:D:S:H:461t:u:T:U:"; @@ -1330,10 +1336,13 @@ void conf(struct ctx *c, int argc, char **argv) unsigned int ifi4 = 0, ifi6 = 0; bool opt_a_is_prefix = false; const char *logfile = NULL; + unsigned long optcode; char *runas = NULL; size_t logsize = 0; + const char *comma; uint8_t opt_n = 0; int name, ret; + char *end;
It's probably worth putting a block in case 34 rather than adding these new variables to the top of conf().
uid_t uid; gid_t gid;
@@ -1557,6 +1566,20 @@ void conf(struct ctx *c, int argc, char **argv) case 32: c->chroot_fallback = true; break; + case 34: + comma = strchr(optarg, ','); + if (!comma) + die("--dhcp-opt requires CODE,VALUE format"); + + errno = 0; + optcode = strtoul(optarg, &end, 0); + if (end != comma || errno || + optcode < 1 || optcode > 254) + die("DHCP option code must be 1-254: %s", + optarg); +
This can now be done more neatly with the parse_unsigned() and parse_literal() functions I've added. Specifically: p = optarg; if (!parse_unsigned(&p, 0, &optcode) || !parse_literal(&p, ",")) die("--dhcp-opt requires CODE,VALUE format"); ...
+ dhcp_set_opt(optcode, comma + 1); + break; case 'd': c->debug = 1; c->quiet = 0; diff --git a/dhcp.c b/dhcp.c index 39f7952..cc910ee 100644 --- a/dhcp.c +++ b/dhcp.c @@ -23,6 +23,7 @@ #include
#include #include +#include #include "util.h" #include "ip.h" @@ -37,10 +38,12 @@ * enum opt_state - DHCP option state * @OPT_UNSET: Option not configured * @OPT_DEFAULT: Option set from host config + * @OPT_USER: Option set via --dhcp-opt command line
Ok, now I see the point of enum opt_state.
*/ enum opt_state { OPT_UNSET, OPT_DEFAULT, + OPT_USER, };
/** @@ -50,7 +53,7 @@ enum opt_state { * @s: Option payload from server * @clen: Length of option received from client, -1 if not received * @c: Option payload from client - * @state: Option state (unset or default) + * @state: Option state (unset, default, or user) */ struct opt { int sent; @@ -88,19 +91,20 @@ void dhcp_init(void) int i;
for (i = 0; i < ARRAY_SIZE(opts); i++) - opts[i].state = OPT_UNSET; - - /* Mask */ - opts[1] = (struct opt) { 0, 4, { 0 }, 0, { 0 }, OPT_DEFAULT, }; - /* Router */ - opts[3] = (struct opt) { 0, 4, { 0 }, 0, { 0 }, OPT_DEFAULT, }; - /* Lease time */ - opts[51] = (struct opt) { 0, 4, { 0xff, 0xff, 0xff, 0xff }, + if (opts[i].state != OPT_USER) + opts[i].state = OPT_UNSET; + + if (opts[1].state != OPT_USER) /* Mask */ + opts[1] = (struct opt) { 0, 4, { 0 }, 0, { 0 }, OPT_DEFAULT, }; + if (opts[3].state != OPT_USER) /* Router */ + opts[3] = (struct opt) { 0, 4, { 0 }, 0, { 0 }, OPT_DEFAULT, }; + if (opts[51].state != OPT_USER) /* Lease time */ + opts[51] = (struct opt) { 0, 4, { 0xff, 0xff, 0xff, 0xff }, 0, { 0 }, OPT_DEFAULT, }; - /* Type */ - opts[53] = (struct opt) { 0, 1, { 0 }, 0, { 0 }, OPT_DEFAULT, }; - /* Server ID */ - opts[54] = (struct opt) { 0, 4, { 0 }, 0, { 0 }, OPT_DEFAULT, }; + if (opts[53].state != OPT_USER) /* Type */ + opts[53] = (struct opt) { 0, 1, { 0 }, 0, { 0 }, OPT_DEFAULT, }; + if (opts[54].state != OPT_USER) /* Server ID */ + opts[54] = (struct opt) { 0, 4, { 0 }, 0, { 0 }, OPT_DEFAULT, }; }
/** @@ -145,6 +149,280 @@ struct msg { uint8_t o[OPT_MAX + 1 /* End option */ ]; } __attribute__((__packed__));
+/** + * 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_UINT8: Unsigned 8-bit integer + * @DHCP_OPT_UINT16: Unsigned 16-bit integer + * @DHCP_OPT_UINT32: Unsigned 32-bit integer + * @DHCP_OPT_INT32: Signed 32-bit integer + */ +enum dhcp_opt_type { + DHCP_OPT_NONE, + DHCP_OPT_STR, + DHCP_OPT_IPV4, + DHCP_OPT_IPV4_LIST, + DHCP_OPT_UINT8, + DHCP_OPT_UINT16, + DHCP_OPT_UINT32, + DHCP_OPT_INT32, +}; + +/** + * dhcp_opt_types - Maps option code to RFC 2132 value type, indexed by code + */ +static const enum dhcp_opt_type dhcp_opt_types[] = { + [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_UINT16, /* Boot File Size */ + [15] = DHCP_OPT_STR, /* Domain Name */ + [16] = DHCP_OPT_IPV4, /* Swap Server */ + [17] = DHCP_OPT_STR, /* Root Path */ + [19] = DHCP_OPT_UINT8, /* IP Forwarding */ + [23] = DHCP_OPT_UINT8, /* Default IP TTL */ + [26] = DHCP_OPT_UINT16, /* Interface MTU */ + [28] = DHCP_OPT_IPV4, /* Broadcast Address */ + [37] = DHCP_OPT_UINT8, /* TCP Default TTL */ + [38] = DHCP_OPT_UINT32, /* TCP Keepalive Interval */ + [40] = DHCP_OPT_STR, /* NIS Domain Name */ + [41] = DHCP_OPT_IPV4_LIST, /* NIS Servers */ + [42] = DHCP_OPT_IPV4_LIST, /* NTP Servers */ + [44] = DHCP_OPT_IPV4_LIST, /* NetBIOS Name Server */ + [50] = DHCP_OPT_IPV4, /* Requested IP Address */ + [51] = DHCP_OPT_UINT32, /* IP Address Lease Time */ + [53] = DHCP_OPT_UINT8, /* DHCP Message Type */ + [54] = DHCP_OPT_IPV4, /* Server Identifier */ + [57] = DHCP_OPT_UINT16, /* Max DHCP Message Size */ + [58] = DHCP_OPT_UINT32, /* Renewal (T1) Time */ + [59] = DHCP_OPT_UINT32, /* Rebinding (T2) Time */ + [60] = DHCP_OPT_STR, /* Vendor Class Identifier */ + [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; + unsigned long val; + unsigned int i; + uint8_t width; + size_t slen; + size_t len; + char *end; + + if (code >= ARRAY_SIZE(dhcp_opt_types)) + return -1; + + type = dhcp_opt_types[code]; + + if (!*str) + return -1; + + switch (type) { + case DHCP_OPT_NONE: + return -1; + case DHCP_OPT_IPV4: + case DHCP_OPT_IPV4_LIST: + len = 0; + + while (*str) { + char ipbuf[INET_ADDRSTRLEN]; + size_t chunk; + + chunk = strcspn(str, ","); + + if (!chunk || chunk >= sizeof(ipbuf)) + return -1; + + memcpy(ipbuf, str, chunk); + ipbuf[chunk] = '\0'; + + if (len + sizeof(struct in_addr) > buf_len) + return -1; + + if (inet_pton(AF_INET, ipbuf, buf + len) != 1) + return -1;
The new stuff in parse.c can make this easier too - I can walk you through how to use it if you need.
+ + len += sizeof(struct in_addr); + + if (type == DHCP_OPT_IPV4) { + if (str[chunk] == ',') + return -1; + break; + } + + str += chunk + (str[chunk] == ','); + } + + if (!len) + return -1; + + return len; + case DHCP_OPT_UINT8: + case DHCP_OPT_UINT16: + case DHCP_OPT_UINT32: + case DHCP_OPT_INT32: + if (type == DHCP_OPT_UINT8) + width = 1; + else if (type == DHCP_OPT_UINT16) + width = 2; + else + width = 4; + + if (buf_len < width) + return -1; + + errno = 0; + if (type == DHCP_OPT_INT32) { + long sval; + + sval = strtol(str, &end, 0); + if (*end || errno || + sval < INT32_MIN || sval > INT32_MAX) + return -1; + val = (uint32_t)sval; + } else { + val = strtoul(str, &end, 0); + if (*end || errno || + 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 >= buf_len) + return -1; + + memcpy(buf, str, slen); + + return slen; + } + + return -1; +} + +/** + * dhcp_set_opt() - Parse and store a user-specified DHCP option + * @code: DHCP option code + * @val_str: Value string from command line + */ +void dhcp_set_opt(uint8_t code, const char *val_str) +{ + int ret; + + ret = dhcp_opt_parse(code, val_str, opts[code].s, sizeof(opts[code].s)); + if (ret < 0) + die("Invalid value for DHCP option %u: %s", code, val_str); + + opts[code].slen = ret; + opts[code].state = OPT_USER; +} + +/** + * dhcp_opt_to_str() - Render a binary DHCP option value to a printable string + * @code: DHCP option code + * @buf: Output string buffer + * @buf_len: Size of output buffer + * + * Return: pointer to @buf if option is user-set, NULL otherwise + */ +const char *dhcp_opt_to_str(uint8_t code, char *buf, size_t buf_len)
Arguably, this isn't strictly necessary yet, since we could keep a separate copy of the string values. However, I think this will be useful when/if we allow updating of these values with pesto, so I'm happy enough to see it here.
+{ + enum dhcp_opt_type type; + int off = 0; + unsigned int i; + + if (opts[code].state != OPT_USER || opts[code].slen < 0)
Seems to me this could be useful even when in OPT_DEFAULT rather than OPT_USER state. Also, if I understood the earlier patch correctly, slen < 0 should now be impossible, right?
+ return NULL; + + if (code >= ARRAY_SIZE(dhcp_opt_types)) + return NULL; + + type = dhcp_opt_types[code];
Unlike the parse side an out of bounds code, or an undefined type means we already have something bad in our opts[] table. So, an assert() would be appropriate, rather than just returning an error.
+ + switch (type) { + case DHCP_OPT_IPV4: + case DHCP_OPT_IPV4_LIST: + for (i = 0; i + 4 <= (unsigned int)opts[code].slen; i += 4) {
Prefer sizeof(struct in_addr) to a literal 4.
+ if (off) + off += snprintf(buf + off, buf_len - off, ","); + inet_ntop(AF_INET, opts[code].s + i, + buf + off, buf_len - off); + off = strlen(buf); + } + return buf; + case DHCP_OPT_UINT8: + if (snprintf(buf, buf_len, "%u", + opts[code].s[0]) >= (int)buf_len) + return NULL; + return buf; + case DHCP_OPT_UINT16: + if (snprintf(buf, buf_len, "%u", + ((unsigned)opts[code].s[0] << 8) | + opts[code].s[1]) >= (int)buf_len) + return NULL; + return buf; + case DHCP_OPT_UINT32:
As with the parse side, it should be possible to consolidate more in the various uint cases.
+ if (snprintf(buf, buf_len, "%u", + ((unsigned)opts[code].s[0] << 24) | + ((unsigned)opts[code].s[1] << 16) | + ((unsigned)opts[code].s[2] << 8) | + opts[code].s[3]) >= (int)buf_len) + return NULL; + return buf; + case DHCP_OPT_INT32: + if (snprintf(buf, buf_len, "%d", + (int)(((unsigned)opts[code].s[0] << 24) | + ((unsigned)opts[code].s[1] << 16) | + ((unsigned)opts[code].s[2] << 8) | + opts[code].s[3])) >= (int)buf_len) + return NULL; + return buf; + case DHCP_OPT_STR: + if ((size_t)opts[code].slen < buf_len) { + memcpy(buf, opts[code].s, opts[code].slen); + buf[opts[code].slen] = '\0'; + } else { + memcpy(buf, opts[code].s, buf_len - 1); + buf[buf_len - 1] = '\0'; + }
This can be done more simply using some of printf()s fancier features: snprintf(buf, buf_len, "%.*s", opts[code].slen, opts[code].s); "precision" on %s format specifiers gives the maximum number of characters to format. "*" means the precision is given as a parameter, rather than inline in the format string.
+ return buf; + default: + return NULL; + } +} + /** * fill_one() - Fill a single option into a buffer * @buf: Buffer to write option @@ -482,14 +760,19 @@ int dhcp(const struct ctx *c, struct iov_tail *data) info(" from %s", eth_ntop(m->chaddr, macstr, sizeof(macstr)));
mask.s_addr = htonl(0xffffffff << (32 - c->ip4.prefix_len)); - memcpy(opts[1].s, &mask, sizeof(mask)); - memcpy(opts[3].s, &c->ip4.guest_gw, sizeof(c->ip4.guest_gw)); - memcpy(opts[54].s, &c->ip4.our_tap_addr, sizeof(c->ip4.our_tap_addr)); + if (opts[1].state != OPT_USER) + memcpy(opts[1].s, &mask, sizeof(mask)); + if (opts[3].state != OPT_USER) + memcpy(opts[3].s, &c->ip4.guest_gw, sizeof(c->ip4.guest_gw)); + if (opts[54].state != OPT_USER) + memcpy(opts[54].s, &c->ip4.our_tap_addr, + sizeof(c->ip4.our_tap_addr));
/* If the gateway is not on the assigned subnet, send an option 121 * (Classless Static Routing) adding a dummy route to it. */ - if ((c->ip4.addr.s_addr & mask.s_addr) + if (opts[121].state != OPT_USER && + (c->ip4.addr.s_addr & mask.s_addr) != (c->ip4.guest_gw.s_addr & mask.s_addr)) { /* a.b.c.d/32:0.0.0.0, 0:a.b.c.d */ opts[121].slen = 14; @@ -501,27 +784,32 @@ int dhcp(const struct ctx *c, struct iov_tail *data) &c->ip4.guest_gw, sizeof(c->ip4.guest_gw)); }
- if (c->mtu) { + if (opts[26].state != OPT_USER && c->mtu) { opts[26].slen = 2; opts[26].state = OPT_DEFAULT; opts[26].s[0] = c->mtu / 256; opts[26].s[1] = c->mtu % 256; }
- for (i = 0, opts[6].slen = 0; - !c->no_dhcp_dns && i < ARRAY_SIZE(c->ip4.dns); i++) { - if (IN4_IS_ADDR_UNSPECIFIED(&c->ip4.dns[i])) - break; - ((struct in_addr *)opts[6].s)[i] = c->ip4.dns[i]; - opts[6].slen += sizeof(uint32_t); + if (opts[6].state != OPT_USER) { + for (i = 0, opts[6].slen = 0;
I'd suggest putting the opts[6].slen = 0 on the previous line, rather than the potentially confusing comma operator.
+ !c->no_dhcp_dns && i < ARRAY_SIZE(c->ip4.dns); i++) { + if (IN4_IS_ADDR_UNSPECIFIED(&c->ip4.dns[i])) + break; + ((struct in_addr *)opts[6].s)[i] = c->ip4.dns[i]; + opts[6].slen += sizeof(uint32_t); + } + if (!opts[6].slen) + opts[6].slen = -1; } + if (opts[6].slen) opts[6].state = OPT_DEFAULT; else opts[6].state = OPT_UNSET;
opt_len = strlen(c->hostname); - if (opt_len > 0) { + if (opts[12].state != OPT_USER && opt_len > 0) { opts[12].slen = opt_len; opts[12].state = OPT_DEFAULT; memcpy(opts[12].s, &c->hostname, opt_len); diff --git a/dhcp.h b/dhcp.h index cd50c99..80abda6 100644 --- a/dhcp.h +++ b/dhcp.h @@ -8,5 +8,7 @@
int dhcp(const struct ctx *c, struct iov_tail *data); void dhcp_init(void); +void dhcp_set_opt(uint8_t code, const char *val_str); +const char *dhcp_opt_to_str(uint8_t code, char *buf, size_t buf_len);
#endif /* DHCP_H */ diff --git a/passt.1 b/passt.1 index 995590a..215a289 100644 --- a/passt.1 +++ b/passt.1 @@ -440,6 +440,48 @@ 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-opt " " \fICODE\fR,\fIVALUE\fR +Set DHCP option \fICODE\fR (1\-254) to \fIVALUE\fR. The value format depends +on the option type and is determined automatically from the option code. +Multiple IPv4 addresses are comma-separated. +This option can be specified multiple times. If the same option code is +given more than once, the last value wins. Options set with +\fB\-\-dhcp-opt\fR override built-in values. +.PP +Examples: +.nf + \-\-dhcp-opt 6,8.8.8.8,4.4.4.4 + \-\-dhcp-opt 12,myhostname +.fi +.PP +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), 41 (NIS Servers), +42 (NTP Servers), 44 (NetBIOS Name Server) +.TP +.B Integer options +2 (Time Offset, 32-bit), 13 (Boot File Size, 16-bit), 19 (IP Forwarding, 8-bit), +23 (Default IP TTL, 8-bit), 26 (Interface MTU, 16-bit), +37 (TCP Default TTL, 8-bit), 38 (TCP Keepalive Interval, 32-bit), +51 (IP Address Lease Time, 32-bit), +53 (DHCP Message Type, 8-bit), 57 (Max DHCP Message Size, 16-bit), +58 (Renewal Time, 32-bit), 59 (Rebinding Time, 32-bit) +.TP +.B String options +12 (Host Name), 15 (Domain Name), 17 (Root Path), 40 (NIS Domain Name), +60 (Vendor Class Identifier), 66 (TFTP Server Name), +67 (Bootfile Name), 252 (WPAD URL) +.RE + .TP .BR \-t ", " \-\-tcp-ports " " \fIspec Configure TCP port forwarding to guest or namespace. \fIspec\fR can be either: -- 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