On Fri, Jul 17, 2026 at 11:26:40PM +0530, Anshu Kumari wrote:
When the options field is full, overflow remaining DHCP options into the sname and file fields per RFC 2132 option 52.
Per RFC 2132, Section 9.5, the boot file name is always placed in the 'file' header field. When a boot file is set, the file field is reserved from overload and overflow uses only the sname field.
Link: https://bugs.passt.top/show_bug.cgi?id=192 Signed-off-by: Anshu Kumari
--- v5: - enhanced enum dhcp_overload to follow kernel-doc. - Inline fill_overflow() into fill() - Use state-based checks instead of slen v4: - Converted overload #defines to enum dhcp_overload. - Fixed missing whitespace in comment before */. - Boot file name always placed in 'file' header field per RFC 2132, Section 9.5; file field reserved from overload when bootfile is set; option 67 suppressed from options area.
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 | 88 ++++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 79 insertions(+), 9 deletions(-)
diff --git a/dhcp.c b/dhcp.c index e5d89fc..39f7952 100644 --- a/dhcp.c +++ b/dhcp.c @@ -176,13 +176,31 @@ static bool fill_one(uint8_t *buf, size_t size, int o, int *offset) }
/** - * fill() - Fill options in message - * @m: Message to fill +* enum dhcp_overload - DHCP option overload values (RFC 2132, Section 9.3)
Nit: missing space before the *.
+ * @DHCP_OVERLOAD_NONE: No overload + * @DHCP_OVERLOAD_FILE: file field carries options + * @DHCP_OVERLOAD_SNAME: sname field carries options + */ +enum dhcp_overload { + DHCP_OVERLOAD_NONE, + DHCP_OVERLOAD_FILE, + DHCP_OVERLOAD_SNAME,
Nit: I'd suggest putting specific value assignments on these, even though they're technically redundant. It serves to make it clearer that the specific numerical values matters, since these go "over the wire".
+}; + +/** + * 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) + * @has_bootfile: Reserve file field for boot file name * * Return: current size of options field */ -static int fill(struct msg *m) +static int fill(struct msg *m, enum dhcp_overload *overload, bool has_bootfile) { + int sname_off = 0, file_off = 0; + *overload = DHCP_OVERLOAD_NONE; + /* Reserve 3 bytes for option 52 (overload) if needed */ + size_t size = OPT_MAX - 3; int i, o, offset = 0;
for (o = 0; o < 255; o++) @@ -199,14 +217,47 @@ static int fill(struct msg *m) for (i = 0; i < opts[55].clen; i++) { o = opts[55].c[i]; if (opts[o].state != OPT_UNSET) - 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].state != OPT_UNSET && !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); + } + + /* Overflow unsent options into sname, then file */ + for (o = 0; (size_t)o < ARRAY_SIZE(opts); o++) { + if (opts[o].state == OPT_UNSET || 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].state == OPT_UNSET || opts[o].sent) + continue; + + if (!has_bootfile && + fill_one(m->file, sizeof(m->file) - 1, o, + &file_off)) + debug("DHCP: skipping option %i" + " (overload full)", o);
The logic here will omit all the "skipping option" messages if has_bootfile is true. I think it might be cleaner to change fill_one() to return void, and add a final pass generating the "skipping" messages if !opts[o].sent.
+ } + + if (sname_off) { + m->sname[sname_off] = 255; + *overload |= DHCP_OVERLOAD_SNAME; + } + + if (file_off) { + m->file[file_off] = 255; + *overload |= DHCP_OVERLOAD_FILE; + } + + + if (*overload) { + m->o[offset++] = 52; + m->o[offset++] = 1; + m->o[offset++] = *overload; }
m->o[offset++] = 255; @@ -320,6 +371,7 @@ static void opt_set_dns_search(const struct ctx *c, size_t max_len) int dhcp(const struct ctx *c, struct iov_tail *data) { char macstr[ETH_ADDRSTRLEN]; + enum dhcp_overload overload; size_t mlen, dlen, opt_len; struct in_addr mask, dst; struct ethhdr eh_storage; @@ -328,9 +380,12 @@ int dhcp(const struct ctx *c, struct iov_tail *data) const struct ethhdr *eh; const struct iphdr *iph; const struct udphdr *uh; + uint8_t bootfile[128]; struct msg m_storage; struct msg const *m; + bool has_bootfile; struct msg reply; + int bootfile_len; unsigned int i;
eh = IOV_REMOVE_HEADER(data, eh_storage); @@ -492,9 +547,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)); + /* 3 bytes reserved for option 52 (code, length, value) */ + opt_set_dns_search(c, OPT_MAX - 3); + + /* 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. + */ + has_bootfile = opts[67].slen > 0 && + (size_t)opts[67].slen < sizeof(reply.file); + if (has_bootfile) { + memcpy(bootfile, opts[67].s, opts[67].slen); + bootfile_len = opts[67].slen;
Why copy into the 'bootfile' temporary..
+ } + + dlen = offsetof(struct msg, o) + fill(&reply, &overload, has_bootfile);
- dlen = offsetof(struct msg, o) + fill(&reply); + if (has_bootfile) + memcpy(reply.file, bootfile, bootfile_len); ... then out again, rather than copying directly from opts[67].s into reply.file?
if (m->flags & FLAG_BROADCAST) dst = in4addr_broadcast; -- 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 way | around. http://www.ozlabs.org/~dgibson