]> git.ipfire.org Git - thirdparty/dhcpcd.git/commitdiff
DHCPv6: Add support for sending Option 17 (VSIO) (#383) 378/head
authorspoljak-ent <stipe.poljak.ext@ericsson.com>
Thu, 7 Nov 2024 13:15:49 +0000 (14:15 +0100)
committerGitHub <noreply@github.com>
Thu, 7 Nov 2024 13:15:49 +0000 (13:15 +0000)
* 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>
src/dhcp.c
src/dhcp6.c
src/dhcpcd.conf.5.in
src/if-options.c
src/if-options.h

index 2dbdfe5fab0490dd0528793b66cf772c8d731f1c..11c69bc1eb54c0301ddc40a3ac40f9cc5c4d98e3 100644 (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 &&
index 3fcb4b71ccce515a2dcc9c8ce39f89aff003e6cd..ca076e4ede42bc5d2b48bfcfcaf63c36e9835385 100644 (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;
index dacdc0158d1b2b0de7b526d610646f19b95e8db9..a67c68f772c29b676cd2704bebeb397b7257b9d5 100644 (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.
index 85de533009c01fc6fb52faee521536ba7e5c37fe..c50f65a1c5389706b00f336b337bc6925cb56ad5 100644 (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--)
index b77fc302fba1f2e08ab46c83abbaa8d40027cba9..f3b9c17aa5e71e0befcfe514944506510c5fb22f 100644 (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)
 #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;
 };