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=OPPkucFz; 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 48E7D5A0265 for ; Wed, 01 Jul 2026 18:59:55 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1782925193; 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=dYQPHf4QPKteqK0NLoRXf/xqSjUiIEh8bMpXEdiKRAM=; b=OPPkucFzAygj/1rHRIsj86V3/Fs4gmHd7R8CFu9qF5jcSXYN6AK/0EimvO7r6MhsF5itMn c3+ZPjtEk93QG0VWj0//ro0hXlDAd68vXHhn4TjR83hQYUIyTL40B+yf6QZPa+PTeb16T3 XxvDlwHJoMUIsNU7u/blILq7Rv4/rew= Received: from mail-wr1-f69.google.com (mail-wr1-f69.google.com [209.85.221.69]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-633-RZ5RgrcDOHCY-dV7iGE17A-1; Wed, 01 Jul 2026 12:59:52 -0400 X-MC-Unique: RZ5RgrcDOHCY-dV7iGE17A-1 X-Mimecast-MFC-AGG-ID: RZ5RgrcDOHCY-dV7iGE17A_1782925191 Received: by mail-wr1-f69.google.com with SMTP id ffacd0b85a97d-475e540a0ffso617713f8f.3 for ; Wed, 01 Jul 2026 09:59:52 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1782925191; x=1783529991; h=date:content-transfer-encoding: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; bh=dYQPHf4QPKteqK0NLoRXf/xqSjUiIEh8bMpXEdiKRAM=; b=p2Agc+rilM4PuvIsrbvT0FF0bxPv9gfZaMZXpfpc7W0vXgkjA2K8lG7F4+BSpMXgTi TqzLN4aLj0ljzE2RAz0zFFzBAuAavJ1hV9fpXD9F0TvJ7PjZRYYBLm6aMbJJrzTG+d2v h2H421zt1divn5EnzkGk6VmcLOoAAfbw4Un3oXSJhoWmsmChKes0h+GC+6bkFoUFKc6T Szvjrl5PnQhkkMB95utG77w96QA0KomRmssO2u7lIPVZbNa6WTn434TSuT2Mgo3OEvLj ncbMVr+UsAH/220BNjZz2BDSJQqF0KWLus/w6oVE92brnGMY2lBqG41PxdsD/UKb5tv7 duew== X-Gm-Message-State: AOJu0Yy7UE7456JJRKNhp4DbSyTHayoXAtpK4gxxmpZf1IPAVR4hQo84 pR9o1f8xLIfr/lrVRaWtSzZ0n8iwEBbfRCvl5+IhuUZsiWry/vGWBCeyAli4ATABjmBX/yIUgct 4eOpo5nLrtR7zuF7xOHbyPe1mYF+fEN9/LDLdgrn7B57cP3HZ95Reqb3H3+Sbiw== X-Gm-Gg: AfdE7cnC2QFMrjn8eSRb8LQD6w43SQakWhnRYRwMZr/5SlaV7gd999JtX3iY/hfVdqn C/w49TlavD7WaZDqcQbJqT/QOIX5DKHkoaWjAvGAPJXMsH7xTSiqdDXAGzFf6rf6juiMMkE9mLP RAJrrI7VlfUXOLM6oPcU7WGheXh6B51i/N+cZOg1Ry5uaMayEGwkiNblAzcft4VdA1OImXrFouU +S1qGbnH2BTGNVZqifJLnYaLl4nF9bImqcUN1xs03XQknDxUweGNrezj6Iap7kLIVrL46BorByo kq5X3UOueIA86SRrNJZdTjKtYhaql0zsgjYHG1cs6T8BpV4Dam/Ca0I5eAtcCzu79+usYFdYe3u kNbZMfvgK4HakhzptbqrFrg== X-Received: by 2002:a05:6000:480f:b0:472:55a:ef96 with SMTP id ffacd0b85a97d-4775a4e2bc7mr3742008f8f.39.1782925190745; Wed, 01 Jul 2026 09:59:50 -0700 (PDT) X-Received: by 2002:a05:6000:480f:b0:472:55a:ef96 with SMTP id ffacd0b85a97d-4775a4e2bc7mr3741946f8f.39.1782925189996; Wed, 01 Jul 2026 09:59:49 -0700 (PDT) Received: from maya.myfinge.rs (ifcgrfdd.trafficplex.cloud. [2a10:fc81:a806:d6a9::1]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-477dde19fd3sm1268311f8f.25.2026.07.01.09.59.49 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 01 Jul 2026 09:59:49 -0700 (PDT) From: Stefano Brivio To: Anshu Kumari Subject: Re: [PATCH v2 1/2] dhcpv6: Add --dhcpv6-opt with option type table and value parser Message-ID: <20260701185948.03f1557a@elisabeth> In-Reply-To: <20260618120529.1768765-2-anskuma@redhat.com> References: <20260618120529.1768765-1-anskuma@redhat.com> <20260618120529.1768765-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: Wed, 01 Jul 2026 18:59:49 +0200 (CEST) X-Mimecast-Spam-Score: 0 X-Mimecast-MFC-PROC-ID: g0tVX4W0AgMa9b9kZ3g0euOoZ9kFuEwQ_zULgHepnA0_1782925191 X-Mimecast-Originator: redhat.com Content-Type: text/plain; charset=US-ASCII Content-Transfer-Encoding: 7bit Message-ID-Hash: TK6CRSOC7CZIWVUXEEHYOPPW4YWZHFSU X-Message-ID-Hash: TK6CRSOC7CZIWVUXEEHYOPPW4YWZHFSU 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, david@gibson.dropbear.id.au, lvivier@redhat.com, jmaloy@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 Thu, 18 Jun 2026 17:35:28 +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]. > > 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. > > Link: https://bugs.passt.top/show_bug.cgi?id=192 > Signed-off-by: Anshu Kumari > --- > 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 | 60 +++++++++++++++- > dhcpv6.c | 209 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ > dhcpv6.h | 2 + > passt.1 | 31 +++++++++ > passt.h | 12 ++++ > 5 files changed, 313 insertions(+), 1 deletion(-) > > diff --git a/conf.c b/conf.c > index cd05adf..3981c1b 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" > @@ -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" > + " --dhcpv6-opt CODE,VAL Set DHCPv6 option CODE to VAL\n"); > if (strstr(name, "pasta")) > FPRINTF(f, " default: don't use any search list\n"); > else > @@ -884,6 +886,10 @@ static void conf_print(const struct ctx *c) > info(" our link-local: %s", > inet_ntop(AF_INET6, &c->ip6.our_tap_ll, > buf, sizeof(buf))); > + for (i = 0; i < c->dhcpv6_opts_count; i++) > + info(" v6 option %u: %s", > + c->dhcpv6_opts[i].code, > + c->dhcpv6_opts[i].str); > > dns6: > for (i = 0; i < ARRAY_SIZE(c->ip6.dns); i++) { > @@ -1150,6 +1156,41 @@ static void conf_sock_listen(const struct ctx *c) > die_perror("Couldn't add configuration socket to epoll"); > } > > +/** > + * conf_dhcpv6_option() - Set value for a DHCPv6 option in configuration > + * @c: Execution context > + * @code: DHCPv6 option code > + * @val_str: Value string from command line > + */ > +static void conf_dhcpv6_option(struct ctx *c, uint16_t code, > + const char *val_str) > +{ > + uint8_t tmp[255]; For DHCPv6, we have 16 bits of options length, so this should be UINT16_MAX. > + int idx; > + > + if (dhcpv6_opt_parse(code, val_str, tmp, sizeof(tmp)) < 0) > + die("Invalid value for DHCPv6 option %u: %s", code, val_str); > + > + for (idx = 0; idx < c->dhcpv6_opts_count; idx++) { > + if (c->dhcpv6_opts[idx].code == code) > + break; > + } > + > + if (idx == c->dhcpv6_opts_count) { > + if (c->dhcpv6_opts_count >= MAX_DHCPV6_OPTS) Same as my question about MAX_CUSTOM_DHCP_OPTS in the review of 1/6 of the v3 for DHCP options: https://archives.passt.top/passt-dev/20260612010426.319bc57d@elisabeth/ Unlike DHCP, DHCPv6 doesn't specify a minimum size of the option field (312 bytes for DHCP, RFC 2131, Section 2) that a client or server needs to be ready to accept, implying that, as long as a message fits the MTU, it's acceptable. And our default MTU is 65520 bytes, meaning we have 65472 bytes available for DHCP messages (accounting for 40 bytes of IPv6 header and for 8 bytes of UDP header). So, actually, 32 is likely to be more restrictive than needed in this case. But I see you have that dhcpv6_opts[MAX_DHCPV6_OPTS] in struct ctx... see below for some considerations about that. > + die("Too many --dhcpv6-opt entries (max %d)", > + MAX_DHCPV6_OPTS); > + c->dhcpv6_opts_count++; > + } > + > + c->dhcpv6_opts[idx].code = code; > + > + if (snprintf_check(c->dhcpv6_opts[idx].str, > + sizeof(c->dhcpv6_opts[0].str), > + "%s", val_str)) > + die("DHCPv6 option value too long: %s", val_str); > +} > + > /** > * conf() - Process command-line arguments and set configuration > * @c: Execution context > @@ -1233,6 +1274,7 @@ void conf(struct ctx *c, int argc, char **argv) > {"migrate-no-linger", no_argument, NULL, 30 }, > {"stats", required_argument, NULL, 31 }, > {"conf-path", required_argument, NULL, 'c' }, > + {"dhcpv6-opt", required_argument, NULL, 35 }, > { 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:"; > @@ -1248,10 +1290,13 @@ void conf(struct ctx *c, int argc, char **argv) > uint8_t prefix_len_from_opt = 0; > unsigned int ifi4 = 0, ifi6 = 0; > const char *logfile = NULL; > + unsigned long v6optcode; I'm not suggesting to make this look like Java but... 'v6' isn't really explanatory of what this is. Our 'v6' short-hands typically represent IPv6, not DHCPv6. What about dhcpv6_opt_code? It doesn't make any of the lines you're adding below wrap. > char *runas = NULL; > size_t logsize = 0; > + const char *comma; > long fd_tap_opt; > int name, ret; > + char *end; > uid_t uid; > gid_t gid; > > @@ -1467,6 +1512,19 @@ void conf(struct ctx *c, int argc, char **argv) > die("Can't display statistics if not running in foreground"); > c->stats = strtol(optarg, NULL, 0); > break; > + case 35: > + comma = strchr(optarg, ','); > + if (!comma) > + die("--dhcpv6-opt requires CODE,VALUE format"); > + > + errno = 0; > + v6optcode = strtoul(optarg, &end, 0); > + if (end != comma || errno || v6optcode < 1) > + die("Invalid DHCPv6 option code: %s", > + optarg); > + > + conf_dhcpv6_option(c, v6optcode, comma + 1); > + break; > case 'd': > c->debug = 1; > c->quiet = 0; > diff --git a/dhcpv6.c b/dhcpv6.c > index 97c04e2..1e1dd0d 100644 > --- a/dhcpv6.c > +++ b/dhcpv6.c > @@ -25,6 +25,7 @@ > #include > #include > #include > +#include > > #include "packet.h" > #include "util.h" > @@ -278,6 +279,214 @@ static struct resp_not_on_link_t { > { 0, }, > }; > > +/** > + * enum dhcpv6_opt_type - DHCPv6 option value types > + * @DHCPV6_OPT_NONE: Unsupported or unknown option > + * @DHCPV6_OPT_STR: Variable-length string > + * @DHCPV6_OPT_IPV6: Single IPv6 address > + * @DHCPV6_OPT_IPV6_LIST: Multiple IPv6 addresses, comma-separated > + * @DHCPV6_OPT_INT8: Unsigned 8-bit integer > + * @DHCPV6_OPT_INT16: Unsigned 16-bit integer > + * @DHCPV6_OPT_INT32: Unsigned 32-bit integer > + * @DHCPV6_OPT_VENDOR_CLASS: Enterprise number + length-prefixed data > + * @DHCPV6_OPT_LEN_STR_LIST: Length-prefixed string list By the way, I just realised that RFC 7227 (Best Current Practice, nothing we have to take care of here) has a nice summary of option types in Section 5, if it helps cross-checking things. > + */ > +enum dhcpv6_opt_type { > + DHCPV6_OPT_NONE, > + DHCPV6_OPT_STR, > + DHCPV6_OPT_IPV6, > + DHCPV6_OPT_IPV6_LIST, > + DHCPV6_OPT_INT8, > + DHCPV6_OPT_INT16, > + DHCPV6_OPT_INT32, > + DHCPV6_OPT_VENDOR_CLASS, > + DHCPV6_OPT_LEN_STR_LIST, > +}; > + > +/** > + * dhcpv6_opt_types - Maps DHCPv6 option code to value type, indexed by code > + * RFC 8415 Options: 7, 15, 16, 17, 32, 82, 83 > + * RFC 5970 Options: 59, 60 > + * RFC 4075 Options: 31 > + */ > +static const enum dhcpv6_opt_type dhcpv6_opt_types[] = { > + [7] = DHCPV6_OPT_INT8, /* Preference */ > + [15] = DHCPV6_OPT_LEN_STR_LIST, /* User Class */ > + [16] = DHCPV6_OPT_VENDOR_CLASS, /* Vendor Class */ > + [17] = DHCPV6_OPT_VENDOR_CLASS, /* Vendor Opts */ > + [31] = DHCPV6_OPT_IPV6_LIST, /* SNTP Servers */ > + [32] = DHCPV6_OPT_INT32, /* Information Refresh Time */ > + [59] = DHCPV6_OPT_STR, /* Boot File URL */ > + [60] = DHCPV6_OPT_LEN_STR_LIST, /* Boot File Params */ > + [82] = DHCPV6_OPT_INT32, /* SOL_MAX_RT */ > + [83] = DHCPV6_OPT_INT32, /* INF_MAX_RT */ > +}; > + > +/** > + * dhcpv6_opt_parse() - Parse a DHCPv6 option value string into binary > + * @code: DHCPv6 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 dhcpv6_opt_parse(uint16_t code, const char *str, > + uint8_t *buf, size_t buf_len) > +{ > + enum dhcpv6_opt_type type; > + unsigned long val; > + unsigned int i; > + uint8_t width; > + size_t slen; > + char *end; > + int len; > + > + if (!*str) > + return -1; > + > + if (code >= ARRAY_SIZE(dhcpv6_opt_types)) > + return -1; > + > + type = dhcpv6_opt_types[code]; > + > + switch (type) { > + case DHCPV6_OPT_NONE: > + return -1; > + case DHCPV6_OPT_IPV6: > + case DHCPV6_OPT_IPV6_LIST: > + len = 0; > + > + while (*str) { > + char chunk[INET6_ADDRSTRLEN]; > + size_t clen; > + > + clen = strcspn(str, ","); > + if (!clen || clen >= sizeof(chunk)) > + return -1; > + > + if (len + (int)sizeof(struct in6_addr) > (int)buf_len) > + return -1; > + > + memcpy(chunk, str, clen); > + chunk[clen] = '\0'; > + > + if (inet_pton(AF_INET6, chunk, buf + len) != 1) > + return -1; > + > + len += sizeof(struct in6_addr); > + > + if (type == DHCPV6_OPT_IPV6) { > + if (str[clen] == ',') > + return -1; > + break; > + } > + > + str += clen + (str[clen] == ','); > + } > + > + if (!len) > + return -1; > + > + return len; > + case DHCPV6_OPT_INT8: > + case DHCPV6_OPT_INT16: > + case DHCPV6_OPT_INT32: > + if (type == DHCPV6_OPT_INT8) > + width = 1; > + else if (type == DHCPV6_OPT_INT16) > + width = 2; > + else > + width = 4; > + > + if (buf_len < width) > + return -1; > + > + errno = 0; > + val = strtoul(str, &end, 0); > + > + if (*end || errno || val >= (1ULL << (width * 8))) > + return -1; > + > + for (i = width; i > 0; i--) { > + buf[i - 1] = val & 0xff; > + val >>= 8; > + } This looks correct to me, and it's safe regardless of the underlying endianness because you're not addressing 'val' byte by byte. However, it's in part a reimplementation of htons() and htonl(). Given that you already have explicit conditions setting the width above, _maybe_ you could try to shuffle things around and turn those into: if (type == DHCPV6_OPT_INT8) { width = 1; } else if (type == DHCPV6_OPT_INT16) { width = 2; *(uint16_t *)(buf + i) = htons(val); } else { width = 4; *(uint32_t *)(buf + i) = htons(val); } but it needs to be combined with the checking you already added. I'm not sure if it's worth it. > + > + return width; > + case DHCPV6_OPT_STR: > + slen = strlen(str); > + > + if (slen >= buf_len) > + return -1; > + > + memcpy(buf, str, slen); > + > + return slen; > + case DHCPV6_OPT_VENDOR_CLASS: { > + const char *colon; > + uint32_t ent; > + > + colon = strchr(str, ':'); > + if (!colon) > + return -1; > + > + errno = 0; > + val = strtoul(str, &end, 0); > + if (end != colon || errno || val > UINT32_MAX) > + return -1; > + > + slen = strlen(colon + 1); > + if (!slen) > + return -1; > + > + len = sizeof(uint32_t) + sizeof(uint16_t) + slen; > + if ((size_t)len > buf_len) > + return -1; > + > + ent = htonl(val); > + memcpy(buf, &ent, sizeof(ent)); > + > + buf[4] = slen >> 8; > + buf[5] = slen & 0xff; Similar to my comment above: this could be done with htons(). > + > + memcpy(buf + sizeof(uint32_t) + sizeof(uint16_t), > + colon + 1, slen); > + > + return len; > + } > + case DHCPV6_OPT_LEN_STR_LIST: > + len = 0; > + > + while (*str) { > + slen = strcspn(str, ","); > + if (!slen) > + return -1; > + > + if (len + (int)(sizeof(uint16_t) + slen) > (int)buf_len) > + return -1; > + > + buf[len] = slen >> 8; > + buf[len + 1] = slen & 0xff; Same here. > + len += sizeof(uint16_t); > + > + memcpy(buf + len, str, slen); > + len += slen; > + > + str += slen; > + if (*str == ',') > + str++; > + } > + > + if (!len) > + return -1; > + > + return len; > + } > + > + return -1; > +} > + > /** > * dhcpv6_opt() - Get option from DHCPv6 message > * @data: Buffer with options, set to matching option on return > diff --git a/dhcpv6.h b/dhcpv6.h > index c706dfd..2da1c76 100644 > --- a/dhcpv6.h > +++ b/dhcpv6.h > @@ -9,5 +9,7 @@ > int dhcpv6(struct ctx *c, struct iov_tail *data, > struct in6_addr *saddr, struct in6_addr *daddr); > void dhcpv6_init(const struct ctx *c); > +int dhcpv6_opt_parse(uint16_t code, const char *str, > + uint8_t *buf, size_t buf_len); > > #endif /* DHCPV6_H */ > diff --git a/passt.1 b/passt.1 > index 908fd4a..0f771cb 100644 > --- a/passt.1 > +++ b/passt.1 > @@ -430,6 +430,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. > > +.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). > +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), 17 (Vendor-specific Info, RFC 8415). > +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 one of: > diff --git a/passt.h b/passt.h > index 3a07294..8f4ddef 100644 > --- a/passt.h > +++ b/passt.h > @@ -182,6 +182,10 @@ struct ip6_ctx { > * @dns_search: DNS search list > * @hostname: Guest hostname > * @fqdn: Guest FQDN > + * @dhcpv6_opts: User-specified DHCPv6 options from --dhcpv6-opt > + * @dhcpv6_opts.code: DHCPv6 option code > + * @dhcpv6_opts.str: String value from command line > + * @dhcpv6_opts_count: Number of entries in @dhcpv6_opts > * @ifi6: Template interface for IPv6, -1: none, 0: IPv6 disabled > * @ip6: IPv6 configuration > * @pasta_ifn: Name of namespace interface for pasta > @@ -264,6 +268,14 @@ struct ctx { > char hostname[PASST_MAXDNAME]; > char fqdn[PASST_MAXDNAME]; > > +#define MAX_DHCPV6_OPTS 32 > + > + struct { > + uint16_t code; > + char str[255]; Same as above: this needs to be UINT16_MAX, which, if we want to support, say, 255 options, makes this approximately 16 MiB. On the other hand, as long as the user doesn't use items in this array, that memory isn't actually allocated anywhere, so the real size would be very reasonable in practice. Unlike DHCP, though, DHCPv6 has 16 bits of option code, and adopting the same approach as for DHCP means 4 GiB of potential memory. Yes, it's not allocated if not used, but it starts looking scary, and it will give us problems with dynamic memory checkers such as valgrind. Realistically, while a maximum number of 32 options looks exceedingly restrictive to me, IANA allocated only up to option 150, and, given that we only accept options we know about, we could limit this to the current count of known options, and then, same as for IPv4, index those options directly. There are a few tricks to get, here, the size of the array as initialised. You can't just move the enum to dhcpv6.h and add an extern declaration such as, say: extern const enum dhcpv6_opt_type dhcpv6_opt_types[]; because, with that, the storage size isn't known. I think the least ugly way is to move the actual initialisation of the array in the header file, and use the __weak__ attribute, so that it will be defined only once (and not as many times as the header is included, which would lead to duplicate definitions). That is, you could move the whole thing to dhcpv6.h like this: /** * enum dhcpv6_opt_type - DHCPv6 option value types * @DHCPV6_OPT_NONE: Unsupported or unknown option ... */ enum dhcpv6_opt_type { DHCPV6_OPT_NONE, ... }; /** * dhcpv6_opt_types - Maps DHCPv6 option code to value type, indexed by code * RFC 8415 Options: 7, 15, 16, 17, 32, 82, 83 * RFC 5970 Options: 59, 60 * RFC 4075 Options: 31 */ __attribute__ ((weak)) const enum dhcpv6_opt_type dhcpv6_opt_types[] = { [7] = DHCPV6_OPT_INT8, /* Preference */ ... }; and then include dhcpv6.h from passt.h. At that point, you could use something like this here: struct { char str[UINT16_MAX - 4 /* code and length */]; } dhcpv6_opts[ARRAY_SIZE(dhcpv6_opt_types)]; ...and you don't need 'code' anymore because the position in the array represents that. That being said, I would rather suggest to use this to store the parsed value, not the original value, so that we avoid parsing stuff twice. See my follow-up comment to 2/2. > + } dhcpv6_opts[MAX_DHCPV6_OPTS]; > + int dhcpv6_opts_count; > + > int ifi6; > struct ip6_ctx ip6; > -- Stefano