From mboxrd@z Thu Jan 1 00:00:00 1970 Authentication-Results: passt.top; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: passt.top; dkim=pass (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=IJUicji9; dkim-atps=neutral Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by passt.top (Postfix) with ESMTPS id 182555A0262 for ; Fri, 19 Jun 2026 09:29:30 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1781854169; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: in-reply-to:in-reply-to:references:references; bh=iDGGMvvTkKJtpGFzQYEv5I8HXBBlSymsc2tYDVf0Slw=; b=IJUicji9BdBgUOvzJee6ufYZzbUYTWK/qOq/RRQCjo6ALkG40Q2a2n43e8IwM+E2oPU5tm sDXDKLIgXTrxlx2OcHH6EWvHkteRCB4GLHnaqJID+A3pR0JaNf3a/5p1wf7YmbpLky9Aox L1n8le2lk3/ke3sXXF821aOZ9/7Tt/4= Received: from mail-lj1-f200.google.com (mail-lj1-f200.google.com [209.85.208.200]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-263-P3JfkxqQM-O9Jw8VEikzug-1; Fri, 19 Jun 2026 03:29:27 -0400 X-MC-Unique: P3JfkxqQM-O9Jw8VEikzug-1 X-Mimecast-MFC-AGG-ID: P3JfkxqQM-O9Jw8VEikzug_1781854165 Received: by mail-lj1-f200.google.com with SMTP id 38308e7fff4ca-399885d284aso4408851fa.0 for ; Fri, 19 Jun 2026 00:29:26 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1781854165; x=1782458965; h=cc:to:subject:message-id:date:from:in-reply-to:references :mime-version:x-gm-gg:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to; bh=iDGGMvvTkKJtpGFzQYEv5I8HXBBlSymsc2tYDVf0Slw=; b=ct6c3X/6EfGA5Cvu8uRgwJq85xyqJ+gY/+SEvtYNfJRMx/GTDKJWN6+IoIh6622jS2 8ykdatCBUCgS4mAL6eq2i0y/SkSafh/SOKENkbRIAtRsWmFSR8Ho4YUi6Oex8vVDHVad fY+ZyIvypSKFZRcCtEb+/edY3w9ZkDZqQ7VhafMESyR0glHUNM8lk47WLL9jkUVxvEyW o/VEjmqDevDwWeUJ4f69nWxIJ4UZmxGxM+LBJ+xEY74WW/xy/szBVC3RuU2sHkWEqdpP GwmBqAKiPIM2sHSJtVme5dljWSrDV8eH+9d1CbNA/psiIeszS3VBj7rHv8lQXAjLFMAV N9Tw== X-Gm-Message-State: AOJu0YxjE5m2sDHoURDnRA3XWqTPSsrz9+EskfWD9AZeA3lCWnc2WxRw fakVWN+AEUflotjfmWyERWUkNBuezqUCC4sW0y0IbnXMLhDwKuvGkzG/jfz/1e1sN3PPGTUiddy D/eXbEVLcoQEbEZfKS5YO1qZHjvu1PsvtGdNOjwQmk90YGnP8IsA8W5vGXOyrjRaYx99RA5NBl5 8sdI4XLPaM988vZU5wrPmFbphpZwwK X-Gm-Gg: AfdE7ckZPQEUU4TwRjuceDN6q1qZo1jmF4LPmbddPjeWd0qRCIM1bZtMeXPZteky2IA jPidUT0FHWOavSVAhiqOMIuyKH8mzKs27MT2iZLzHJG1rz/G42NnFWBVEHMTvYe4GBrKF+grcVf vLn/0VRTPodHI8jF8JfgtTFd7ceWOHZr+ZHg2MmdCvzOgqNeJPY/TYupKmPZpx9UHl0l8C0JzzB jurozdNAChJnNxf87HK5V4dbSsu X-Received: by 2002:a05:651c:50a:b0:38d:ed62:f1d5 with SMTP id 38308e7fff4ca-3998bd67958mr5513091fa.17.1781854163701; Fri, 19 Jun 2026 00:29:23 -0700 (PDT) X-Received: by 2002:a05:651c:50a:b0:38d:ed62:f1d5 with SMTP id 38308e7fff4ca-3998bd67958mr5512771fa.17.1781854162090; Fri, 19 Jun 2026 00:29:22 -0700 (PDT) MIME-Version: 1.0 References: <20260617132243.1499556-1-anskuma@redhat.com> <20260617132243.1499556-4-anskuma@redhat.com> In-Reply-To: From: Anshu Kumari Date: Fri, 19 Jun 2026 12:59:10 +0530 X-Gm-Features: AVVi8Ce8Kc63ZEgL4FKETLfsrnS8DKsRhAFM204vSAf41NQrfBm-Vx_bMUXgIR4 Message-ID: Subject: Re: [PATCH v4 3/4] dhcp: Add --dhcp-opt with option table and value parser To: David Gibson X-Mimecast-Spam-Score: 0 X-Mimecast-MFC-PROC-ID: AbPvZntGrWaWeSsjOm5hrZwqugL33IntJOLNTd1QOR8_1781854165 X-Mimecast-Originator: redhat.com Content-Type: multipart/alternative; boundary="0000000000005f94c40654964029" Message-ID-Hash: CMSQRYPYKIXW5MLLCKMFX6TG2SAMSBJP X-Message-ID-Hash: CMSQRYPYKIXW5MLLCKMFX6TG2SAMSBJP X-MailFrom: anskuma@redhat.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; emergency; loop; banned-address; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: passt-dev@passt.top, sbrivio@redhat.com, jmaloy@redhat.com, lvivier@redhat.com X-Mailman-Version: 3.3.8 Precedence: list List-Id: Development discussion and patches for passt Archived-At: Archived-At: List-Archive: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: --0000000000005f94c40654964029 Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable On Fri, Jun 19, 2026 at 9:24=E2=80=AFAM David Gibson wrote: > On Wed, Jun 17, 2026 at 06:52:37PM +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 ctx 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=3D192 > > Signed-off-by: Anshu Kumari > > --- > > 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. > > Hmm. So each option is parsed twice. What prevents you from parsing > directly into the opts[] array at conf() time? > The first parse acts as a validation step to check that the user has entered the correct value format for the option. Without it, if the user passes something like *--dhcp-opt 3,notanip*, the error would surface only when the first DHCP client connects, not at startup. I think it's better to fail during startup if correct value format is not entered in command-line rather than failing at later stage during 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 =3D 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 =E2=80=94= parse > > both as list, error if >1 in single case. > > - Added errno =3D 0 before strtoul() and check after. > > - Fixed range check: 1ULL << (width * 8) for all widths including > > width=3D=3D4. > > - strncpy =E2=86=92 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 =E2=86=92 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 | 45 ++++++++++++- > > dhcp.c | 191 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ > > dhcp.h | 2 + > > passt.1 | 42 +++++++++++++ > > passt.h | 6 ++ > > 5 files changed, 285 insertions(+), 1 deletion(-) > > > > diff --git a/conf.c b/conf.c > > index cd05adf..836b297 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 CODE to VAL\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 =3D 1; i < 255; i++) > > + if (*c->dhcp_opts[i].str) > > + info(" option %u: %s", i, > > + c->dhcp_opts[i].str); > > } > > > > for (i =3D 0; i < ARRAY_SIZE(c->ip4.dns); i++) { > > @@ -1150,6 +1156,25 @@ static void conf_sock_listen(const struct ctx *c= ) > > die_perror("Couldn't add configuration socket to epoll"); > > } > > > > +/** > > + * conf_dhcp_option() - Set value for a DHCP option in configuration > > + * @c: Execution context > > + * @code: DHCP option code > > + * @val_str: Value string from command line > > + */ > > +static void conf_dhcp_option(struct ctx *c, uint8_t code, const char > *val_str) > > +{ > > + uint8_t tmp[255]; > > + > > + if (dhcp_opt_parse(code, val_str, tmp, sizeof(tmp)) < 0) > > + die("Invalid value for DHCP option %u: %s", code, val_str= ); > > + > > + if (snprintf_check(c->dhcp_opts[code].str, > > + sizeof(c->dhcp_opts[0].str), > > + "%s", val_str)) > > + die("DHCP option value too long: %s", val_str); > > +} > > + > > /** > > * conf() - Process command-line arguments and set configuration > > * @c: Execution context > > @@ -1233,6 +1258,7 @@ void conf(struct ctx *c, int argc, char **argv) > > {"migrate-no-linger", no_argument, NULL, 3= 0 > }, > > {"stats", required_argument, NULL, 3= 1 > }, > > {"conf-path", required_argument, NULL, > 'c' }, > > + {"dhcp-opt", required_argument, NULL, 3= 4 > }, > > { 0 }, > > }; > > const char *optstring =3D > "+dqfel:hs:c:F:I:p:P:m:a:n:M:g:i:o:D:S:H:461t:u:T:U:"; > > @@ -1248,10 +1274,13 @@ void conf(struct ctx *c, int argc, char **argv) > > uint8_t prefix_len_from_opt =3D 0; > > unsigned int ifi4 =3D 0, ifi6 =3D 0; > > const char *logfile =3D NULL; > > + unsigned long optcode; > > char *runas =3D NULL; > > size_t logsize =3D 0; > > + const char *comma; > > long fd_tap_opt; > > int name, ret; > > + char *end; > > uid_t uid; > > gid_t gid; > > > > @@ -1467,6 +1496,20 @@ void conf(struct ctx *c, int argc, char **argv) > > die("Can't display statistics if not > running in foreground"); > > c->stats =3D strtol(optarg, NULL, 0); > > break; > > + case 34: > > + comma =3D strchr(optarg, ','); > > + if (!comma) > > + die("--dhcp-opt requires CODE,VALUE > format"); > > + > > + errno =3D 0; > > + optcode =3D strtoul(optarg, &end, 0); > > + if (end !=3D comma || errno || > > + optcode < 1 || optcode > 254) > > + die("DHCP option code must be 1-254: %s", > > + optarg); > > + > > + conf_dhcp_option(c, optcode, comma + 1); > > + break; > > case 'd': > > c->debug =3D 1; > > c->quiet =3D 0; > > diff --git a/dhcp.c b/dhcp.c > > index 78790d8..47bb524 100644 > > --- a/dhcp.c > > +++ b/dhcp.c > > @@ -23,6 +23,7 @@ > > #include > > #include > > #include > > +#include > > > > #include "util.h" > > #include "ip.h" > > @@ -130,6 +131,189 @@ 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_INT8: Unsigned 8-bit integer > > + * @DHCP_OPT_INT16: Unsigned 16-bit integer > > + * @DHCP_OPT_INT32: Unsigned 32-bit integer > > + * @DHCP_OPT_SINT32: Signed 32-bit integer > > For consistency with C conventions, I'd suggset UINT{8,16,32} and just > INT32 for the signed case. > > > + */ > > +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_SINT32, > > +}; > > + > > +/** > > + * dhcp_opt_types - Maps option code to RFC 2132 value type, indexed b= y > code > > + */ > > +static const enum dhcp_opt_type dhcp_opt_types[] =3D { > > + [1] =3D DHCP_OPT_IPV4, /* Subnet Mask */ > > + [2] =3D DHCP_OPT_SINT32, /* Time Offset */ > > + [3] =3D DHCP_OPT_IPV4_LIST, /* Router */ > > + [4] =3D DHCP_OPT_IPV4_LIST, /* Time Server */ > > + [5] =3D DHCP_OPT_IPV4_LIST, /* Name Server */ > > + [6] =3D DHCP_OPT_IPV4_LIST, /* Domain Name Server */ > > + [7] =3D DHCP_OPT_IPV4_LIST, /* Log Server */ > > + [8] =3D DHCP_OPT_IPV4_LIST, /* Cookie Server */ > > + [9] =3D DHCP_OPT_IPV4_LIST, /* LPR Server */ > > + [10] =3D DHCP_OPT_IPV4_LIST, /* Impress Server */ > > + [11] =3D DHCP_OPT_IPV4_LIST, /* Resource Location Server */ > > + [12] =3D DHCP_OPT_STR, /* Host Name */ > > + [13] =3D DHCP_OPT_INT16, /* Boot File Size */ > > + [15] =3D DHCP_OPT_STR, /* Domain Name */ > > + [16] =3D DHCP_OPT_IPV4, /* Swap Server */ > > + [17] =3D DHCP_OPT_STR, /* Root Path */ > > + [19] =3D DHCP_OPT_INT8, /* IP Forwarding */ > > + [23] =3D DHCP_OPT_INT8, /* Default IP TTL */ > > + [26] =3D DHCP_OPT_INT16, /* Interface MTU */ > > + [28] =3D DHCP_OPT_IPV4, /* Broadcast Address */ > > + [37] =3D DHCP_OPT_INT8, /* TCP Default TTL */ > > + [38] =3D DHCP_OPT_INT32, /* TCP Keepalive Interval */ > > + [40] =3D DHCP_OPT_STR, /* NIS Domain Name */ > > + [41] =3D DHCP_OPT_IPV4_LIST, /* NIS Servers */ > > + [42] =3D DHCP_OPT_IPV4_LIST, /* NTP Servers */ > > + [44] =3D DHCP_OPT_IPV4_LIST, /* NetBIOS Name Server */ > > + [50] =3D DHCP_OPT_IPV4, /* Requested IP Address */ > > + [51] =3D DHCP_OPT_INT32, /* IP Address Lease Time */ > > + [53] =3D DHCP_OPT_INT8, /* DHCP Message Type */ > > + [54] =3D DHCP_OPT_IPV4, /* Server Identifier */ > > + [57] =3D DHCP_OPT_INT16, /* Max DHCP Message Size */ > > + [58] =3D DHCP_OPT_INT32, /* Renewal (T1) Time */ > > + [59] =3D DHCP_OPT_INT32, /* Rebinding (T2) Time */ > > + [60] =3D DHCP_OPT_STR, /* Vendor Class Identifier */ > > + [66] =3D DHCP_OPT_STR, /* TFTP Server Name */ > > + [67] =3D DHCP_OPT_STR, /* Bootfile Name */ > > + [252] =3D 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 > > + */ > > +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; > > + char *end; > > + int len; > > + > > + if (code >=3D ARRAY_SIZE(dhcp_opt_types)) > > + return -1; > > + > > + type =3D 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 =3D 0; > > + > > + while (*str) { > > + char ipbuf[INET_ADDRSTRLEN]; > > + size_t chunk; > > + > > + chunk =3D strcspn(str, ","); > > + > > + if (!chunk || chunk >=3D sizeof(ipbuf)) > > + return -1; > > + > > + memcpy(ipbuf, str, chunk); > > + ipbuf[chunk] =3D '\0'; > > + > > + if (len + (int)sizeof(struct in_addr) > > (int)buf_len) > > Both sides are necessarily non-negative, so it would make more sense > to make len unsigned than to cast the other things to signed. > > > + return -1; > > + > > + if (inet_pton(AF_INET, ipbuf, buf + len) !=3D 1) > > + return -1; > > + > > + len +=3D sizeof(struct in_addr); > > + > > + if (type =3D=3D DHCP_OPT_IPV4) { > > + if (str[chunk] =3D=3D ',') > > + return -1; > > + break; > > + } > > + > > + str +=3D chunk + (str[chunk] =3D=3D ','); > > + } > > + > > + if (!len) > > + return -1; > > + > > + return len; > > + case DHCP_OPT_INT8: > > + case DHCP_OPT_INT16: > > + case DHCP_OPT_INT32: > > + case DHCP_OPT_SINT32: > > + if (type =3D=3D DHCP_OPT_INT8) > > + width =3D 1; > > + else if (type =3D=3D DHCP_OPT_INT16) > > + width =3D 2; > > + else > > + width =3D 4; > > + > > + if (buf_len < width) > > + return -1; > > + > > + errno =3D 0; > > + if (type =3D=3D DHCP_OPT_SINT32) { > > + long sval; > > + > > + sval =3D strtol(str, &end, 0); > > + if (*end || errno || > > + sval < INT32_MIN || sval > INT32_MAX) > > + return -1; > > + val =3D (uint32_t)sval; > > + } else { > > + val =3D strtoul(str, &end, 0); > > + if (*end || errno || > > + val >=3D (1ULL << (width * 8))) > > + return -1; > > + } > > + > > + for (i =3D width; i > 0; i--) { > > + buf[i - 1] =3D val & 0xff; > > + val >>=3D 8; > > + } > > + > > + return width; > > + case DHCP_OPT_STR: > > + slen =3D strlen(str); > > + > > + if (slen >=3D buf_len) > > + return -1; > > + > > + memcpy(buf, str, slen); > > + > > + return slen; > > + } > > + > > + return -1; > > +} > > + > > /** > > * fill_one() - Fill a single option into a buffer > > * @buf: Buffer to write option > > @@ -541,6 +725,13 @@ int dhcp(const struct ctx *c, struct iov_tail *dat= a) > > if (!c->no_dhcp_dns_search) > > opt_set_dns_search(c, OPT_MAX - 3); > > > > + for (i =3D 1; i < 255; i++) { > > + if (!c->dhcp_opts[i].str[0]) > > + continue; > > + opts[i].slen =3D dhcp_opt_parse(i, c->dhcp_opts[i].str, > > + opts[i].s, > sizeof(opts[i].s)); > > + } > > + > > /* RFC 2132, Section 9.5: put boot file name in the 'file' header > > * field. Suppress option 67 from the options area and reserve > > * the file field from overload. > > diff --git a/dhcp.h b/dhcp.h > > index cd50c99..cc8d5dd 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); > > +int dhcp_opt_parse(uint8_t code, const char *str, > > + uint8_t *buf, size_t buf_len); > > > > #endif /* DHCP_H */ > > diff --git a/passt.1 b/passt.1 > > index 908fd4a..ccdcbb2 100644 > > --- a/passt.1 > > +++ b/passt.1 > > @@ -430,6 +430,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 cod= e. > > +Multiple IPv4 addresses are comma-separated. > > +This option can be specified multiple times. If the same option code i= s > > +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 > one of: > > diff --git a/passt.h b/passt.h > > index 3a07294..15e2d83 100644 > > --- a/passt.h > > +++ b/passt.h > > @@ -182,6 +182,8 @@ struct ip6_ctx { > > * @dns_search: DNS search list > > * @hostname: Guest hostname > > * @fqdn: Guest FQDN > > + * @dhcp_opts: User-specified DHCP options from --dhcp-o= pt > > + * @dhcp_opts.str: String value from command line > > * @ifi6: Template interface for IPv6, -1: none, 0: IPv6 > disabled > > * @ip6: IPv6 configuration > > * @pasta_ifn: Name of namespace interface for pasta > > @@ -264,6 +266,10 @@ struct ctx { > > char hostname[PASST_MAXDNAME]; > > char fqdn[PASST_MAXDNAME]; > > > > + struct { > > + char str[255]; > > + } dhcp_opts[256]; > > + > > 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 wa= y > | around. > http://www.ozlabs.org/~dgibson > --0000000000005f94c40654964029 Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: quoted-printable


On Fri, Jun 19,= 2026 at 9:24=E2=80=AFAM David Gibson <david@gibson.dropbear.id.au> wrote:
On Wed, Jun 17, 2026 at 06:52:37PM= +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.=C2=A0 Parsed options are stored in<= br> > struct ctx and injected into DHCP replies.=C2=A0 If the same option co= de
> is given more than once, the last value wins.
>
> Link: https://bugs.passt.top/show_bug.cgi?id=3D192<= /a>
> Signed-off-by: Anshu Kumari <
anskuma@redhat.com>
> ---
> v4:
>=C2=A0 =C2=A0- Renamed custom_opts to dhcp_opts, 256 entries indexed by= option
>=C2=A0 =C2=A0 =C2=A0code, removed MAX_CUSTOM_DHCP_OPTS and count field.=
>=C2=A0 =C2=A0- Changed str buffer from 256 to 255 bytes.
>=C2=A0 =C2=A0- Moved function to conf.c as static conf_dhcp_option(), r= enamed
>=C2=A0 =C2=A0 =C2=A0from dhcp_add_option().
>=C2=A0 =C2=A0- Made dhcp_opt_parse() non-static, declared in dhcp.h
>=C2=A0 =C2=A0- Dropped val/len from ctx struct; conf_dhcp_option() vali= dates
>=C2=A0 =C2=A0 =C2=A0with temp buffer, dhcp() parses str directly into o= pts[] at
>=C2=A0 =C2=A0 =C2=A0reply time.

Hmm.=C2=A0 So each option is parsed twice.=C2=A0 What prevents you from par= sing
directly into the opts[] array at conf() time?

The first parse acts as a validation step to check that the user ha= s entered the
correct value format for the option. Without it, if= the user passes something
like --dhcp-opt 3,notanip, the = error would surface only when the first DHCP client connects, not at startu= p.

I think it's better to fail during startup = if correct value format is not entered in command-line rather than failing = at later
stage during reply time.

>=C2=A0 =C2=A0- Replaced strtok_r() + 256-byte buffer with strcspn() + >=C2=A0 =C2=A0 =C2=A0INET_ADDRSTRLEN buffer.
>=C2=A0 =C2=A0- Added DHCP_OPT_SINT32 for option 2 (Time Offset), uses s= trtol()
>=C2=A0 =C2=A0 =C2=A0per RFC 2132 Section 8.2.
>=C2=A0 =C2=A0- All errors in dhcp_opt_parse() return -1, removed die() = calls;
>=C2=A0 =C2=A0 =C2=A0caller handles error message consistently.
>=C2=A0 =C2=A0- Removed redundant !slen check in DHCP_OPT_STR case.
>=C2=A0 =C2=A0- Omitted explicit array size for dhcp_opt_types[], arrayd= ded bounds
>=C2=A0 =C2=A0 =C2=A0check before lookup.
>=C2=A0 =C2=A0- Added errno =3D 0 + errno check for strtoul() in case 34= .
>=C2=A0 =C2=A0- Fixed usage text: "Set DHCP option CODE to VAL"= ;.
>=C2=A0 =C2=A0- Improved man page: added format description and examples=
>
> v3:
>=C2=A0 =C2=A0- Replaced DHCP_OPT_INTEGER with separate DHCP_OPT_INT8/IN= T16/INT32
>=C2=A0 =C2=A0 =C2=A0enums, removed dhcp_opt_int_width[] array.
>=C2=A0 =C2=A0- Shared logic between DHCP_OPT_IPV4 and DHCP_OPT_IPV4_LIS= T =E2=80=94 parse
>=C2=A0 =C2=A0 =C2=A0both as list, error if >1 in single case.
>=C2=A0 =C2=A0- Added errno =3D 0 before strtoul() and check after.
>=C2=A0 =C2=A0- Fixed range check: 1ULL << (width * 8) for all wid= ths including
>=C2=A0 =C2=A0 =C2=A0width=3D=3D4.
>=C2=A0 =C2=A0- strncpy =E2=86=92 memcpy for DHCP_OPT_STR.
>=C2=A0 =C2=A0- Moved enum to dhcp.c since not used in other files.
>=C2=A0 =C2=A0- Removed options 55, 61 (client-only), 119 (DNS compressi= on, use
>=C2=A0 =C2=A0 =C2=A0--dhcp-search instead), 33 (IP pairs not supported)= .
>=C2=A0 =C2=A0- DHCP_OPT_PARSE_BUF 1024 =E2=86=92 char tmp[256].
>=C2=A0 =C2=A0- Upgraded dhcp_add_option() to call dhcp_opt_parse() and = populate
>=C2=A0 =C2=A0 =C2=A0val[]/len.
>=C2=A0 =C2=A0- Aligned array entries for readability.
>=C2=A0 =C2=A0- Added tab after @DHCP_OPT_IPV4_LIST: in kerneldoc.
>=C2=A0 =C2=A0- Reject empty value strings before parsing
>=C2=A0 =C2=A0- Reject leading/trailing/consecutive commas in IP list va= lues.
>
> v2:
>=C2=A0 =C2=A0- Replaced struct lookup table + dhcp_opt_type_lookup() fu= nction with flat dhcp_opt_types[256] array indexed by code.
>=C2=A0 =C2=A0- Consolidated DHCP_OPT_UINT8/UINT16/UINT32 into single DH= CP_OPT_INTEGER with dhcp_opt_int_width[256] table.
>=C2=A0 =C2=A0- Dropped DHCP_OPT_ROUTES / option 121 entirely.
>=C2=A0 =C2=A0- Added kerneldoc for enum dhcp_opt_type values.
>=C2=A0 =C2=A0- Removed curly braces from switch cases, declarations bef= ore switch.
>=C2=A0 =C2=A0- Added newlines before return statements.
>=C2=A0 =C2=A0- Changed IP list delimiter from space to comma (--dhcp-op= t 6,1.1.1.1,8.8.8.8).
>=C2=A0 =C2=A0- Defined DHCP_OPT_PARSE_BUF constant for bare 1024.
>=C2=A0 =C2=A0- Added len and val[255] fields to struct here (moved from= patch 1).
>=C2=A0 =C2=A0- Added kerneldoc for @custom_opts.len and @custom_opts.va= l.
>=C2=A0 =C2=A0- Wired dhcp_opt_parse() into case 32 (--dhcp-boot) to pop= ulate val/len.
> ---
>=C2=A0 conf.c=C2=A0 |=C2=A0 45 ++++++++++++-
>=C2=A0 dhcp.c=C2=A0 | 191 +++++++++++++++++++++++++++++++++++++++++++++= +++++++++++
>=C2=A0 dhcp.h=C2=A0 |=C2=A0 =C2=A02 +
>=C2=A0 passt.1 |=C2=A0 42 +++++++++++++
>=C2=A0 passt.h |=C2=A0 =C2=A06 ++
>=C2=A0 5 files changed, 285 insertions(+), 1 deletion(-)
>
> diff --git a/conf.c b/conf.c
> index cd05adf..836b297 100644
> --- a/conf.c
> +++ b/conf.c
> @@ -47,6 +47,7 @@
>=C2=A0 #include "lineread.h"
>=C2=A0 #include "isolation.h"
>=C2=A0 #include "log.h"
> +#include "dhcp.h"
>=C2=A0 #include "vhost_user.h"
>=C2=A0 #include "epoll_ctl.h"
>=C2=A0 #include "conf.h"
> @@ -616,7 +617,8 @@ static void usage(const char *name, FILE *f, int s= tatus)
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0"=C2=A0 -S,= --search LIST=C2=A0 =C2=A0 Space-separated list, search domains\n" >=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0"=C2=A0 =C2= =A0 a single, empty option disables the DNS search list\n"
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0"=C2=A0 -H,= --hostname NAME=C2=A0 Hostname to configure client with\n"
> -=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0"=C2=A0 --fqdn N= AME=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 FQDN to configure client with\n"= );
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0"=C2=A0 --fqdn N= AME=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 FQDN to configure client with\n"=
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0"=C2=A0 --dhcp-o= pt CODE,VAL=C2=A0 Set DHCP option CODE to VAL\n");
>=C2=A0 =C2=A0 =C2=A0 =C2=A0if (strstr(name, "pasta"))
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0FPRINTF(f, "= ;=C2=A0 =C2=A0 default: don't use any search list\n");
>=C2=A0 =C2=A0 =C2=A0 =C2=A0else
> @@ -844,6 +846,10 @@ static void conf_print(const struct ctx *c)
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0info("=C2=A0 =C2=A0 router: %s",
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0 =C2=A0 =C2=A0 inet_ntop(AF_INET, &c->ip4.guest_gw,
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 buf, sizeof(= buf)));
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0for (i =3D 1; i < 255; i++)
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0if (*c->dhcp_opts[i].str)
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0info("= =C2=A0 =C2=A0 option %u: %s", i,
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 c->dhcp_opts[i].str);
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0}
>=C2=A0
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0for (i =3D 0; i = < ARRAY_SIZE(c->ip4.dns); i++) {
> @@ -1150,6 +1156,25 @@ static void conf_sock_listen(const struct ctx *= c)
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0die_perror("= ;Couldn't add configuration socket to epoll");
>=C2=A0 }
>=C2=A0
> +/**
> + * conf_dhcp_option() - Set value for a DHCP option in configuration<= br> > + * @c:=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0Executio= n context
> + * @code:=C2=A0 =C2=A0 DHCP option code
> + * @val_str: Value string from command line
> + */
> +static void conf_dhcp_option(struct ctx *c, uint8_t code, const char = *val_str)
> +{
> +=C2=A0 =C2=A0 =C2=A0uint8_t tmp[255];
> +
> +=C2=A0 =C2=A0 =C2=A0if (dhcp_opt_parse(code, val_str, tmp, sizeof(tmp= )) < 0)
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0die("Invalid val= ue for DHCP option %u: %s", code, val_str);
> +
> +=C2=A0 =C2=A0 =C2=A0if (snprintf_check(c->dhcp_opts[code].str,
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0 =C2=A0 sizeof(c->dhcp_opts[0].str),
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0 =C2=A0 "%s", val_str))
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0die("DHCP option= value too long: %s", val_str);
> +}
> +
>=C2=A0 /**
>=C2=A0 =C2=A0* conf() - Process command-line arguments and set configur= ation
>=C2=A0 =C2=A0* @c:=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0Execution context
> @@ -1233,6 +1258,7 @@ void conf(struct ctx *c, int argc, char **argv)<= br> >=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0{"migrate-n= o-linger", no_argument,=C2=A0 =C2=A0 =C2=A0 NULL,=C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0 =C2=A030 },
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0{"stats&quo= t;, required_argument,=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 NULL,=C2=A0= =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A031 },
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0{"conf-path= ",=C2=A0 =C2=A0required_argument,=C2=A0 =C2=A0 =C2=A0 NULL,=C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0'c' },
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0{"dhcp-opt"= , required_argument,=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0NULL,=C2=A0 =C2=A0 = =C2=A0 =C2=A0 =C2=A0 =C2=A034 },
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0{ 0 },
>=C2=A0 =C2=A0 =C2=A0 =C2=A0};
>=C2=A0 =C2=A0 =C2=A0 =C2=A0const char *optstring =3D "+dqfel:hs:c:= F:I:p:P:m:a:n:M:g:i:o:D:S:H:461t:u:T:U:";
> @@ -1248,10 +1274,13 @@ void conf(struct ctx *c, int argc, char **argv= )
>=C2=A0 =C2=A0 =C2=A0 =C2=A0uint8_t prefix_len_from_opt =3D 0;
>=C2=A0 =C2=A0 =C2=A0 =C2=A0unsigned int ifi4 =3D 0, ifi6 =3D 0;
>=C2=A0 =C2=A0 =C2=A0 =C2=A0const char *logfile =3D NULL;
> +=C2=A0 =C2=A0 =C2=A0unsigned long optcode;
>=C2=A0 =C2=A0 =C2=A0 =C2=A0char *runas =3D NULL;
>=C2=A0 =C2=A0 =C2=A0 =C2=A0size_t logsize =3D 0;
> +=C2=A0 =C2=A0 =C2=A0const char *comma;
>=C2=A0 =C2=A0 =C2=A0 =C2=A0long fd_tap_opt;
>=C2=A0 =C2=A0 =C2=A0 =C2=A0int name, ret;
> +=C2=A0 =C2=A0 =C2=A0char *end;
>=C2=A0 =C2=A0 =C2=A0 =C2=A0uid_t uid;
>=C2=A0 =C2=A0 =C2=A0 =C2=A0gid_t gid;
>=C2=A0
> @@ -1467,6 +1496,20 @@ void conf(struct ctx *c, int argc, char **argv)=
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0die("Can't display statis= tics if not running in foreground");
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0c->stats =3D strtol(optarg, NULL, 0);
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0break;
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0case 34:
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0comma =3D strchr(optarg, ',');
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0if (!comma)
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0die("--dhcp-opt requires CODE,VALUE= format");
> +
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0errno =3D 0;
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0optcode =3D strtoul(optarg, &end, 0);
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0if (end !=3D comma || errno ||
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0 =C2=A0 =C2=A0optcode < 1 || optcode > 254)
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0die("DHCP option code must be 1-254= : %s",
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0optarg);
> +
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0conf_dhcp_option(c, optcode, comma + 1);
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0break;
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0case 'd'= :
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0c->debug =3D 1;
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0c->quiet =3D 0;
> diff --git a/dhcp.c b/dhcp.c
> index 78790d8..47bb524 100644
> --- a/dhcp.c
> +++ b/dhcp.c
> @@ -23,6 +23,7 @@
>=C2=A0 #include <unistd.h>
>=C2=A0 #include <string.h>
>=C2=A0 #include <limits.h>
> +#include <errno.h>
>=C2=A0
>=C2=A0 #include "util.h"
>=C2=A0 #include "ip.h"
> @@ -130,6 +131,189 @@ struct msg {
>=C2=A0 =C2=A0 =C2=A0 =C2=A0uint8_t o[OPT_MAX + 1 /* End option */ ]; >=C2=A0 } __attribute__((__packed__));
>=C2=A0
> +/**
> + * enum dhcp_opt_type - DHCP option value types per RFC 2132
> + * @DHCP_OPT_NONE:=C2=A0 =C2=A0Unsupported or unknown option
> + * @DHCP_OPT_STR:=C2=A0 =C2=A0 Variable-length string
> + * @DHCP_OPT_IPV4:=C2=A0 =C2=A0Single IPv4 address
> + * @DHCP_OPT_IPV4_LIST:=C2=A0 =C2=A0 =C2=A0 Multiple IPv4 addresses, = comma-separated
> + * @DHCP_OPT_INT8:=C2=A0 =C2=A0Unsigned 8-bit integer
> + * @DHCP_OPT_INT16:=C2=A0 Unsigned 16-bit integer
> + * @DHCP_OPT_INT32:=C2=A0 Unsigned 32-bit integer
> + * @DHCP_OPT_SINT32: Signed 32-bit integer

For consistency with C conventions, I'd suggset UINT{8,16,32} and just<= br> INT32 for the signed case.

> + */
> +enum dhcp_opt_type {
> +=C2=A0 =C2=A0 =C2=A0DHCP_OPT_NONE,
> +=C2=A0 =C2=A0 =C2=A0DHCP_OPT_STR,
> +=C2=A0 =C2=A0 =C2=A0DHCP_OPT_IPV4,
> +=C2=A0 =C2=A0 =C2=A0DHCP_OPT_IPV4_LIST,
> +=C2=A0 =C2=A0 =C2=A0DHCP_OPT_INT8,
> +=C2=A0 =C2=A0 =C2=A0DHCP_OPT_INT16,
> +=C2=A0 =C2=A0 =C2=A0DHCP_OPT_INT32,
> +=C2=A0 =C2=A0 =C2=A0DHCP_OPT_SINT32,
> +};
> +
> +/**
> + * dhcp_opt_types - Maps option code to RFC 2132 value type, indexed = by code
> + */
> +static const enum dhcp_opt_type dhcp_opt_types[] =3D {
> +=C2=A0 =C2=A0 =C2=A0[1]=C2=A0 =C2=A0=3D DHCP_OPT_IPV4,=C2=A0 =C2=A0 = =C2=A0 =C2=A0 =C2=A0 /* Subnet Mask */
> +=C2=A0 =C2=A0 =C2=A0[2]=C2=A0 =C2=A0=3D DHCP_OPT_SINT32,=C2=A0 =C2=A0= =C2=A0 =C2=A0 /* Time Offset */
> +=C2=A0 =C2=A0 =C2=A0[3]=C2=A0 =C2=A0=3D DHCP_OPT_IPV4_LIST,=C2=A0 =C2= =A0 =C2=A0/* Router */
> +=C2=A0 =C2=A0 =C2=A0[4]=C2=A0 =C2=A0=3D DHCP_OPT_IPV4_LIST,=C2=A0 =C2= =A0 =C2=A0/* Time Server */
> +=C2=A0 =C2=A0 =C2=A0[5]=C2=A0 =C2=A0=3D DHCP_OPT_IPV4_LIST,=C2=A0 =C2= =A0 =C2=A0/* Name Server */
> +=C2=A0 =C2=A0 =C2=A0[6]=C2=A0 =C2=A0=3D DHCP_OPT_IPV4_LIST,=C2=A0 =C2= =A0 =C2=A0/* Domain Name Server */
> +=C2=A0 =C2=A0 =C2=A0[7]=C2=A0 =C2=A0=3D DHCP_OPT_IPV4_LIST,=C2=A0 =C2= =A0 =C2=A0/* Log Server */
> +=C2=A0 =C2=A0 =C2=A0[8]=C2=A0 =C2=A0=3D DHCP_OPT_IPV4_LIST,=C2=A0 =C2= =A0 =C2=A0/* Cookie Server */
> +=C2=A0 =C2=A0 =C2=A0[9]=C2=A0 =C2=A0=3D DHCP_OPT_IPV4_LIST,=C2=A0 =C2= =A0 =C2=A0/* LPR Server */
> +=C2=A0 =C2=A0 =C2=A0[10]=C2=A0 =3D DHCP_OPT_IPV4_LIST,=C2=A0 =C2=A0 = =C2=A0/* Impress Server */
> +=C2=A0 =C2=A0 =C2=A0[11]=C2=A0 =3D DHCP_OPT_IPV4_LIST,=C2=A0 =C2=A0 = =C2=A0/* Resource Location Server */
> +=C2=A0 =C2=A0 =C2=A0[12]=C2=A0 =3D DHCP_OPT_STR,=C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0 =C2=A0/* Host Name */
> +=C2=A0 =C2=A0 =C2=A0[13]=C2=A0 =3D DHCP_OPT_INT16,=C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0/* Boot File Size */
> +=C2=A0 =C2=A0 =C2=A0[15]=C2=A0 =3D DHCP_OPT_STR,=C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0 =C2=A0/* Domain Name */
> +=C2=A0 =C2=A0 =C2=A0[16]=C2=A0 =3D DHCP_OPT_IPV4,=C2=A0 =C2=A0 =C2=A0= =C2=A0 =C2=A0 /* Swap Server */
> +=C2=A0 =C2=A0 =C2=A0[17]=C2=A0 =3D DHCP_OPT_STR,=C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0 =C2=A0/* Root Path */
> +=C2=A0 =C2=A0 =C2=A0[19]=C2=A0 =3D DHCP_OPT_INT8,=C2=A0 =C2=A0 =C2=A0= =C2=A0 =C2=A0 /* IP Forwarding */
> +=C2=A0 =C2=A0 =C2=A0[23]=C2=A0 =3D DHCP_OPT_INT8,=C2=A0 =C2=A0 =C2=A0= =C2=A0 =C2=A0 /* Default IP TTL */
> +=C2=A0 =C2=A0 =C2=A0[26]=C2=A0 =3D DHCP_OPT_INT16,=C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0/* Interface MTU */
> +=C2=A0 =C2=A0 =C2=A0[28]=C2=A0 =3D DHCP_OPT_IPV4,=C2=A0 =C2=A0 =C2=A0= =C2=A0 =C2=A0 /* Broadcast Address */
> +=C2=A0 =C2=A0 =C2=A0[37]=C2=A0 =3D DHCP_OPT_INT8,=C2=A0 =C2=A0 =C2=A0= =C2=A0 =C2=A0 /* TCP Default TTL */
> +=C2=A0 =C2=A0 =C2=A0[38]=C2=A0 =3D DHCP_OPT_INT32,=C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0/* TCP Keepalive Interval */
> +=C2=A0 =C2=A0 =C2=A0[40]=C2=A0 =3D DHCP_OPT_STR,=C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0 =C2=A0/* NIS Domain Name */
> +=C2=A0 =C2=A0 =C2=A0[41]=C2=A0 =3D DHCP_OPT_IPV4_LIST,=C2=A0 =C2=A0 = =C2=A0/* NIS Servers */
> +=C2=A0 =C2=A0 =C2=A0[42]=C2=A0 =3D DHCP_OPT_IPV4_LIST,=C2=A0 =C2=A0 = =C2=A0/* NTP Servers */
> +=C2=A0 =C2=A0 =C2=A0[44]=C2=A0 =3D DHCP_OPT_IPV4_LIST,=C2=A0 =C2=A0 = =C2=A0/* NetBIOS Name Server */
> +=C2=A0 =C2=A0 =C2=A0[50]=C2=A0 =3D DHCP_OPT_IPV4,=C2=A0 =C2=A0 =C2=A0= =C2=A0 =C2=A0 /* Requested IP Address */
> +=C2=A0 =C2=A0 =C2=A0[51]=C2=A0 =3D DHCP_OPT_INT32,=C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0/* IP Address Lease Time */
> +=C2=A0 =C2=A0 =C2=A0[53]=C2=A0 =3D DHCP_OPT_INT8,=C2=A0 =C2=A0 =C2=A0= =C2=A0 =C2=A0 /* DHCP Message Type */
> +=C2=A0 =C2=A0 =C2=A0[54]=C2=A0 =3D DHCP_OPT_IPV4,=C2=A0 =C2=A0 =C2=A0= =C2=A0 =C2=A0 /* Server Identifier */
> +=C2=A0 =C2=A0 =C2=A0[57]=C2=A0 =3D DHCP_OPT_INT16,=C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0/* Max DHCP Message Size */
> +=C2=A0 =C2=A0 =C2=A0[58]=C2=A0 =3D DHCP_OPT_INT32,=C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0/* Renewal (T1) Time */
> +=C2=A0 =C2=A0 =C2=A0[59]=C2=A0 =3D DHCP_OPT_INT32,=C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0/* Rebinding (T2) Time */
> +=C2=A0 =C2=A0 =C2=A0[60]=C2=A0 =3D DHCP_OPT_STR,=C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0 =C2=A0/* Vendor Class Identifier */
> +=C2=A0 =C2=A0 =C2=A0[66]=C2=A0 =3D DHCP_OPT_STR,=C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0 =C2=A0/* TFTP Server Name */
> +=C2=A0 =C2=A0 =C2=A0[67]=C2=A0 =3D DHCP_OPT_STR,=C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0 =C2=A0/* Bootfile Name */
> +=C2=A0 =C2=A0 =C2=A0[252] =3D DHCP_OPT_STR,=C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0/* WPAD URL */
> +};
> +
> +/**
> + * dhcp_opt_parse() - Parse a DHCP option value
> + * @code:=C2=A0 =C2=A0 DHCP option code
> + * @str:=C2=A0 =C2=A0 =C2=A0Value string from command line
> + * @buf:=C2=A0 =C2=A0 =C2=A0Output buffer for binary value
> + * @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,
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 uint8_t *buf,= size_t buf_len)
> +{
> +=C2=A0 =C2=A0 =C2=A0enum dhcp_opt_type type;
> +=C2=A0 =C2=A0 =C2=A0unsigned long val;
> +=C2=A0 =C2=A0 =C2=A0unsigned int i;
> +=C2=A0 =C2=A0 =C2=A0uint8_t width;
> +=C2=A0 =C2=A0 =C2=A0size_t slen;
> +=C2=A0 =C2=A0 =C2=A0char *end;
> +=C2=A0 =C2=A0 =C2=A0int len;
> +
> +=C2=A0 =C2=A0 =C2=A0if (code >=3D ARRAY_SIZE(dhcp_opt_types))
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0return -1;
> +
> +=C2=A0 =C2=A0 =C2=A0type =3D dhcp_opt_types[code];
> +
> +=C2=A0 =C2=A0 =C2=A0if (!*str)
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0return -1;
> +
> +=C2=A0 =C2=A0 =C2=A0switch (type) {
> +=C2=A0 =C2=A0 =C2=A0case DHCP_OPT_NONE:
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0return -1;
> +=C2=A0 =C2=A0 =C2=A0case DHCP_OPT_IPV4:
> +=C2=A0 =C2=A0 =C2=A0case DHCP_OPT_IPV4_LIST:
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0len =3D 0;
> +
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0while (*str) {
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0char ipbuf[INET_ADDRSTRLEN];
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0size_t chunk;
> +
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0chunk =3D strcspn(str, ",");
> +
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0if (!chunk || chunk >=3D sizeof(ipbuf))
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0return -1;
> +
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0memcpy(ipbuf, str, chunk);
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0ipbuf[chunk] =3D '\0';
> +
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0if (len + (int)sizeof(struct in_addr) > (int)buf_len)

Both sides are necessarily non-negative, so it would make more sense
to make len unsigned than to cast the other things to signed.

> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0return -1;
> +
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0if (inet_pton(AF_INET, ipbuf, buf + len) !=3D 1)
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0return -1;
> +
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0len +=3D sizeof(struct in_addr);
> +
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0if (type =3D=3D DHCP_OPT_IPV4) {
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0if (str[chunk] =3D=3D ',')
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0return -1; > +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0break;
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0}
> +
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0str +=3D chunk + (str[chunk] =3D=3D ',');
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0}
> +
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0if (!len)
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0return -1;
> +
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0return len;
> +=C2=A0 =C2=A0 =C2=A0case DHCP_OPT_INT8:
> +=C2=A0 =C2=A0 =C2=A0case DHCP_OPT_INT16:
> +=C2=A0 =C2=A0 =C2=A0case DHCP_OPT_INT32:
> +=C2=A0 =C2=A0 =C2=A0case DHCP_OPT_SINT32:
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0if (type =3D=3D DHCP_= OPT_INT8)
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0width =3D 1;
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0else if (type =3D=3D = DHCP_OPT_INT16)
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0width =3D 2;
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0else
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0width =3D 4;
> +
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0if (buf_len < widt= h)
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0return -1;
> +
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0errno =3D 0;
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0if (type =3D=3D DHCP_= OPT_SINT32) {
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0long sval;
> +
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0sval =3D strtol(str, &end, 0);
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0if (*end || errno ||
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0 =C2=A0 =C2=A0sval < INT32_MIN || sval > INT32_MAX)
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0return -1;
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0val =3D (uint32_t)sval;
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0} else {
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0val =3D strtoul(str, &end, 0);
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0if (*end || errno ||
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0 =C2=A0 =C2=A0val >=3D (1ULL << (width * 8)))
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0return -1;
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0}
> +
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0for (i =3D width; i &= gt; 0; i--) {
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0buf[i - 1] =3D val & 0xff;
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0val >>=3D 8;
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0}
> +
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0return width;
> +=C2=A0 =C2=A0 =C2=A0case DHCP_OPT_STR:
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0slen =3D strlen(str);=
> +
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0if (slen >=3D buf_= len)
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0return -1;
> +
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0memcpy(buf, str, slen= );
> +
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0return slen;
> +=C2=A0 =C2=A0 =C2=A0}
> +
> +=C2=A0 =C2=A0 =C2=A0return -1;
> +}
> +
>=C2=A0 /**
>=C2=A0 =C2=A0* fill_one() - Fill a single option into a buffer
>=C2=A0 =C2=A0* @buf:=C2=A0 =C2=A0 =C2=A0Buffer to write option
> @@ -541,6 +725,13 @@ int dhcp(const struct ctx *c, struct iov_tail *da= ta)
>=C2=A0 =C2=A0 =C2=A0 =C2=A0if (!c->no_dhcp_dns_search)
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0opt_set_dns_sear= ch(c, OPT_MAX - 3);
>=C2=A0
> +=C2=A0 =C2=A0 =C2=A0for (i =3D 1; i < 255; i++) {
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0if (!c->dhcp_opts[= i].str[0])
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0continue;
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0opts[i].slen =3D dhcp= _opt_parse(i, c->dhcp_opts[i].str,
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0opts[i].s, sizeof(opts[i].s));
> +=C2=A0 =C2=A0 =C2=A0}
> +
>=C2=A0 =C2=A0 =C2=A0 =C2=A0/* RFC 2132, Section 9.5: put boot file name= in the 'file' header
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 * field.=C2=A0 Suppress option 67 from the = options area and reserve
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 * the file field from overload.
> diff --git a/dhcp.h b/dhcp.h
> index cd50c99..cc8d5dd 100644
> --- a/dhcp.h
> +++ b/dhcp.h
> @@ -8,5 +8,7 @@
>=C2=A0
>=C2=A0 int dhcp(const struct ctx *c, struct iov_tail *data);
>=C2=A0 void dhcp_init(void);
> +int dhcp_opt_parse(uint8_t code, const char *str,
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 uint8_t *buf,= size_t buf_len);
>=C2=A0
>=C2=A0 #endif /* DHCP_H */
> diff --git a/passt.1 b/passt.1
> index 908fd4a..ccdcbb2 100644
> --- a/passt.1
> +++ b/passt.1
> @@ -430,6 +430,48 @@ Send \fIname\fR as DHCP option 12 (hostname).
>=C2=A0 FQDN to configure the client with.
>=C2=A0 Send \fIname\fR as Client FQDN: DHCP option 81 and DHCPv6 option= 39.
>=C2=A0
> +.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 co= de.
> +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
> +=C2=A0 \-\-dhcp-opt 6,8.8.8.8,4.4.4.4
> +=C2=A0 \-\-dhcp-opt 12,myhostname
> +.fi
> +.PP
> +Only the following option codes are supported (unsupported codes caus= e an error):
> +.RS
> +.TP
> +.B IPv4 address options
> +1 (Subnet Mask), 16 (Swap Server), 28 (Broadcast Address), 50 (Reques= ted 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 Forwardi= ng, 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
> +
>=C2=A0 .TP
>=C2=A0 .BR \-t ", " \-\-tcp-ports " " \fIspec
>=C2=A0 Configure TCP port forwarding to guest or namespace. \fIspec\fR = can be one of:
> diff --git a/passt.h b/passt.h
> index 3a07294..15e2d83 100644
> --- a/passt.h
> +++ b/passt.h
> @@ -182,6 +182,8 @@ struct ip6_ctx {
>=C2=A0 =C2=A0* @dns_search:=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0 DNS search list
>=C2=A0 =C2=A0* @hostname:=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 Guest hostname
>=C2=A0 =C2=A0* @fqdn:=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 Guest FQ= DN
> + * @dhcp_opts:=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= User-specified DHCP options from --dhcp-opt
> + * @dhcp_opts.str:=C2=A0 =C2=A0String value from command line
>=C2=A0 =C2=A0* @ifi6:=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 Template= interface for IPv6, -1: none, 0: IPv6 disabled
>=C2=A0 =C2=A0* @ip6:=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0IPv= 6 configuration
>=C2=A0 =C2=A0* @pasta_ifn:=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0Name of namespace interface for pasta
> @@ -264,6 +266,10 @@ struct ctx {
>=C2=A0 =C2=A0 =C2=A0 =C2=A0char hostname[PASST_MAXDNAME];
>=C2=A0 =C2=A0 =C2=A0 =C2=A0char fqdn[PASST_MAXDNAME];
>=C2=A0
> +=C2=A0 =C2=A0 =C2=A0struct {
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0char str[255];
> +=C2=A0 =C2=A0 =C2=A0} dhcp_opts[256];
> +
>=C2=A0 =C2=A0 =C2=A0 =C2=A0int ifi6;
>=C2=A0 =C2=A0 =C2=A0 =C2=A0struct ip6_ctx ip6;
>=C2=A0
> --
> 2.54.0
>

--
David Gibson (he or they)=C2=A0 =C2=A0 =C2=A0 =C2=A0| I'll have my musi= c baroque, and my code
david AT gibson.dropbear.id.au=C2=A0 | minimalist, thank you, not th= e other way
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 | around.
http://www.ozlabs.org/~dgibson
--0000000000005f94c40654964029--