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)
{
}
}
+#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 &&
!((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)
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)
{
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;
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. */
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,
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;
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;
.\" 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
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
.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.
#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'},
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
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, ',');
#ifdef AUTH
struct token *token;
#endif
+#ifndef SMALL
+ struct vsio *vsio;
+ struct vsio_so *vsio_so;
+#endif
if (ifo == NULL)
return;
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--)
#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)
#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[];
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];
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;
};