Skip to content

Commit

Permalink
DHCPv6: Add support for sending Option 17 (VSIO) (#383)
Browse files Browse the repository at this point in the history
* 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]>
  • Loading branch information
spoljak-ent and rsmarples authored Nov 7, 2024
1 parent fd2f663 commit 371c7c6
Show file tree
Hide file tree
Showing 5 changed files with 426 additions and 6 deletions.
114 changes: 114 additions & 0 deletions src/dhcp.c
Original file line number Diff line number Diff line change
Expand Up @@ -723,6 +723,76 @@ dhcp_message_add_addr(struct bootp *bootp,
return 0;
}

#ifndef SMALL
struct rfc3396_ctx {
uint8_t code;
uint8_t *len;
uint8_t **buf;
size_t buflen;
};

/* Encode data as a DHCP Long Option, RFC 3396. */
/* NOTE: Wireshark does not decode this correctly
* when the option overflows the boundary and another option
* is created to hold the resta of the data.
* Tested against Wireshark-4.4.1 */
#define RFC3396_BOUNDARY UINT8_MAX
static ssize_t
rfc3396_write(struct rfc3396_ctx *ctx, void *data, size_t len)
{
uint8_t *datap = data;
size_t wlen, left, r = 0;

while (len != 0) {
if (ctx->len == NULL || *ctx->len == RFC3396_BOUNDARY) {
if (ctx->buflen < 2) {
errno = ENOMEM;
return -1;
}
*(*ctx->buf)++ = ctx->code;
ctx->len = (*ctx->buf)++;
*ctx->len = 0;
r += 2;
}

wlen = len < RFC3396_BOUNDARY ? len : RFC3396_BOUNDARY;
left = RFC3396_BOUNDARY - *ctx->len;
if (left < wlen)
wlen = left;
if (ctx->buflen < wlen) {
errno = ENOMEM;
return -1;
}

memcpy(*ctx->buf, datap, wlen);
datap += wlen;
*ctx->buf += wlen;
ctx->buflen -= wlen;
*ctx->len = (uint8_t)(*ctx->len + wlen);
len -= wlen;
r += wlen;
}

return (ssize_t)r;
}

static ssize_t
rfc3396_write_byte(struct rfc3396_ctx *ctx, uint8_t byte)
{

return rfc3396_write(ctx, &byte, sizeof(byte));
}

static uint8_t *
rfc3396_zero(struct rfc3396_ctx *ctx) {
uint8_t *zerop = *ctx->buf, zero = 0;

if (rfc3396_write(ctx, &zero, sizeof(zero)) == -1)
return NULL;
return zerop;
}
#endif

static ssize_t
make_message(struct bootp **bootpm, const struct interface *ifp, uint8_t type)
{
Expand Down Expand Up @@ -1095,6 +1165,50 @@ make_message(struct bootp **bootpm, const struct interface *ifp, uint8_t type)
}
}

#ifndef SMALL
if (ifo->vsio_len &&
!has_option_mask(ifo->nomask, DHO_VIVSO))
{
struct vsio *vso = ifo->vsio;
size_t vlen = ifo->vsio_len;
struct vsio_so *so;
size_t slen;
struct rfc3396_ctx rctx = {
.code = DHO_VIVSO,
.buf = &p,
.buflen = AREA_LEFT,
};

for (; vlen > 0; vso++, vlen--) {
if (vso->so_len == 0)
continue;

so = vso->so;
slen = vso->so_len;

ul = htonl(vso->en);
if (rfc3396_write(&rctx, &ul, sizeof(ul)) == -1)
goto toobig;
lp = rfc3396_zero(&rctx);
if (lp == NULL)
goto toobig;

for (; slen > 0; so++, slen--) {
if (rfc3396_write_byte(&rctx,
(uint8_t)so->opt) == -1)
goto toobig;
if (rfc3396_write_byte(&rctx,
(uint8_t)so->len) == -1)
goto toobig;
if (rfc3396_write(&rctx,
so->data, so->len) == -1)
goto toobig;
*lp = (uint8_t)(*lp + so->len + 2);
}
}
}
#endif

#ifdef AUTH
if ((ifo->auth.options & DHCPCD_AUTH_SENDREQUIRE) !=
DHCPCD_AUTH_SENDREQUIRE &&
Expand Down
90 changes: 86 additions & 4 deletions src/dhcp6.c
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,11 @@ static int dhcp6_hasprefixdelegation(struct interface *);
!((ia)->flags & IPV6_AF_STALE) && \
(ia)->prefix_vltime != 0)


/* Gets a pointer to the length part of the option to fill it
* in later. */
#define NEXTLEN(p) ((p) + offsetof(struct dhcp6_option, len))

void
dhcp6_printoptions(const struct dhcpcd_ctx *ctx,
const struct dhcp_opt *opts, size_t opts_len)
Expand Down Expand Up @@ -337,6 +342,74 @@ dhcp6_makevendor(void *data, const struct interface *ifp)
return sizeof(o) + len;
}

#ifndef SMALL
/* DHCPv6 Option 17 (Vendor-Specific Information Option) */
static size_t
dhcp6_makevendoropts(void *data, const struct interface *ifp)
{
uint8_t *p = data, *olenp;
const struct if_options *ifo = ifp->options;
size_t len = 0, olen;
const struct vsio *vsio, *vsio_endp = ifo->vsio6 + ifo->vsio6_len;
const struct vsio_so *so, *so_endp;
struct dhcp6_option o;
uint32_t en;
uint16_t opt, slen;

for (vsio = ifo->vsio6; vsio != vsio_endp; ++vsio) {
if (vsio->so_len == 0)
continue;

if (p != NULL) {
olenp = NEXTLEN(p);
o.code = htons(D6_OPTION_VENDOR_OPTS);
o.len = 0;
memcpy(p, &o, sizeof(o));
p += sizeof(o);

en = htonl(vsio->en);
memcpy(p, &en, sizeof(en));
p += sizeof(en);
} else
olenp = NULL;

olen = sizeof(en);

so_endp = vsio->so + vsio->so_len;
for (so = vsio->so; so != so_endp; so++) {
if (olen + sizeof(opt) + sizeof(slen)
+ so->len > UINT16_MAX)
{
logerrx("%s: option too big", __func__);
break;
}

if (p != NULL) {
opt = htons(so->opt);
memcpy(p, &opt, sizeof(opt));
p += sizeof(opt);
slen = htons(so->len);
memcpy(p, &slen, sizeof(slen));
p += sizeof(slen);
memcpy(p, so->data, so->len);
p += so->len;
}

olen += sizeof(opt) + sizeof(slen) + so->len;
}

if (olenp != NULL) {
slen = htons((uint16_t)olen);
memcpy(olenp, &slen, sizeof(slen));
}

len += sizeof(o) + olen;
}

return len;
}
#endif

static void *
dhcp6_findoption(void *data, size_t data_len, uint16_t code, uint16_t *len)
{
Expand Down Expand Up @@ -805,6 +878,11 @@ dhcp6_makemessage(struct interface *ifp)
if (!has_option_mask(ifo->nomask6, D6_OPTION_VENDOR_CLASS))
len += dhcp6_makevendor(NULL, ifp);

#ifndef SMALL
if (!has_option_mask(ifo->nomask6, D6_OPTION_VENDOR_OPTS))
len += dhcp6_makevendoropts(NULL, ifp);
#endif

/* IA */
m = NULL;
ml = 0;
Expand Down Expand Up @@ -950,7 +1028,6 @@ dhcp6_makemessage(struct interface *ifp)
p += (_len); \
} \
} while (0 /* CONSTCOND */)
#define NEXTLEN (p + offsetof(struct dhcp6_option, len))

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

for (l = 0; IA && l < ifo->ia_len; l++) {
ifia = &ifo->ia[l];
o_lenp = NEXTLEN;
o_lenp = NEXTLEN(p);
/* TA structure is the same as the others,
* it just lacks the T1 and T2 timers.
* These happen to be at the end of the struct,
Expand Down Expand Up @@ -1064,7 +1141,7 @@ dhcp6_makemessage(struct interface *ifp)
state->send->type != DHCP6_DECLINE &&
n_options)
{
o_lenp = NEXTLEN;
o_lenp = NEXTLEN(p);
o.len = 0;
COPYIN1(D6_OPTION_ORO, 0);
for (l = 0, opt = ifp->ctx->dhcp6_opts;
Expand Down Expand Up @@ -1125,11 +1202,16 @@ dhcp6_makemessage(struct interface *ifp)
if (!has_option_mask(ifo->nomask6, D6_OPTION_VENDOR_CLASS))
p += dhcp6_makevendor(p, ifp);

#ifndef SMALL
if (!has_option_mask(ifo->nomask6, D6_OPTION_VENDOR_OPTS))
p += dhcp6_makevendoropts(p, ifp);
#endif

if (state->send->type != DHCP6_RELEASE &&
state->send->type != DHCP6_DECLINE)
{
if (fqdn != FQDN_DISABLE) {
o_lenp = NEXTLEN;
o_lenp = NEXTLEN(p);
COPYIN1(D6_OPTION_FQDN, 0);
if (hl == 0)
*p = D6_FQDN_NONE;
Expand Down
38 changes: 36 additions & 2 deletions src/dhcpcd.conf.5.in
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
.Dd October 11, 2024
.Dd November 1, 2024
.Dt DHCPCD.CONF 5
.Os
.Sh NAME
Expand Down Expand Up @@ -766,7 +766,7 @@ It should only be used for Microsoft DHCP servers and the
should be set to "MSFT 98" or "MSFT 5.0".
This option is not RFC compliant.
.It Ic vendor Ar code , Ns Ar value
Add an encapsulated vendor option.
Add an encapsulated vendor-specific information option (DHCP Option 43).
.Ar code
should be between 1 and 254 inclusive.
To add a raw vendor string, omit
Expand All @@ -782,6 +782,40 @@ Set the vendor option 03 with an IP address as a string.
.D1 vendor 03,\e"192.168.0.2\e"
Set un-encapsulated vendor option to hello world.
.D1 vendor ,"hello world"
.It Ic vsio Ar en Ar code, Ns Ar value
Add an encapsulated vendor-specific information option (DHCP Option 125) with
IANA assigned Enterprise Number
.Ar en
proceeding with the
.Ar code
which should be between 1 and 255 inclusive, and the
.Ar value
after the comma.
Examples:
.Pp
Set the vsio for enterprise number 155 option 01 with an IPv4 address.
.D1 vsio 155 01,192.168.1.1
Set the vsio for enterprise number 155 option 02 with a string.
.D1 vsio 155 02,"hello world"
Set the vsio for enterprise number 255 option 01 with a hex code.
.D1 vsio 255 01,01:02:03:04:05
.It Ic vsio6 Ar en Ar code, Ns Ar value
Add an encapsulated vendor-specific information option (DHCPv6 Option 17) with
IANA assigned Enterprise Number
.Ar en
proceeding with the
.Ar code
which should be between 1 and 65535 inclusive, and the
.Ar value
after the comma.
Examples:
.Pp
Set the vsio for enterprise number 155 option 01 with an IPv6 address.
.D1 vsio6 155 01,2001:0db8:85a3:0000:0000:8a2e:0370:7334
Set the vsio for enterprise number 155 option 02 with a string.
.D1 vsio6 155 02,"hello world"
Set the vsio for enterprise number 255 option 01 with a hex code.
.D1 vsio6 255 01,01:02:03:04:05
.It Ic vendorclassid Ar string
Set the DHCP Vendor Class.
DHCPv6 has its own option as shown below.
Expand Down
Loading

0 comments on commit 371c7c6

Please sign in to comment.