diff options
| author | Anshu Kumari <anskuma@redhat.com> | 2026-10-26 16:26:55 +0530 |
|---|---|---|
| committer | Stefano Brivio <sbrivio@redhat.com> | 2026-11-10 23:03:07 +0100 |
| commit | 5e00f7ac41b1e0d3c85106ac8dc78fd216a93529 (patch) | |
| tree | 35cb8b246e7a7e29e8440e4e20b4336ca3408c17 | |
| parent | 64e69136ecbcc5fedfd779d929d713b451c91b67 (diff) | |
| download | passt-5e00f7ac41b1e0d3c85106ac8dc78fd216a93529.tar passt-5e00f7ac41b1e0d3c85106ac8dc78fd216a93529.tar.gz passt-5e00f7ac41b1e0d3c85106ac8dc78fd216a93529.tar.bz2 passt-5e00f7ac41b1e0d3c85106ac8dc78fd216a93529.tar.lz passt-5e00f7ac41b1e0d3c85106ac8dc78fd216a93529.tar.xz passt-5e00f7ac41b1e0d3c85106ac8dc78fd216a93529.tar.zst passt-5e00f7ac41b1e0d3c85106ac8dc78fd216a93529.zip | |
dhcp: Add --dhcp-opt with option table and value parser
Add a --dhcp-opt CODE,VALUE flag that sets any DHCP option by
numeric code with type-aware parsing per RFC 2132.
A type lookup table maps option codes to RFC 2132 value types
(IPv4, IPv4 list, integer, string). dhcp_opt_parse() converts
CLI strings to binary wire format; parsed options are stored in
opts[] and injected into DHCP replies. Options set via --dhcp-opt
(OPT_USER) take priority over host-derived defaults (OPT_DEFAULT).
Link: https://bugs.passt.top/show_bug.cgi?id=192
Signed-off-by: Anshu Kumari <anskuma@redhat.com>
Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
| -rw-r--r-- | conf.c | 25 | ||||
| -rw-r--r-- | dhcp.c | 333 | ||||
| -rw-r--r-- | dhcp.h | 2 | ||||
| -rw-r--r-- | passt.1 | 41 |
4 files changed, 375 insertions, 26 deletions
@@ -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" @@ -632,7 +633,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 @@ -861,6 +863,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 = 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++) { @@ -1317,6 +1323,7 @@ void conf(struct ctx *c, int argc, char **argv) {"conf-path", required_argument, NULL, 'c' }, {"chroot-fallback", no_argument, NULL, 32 }, {"no-pidns", no_argument, NULL, 33 }, + {"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:"; @@ -1331,6 +1338,7 @@ 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; uint8_t opt_n = 0; @@ -1567,6 +1575,21 @@ void conf(struct ctx *c, int argc, char **argv) c->no_pidns = true; break; + case 34: + p = optarg; + + if (!parse_unsigned(&p, 0, &optcode) || + !parse_literal(&p, ",")) { + die("--dhcp-opt requires CODE,VALUE format"); + } + + if (optcode < 1 || optcode > 254) { + die("DHCP option code must be 1-254: %s", + optarg); + } + + dhcp_set_opt(optcode, p); + break; case 'd': c->debug = 1; c->quiet = 0; @@ -23,6 +23,7 @@ #include <unistd.h> #include <string.h> #include <limits.h> +#include <errno.h> #include "util.h" #include "ip.h" @@ -32,15 +33,18 @@ #include "tap.h" #include "log.h" #include "dhcp.h" +#include "parse.h" /** * enum opt_conf - DHCP option configuration * @OPT_UNSET: Option not configured * @OPT_DEFAULT: Option derived from host configuration + * @OPT_USER: Option set via --dhcp-opt command line */ enum opt_conf { OPT_UNSET = 0, OPT_DEFAULT, + OPT_USER, }; /** @@ -50,7 +54,7 @@ enum opt_conf { * @s: Option payload from server * @clen: Length of option received from client, -1 if not received * @c: Option payload from client - * @conf: Option configuration (unset or default) + * @conf: Option configuration (unset, default, or user) */ struct opt { int sent; @@ -85,17 +89,20 @@ static struct opt opts[256]; */ void dhcp_init(void) { - /* 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 }, - 0, { 0 }, OPT_DEFAULT }; + if (opts[1].conf != OPT_USER) /* Mask */ + opts[1] = (struct opt) { 0, 4, { 0 }, 0, { 0 }, OPT_DEFAULT }; + if (opts[3].conf != OPT_USER) /* Router */ + opts[3] = (struct opt) { 0, 4, { 0 }, 0, { 0 }, OPT_DEFAULT }; + if (opts[51].conf != 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[54].conf != OPT_USER) { /* Server ID */ + opts[54] = (struct opt) { 0, 4, { 0 }, 0, { 0 }, + OPT_DEFAULT }; + } } /** @@ -141,6 +148,272 @@ struct msg { } __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 */ + [51] = DHCP_OPT_UINT32, /* IP Address Lease Time */ + [54] = DHCP_OPT_IPV4, /* Server Identifier */ + [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; + 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; + + do { + if (len + sizeof(struct in_addr) > buf_len) + return -1; + + if (!parse_ipv4(&str, + (struct in_addr *)(buf + len))) + return -1; + + len += sizeof(struct in_addr); + + if (type == DHCP_OPT_IPV4) + break; + } while (parse_literal(&str, ",")); + + if (!len || !parse_eoi(str)) + 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; + + if (type == DHCP_OPT_INT32) { + long sval; + + errno = 0; + sval = strtol(str, &end, 0); + if (*end || errno || + sval < INT32_MIN || sval > INT32_MAX) + return -1; + val = (uint32_t)sval; + } else { + if (!parse_unsigned(&str, 0, &val) || + !parse_eoi(str) || + val >= (1ULL << (width * 8))) + return -1; + } + + if (type == DHCP_OPT_UINT16) { + uint16_t v16 = htons(val); + memcpy(buf, &v16, sizeof(v16)); + } else if (type == DHCP_OPT_UINT32 || + type == DHCP_OPT_INT32) { + uint32_t v32 = htonl(val); + memcpy(buf, &v32, sizeof(v32)); + } else { + buf[0] = val; + } + + 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].conf = 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 set, NULL otherwise + */ +const char *dhcp_opt_to_str(uint8_t code, char *buf, size_t buf_len) +{ + enum dhcp_opt_type type; + unsigned int slen; + unsigned int i; + int off = 0; + + if (opts[code].conf == OPT_UNSET) + return NULL; + + assert(code < ARRAY_SIZE(dhcp_opt_types)); + + type = dhcp_opt_types[code]; + slen = opts[code].slen; + + switch (type) { + case DHCP_OPT_IPV4: + case DHCP_OPT_IPV4_LIST: + for (i = 0; i + sizeof(struct in_addr) <= slen; + i += sizeof(struct in_addr)) { + if (off) { + if (off + 1 >= (int)buf_len) + return NULL; + buf[off++] = ','; + } + if (!inet_ntop(AF_INET, opts[code].s + i, + buf + off, buf_len - off)) + return NULL; + off += strlen(buf + off); + } + return buf; + case DHCP_OPT_UINT8: + case DHCP_OPT_UINT16: + case DHCP_OPT_UINT32: { + uint32_t uval = 0; + + if (slen == 1) { + uval = opts[code].s[0]; + } else if (slen == 2) { + uint16_t v16; + memcpy(&v16, opts[code].s, sizeof(v16)); + uval = ntohs(v16); + } else if (slen == 4) { + memcpy(&uval, opts[code].s, sizeof(uval)); + uval = ntohl(uval); + } + + if (snprintf(buf, buf_len, "%u", uval) >= (int)buf_len) + return NULL; + return buf; + } + case DHCP_OPT_INT32: { + int32_t sval; + uint32_t v32; + + assert(slen == 4); + memcpy(&v32, opts[code].s, sizeof(v32)); + sval = (int32_t)ntohl(v32); + + if (snprintf(buf, buf_len, "%d", sval) >= (int)buf_len) + return NULL; + return buf; + } + case DHCP_OPT_STR: + (void)snprintf(buf, buf_len, "%.*s", slen, opts[code].s); + return buf; + default: + assert(0); + } +} + +/** * fill_one() - Fill a single option into a buffer * @buf: Buffer to write option * @size: Usable size of @buf (excluding end marker) @@ -424,9 +697,14 @@ 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].conf == OPT_DEFAULT) + memcpy(opts[1].s, &mask, sizeof(mask)); + if (opts[3].conf == OPT_DEFAULT) + memcpy(opts[3].s, &c->ip4.guest_gw, sizeof(c->ip4.guest_gw)); + if (opts[54].conf == OPT_DEFAULT) { + 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. @@ -443,25 +721,30 @@ 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].conf != OPT_USER && c->mtu) { + uint16_t mtu_n = htons(c->mtu); + opts[26].slen = 2; opts[26].conf = OPT_DEFAULT; - opts[26].s[0] = c->mtu / 256; - opts[26].s[1] = c->mtu % 256; + memcpy(opts[26].s, &mtu_n, sizeof(mtu_n)); } - 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].conf != OPT_USER) { + opts[6].slen = 0; + for (i = 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].slen) + opts[6].conf = OPT_DEFAULT; } - if (opts[6].slen) - opts[6].conf = OPT_DEFAULT; opt_len = strlen(c->hostname); - if (opt_len > 0) { + if (opts[12].conf != OPT_USER && opt_len > 0) { opts[12].slen = opt_len; opts[12].conf = OPT_DEFAULT; memcpy(opts[12].s, &c->hostname, opt_len); @@ -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 */ @@ -441,6 +441,47 @@ 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), +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), +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: .RS |
