From mboxrd@z Thu Jan 1 00:00:00 1970 Authentication-Results: passt.top; dmarc=fail (p=quarantine dis=none) header.from=redhat.com Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by passt.top (Postfix) with ESMTPS id 071B85A026E for ; Sun, 25 Oct 2026 13:01:40 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1792929699; 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: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=TXJAoR2bfYfySVizPET4OK/FvRPDq/rHv4dtVfqyeFo=; b=XGnHdqkvQTj9EJZYQDsRVrncvvkyf+rcKHNgccElETbQIrzSkZt2W6D3bDU8spDDYDv43p iMCvrBwMhQsp08De5kAkk8OaPRlNIVFZMvGWkMdKXxuLvq9iHNMMkNvwK5U2ENOzppOejk 3Wzut412SXMqg0kCAt4rsw39Z0crbgA= Received: from mail-ej1-f69.google.com (mail-ej1-f69.google.com [209.85.218.69]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-484-qVUS7bOGM2eMWoSbqrBcRA-1; Sun, 25 Oct 2026 08:01:38 -0400 X-MC-Unique: qVUS7bOGM2eMWoSbqrBcRA-1 X-Mimecast-MFC-AGG-ID: qVUS7bOGM2eMWoSbqrBcRA_1792929697 Received: by mail-ej1-f69.google.com with SMTP id a640c23a62f3a-c429d9d05f0so212324566b.1 for ; Sun, 25 Oct 2026 05:01:38 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1792929697; x=1793534497; h=date:content-transfer-encoding:content-type:mime-version :organization:references:in-reply-to:message-id:subject:cc:to:from :x-gm-gg:x-gm-message-state:from:to:cc:subject:date:message-id :reply-to:content-type; bh=v5BLT9BRTZJshGfCRxl4Eg3GgnF6z/IXLZQnDoFl+aY=; b=lHHBZ834Akuvqd5Jk+iU8xJRhCAJ7ZKdmq20wkk8sI7Ooj4ks8eJs4OxaTzpqOw49D /DfnCiVnXGiOWuViqIkw+9Nc3IQRlP5iuTE5RO31mlemI4f/QLNsCf1Z31aicaKDYoZn VUCacWlrRD/E0v/2UHqgZAgwaH5d9yzTMUN3mz8bJ7jQ1D3QFKJreagQ3IGaOvaMSgg1 IO7lFJZXW9EXTuNuwKf59xDIRqNcbIBnBYDfD4lZ2G5T7uw8+09eZILXRBkDQ3oWuAW/ crS/cH/Rtqx6rUKlgvLWWOlg1ONgGoiVm8BXKp9zZkRsMyodt9lmpdG2K6dp4hjLeVJb rD2Q== X-Gm-Message-State: AFq9FYL8vprDKLka5DqStKgMkzslZUq4DNHKyH7CTMChK7OX0CsV2/+8 PereIPOFdi6XEcqbAsQyEo1c0zN8UICprQWGImpnFTUjsyDM2y8pEYA6J8pADSPkrLKs6/U1aY1 jmSjfXR1WgyHFDTE+4NJ597eLtwQuHUQ5E+obi2bosFXJdV9TNqFttH9OcWMcWe2EWz2svHVKFu bLyvMLshCPLLuIqB7NiFty7Mkwr2moRVH9WKsm X-Gm-Gg: AYBFou1BENzrW8h4c3aDluZBeaEUgEELHJ91KEyEheTeo4dw3i2PH4os63q/SDwgPln Cg57/InbUNIS5Sg4N2Uv8W3uzaj6uGAUNvGgITURCSM2JWHyqHsBDzfL3+KNyjpt2GIzEm5FjtA 5GfoJMSjHbZfv8I1wm86+ENMFYRsE+Z7eGpsJVht0PZkpZjEAmBqjRyVHBQt7SNfGHZv/fOzjUW /2nJPiaKluRjwUgD0vckKFjmbuBxtjq4oXkumQVgsfiFdS2Hlslm/fR0TcM4tMGmNvbbelhMr5d yd1dRgSkmLqxqEMcV6Mb4YREAdKLb3u3kK8VCeG49N4fxUXkpFhX3H0ne64reh51M/OJPYeYqN3 3Qwp13xfuKQ== X-Received: by 2002:a05:6402:c14b:b0:6b4:2d6:cbea with SMTP id 4fb4d7f45d1cf-6b442fcab59mr3290475a12.9.1792929696635; Sun, 25 Oct 2026 05:01:36 -0700 (PDT) X-Received: by 2002:a05:6402:c14b:b0:6b4:2d6:cbea with SMTP id 4fb4d7f45d1cf-6b442fcab59mr3290433a12.9.1792929695914; Sun, 25 Oct 2026 05:01:35 -0700 (PDT) Received: from maya.myfinge.rs (ifcgrfdd.trafficplex.cloud. [2a10:fc81:a806:d6a9::1]) by smtp.gmail.com with ESMTPSA id 4fb4d7f45d1cf-6b45f5093bfsm843870a12.15.2026.10.25.05.01.34 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 25 Oct 2026 05:01:34 -0700 (PDT) From: Stefano Brivio To: Anshu Kumari Subject: Re: [PATCH v4 1/2] dhcpv6: Add --dhcpv6-opt with option type table and value parser Message-ID: <20261025130132.40f2eca4@elisabeth> In-Reply-To: <20261006090454.1474409-2-anskuma@redhat.com> References: <20261006090454.1474409-1-anskuma@redhat.com> <20261006090454.1474409-2-anskuma@redhat.com> Organization: Red Hat X-Mailer: Claws Mail 4.2.0 (GTK 3.24.49; x86_64-pc-linux-gnu) MIME-Version: 1.0 Date: Sun, 25 Oct 2026 13:01:33 +0100 (CET) X-Mimecast-Spam-Score: 0 X-Mimecast-MFC-PROC-ID: IXAEV0MNV7U45_RGYdrXKkwYK_VDbzVGWZO3Lxj4fNs_1792929697 X-Mimecast-Originator: redhat.com Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: quoted-printable Message-ID-Hash: 7FZFWNSNGCYOVJTGOQEPUNADR6V3BKKR X-Message-ID-Hash: 7FZFWNSNGCYOVJTGOQEPUNADR6V3BKKR X-MailFrom: sbrivio@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, 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: On Tue, 6 Oct 2026 14:34:52 +0530 Anshu Kumari wrote: > Introduce the --dhcpv6-opt flag that allows setting arbitrary DHCPv6 > options from command-line in the form [--dhcpv6-opt CODE,VALUE]. >=20 > Add a type lookup table mapping option codes to value types (IPv6, > IPv6 list, integer, string, vendor class, length-prefixed string > list) and dhcpv6_opt_parse() to convert CLI strings to binary wire > format. If the same option code is given more than once, the > last value wins. >=20 > Link: https://bugs.passt.top/show_bug.cgi?id=3D192 > Signed-off-by: Anshu Kumari > --- > v4: > - The dhcpv6_opt_code < 1 check was replaced with proper validation: > dhcpv6_opt_code =3D=3D 0 || dhcpv6_opt_code > UINT16_MAX in conf.c > - All uint16_t/uint32_t writes to byte buffers now use intermediate > variables + memcpy() instead of direct pointer casts. > - The DHCPV6_OPT_STR case now uses slen > buf_len instead of slen >=3D = buf_len. > - Option 17 (Vendor-specific Info) excluded. > - A separate char optbuf[512] was added in conf_print() instead of > reusing the small INANY_ADDRSTRLEN-sized buf for option rendering. > - inet_ntop() return value checked. > - Uses off +=3D strlen(buf + off) instead of off =3D strlen(buf) in > the IPv6 list rendering loop. > - Iterator macro added =E2=80=94 for_each_in6_off(off, len). > - The addr and af variables are declared at function scope > in dhcpv6_opt_parse() rather than inside block wrappers. > - snprintf() alignment and line formatting. >=20 > v3: > - Use parse_unsigned(), parse_literal(), parse_eoi() from parse.c > instead of manual strtoul()/errno/strchr() in dhcpv6_opt_parse() > for UINT and VENDOR_CLASS cases. > - Store options as pre-parsed binary in a static array indexed by > option code, instead of storing raw strings. > - Add dhcpv6_opt_to_str() to render binary option values back to > printable strings for startup logging in conf_print(). >=20 > v2: > - Renamed custom_v6opts to dhcpv6_opts, MAX_CUSTOM_DHCPV6_OPTS > to MAX_DHCPV6_OPTS. > - Dropped val/len from ctx struct. > - Moved dhcpv6_add_option() to conf.c as static > conf_dhcpv6_option(). > - Made dhcpv6_opt_parse() non-static, declared in dhcpv6.h > - Omitted explicit [256] from dhcpv6_opt_types[]. > - Moved chunk declaration into while block. > - Removed redundant !slen check in DHCPV6_OPT_STR case. > - All errors in dhcpv6_opt_parse() return -1, removed die() > calls. > --- > conf.c | 26 ++++- > dhcpv6.c | 339 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ > dhcpv6.h | 2 + > passt.1 | 31 +++++ > 4 files changed, 397 insertions(+), 1 deletion(-) Note: this will have a trivial conflict with --no-pidns in conf(), will need a rebase. > diff --git a/conf.c b/conf.c > index faf26814..dba62bf8 100644 > --- a/conf.c > +++ b/conf.c > @@ -47,6 +47,7 @@ > #include "lineread.h" > #include "isolation.h" > #include "log.h" > +#include "dhcpv6.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 stat= us) > =09=09" -S, --search LIST=09Space-separated list, search domains\n" > =09=09" a single, empty option disables the DNS search list\n" > =09=09" -H, --hostname NAME =09Hostname to configure client with\n" > -=09=09" --fqdn NAME=09=09FQDN to configure client with\n"); > +=09=09" --fqdn NAME=09=09FQDN to configure client with\n" > +=09=09" --dhcpv6-opt CODE,VAL=09Set DHCPv6 option CODE to VAL\n"); > =09if (strstr(name, "pasta")) > =09=09FPRINTF(f, " default: don't use any search list\n"); > =09else > @@ -800,6 +802,7 @@ enum passt_modes conf_mode(int argc, char *argv[]) > static void conf_print(const struct ctx *c) > { > =09char buf[INANY_ADDRSTRLEN]; > +=09char optbuf[512]; We could actually pack about 4k IPv6 addresses in a DHCPv6 option, and they need up to 46 bytes (IPv4-mapped address, with separator) to be represented, but having ~ 190 KiB on the stack for that isn't great. This doesn't make any practical sense for the moment by the way, as all we can encode as list of addresses is SNTP servers (and we won't have 12 of them), but maybe in the future we might want to support a larger number of addresses for some other option. So you could do this: use a reasonable multiple of INANY_ADDRSTRLEN here (say, INANY_ADDRSTRLEN * 2, note that it already includes a terminator which you can use as separator), and then: > =09int i; > =20 > =09if (c->fd_control_listen >=3D 0) > @@ -900,6 +903,10 @@ static void conf_print(const struct ctx *c) > =09=09info(" our link-local: %s", > =09=09 inet_ntop(AF_INET6, &c->ip6.our_tap_ll, > =09=09=09 buf, sizeof(buf))); > +=09=09for (i =3D 1; i < UINT16_MAX; i++) { > +=09=09=09if (dhcpv6_opt_to_str(i, optbuf, sizeof(optbuf))) ...pass an offset, starting from 0, opaque to the caller, and turn this into a loop, say: =09=09=09while (dhcpv6_opt_to_str(i, buf, sizeof(buf)), &off) and dhcpv6_opt_to_str() will use that offset to go over addresses (see comments there). With that, you would print several "option" lines in case you exceed the buffer, but it's actually better than the alternative, I think. By limiting the buffer to 2 addresses, the output could be even easier to read. > +=09=09=09=09info(" option %u: %s", i, optbuf); > +=09=09} > =20 > dns6: > =09=09for (i =3D 0; i < ARRAY_SIZE(c->ip6.dns); i++) { > @@ -1348,6 +1355,7 @@ void conf(struct ctx *c, int argc, char **argv) > =09=09{"stats", required_argument,=09=09NULL,=09=0931 }, > =09=09{"conf-path",=09required_argument,=09NULL,=09=09'c' }, > =09=09{"chroot-fallback", no_argument,=09NULL, =09=0932 }, > +=09=09{"dhcpv6-opt", required_argument,=09NULL,=09=0935 }, At some point it wasn't done consistently anymore, but I think it would be nice to not break this further (and maybe make this all consistent at some point): the 'has_arg' fields were aligned with tabs where possible, that is: =09=09{"dhcpv6-opt",=09required_argument,=09NULL,=09=0935 }, instead. > =09=09{ 0 }, > =09}; > =09const char *optstring =3D "+dqfel:hs:c:F:I:p:P:m:a:n:M:g:i:o:D:S:H:46= 1t:u:T:U:"; > @@ -1589,6 +1597,22 @@ void conf(struct ctx *c, int argc, char **argv) > =09=09case 32: > =09=09=09c->chroot_fallback =3D true; > =09=09=09break; > +=09=09case 35: { Same comment as for the DHCP patches about extra / artificial code blocks. > +=09=09=09unsigned long dhcpv6_opt_code; > + > +=09=09=09p =3D optarg; > + > +=09=09=09if (!parse_unsigned(&p, 0, &dhcpv6_opt_code) || > +=09=09=09 !parse_literal(&p, ",")) > +=09=09=09=09die("--dhcpv6-opt requires CODE,VALUE format"); > + > +=09=09=09if (dhcpv6_opt_code =3D=3D 0 || > +=09=09=09 dhcpv6_opt_code > UINT16_MAX) > +=09=09=09=09die("Invalid DHCPv6 option code: %s", optarg); > + > +=09=09=09dhcpv6_set_opt(dhcpv6_opt_code, p); > +=09=09=09break; > +=09=09} > =09=09case 'd': > =09=09=09c->debug =3D 1; > =09=09=09c->quiet =3D 0; > diff --git a/dhcpv6.c b/dhcpv6.c > index 29c7e320..abc45813 100644 > --- a/dhcpv6.c > +++ b/dhcpv6.c > @@ -31,6 +31,8 @@ > #include "passt.h" > #include "tap.h" > #include "log.h" > +#include "inany.h" > +#include "parse.h" > =20 > /** > * struct opt_hdr - DHCPv6 option header > @@ -63,6 +65,11 @@ struct opt_hdr { > #define OPT_MAX_SIZE=09=09IPV6_MIN_MTU - (sizeof(struct ipv6hdr) + \ > =09=09=09=09=09=09UDP_MSG_HDR_SIZE) > =20 > +#define for_each_in6_off(off, len)=09=09=09=09=09\ > +=09for ((off) =3D 0;=09=09=09=09=09=09=09\ > +=09 (off) + sizeof(struct in6_addr) <=3D (unsigned int)(len);=09\ > +=09 (off) +=3D sizeof(struct in6_addr)) > + > /** > * struct opt_client_id - DHCPv6 Client Identifier option > * @hdr:=09=09Option header > @@ -278,6 +285,338 @@ static struct resp_not_on_link_t { > =09{ 0, }, > }; > =20 > +/** > + * enum dhcpv6_opt_type - DHCPv6 option value types > + * @DHCPV6_OPT_NONE:=09=09Unsupported or unknown option > + * @DHCPV6_OPT_STR:=09=09Variable-length string > + * @DHCPV6_OPT_IPV6:=09=09Single IPv6 address > + * @DHCPV6_OPT_IPV6_LIST:=09Multiple IPv6 addresses, comma-separated > + * @DHCPV6_OPT_UINT8:=09=09Unsigned 8-bit integer > + * @DHCPV6_OPT_UINT16:=09=09Unsigned 16-bit integer > + * @DHCPV6_OPT_UINT32:=09=09Unsigned 32-bit integer > + * @DHCPV6_OPT_VENDOR_CLASS:=09Enterprise number + length-prefixed data > + * @DHCPV6_OPT_LEN_STR_LIST:=09Length-prefixed string list > + */ > +enum dhcpv6_opt_type { > +=09DHCPV6_OPT_NONE, > +=09DHCPV6_OPT_STR, > +=09DHCPV6_OPT_IPV6, > +=09DHCPV6_OPT_IPV6_LIST, > +=09DHCPV6_OPT_UINT8, > +=09DHCPV6_OPT_UINT16, > +=09DHCPV6_OPT_UINT32, > +=09DHCPV6_OPT_VENDOR_CLASS, > +=09DHCPV6_OPT_LEN_STR_LIST, > +}; > + > +/** > + * dhcpv6_opt_types - Maps DHCPv6 option code to value type, indexed by = code It's an enum, so this should say " * enum ...". And "map" (imperative) instead of "maps" (third-person indicative) as it's the convention we use in all code comments. About the name: I was about to suggest dhcpv6_opt_type, because the fact it's an array makes it plural ('s' -> '[]'), but it conflicts with the enum describing types. What about calling this dhcpv6_opt in a more general sense? Say: * enum dhcpv6_opt - Description of DHCPv6 options (value type), indexed by= code ? > + * RFC 8415 Options: 7, 15, 16, 32, 82, 83 I was mentioning it here a while ago: https://archives.passt.top/passt-dev/20260620000930.62f30fee@elisabeth/ ...RFC 8415 was obsoleted by RFC 9915. Option codes remain the same. > + * RFC 5970 Options: 59, 60 > + * RFC 4075 Options: 31 > + */ > +static const enum dhcpv6_opt_type dhcpv6_opt_types[] =3D { > +=09[7] =3D DHCPV6_OPT_UINT8,=09=09/* Preference */ > +=09[15] =3D DHCPV6_OPT_LEN_STR_LIST,=09/* User Class */ > +=09[16] =3D DHCPV6_OPT_VENDOR_CLASS,=09/* Vendor Class */ > +=09[31] =3D DHCPV6_OPT_IPV6_LIST,=09=09/* SNTP Servers */ > +=09[32] =3D DHCPV6_OPT_UINT32,=09=09/* Information Refresh Time */ > +=09[59] =3D DHCPV6_OPT_STR,=09=09=09/* Boot File URL */ > +=09[60] =3D DHCPV6_OPT_LEN_STR_LIST,=09/* Boot File Params */ > +=09[82] =3D DHCPV6_OPT_UINT32,=09=09/* SOL_MAX_RT */ > +=09[83] =3D DHCPV6_OPT_UINT32,=09=09/* INF_MAX_RT */ > +}; > + > +/** > + * dhcpv6_opt_parse() - Parse a DHCPv6 option value string into binary > + * @code:=09DHCPv6 option code > + * @str:=09Value string from command line > + * @buf:=09Output buffer for binary value > + * @buf_len:=09Size of output buffer > + * > + * Return: number of bytes written to @buf, or -1 on error > + */ > +static int dhcpv6_opt_parse(uint16_t code, const char *str, > +=09=09=09 uint8_t *buf, size_t buf_len) > +{ > +=09enum dhcpv6_opt_type type; > +=09union inany_addr addr; > +=09unsigned long val; > +=09uint16_t slen_net; > +=09sa_family_t af; > +=09uint8_t width; > +=09uint32_t ent; > +=09size_t slen; > +=09int len; > + > +=09if (!*str) > +=09=09return -1; > + > +=09if (code >=3D ARRAY_SIZE(dhcpv6_opt_types)) > +=09=09return -1; > + > +=09type =3D dhcpv6_opt_types[code]; > + > +=09switch (type) { > +=09case DHCPV6_OPT_NONE: > +=09=09return -1; > +=09case DHCPV6_OPT_IPV6: > +=09case DHCPV6_OPT_IPV6_LIST: > +=09=09len =3D 0; > + > +=09=09do { > +=09=09=09if (len + sizeof(struct in6_addr) > buf_len) > +=09=09=09=09return -1; > + > +=09=09=09if (!parse_inany_(&str, &addr, &af) || af !=3D AF_INET6) > +=09=09=09=09return -1; > + > +=09=09=09memcpy(buf + len, &addr.a6, sizeof(struct in6_addr)); > +=09=09=09len +=3D sizeof(struct in6_addr); > + > +=09=09=09if (type =3D=3D DHCPV6_OPT_IPV6) > +=09=09=09=09break; > +=09=09} while (parse_literal(&str, ",")); > + > +=09=09if (!len || !parse_eoi(str)) > +=09=09=09return -1; > + > +=09=09return len; > +=09case DHCPV6_OPT_UINT8: > +=09case DHCPV6_OPT_UINT16: > +=09case DHCPV6_OPT_UINT32: > +=09=09if (type =3D=3D DHCPV6_OPT_UINT8) > +=09=09=09width =3D 1; > +=09=09else if (type =3D=3D DHCPV6_OPT_UINT16) > +=09=09=09width =3D 2; > +=09=09else > +=09=09=09width =3D 4; > + > +=09=09if (buf_len < width) > +=09=09=09return -1; > + > +=09=09if (!parse_unsigned(&str, 0, &val) || !parse_eoi(str) || > +=09=09 val >=3D (1ULL << (width * 8))) > +=09=09=09return -1; > + > +=09=09if (type =3D=3D DHCPV6_OPT_UINT16) { > +=09=09=09uint16_t v16 =3D htons(val); > +=09=09=09memcpy(buf, &v16, sizeof(v16)); > +=09=09} else if (type =3D=3D DHCPV6_OPT_UINT32) { > +=09=09=09uint32_t v32 =3D htonl(val); > +=09=09=09memcpy(buf, &v32, sizeof(v32)); > +=09=09} else { > +=09=09=09buf[0] =3D val; > +=09=09} > + > +=09=09return width; > +=09case DHCPV6_OPT_STR: > +=09=09slen =3D strlen(str); > + > +=09=09if (slen > buf_len) > +=09=09=09return -1; > + > +=09=09memcpy(buf, str, slen); > + > +=09=09return slen; > +=09case DHCPV6_OPT_VENDOR_CLASS: > +=09=09if (!parse_unsigned(&str, 0, &val) || > +=09=09 !parse_literal(&str, ":") || val > UINT32_MAX) > +=09=09=09return -1; > + > +=09=09slen =3D strlen(str); > +=09=09if (!slen) > +=09=09=09return -1; > + > +=09=09len =3D sizeof(uint32_t) + sizeof(uint16_t) + slen; > +=09=09if ((size_t)len > buf_len) > +=09=09=09return -1; > + > +=09=09ent =3D htonl(val); > +=09=09memcpy(buf, &ent, sizeof(ent)); > + > +=09=09slen_net =3D htons(slen); > +=09=09memcpy(buf + sizeof(uint32_t), &slen_net, sizeof(slen_net)); > + > +=09=09memcpy(buf + sizeof(uint32_t) + sizeof(uint16_t), str, slen); > + > +=09=09return len; > +=09case DHCPV6_OPT_LEN_STR_LIST: > +=09=09len =3D 0; > + > +=09=09while (*str) { > +=09=09=09slen =3D strcspn(str, ","); > +=09=09=09if (!slen) > +=09=09=09=09return -1; > + > +=09=09=09if (len + (int)(sizeof(uint16_t) + slen) > (int)buf_len) > +=09=09=09=09return -1; > + > +=09=09=09slen_net =3D htons(slen); > +=09=09=09memcpy(buf + len, &slen_net, sizeof(slen_net)); > +=09=09=09len +=3D sizeof(uint16_t); > + > +=09=09=09memcpy(buf + len, str, slen); > +=09=09=09len +=3D slen; > + > +=09=09=09str +=3D slen; > +=09=09=09if (*str =3D=3D ',') > +=09=09=09=09str++; > +=09=09} > + > +=09=09if (!len) > +=09=09=09return -1; > + > +=09=09return len; > +=09} > + > +=09return -1; > +} > + > +/** > + * struct dhcpv6_user_opts - User-specified DHCPv6 options from --dhcpv6= -opt > + * @val:=09Binary option value in wire format > + * @len:=09Length of @val in bytes, 0 if not set > + */ > +static struct { > +=09uint8_t val[UINT16_MAX]; > +=09int len; > +} dhcpv6_user_opts[ARRAY_SIZE(dhcpv6_opt_types)]; > + > +/** > + * dhcpv6_opt_to_str() - Render a binary DHCPv6 option value to printabl= e string > + * @code:=09DHCPv6 option code > + * @buf:=09Output string buffer > + * @buf_len:=09Size of output buffer > + * > + * Return: pointer to @buf if option is set, NULL otherwise If you change this to accept an offset that can be used for DHCPV6_OPT_IPV6_LIST, you can either keep returning a pointer to @buf *if this function wrote something* (the comment would need to be updated), or return a number of bytes written, which is perhaps less surprising. Both would work though. > + */ > +const char *dhcpv6_opt_to_str(uint16_t code, char *buf, size_t buf_len) > +{ > +=09enum dhcpv6_opt_type type; > +=09unsigned int i; > +=09uint16_t slen; > +=09int off =3D 0; > + > +=09if (code >=3D ARRAY_SIZE(dhcpv6_user_opts) || > +=09 !dhcpv6_user_opts[code].len) This fits on one line now. > +=09=09return NULL; > + > +=09type =3D dhcpv6_opt_types[code]; > + > +=09switch (type) { > +=09case DHCPV6_OPT_IPV6: > +=09case DHCPV6_OPT_IPV6_LIST: > +=09=09for_each_in6_off(i, dhcpv6_user_opts[code].len) { ...to implement an opaque offset ("opaque" because the caller doesn't need to know what it is about, and just passes it as a pointer), this function can use that as initial value for 'i', and update it from 'i' before returning. You could call that parameter 'off', and do, before this loop: =09=09i =3D *off; and rename the current 'off' to 'buf_off' or similar. The rest would remain the same, I think. > +=09=09=09if (off) { > +=09=09=09=09if (off + 1 >=3D (int)buf_len) > +=09=09=09=09=09return NULL; > +=09=09=09=09buf[off++] =3D ','; > +=09=09=09} > +=09=09=09if (!inet_ntop(AF_INET6, > +=09=09=09=09 dhcpv6_user_opts[code].val + i, > +=09=09=09=09 buf + off, buf_len - off)) > +=09=09=09=09return NULL; > +=09=09=09off +=3D strlen(buf + off); > +=09=09} And before returning, you would update *off =3D i;. > +=09=09return buf; > +=09case DHCPV6_OPT_UINT8: > +=09case DHCPV6_OPT_UINT16: > +=09case DHCPV6_OPT_UINT32: { Usual comment about extra code blocks (I'd suggest to just add a function-wide 'uint32_t val' and also use it for 'ent' below). > +=09=09uint32_t val =3D 0; > + > +=09=09if (dhcpv6_user_opts[code].len =3D=3D 1) { > +=09=09=09val =3D dhcpv6_user_opts[code].val[0]; > +=09=09} else if (dhcpv6_user_opts[code].len =3D=3D 2) { > +=09=09=09uint16_t v16; > +=09=09=09memcpy(&v16, dhcpv6_user_opts[code].val, sizeof(v16)); > +=09=09=09val =3D ntohs(v16); > +=09=09} else if (dhcpv6_user_opts[code].len =3D=3D 4) { > +=09=09=09memcpy(&val, dhcpv6_user_opts[code].val, sizeof(val)); > +=09=09=09val =3D ntohl(val); > +=09=09} > + > +=09=09(void)snprintf(buf, buf_len, "%u", val); > +=09=09return buf; > +=09} > +=09case DHCPV6_OPT_STR: > +=09=09(void)snprintf(buf, buf_len, "%.*s", > +=09=09 dhcpv6_user_opts[code].len, > +=09=09 dhcpv6_user_opts[code].val); These two lines should be intended with three tabs plus spaces. > +=09=09return buf; > +=09case DHCPV6_OPT_VENDOR_CLASS: { > +=09=09uint32_t ent; > +=09=09int doff; ...and you could use 'off' for this one instead. It's also an offset. > + > +=09=09if (dhcpv6_user_opts[code].len < > +=09=09 (int)(sizeof(uint32_t) + sizeof(uint16_t))) > +=09=09=09return NULL; > + > +=09=09memcpy(&ent, dhcpv6_user_opts[code].val, sizeof(ent)); > +=09=09memcpy(&slen, dhcpv6_user_opts[code].val + sizeof(uint32_t), > +=09=09 sizeof(slen)); > +=09=09doff =3D sizeof(uint32_t) + sizeof(uint16_t); > + > +=09=09(void)snprintf(buf, buf_len, "%u:%.*s", > +=09=09 ntohl(ent), ntohs(slen), > +=09=09 dhcpv6_user_opts[code].val + doff); Same here, three tabs plus spaces. > +=09=09return buf; > +=09} > +=09case DHCPV6_OPT_LEN_STR_LIST: > +=09=09off =3D 0; > +=09=09i =3D 0; > + > +=09=09while (i + sizeof(uint16_t) <=3D > +=09=09 (unsigned int)dhcpv6_user_opts[code].len) { If you use a pointer to refer to dhcpv6_user_opts[code].len (unsigned int *opt_len maybe), this becomes more readable. > +=09=09=09memcpy(&slen, dhcpv6_user_opts[code].val + i, Same for .val, here... > +=09=09=09 sizeof(slen)); > +=09=09=09slen =3D ntohs(slen); > +=09=09=09i +=3D sizeof(uint16_t); > + > +=09=09=09if (i + slen > (unsigned int)dhcpv6_user_opts[code].len) (same here for .len) > +=09=09=09=09break; > + > +=09=09=09if (off) { > +=09=09=09=09if (off + 1 >=3D (int)buf_len) > +=09=09=09=09=09return NULL; > +=09=09=09=09buf[off++] =3D ','; > +=09=09=09} > +=09=09=09if (off + slen >=3D (int)buf_len) > +=09=09=09=09return NULL; > +=09=09=09memcpy(buf + off, dhcpv6_user_opts[code].val + i, ...and here. > +=09=09=09 slen); > +=09=09=09off +=3D slen; > +=09=09=09i +=3D slen; > +=09=09} > +=09=09buf[off] =3D '\0'; > +=09=09return buf; > +=09default: > +=09=09return NULL; > +=09} > +} > + > +/** > + * dhcpv6_set_opt() - Parse and store a user-specified DHCPv6 option > + * @code:=09DHCPv6 option code > + * @val_str:=09Value string from command line > + */ > +void dhcpv6_set_opt(uint16_t code, const char *val_str) > +{ > +=09int ret; > + > +=09if (code >=3D ARRAY_SIZE(dhcpv6_opt_types) || > +=09 dhcpv6_opt_types[code] =3D=3D DHCPV6_OPT_NONE) > +=09=09die("DHCPv6 option %u is not supported", code); > + > +=09ret =3D dhcpv6_opt_parse(code, val_str, > +=09=09=09 dhcpv6_user_opts[code].val, > +=09=09=09 sizeof(dhcpv6_user_opts[code].val)); > +=09if (ret < 0) > +=09=09die("Invalid value for DHCPv6 option %u: %s", code, val_str); > + > +=09dhcpv6_user_opts[code].len =3D ret; > +} > + > /** > * dhcpv6_opt() - Get option from DHCPv6 message > * @data:=09Buffer with options, set to matching option on return > diff --git a/dhcpv6.h b/dhcpv6.h > index 1015a1a7..973c477c 100644 > --- a/dhcpv6.h > +++ b/dhcpv6.h > @@ -9,5 +9,7 @@ > int dhcpv6(struct ctx *c, struct iov_tail *data, > =09 const struct in6_addr *saddr, const struct in6_addr *daddr); > void dhcpv6_init(const struct ctx *c); > +void dhcpv6_set_opt(uint16_t code, const char *val_str); > +const char *dhcpv6_opt_to_str(uint16_t code, char *buf, size_t buf_len); > =20 > #endif /* DHCPV6_H */ > diff --git a/passt.1 b/passt.1 > index 53e072a9..249ed5cf 100644 > --- a/passt.1 > +++ b/passt.1 > @@ -440,6 +440,37 @@ 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. > =20 > +.TP > +.BR \-\-dhcpv6-opt " " \fICODE\fR,\fIVALUE\fR > +Set DHCPv6 option \fICODE\fR to \fIVALUE\fR. The value format depends > +on the option type and is determined automatically from the option code. > +Multiple IPv6 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. > +.RS > +.TP > +.B String options > +59 (Boot File URL, RFC 5970) > +.TP > +.B Length-prefixed string list options (comma-separated entries) > +15 (User Class, RFC 8415), 60 (Boot File Params, RFC 5970). Same as above about RFC 8415, here... > +Each comma-separated entry is encoded with a 2-byte length prefix. > +Example: \fB\-\-dhcpv6-opt 15,class1,class2\fR. > +.TP > +.B Vendor class options (ENTERPRISE:DATA format) > +16 (Vendor Class, RFC 8415). and here. > +VALUE is \fIENTERPRISE\fR:\fIDATA\fR where \fIENTERPRISE\fR is the IANA > +Private Enterprise Number and \fIDATA\fR is the vendor class string. > +Example: \fB\-\-dhcpv6-opt 16,0:HTTPClient\fR for UEFI HTTP Boot. > +.TP > +.B IPv6 address list options (comma-separated) > +31 (SNTP Servers) > +.TP > +.B Integer options > +7 (Preference, 8-bit), 32 (Information Refresh Time, 32-bit), > +82 (SOL_MAX_RT, 32-bit), 83 (INF_MAX_RT, 32-bit) > +.RE > + > .TP > .BR \-t ", " \-\-tcp-ports " " \fIspec > Configure TCP port forwarding to guest or namespace. \fIspec\fR can be e= ither: --=20 Stefano