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) <stipe.poljak.ext@ericsson.com>
Co-authored-by: Roy Marples <roy@marples.name>
This commit is contained in:
spoljak-ent 2024-11-07 14:15:49 +01:00 committed by GitHub
parent fd2f663416
commit 371c7c69bc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 426 additions and 6 deletions

View File

@ -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)
{
@ -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 &&

View File

@ -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)
@ -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)
{
@ -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;
@ -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. */
@ -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,
@ -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;
@ -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;

View File

@ -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
@ -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
@ -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.

View File

@ -87,6 +87,8 @@ const struct option cf_options[] = {
#ifndef SMALL
{"msuserclass", required_argument, NULL, O_MSUSERCLASS},
#endif
{"vsio", required_argument, NULL, O_VSIO},
{"vsio6", required_argument, NULL, O_VSIO6},
{"vendor", required_argument, NULL, 'v'},
{"waitip", optional_argument, NULL, 'w'},
{"exit", no_argument, NULL, 'x'},
@ -666,7 +668,11 @@ parse_option(struct dhcpcd_ctx *ctx, const char *ifname, struct if_options *ifo,
struct if_ia *ia;
uint8_t iaid[4];
#ifndef SMALL
struct in6_addr in6addr;
struct if_sla *sla, *slap;
struct vsio **vsiop = NULL, *vsio;
size_t *vsio_lenp = NULL, opt_max, opt_header;
struct vsio_so *vsio_so;
#endif
#endif
@ -897,6 +903,139 @@ parse_option(struct dhcpcd_ctx *ctx, const char *ifname, struct if_options *ifo,
ifo->userclass[0] = (uint8_t)s;
break;
#endif
case O_VSIO:
#ifndef SMALL
vsiop = &ifo->vsio;
vsio_lenp = &ifo->vsio_len;
opt_max = UINT8_MAX;
opt_header = sizeof(uint8_t) + sizeof(uint8_t);
#endif
/* FALLTHROUGH */
case O_VSIO6:
#ifndef SMALL
if (vsiop == NULL) {
vsiop = &ifo->vsio6;
vsio_lenp = &ifo->vsio6_len;
opt_max = UINT16_MAX;
opt_header = sizeof(uint16_t) + sizeof(uint16_t);
}
#endif
ARG_REQUIRED;
#ifdef SMALL
logwarnx("%s: vendor options not compiled in", ifname);
return -1;
#else
fp = strwhite(arg);
if (fp)
*fp++ = '\0';
u = (uint32_t)strtou(arg, NULL, 0, 0, UINT32_MAX, &e);
if (e) {
logerrx("invalid code: %s", arg);
return -1;
}
fp = strskipwhite(fp);
p = strchr(fp, ',');
if (!p || !p[1]) {
logerrx("invalid vendor format: %s", arg);
return -1;
}
/* Strip and preserve the comma */
*p = '\0';
i = (int)strtoi(fp, NULL, 0, 1, (intmax_t)opt_max, &e);
*p = ',';
if (e) {
logerrx("vendor option should be between"
" 1 and %zu inclusive", opt_max);
return -1;
}
fp = p + 1;
if (fp) {
if (inet_pton(AF_INET, fp, &addr) == 1) {
s = sizeof(addr.s_addr);
dl = (size_t)s;
np = malloc(dl);
if (np == NULL) {
logerr(__func__);
return -1;
}
memcpy(np, &addr.s_addr, dl);
} else if (inet_pton(AF_INET6, fp, &in6addr) == 1) {
s = sizeof(in6addr.s6_addr);
dl = (size_t)s;
np = malloc(dl);
if (np == NULL) {
logerr(__func__);
return -1;
}
memcpy(np, &in6addr.s6_addr, dl);
} else {
s = parse_string(NULL, 0, fp);
if (s == -1) {
logerr(__func__);
return -1;
}
dl = (size_t)s;
np = malloc(dl);
if (np == NULL) {
logerr(__func__);
return -1;
}
parse_string(np, dl, fp);
}
} else {
dl = 0;
np = NULL;
}
for (sl = 0, vsio = *vsiop; sl < *vsio_lenp; sl++, vsio++) {
if (vsio->en == (uint32_t)u)
break;
}
if (sl == *vsio_lenp) {
vsio = reallocarray(*vsiop, *vsio_lenp + 1,
sizeof(**vsiop));
if (vsio == NULL) {
logerr("%s: reallocarray vsio", __func__);
free(np);
return -1;
}
*vsiop = vsio;
vsio = &(*vsiop)[(*vsio_lenp)++];
vsio->en = (uint32_t)u;
vsio->so = NULL;
vsio->so_len = 0;
}
for (sl = 0, vsio_so = vsio->so;
sl < vsio->so_len;
sl++, vsio_so++)
opt_max -= opt_header + vsio_so->len;
if (opt_header + dl > opt_max) {
logerrx("vsio is too big: %s", fp);
free(np);
return -1;
}
vsio_so = reallocarray(vsio->so, vsio->so_len + 1,
sizeof(*vsio_so));
if (vsio_so == NULL) {
logerr("%s: reallocarray vsio_so", __func__);
free(np);
return -1;
}
vsio->so = vsio_so;
vsio_so = &vsio->so[vsio->so_len++];
vsio_so->opt = (uint16_t)i;
vsio_so->len = (uint16_t)dl;
vsio_so->data = np;
break;
#endif
case 'v':
ARG_REQUIRED;
p = strchr(arg, ',');
@ -2801,6 +2940,10 @@ free_options(struct dhcpcd_ctx *ctx, struct if_options *ifo)
#ifdef AUTH
struct token *token;
#endif
#ifndef SMALL
struct vsio *vsio;
struct vsio_so *vsio_so;
#endif
if (ifo == NULL)
return;
@ -2856,6 +2999,30 @@ free_options(struct dhcpcd_ctx *ctx, struct if_options *ifo)
vo++, ifo->vivco_len--)
free(vo->data);
free(ifo->vivco);
#ifndef SMALL
for (vsio = ifo->vsio;
ifo->vsio_len > 0;
vsio++, ifo->vsio_len--)
{
for (vsio_so = vsio->so;
vsio->so_len > 0;
vsio_so++, vsio->so_len--)
free(vsio_so->data);
free(vsio->so);
}
free(ifo->vsio);
for (vsio = ifo->vsio6;
ifo->vsio6_len > 0;
vsio++, ifo->vsio6_len--)
{
for (vsio_so = vsio->so;
vsio->so_len > 0;
vsio_so++, vsio->so_len--)
free(vsio_so->data);
free(vsio->so);
}
free(ifo->vsio6);
#endif
for (opt = ifo->vivso_override;
ifo->vivso_override_len > 0;
opt++, ifo->vivso_override_len--)

View File

@ -61,6 +61,7 @@
#define USERCLASS_MAX_LEN 255
#define VENDOR_MAX_LEN 255
#define MUDURL_MAX_LEN 255
#define ENTERPRISE_NUMS_MAX_LEN 255
#define DHCPCD_ARP (1ULL << 0)
#define DHCPCD_RELEASE (1ULL << 1)
@ -191,6 +192,8 @@
#define O_REQUEST_TIME O_BASE + 54
#define O_FALLBACK_TIME O_BASE + 55
#define O_IPV4LL_TIME O_BASE + 56
#define O_VSIO O_BASE + 57
#define O_VSIO6 O_BASE + 58
extern const struct option cf_options[];
@ -222,6 +225,19 @@ struct vivco {
uint8_t *data;
};
#ifndef SMALL
struct vsio_so {
uint16_t opt;
uint16_t len;
void *data;
};
struct vsio {
uint32_t en;
size_t so_len;
struct vsio_so *so;
};
#endif
struct if_options {
time_t mtime;
uint8_t iaid[4];
@ -293,6 +309,13 @@ struct if_options {
struct dhcp_opt *vivso_override;
size_t vivso_override_len;
#ifndef SMALL
size_t vsio_len;
struct vsio *vsio;
size_t vsio6_len;
struct vsio *vsio6;
#endif
struct auth auth;
};