Skip to content

Commit 371c7c6

Browse files
DHCPv6: Add support for sending Option 17 (VSIO) (#383)
* DHCP: Add support for sending DHCP option 125 and DHCPv6 Option 17 (VSIO) Note wireshark doesn't decode option 125 correctly when the it needs to be split into more options if it exceeds 255 bytes. --------- Signed-off-by: Stipe Poljak (EXT) <[email protected]> Co-authored-by: Roy Marples <[email protected]>
1 parent fd2f663 commit 371c7c6

File tree

5 files changed

+426
-6
lines changed

5 files changed

+426
-6
lines changed

src/dhcp.c

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -723,6 +723,76 @@ dhcp_message_add_addr(struct bootp *bootp,
723723
return 0;
724724
}
725725

726+
#ifndef SMALL
727+
struct rfc3396_ctx {
728+
uint8_t code;
729+
uint8_t *len;
730+
uint8_t **buf;
731+
size_t buflen;
732+
};
733+
734+
/* Encode data as a DHCP Long Option, RFC 3396. */
735+
/* NOTE: Wireshark does not decode this correctly
736+
* when the option overflows the boundary and another option
737+
* is created to hold the resta of the data.
738+
* Tested against Wireshark-4.4.1 */
739+
#define RFC3396_BOUNDARY UINT8_MAX
740+
static ssize_t
741+
rfc3396_write(struct rfc3396_ctx *ctx, void *data, size_t len)
742+
{
743+
uint8_t *datap = data;
744+
size_t wlen, left, r = 0;
745+
746+
while (len != 0) {
747+
if (ctx->len == NULL || *ctx->len == RFC3396_BOUNDARY) {
748+
if (ctx->buflen < 2) {
749+
errno = ENOMEM;
750+
return -1;
751+
}
752+
*(*ctx->buf)++ = ctx->code;
753+
ctx->len = (*ctx->buf)++;
754+
*ctx->len = 0;
755+
r += 2;
756+
}
757+
758+
wlen = len < RFC3396_BOUNDARY ? len : RFC3396_BOUNDARY;
759+
left = RFC3396_BOUNDARY - *ctx->len;
760+
if (left < wlen)
761+
wlen = left;
762+
if (ctx->buflen < wlen) {
763+
errno = ENOMEM;
764+
return -1;
765+
}
766+
767+
memcpy(*ctx->buf, datap, wlen);
768+
datap += wlen;
769+
*ctx->buf += wlen;
770+
ctx->buflen -= wlen;
771+
*ctx->len = (uint8_t)(*ctx->len + wlen);
772+
len -= wlen;
773+
r += wlen;
774+
}
775+
776+
return (ssize_t)r;
777+
}
778+
779+
static ssize_t
780+
rfc3396_write_byte(struct rfc3396_ctx *ctx, uint8_t byte)
781+
{
782+
783+
return rfc3396_write(ctx, &byte, sizeof(byte));
784+
}
785+
786+
static uint8_t *
787+
rfc3396_zero(struct rfc3396_ctx *ctx) {
788+
uint8_t *zerop = *ctx->buf, zero = 0;
789+
790+
if (rfc3396_write(ctx, &zero, sizeof(zero)) == -1)
791+
return NULL;
792+
return zerop;
793+
}
794+
#endif
795+
726796
static ssize_t
727797
make_message(struct bootp **bootpm, const struct interface *ifp, uint8_t type)
728798
{
@@ -1095,6 +1165,50 @@ make_message(struct bootp **bootpm, const struct interface *ifp, uint8_t type)
10951165
}
10961166
}
10971167

1168+
#ifndef SMALL
1169+
if (ifo->vsio_len &&
1170+
!has_option_mask(ifo->nomask, DHO_VIVSO))
1171+
{
1172+
struct vsio *vso = ifo->vsio;
1173+
size_t vlen = ifo->vsio_len;
1174+
struct vsio_so *so;
1175+
size_t slen;
1176+
struct rfc3396_ctx rctx = {
1177+
.code = DHO_VIVSO,
1178+
.buf = &p,
1179+
.buflen = AREA_LEFT,
1180+
};
1181+
1182+
for (; vlen > 0; vso++, vlen--) {
1183+
if (vso->so_len == 0)
1184+
continue;
1185+
1186+
so = vso->so;
1187+
slen = vso->so_len;
1188+
1189+
ul = htonl(vso->en);
1190+
if (rfc3396_write(&rctx, &ul, sizeof(ul)) == -1)
1191+
goto toobig;
1192+
lp = rfc3396_zero(&rctx);
1193+
if (lp == NULL)
1194+
goto toobig;
1195+
1196+
for (; slen > 0; so++, slen--) {
1197+
if (rfc3396_write_byte(&rctx,
1198+
(uint8_t)so->opt) == -1)
1199+
goto toobig;
1200+
if (rfc3396_write_byte(&rctx,
1201+
(uint8_t)so->len) == -1)
1202+
goto toobig;
1203+
if (rfc3396_write(&rctx,
1204+
so->data, so->len) == -1)
1205+
goto toobig;
1206+
*lp = (uint8_t)(*lp + so->len + 2);
1207+
}
1208+
}
1209+
}
1210+
#endif
1211+
10981212
#ifdef AUTH
10991213
if ((ifo->auth.options & DHCPCD_AUTH_SENDREQUIRE) !=
11001214
DHCPCD_AUTH_SENDREQUIRE &&

src/dhcp6.c

Lines changed: 86 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,11 @@ static int dhcp6_hasprefixdelegation(struct interface *);
204204
!((ia)->flags & IPV6_AF_STALE) && \
205205
(ia)->prefix_vltime != 0)
206206

207+
208+
/* Gets a pointer to the length part of the option to fill it
209+
* in later. */
210+
#define NEXTLEN(p) ((p) + offsetof(struct dhcp6_option, len))
211+
207212
void
208213
dhcp6_printoptions(const struct dhcpcd_ctx *ctx,
209214
const struct dhcp_opt *opts, size_t opts_len)
@@ -337,6 +342,74 @@ dhcp6_makevendor(void *data, const struct interface *ifp)
337342
return sizeof(o) + len;
338343
}
339344

345+
#ifndef SMALL
346+
/* DHCPv6 Option 17 (Vendor-Specific Information Option) */
347+
static size_t
348+
dhcp6_makevendoropts(void *data, const struct interface *ifp)
349+
{
350+
uint8_t *p = data, *olenp;
351+
const struct if_options *ifo = ifp->options;
352+
size_t len = 0, olen;
353+
const struct vsio *vsio, *vsio_endp = ifo->vsio6 + ifo->vsio6_len;
354+
const struct vsio_so *so, *so_endp;
355+
struct dhcp6_option o;
356+
uint32_t en;
357+
uint16_t opt, slen;
358+
359+
for (vsio = ifo->vsio6; vsio != vsio_endp; ++vsio) {
360+
if (vsio->so_len == 0)
361+
continue;
362+
363+
if (p != NULL) {
364+
olenp = NEXTLEN(p);
365+
o.code = htons(D6_OPTION_VENDOR_OPTS);
366+
o.len = 0;
367+
memcpy(p, &o, sizeof(o));
368+
p += sizeof(o);
369+
370+
en = htonl(vsio->en);
371+
memcpy(p, &en, sizeof(en));
372+
p += sizeof(en);
373+
} else
374+
olenp = NULL;
375+
376+
olen = sizeof(en);
377+
378+
so_endp = vsio->so + vsio->so_len;
379+
for (so = vsio->so; so != so_endp; so++) {
380+
if (olen + sizeof(opt) + sizeof(slen)
381+
+ so->len > UINT16_MAX)
382+
{
383+
logerrx("%s: option too big", __func__);
384+
break;
385+
}
386+
387+
if (p != NULL) {
388+
opt = htons(so->opt);
389+
memcpy(p, &opt, sizeof(opt));
390+
p += sizeof(opt);
391+
slen = htons(so->len);
392+
memcpy(p, &slen, sizeof(slen));
393+
p += sizeof(slen);
394+
memcpy(p, so->data, so->len);
395+
p += so->len;
396+
}
397+
398+
olen += sizeof(opt) + sizeof(slen) + so->len;
399+
}
400+
401+
if (olenp != NULL) {
402+
slen = htons((uint16_t)olen);
403+
memcpy(olenp, &slen, sizeof(slen));
404+
}
405+
406+
len += sizeof(o) + olen;
407+
}
408+
409+
return len;
410+
}
411+
#endif
412+
340413
static void *
341414
dhcp6_findoption(void *data, size_t data_len, uint16_t code, uint16_t *len)
342415
{
@@ -805,6 +878,11 @@ dhcp6_makemessage(struct interface *ifp)
805878
if (!has_option_mask(ifo->nomask6, D6_OPTION_VENDOR_CLASS))
806879
len += dhcp6_makevendor(NULL, ifp);
807880

881+
#ifndef SMALL
882+
if (!has_option_mask(ifo->nomask6, D6_OPTION_VENDOR_OPTS))
883+
len += dhcp6_makevendoropts(NULL, ifp);
884+
#endif
885+
808886
/* IA */
809887
m = NULL;
810888
ml = 0;
@@ -950,7 +1028,6 @@ dhcp6_makemessage(struct interface *ifp)
9501028
p += (_len); \
9511029
} \
9521030
} while (0 /* CONSTCOND */)
953-
#define NEXTLEN (p + offsetof(struct dhcp6_option, len))
9541031

9551032
/* Options are listed in numerical order as per RFC 7844 Section 4.1
9561033
* XXX: They should be randomised. */
@@ -968,7 +1045,7 @@ dhcp6_makemessage(struct interface *ifp)
9681045

9691046
for (l = 0; IA && l < ifo->ia_len; l++) {
9701047
ifia = &ifo->ia[l];
971-
o_lenp = NEXTLEN;
1048+
o_lenp = NEXTLEN(p);
9721049
/* TA structure is the same as the others,
9731050
* it just lacks the T1 and T2 timers.
9741051
* These happen to be at the end of the struct,
@@ -1064,7 +1141,7 @@ dhcp6_makemessage(struct interface *ifp)
10641141
state->send->type != DHCP6_DECLINE &&
10651142
n_options)
10661143
{
1067-
o_lenp = NEXTLEN;
1144+
o_lenp = NEXTLEN(p);
10681145
o.len = 0;
10691146
COPYIN1(D6_OPTION_ORO, 0);
10701147
for (l = 0, opt = ifp->ctx->dhcp6_opts;
@@ -1125,11 +1202,16 @@ dhcp6_makemessage(struct interface *ifp)
11251202
if (!has_option_mask(ifo->nomask6, D6_OPTION_VENDOR_CLASS))
11261203
p += dhcp6_makevendor(p, ifp);
11271204

1205+
#ifndef SMALL
1206+
if (!has_option_mask(ifo->nomask6, D6_OPTION_VENDOR_OPTS))
1207+
p += dhcp6_makevendoropts(p, ifp);
1208+
#endif
1209+
11281210
if (state->send->type != DHCP6_RELEASE &&
11291211
state->send->type != DHCP6_DECLINE)
11301212
{
11311213
if (fqdn != FQDN_DISABLE) {
1132-
o_lenp = NEXTLEN;
1214+
o_lenp = NEXTLEN(p);
11331215
COPYIN1(D6_OPTION_FQDN, 0);
11341216
if (hl == 0)
11351217
*p = D6_FQDN_NONE;

src/dhcpcd.conf.5.in

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
2525
.\" SUCH DAMAGE.
2626
.\"
27-
.Dd October 11, 2024
27+
.Dd November 1, 2024
2828
.Dt DHCPCD.CONF 5
2929
.Os
3030
.Sh NAME
@@ -766,7 +766,7 @@ It should only be used for Microsoft DHCP servers and the
766766
should be set to "MSFT 98" or "MSFT 5.0".
767767
This option is not RFC compliant.
768768
.It Ic vendor Ar code , Ns Ar value
769-
Add an encapsulated vendor option.
769+
Add an encapsulated vendor-specific information option (DHCP Option 43).
770770
.Ar code
771771
should be between 1 and 254 inclusive.
772772
To add a raw vendor string, omit
@@ -782,6 +782,40 @@ Set the vendor option 03 with an IP address as a string.
782782
.D1 vendor 03,\e"192.168.0.2\e"
783783
Set un-encapsulated vendor option to hello world.
784784
.D1 vendor ,"hello world"
785+
.It Ic vsio Ar en Ar code, Ns Ar value
786+
Add an encapsulated vendor-specific information option (DHCP Option 125) with
787+
IANA assigned Enterprise Number
788+
.Ar en
789+
proceeding with the
790+
.Ar code
791+
which should be between 1 and 255 inclusive, and the
792+
.Ar value
793+
after the comma.
794+
Examples:
795+
.Pp
796+
Set the vsio for enterprise number 155 option 01 with an IPv4 address.
797+
.D1 vsio 155 01,192.168.1.1
798+
Set the vsio for enterprise number 155 option 02 with a string.
799+
.D1 vsio 155 02,"hello world"
800+
Set the vsio for enterprise number 255 option 01 with a hex code.
801+
.D1 vsio 255 01,01:02:03:04:05
802+
.It Ic vsio6 Ar en Ar code, Ns Ar value
803+
Add an encapsulated vendor-specific information option (DHCPv6 Option 17) with
804+
IANA assigned Enterprise Number
805+
.Ar en
806+
proceeding with the
807+
.Ar code
808+
which should be between 1 and 65535 inclusive, and the
809+
.Ar value
810+
after the comma.
811+
Examples:
812+
.Pp
813+
Set the vsio for enterprise number 155 option 01 with an IPv6 address.
814+
.D1 vsio6 155 01,2001:0db8:85a3:0000:0000:8a2e:0370:7334
815+
Set the vsio for enterprise number 155 option 02 with a string.
816+
.D1 vsio6 155 02,"hello world"
817+
Set the vsio for enterprise number 255 option 01 with a hex code.
818+
.D1 vsio6 255 01,01:02:03:04:05
785819
.It Ic vendorclassid Ar string
786820
Set the DHCP Vendor Class.
787821
DHCPv6 has its own option as shown below.

0 commit comments

Comments
 (0)