public inbox for passt-dev@passt.top
 help / color / mirror / code / Atom feed
From: Stefano Brivio <sbrivio@redhat.com>
To: Anshu Kumari <anskuma@redhat.com>
Cc: passt-dev@passt.top, david@gibson.dropbear.id.au,
	jmaloy@redhat.com, lvivier@redhat.com
Subject: Re: [PATCH v3 5/6] dhcp: Add option overload
Date: Fri, 12 Jun 2026 01:04:55 +0200 (CEST)	[thread overview]
Message-ID: <20260612010454.50b8acec@elisabeth> (raw)
In-Reply-To: <20260601073758.1571317-6-anskuma@redhat.com>

On Mon,  1 Jun 2026 13:07:55 +0530
Anshu Kumari <anskuma@redhat.com> wrote:

> A user can enter lots of options in command-line which may not fit in
> existing buffer, So when the options field is full, overflow remaining
> DHCP options into the file and sname fields per RFC 2132 option 52.

As David already pointed out, this hides the fact that this patch
actually implements the insertion of options in the actual DHCP
messages.

This makes it pretty hard / impossible to find things later if we need
to investigate or fix some issue.

Would it be doable to have a separate change just implementing option
overload? If not, I think it would be preferable to merge this together
with the current 3/6.

> Also, when the file field is not used for overload, copy the boot
> file URL there directly for legacy PXE clients.
> 
> Link: https://bugs.passt.top/show_bug.cgi?id=192
> Signed-off-by: Anshu Kumari <anskuma@redhat.com>
> ---
> v3:
>   - Added RFC 2132 Section 9.3 reference comment on overload
>     constants.
>   - Use ARRAY_SIZE(opts) instead of raw 255 in fill_overflow().
>   - Swapped overflow order: try sname (64 bytes) first, then file
>     (128 bytes) — better packing and keeps file field available for
>     boot file name.
>   - Removed '&' from &reply.file.
>   - Removed '+1' from memcpy — reply.file already zeroed.
>   - opt_set_dns_search() max_len: OPT_MAX - 3 instead of
>     sizeof(m->o).
> 
> v2:
>   - Added #define DHCP_OVERLOAD_FILE and #define DHCP_OVERLOAD_SNAME constants
>   - Added comment documenting space reservation: /* Reserve 3 bytes for option 52 */
>   - Fixed DNS search length: sizeof(m->o) only, not combined with file+sname
>   - Removed dhcp_boot references — reply.file copy now reads from opts[67]
>   - Used DHCP_OVERLOAD_FILE constant in reply.file guard
> ---
>  dhcp.c | 91 +++++++++++++++++++++++++++++++++++++++++++++++++++-------
>  1 file changed, 81 insertions(+), 10 deletions(-)
> 
> diff --git a/dhcp.c b/dhcp.c
> index 5c6a492..a9d56fd 100644
> --- a/dhcp.c
> +++ b/dhcp.c
> @@ -372,14 +372,64 @@ static bool fill_one(uint8_t *buf, size_t size, int o, int *offset)
>  	return false;
>  }
>  
> +/* RFC 2132, Section 9.3 - Option Overload*/

Nit: missing whitespace before */

> +#define DHCP_OVERLOAD_FILE	1
> +#define DHCP_OVERLOAD_SNAME	2
> +
>  /**
> - * fill() - Fill options in message
> + * fill_overflow() - Fill remaining options into sname and file fields
> + * @m:		Message whose sname/file fields may be used for overflow
> + *
> + * Try the smaller sname field first: small options go there, leaving
> + * the larger file field available for big options and for the boot
> + * file name (option 67) if set.
> + *
> + * Return: option 52 overload value: 0 if no overflow,
> + *         DHCP_OVERLOAD_FILE for file, DHCP_OVERLOAD_SNAME for sname,
> + *         or both OR'd together
> + */
> +static int fill_overflow(struct msg *m)
> +{
> +	int sname_off = 0, file_off = 0, overload = 0;
> +	int o;
> +
> +	for (o = 0; (size_t)o < ARRAY_SIZE(opts); o++) {
> +		if (opts[o].slen == -1 || opts[o].sent)
> +			continue;
> +		fill_one(m->sname, sizeof(m->sname) - 1, o, &sname_off);
> +	}
> +
> +	for (o = 0; (size_t)o < ARRAY_SIZE(opts); o++) {
> +		if (opts[o].slen == -1 || opts[o].sent)
> +			continue;
> +		if (fill_one(m->file, sizeof(m->file) - 1, o, &file_off))
> +			debug("DHCP: skipping option %i (overload full)", o);
> +	}
> +
> +	if (sname_off) {
> +		m->sname[sname_off] = 255;
> +		overload |= DHCP_OVERLOAD_SNAME;
> +	}
> +
> +	if (file_off) {
> +		m->file[file_off] = 255;
> +		overload |= DHCP_OVERLOAD_FILE;
> +	}
> +
> +	return overload;
> +}
> +
> +/**
> + * fill() - Fill options in message, with overload into file/sname if needed
>   * @m:		Message to fill
> + * @overload:	Set to option 52 value (0 if none, 1/2/3 per RFC 2132)

Sounds like an enum to me.

>   *
>   * Return: current size of options field
>   */
> -static int fill(struct msg *m)
> +static int fill(struct msg *m, int *overload)
>  {
> +	/* Reserve 3 bytes for option 52 (overload) if needed */
> +	size_t size = OPT_MAX - 3;

This isn't ideal in the sense that if we have, at the end of the
options, an option that's three or less bytes in total (one or zero
bytes of value), and we are at OPT_MAX - 3, we could encode it without
overload.

I don't see a real issue in keeping this for the sake of simplicity,
especially because that's such an unlikely corner case, and the
consequences of this non-ideal implementation are really minor if any,
but maybe (I haven't really thought it through) to always keep three
bytes free, and then add another loop checking if we have a single
option that's three bytes long or less?

>  	int i, o, offset = 0;
>  
>  	for (o = 0; o < 255; o++)
> @@ -390,20 +440,25 @@ static int fill(struct msg *m)
>  	 * Put it there explicitly, unless requested via option 55.
>  	 */
>  	if (opts[55].clen > 0 && !memchr(opts[55].c, 53, opts[55].clen))
> -		if (fill_one(m->o, OPT_MAX, 53, &offset))
> -			 debug("DHCP: skipping option 53");
> +		fill_one(m->o, size, 53, &offset);
>  
>  	for (i = 0; i < opts[55].clen; i++) {
>  		o = opts[55].c[i];
>  		if (opts[o].slen != -1)
> -			if (fill_one(m->o, OPT_MAX, o, &offset))
> -				debug("DHCP: skipping option %i", o);
> +			fill_one(m->o, size, o, &offset);
>  	}
>  
>  	for (o = 0; o < 255; o++) {
>  		if (opts[o].slen != -1 && !opts[o].sent)
> -			if (fill_one(m->o, OPT_MAX, o, &offset))
> -				debug("DHCP: skipping option %i", o);
> +			fill_one(m->o, size, o, &offset);
> +	}
> +
> +	*overload = fill_overflow(m);
> +
> +	if (*overload) {
> +		m->o[offset++] = 52;
> +		m->o[offset++] = 1;
> +		m->o[offset++] = *overload;
>  	}
>  
>  	m->o[offset++] = 255;
> @@ -528,6 +583,7 @@ int dhcp(const struct ctx *c, struct iov_tail *data)
>  	struct msg const *m;
>  	struct msg reply;
>  	unsigned int i;
> +	int overload;
>  
>  	eh = IOV_REMOVE_HEADER(data, eh_storage);
>  	iph = IOV_PEEK_HEADER(data, iph_storage);
> @@ -677,9 +733,24 @@ int dhcp(const struct ctx *c, struct iov_tail *data)
>  	}
>  
>  	if (!c->no_dhcp_dns_search)
> -		opt_set_dns_search(c, sizeof(m->o));
> +		opt_set_dns_search(c, OPT_MAX - 3);
> +
> +	for (i = 0; i < (unsigned int)c->custom_opts_count; i++) {
> +		uint8_t code = c->custom_opts[i].code;
> +
> +		opts[code].slen = c->custom_opts[i].len;
> +		memcpy(opts[code].s, c->custom_opts[i].val,
> +		       c->custom_opts[i].len);
> +	}
> +
> +	dlen = offsetof(struct msg, o) + fill(&reply, &overload);
>  
> -	dlen = offsetof(struct msg, o) + fill(&reply);
> +	/* Copy boot file name into the file field for legacy PXE clients,
> +	 * unless the file field is already used for option overload.
> +	 */

But RFC 2132, section 9.5, says:

   This option is used to identify a bootfile when the 'file' field in
   the DHCP header has been used for DHCP options.

and I think we should interpret that "when" as "if and only if" by
default (that's the least surprising way from a linguistic
perspective), so, as a consequence, we should never duplicate that in
the replies we send.

That is, if we can insert it in 'file', we should do that, and we
should use option 67 _only if_ we can't. Not always. Not if it's
already in 'file'.

Now, we don't know if we need option overload until we're done
inserting options, 

> +	if (!(overload & DHCP_OVERLOAD_FILE) &&
> +	    opts[67].slen > 0 && (size_t)opts[67].slen < sizeof(reply.file))
> +		memcpy(reply.file, opts[67].s, opts[67].slen);
>  
>  	if (m->flags & FLAG_BROADCAST)
>  		dst = in4addr_broadcast;

-- 
Stefano


  parent reply	other threads:[~2026-06-11 23:05 UTC|newest]

Thread overview: 19+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-06-01  7:37 [PATCH v3 0/6] Add --dhcp-boot and --dhcp-opt options Anshu Kumari
2026-06-01  7:37 ` [PATCH v3 1/6] conf: Add --dhcp-opt command-line option Anshu Kumari
2026-06-02  2:00   ` David Gibson
2026-06-11 23:04   ` Stefano Brivio
2026-06-01  7:37 ` [PATCH v3 2/6] conf: Add --dhcp-boot " Anshu Kumari
2026-06-02  2:02   ` David Gibson
2026-06-11 23:05   ` Stefano Brivio
2026-06-01  7:37 ` [PATCH v3 3/6] dhcp: Add option type table and value parser Anshu Kumari
2026-06-02  2:23   ` David Gibson
2026-06-11 23:04     ` Stefano Brivio
2026-06-11 23:04   ` Stefano Brivio
2026-06-01  7:37 ` [PATCH v3 4/6] dhcp: Refactor fill_one() to operate on a generic buffer Anshu Kumari
2026-06-02  2:25   ` David Gibson
2026-06-01  7:37 ` [PATCH v3 5/6] dhcp: Add option overload Anshu Kumari
2026-06-02  2:50   ` David Gibson
2026-06-11 23:04   ` Stefano Brivio [this message]
2026-06-01  7:37 ` [PATCH v3 6/6] doc: Add --dhcp-boot and --dhcp-opt to man page Anshu Kumari
2026-06-02  2:54   ` David Gibson
2026-06-11 23:05   ` Stefano Brivio

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260612010454.50b8acec@elisabeth \
    --to=sbrivio@redhat.com \
    --cc=anskuma@redhat.com \
    --cc=david@gibson.dropbear.id.au \
    --cc=jmaloy@redhat.com \
    --cc=lvivier@redhat.com \
    --cc=passt-dev@passt.top \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this public inbox

	https://passt.top/passt

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for IMAP folder(s).