+/*
+ * Walk the entire tag set looking for option. The sought option may be
+ * encapsulated. ENOENT indicates the walk completed without finding the
+ * option. If we run out of buffer during the walk the function will return
+ * ENOSPC.
+ */
+static efx_rc_t
+efx_dhcp_walk_tags(
+ __deref_inout uint8_t **tagpp,
+ __inout size_t *buffer_sizep,
+ __in uint16_t opt)
+{
+ efx_rc_t rc = 0;
+ boolean_t is_encap = B_FALSE;
+
+ if (DHCP_IS_ENCAP_OPT(opt)) {
+ /*
+ * Look for the encapsulator and, if found, limit ourselves
+ * to its payload. If it's not found then the entire tag
+ * cannot be found, so the encapsulated opt search is
+ * skipped.
+ */
+ rc = efx_dhcp_walk_tags(tagpp, buffer_sizep,
+ DHCP_ENCAPSULATOR(opt));
+ if (rc == 0) {
+ *buffer_sizep = ((efx_dhcp_tag_hdr_t *)*tagpp)->length;
+ (*tagpp) += sizeof (efx_dhcp_tag_hdr_t);
+ }
+ opt = DHCP_ENCAPSULATED(opt);
+ is_encap = B_TRUE;
+ }
+
+ EFSYS_ASSERT(!DHCP_IS_ENCAP_OPT(opt));
+
+ while (rc == 0) {
+ size_t size;
+
+ if (*buffer_sizep == 0) {
+ rc = ENOSPC;
+ goto fail1;
+ }
+
+ if (DHCP_ENCAPSULATED(**tagpp) == opt)
+ break;
+
+ if ((**tagpp) == EFX_DHCP_END) {
+ rc = ENOENT;
+ break;
+ } else if ((**tagpp) == EFX_DHCP_PAD) {
+ size = 1;
+ } else {
+ if (*buffer_sizep < sizeof (efx_dhcp_tag_hdr_t)) {
+ rc = ENOSPC;
+ goto fail2;
+ }
+
+ size =
+ DHCP_FULL_TAG_LENGTH((efx_dhcp_tag_hdr_t *)*tagpp);
+ }
+
+ if (size > *buffer_sizep) {
+ rc = ENOSPC;
+ goto fail3;
+ }
+
+ (*tagpp) += size;
+ (*buffer_sizep) -= size;
+
+ if ((*buffer_sizep == 0) && is_encap) {
+ /* Search within encapulator tag finished */
+ rc = ENOENT;
+ break;
+ }
+ }
+
+ /*
+ * Returns 0 if found otherwise ENOENT indicating search finished
+ * correctly
+ */
+ return (rc);
+
+fail3:
+ EFSYS_PROBE(fail3);
+fail2:
+ EFSYS_PROBE(fail2);
+fail1:
+ EFSYS_PROBE1(fail1, efx_rc_t, rc);
+
+ return (rc);
+}
+
+/*
+ * Locate value buffer for option in the given buffer.
+ * Returns 0 if found, ENOENT indicating search finished
+ * correctly, otherwise search failed before completion.
+ */
+ __checkReturn efx_rc_t
+efx_dhcp_find_tag(
+ __in_bcount(buffer_length) uint8_t *bufferp,
+ __in size_t buffer_length,
+ __in uint16_t opt,
+ __deref_out uint8_t **valuepp,
+ __out size_t *value_lengthp)
+{
+ efx_rc_t rc;
+ uint8_t *tagp = bufferp;
+ size_t len = buffer_length;
+
+ rc = efx_dhcp_walk_tags(&tagp, &len, opt);
+ if (rc == 0) {
+ efx_dhcp_tag_hdr_t *hdrp;
+
+ hdrp = (efx_dhcp_tag_hdr_t *)tagp;
+ *valuepp = (uint8_t *)(&hdrp[1]);
+ *value_lengthp = hdrp->length;
+ } else if (rc != ENOENT) {
+ goto fail1;
+ }
+
+ return (rc);
+
+fail1:
+ EFSYS_PROBE1(fail1, efx_rc_t, rc);
+
+ return (rc);
+}
+
+/*
+ * Locate the end tag in the given buffer.
+ * Returns 0 if found, ENOENT indicating search finished
+ * correctly but end tag was not found; otherwise search
+ * failed before completion.
+ */
+ __checkReturn efx_rc_t
+efx_dhcp_find_end(
+ __in_bcount(buffer_length) uint8_t *bufferp,
+ __in size_t buffer_length,
+ __deref_out uint8_t **endpp)
+{
+ efx_rc_t rc;
+ uint8_t *endp = bufferp;
+ size_t len = buffer_length;
+
+ rc = efx_dhcp_walk_tags(&endp, &len, EFX_DHCP_END);
+ if (rc == 0)
+ *endpp = endp;
+ else if (rc != ENOENT)
+ goto fail1;
+
+ return (rc);
+
+fail1:
+ EFSYS_PROBE1(fail1, efx_rc_t, rc);
+
+ return (rc);
+}
+
+
+/*
+ * Delete the given tag from anywhere in the buffer. Copes with
+ * encapsulated tags, and updates or deletes the encapsulating opt as
+ * necessary.
+ */
+ __checkReturn efx_rc_t
+efx_dhcp_delete_tag(
+ __inout_bcount(buffer_length) uint8_t *bufferp,
+ __in size_t buffer_length,
+ __in uint16_t opt)
+{
+ efx_rc_t rc;
+ efx_dhcp_tag_hdr_t *hdrp;
+ size_t len;
+ uint8_t *startp;
+ uint8_t *endp;
+
+ len = buffer_length;
+ startp = bufferp;
+
+ if (!DHCP_OPT_HAS_VALUE(DHCP_ENCAPSULATED(opt))) {
+ rc = EINVAL;
+ goto fail1;
+ }
+
+ rc = efx_dhcp_walk_tags(&startp, &len, opt);
+ if (rc != 0)
+ goto fail1;
+
+ hdrp = (efx_dhcp_tag_hdr_t *)startp;
+
+ if (DHCP_IS_ENCAP_OPT(opt)) {
+ uint8_t tag_length = DHCP_FULL_TAG_LENGTH(hdrp);
+ uint8_t *encapp = bufferp;
+ efx_dhcp_tag_hdr_t *encap_hdrp;
+
+ len = buffer_length;
+ rc = efx_dhcp_walk_tags(&encapp, &len,
+ DHCP_ENCAPSULATOR(opt));
+ if (rc != 0)
+ goto fail2;
+
+ encap_hdrp = (efx_dhcp_tag_hdr_t *)encapp;
+ if (encap_hdrp->length > tag_length) {
+ encap_hdrp->length = (uint8_t)(
+ (size_t)encap_hdrp->length - tag_length);
+ } else {
+ /* delete the encapsulating tag */
+ hdrp = encap_hdrp;
+ }
+ }
+
+ startp = (uint8_t *)hdrp;
+ endp = (uint8_t *)DHCP_NEXT_TAG(hdrp);
+
+ if (startp < bufferp) {
+ rc = EINVAL;
+ goto fail3;
+ }
+
+ if (endp > &bufferp[buffer_length]) {
+ rc = EINVAL;
+ goto fail4;
+ }
+
+ memmove(startp, endp,
+ buffer_length - (endp - bufferp));
+
+ return (0);
+
+fail4:
+ EFSYS_PROBE(fail4);
+fail3:
+ EFSYS_PROBE(fail3);
+fail2:
+ EFSYS_PROBE(fail2);
+fail1:
+ EFSYS_PROBE1(fail1, efx_rc_t, rc);
+
+ return (rc);
+}
+
+/*
+ * Write the tag header into write_pointp and optionally copies the payload
+ * into the space following.
+ */
+static void
+efx_dhcp_write_tag(
+ __in uint8_t *write_pointp,
+ __in uint16_t opt,
+ __in_bcount_opt(value_length)
+ uint8_t *valuep,
+ __in size_t value_length)
+{
+ efx_dhcp_tag_hdr_t *hdrp = (efx_dhcp_tag_hdr_t *)write_pointp;
+ hdrp->tag = DHCP_ENCAPSULATED(opt);
+ hdrp->length = (uint8_t)value_length;
+ if ((value_length > 0) && (valuep != NULL))
+ memcpy(&hdrp[1], valuep, value_length);
+}
+
+/*
+ * Add the given tag to the end of the buffer. Copes with creating an
+ * encapsulated tag, and updates or creates the encapsulating opt as
+ * necessary.
+ */
+ __checkReturn efx_rc_t
+efx_dhcp_add_tag(
+ __inout_bcount(buffer_length) uint8_t *bufferp,
+ __in size_t buffer_length,
+ __in uint16_t opt,
+ __in_bcount_opt(value_length) uint8_t *valuep,
+ __in size_t value_length)
+{
+ efx_rc_t rc;
+ efx_dhcp_tag_hdr_t *encap_hdrp = NULL;
+ uint8_t *insert_pointp = NULL;
+ uint8_t *endp;
+ size_t available_space;
+ size_t added_length;
+ size_t search_size;
+ uint8_t *searchp;
+
+ if (!DHCP_OPT_HAS_VALUE(DHCP_ENCAPSULATED(opt))) {
+ rc = EINVAL;
+ goto fail1;
+ }
+
+ if (value_length > DHCP_MAX_VALUE) {
+ rc = EINVAL;
+ goto fail2;
+ }
+
+ if ((value_length > 0) && (valuep == NULL)) {
+ rc = EINVAL;
+ goto fail3;
+ }
+
+ endp = bufferp;
+ available_space = buffer_length;
+ rc = efx_dhcp_walk_tags(&endp, &available_space, EFX_DHCP_END);
+ if (rc != 0)
+ goto fail4;
+
+ searchp = bufferp;
+ search_size = buffer_length;
+ if (DHCP_IS_ENCAP_OPT(opt)) {
+ rc = efx_dhcp_walk_tags(&searchp, &search_size,
+ DHCP_ENCAPSULATOR(opt));
+ if (rc == 0) {
+ encap_hdrp = (efx_dhcp_tag_hdr_t *)searchp;
+
+ /* Check encapsulated tag is not present */
+ search_size = encap_hdrp->length;
+ rc = efx_dhcp_walk_tags(&searchp, &search_size,
+ opt);
+ if (rc != ENOENT) {
+ rc = EINVAL;
+ goto fail5;
+ }
+
+ /* Check encapsulator will not overflow */
+ if (((size_t)encap_hdrp->length +
+ DHCP_CALC_TAG_LENGTH(value_length)) >
+ DHCP_MAX_VALUE) {
+ rc = E2BIG;
+ goto fail6;
+ }
+
+ /* Insert at start of existing encapsulator */
+ insert_pointp = (uint8_t *)&encap_hdrp[1];
+ opt = DHCP_ENCAPSULATED(opt);
+ } else if (rc == ENOENT) {
+ encap_hdrp = NULL;
+ } else {
+ goto fail7;
+ }
+ } else {
+ /* Check unencapsulated tag is not present */
+ rc = efx_dhcp_walk_tags(&searchp, &search_size,
+ opt);
+ if (rc != ENOENT) {
+ rc = EINVAL;
+ goto fail8;
+ }
+ }
+
+ if (insert_pointp == NULL) {
+ /* Insert at end of existing tags */
+ insert_pointp = endp;
+ }
+
+ /* Includes the new encapsulator tag hdr if required */
+ added_length = DHCP_CALC_TAG_LENGTH(value_length) +
+ (DHCP_IS_ENCAP_OPT(opt) ? sizeof (efx_dhcp_tag_hdr_t) : 0);
+
+ if (available_space <= added_length) {
+ rc = ENOMEM;
+ goto fail9;
+ }
+
+ memmove(insert_pointp + added_length, insert_pointp,
+ available_space - added_length);
+
+ if (DHCP_IS_ENCAP_OPT(opt)) {
+ /* Create new encapsulator header */
+ added_length -= sizeof (efx_dhcp_tag_hdr_t);
+ efx_dhcp_write_tag(insert_pointp,
+ DHCP_ENCAPSULATOR(opt), NULL, added_length);
+ insert_pointp += sizeof (efx_dhcp_tag_hdr_t);
+ } else if (encap_hdrp)
+ /* Modify existing encapsulator header */
+ encap_hdrp->length +=
+ ((uint8_t)DHCP_CALC_TAG_LENGTH(value_length));
+
+ efx_dhcp_write_tag(insert_pointp, opt, valuep, value_length);
+
+ return (0);
+
+fail9:
+ EFSYS_PROBE(fail9);
+fail8:
+ EFSYS_PROBE(fail8);
+fail7:
+ EFSYS_PROBE(fail7);
+fail6:
+ EFSYS_PROBE(fail6);
+fail5:
+ EFSYS_PROBE(fail5);
+fail4:
+ EFSYS_PROBE(fail4);
+fail3:
+ EFSYS_PROBE(fail3);
+fail2:
+ EFSYS_PROBE(fail2);
+fail1:
+ EFSYS_PROBE1(fail1, efx_rc_t, rc);
+
+ return (rc);
+}
+
+/*
+ * Update an existing tag to the new value. Copes with encapsulated
+ * tags, and updates the encapsulating opt as necessary.
+ */
+ __checkReturn efx_rc_t
+efx_dhcp_update_tag(
+ __inout_bcount(buffer_length) uint8_t *bufferp,
+ __in size_t buffer_length,
+ __in uint16_t opt,
+ __in uint8_t *value_locationp,
+ __in_bcount_opt(value_length) uint8_t *valuep,
+ __in size_t value_length)
+{
+ efx_rc_t rc;
+ uint8_t *write_pointp = value_locationp - sizeof (efx_dhcp_tag_hdr_t);
+ efx_dhcp_tag_hdr_t *hdrp = (efx_dhcp_tag_hdr_t *)write_pointp;
+ efx_dhcp_tag_hdr_t *encap_hdrp = NULL;
+ size_t old_length;
+
+ if (!DHCP_OPT_HAS_VALUE(DHCP_ENCAPSULATED(opt))) {
+ rc = EINVAL;
+ goto fail1;
+ }
+
+ if (value_length > DHCP_MAX_VALUE) {
+ rc = EINVAL;
+ goto fail2;
+ }
+
+ if ((value_length > 0) && (valuep == NULL)) {
+ rc = EINVAL;
+ goto fail3;
+ }
+
+ old_length = hdrp->length;
+
+ if (old_length < value_length) {
+ uint8_t *endp = bufferp;
+ size_t available_space = buffer_length;
+
+ rc = efx_dhcp_walk_tags(&endp, &available_space,
+ EFX_DHCP_END);
+ if (rc != 0)
+ goto fail4;
+
+ if (available_space < (value_length - old_length)) {
+ rc = EINVAL;
+ goto fail5;
+ }
+ }
+
+ if (DHCP_IS_ENCAP_OPT(opt)) {
+ uint8_t *encapp = bufferp;
+ size_t following_encap = buffer_length;
+ size_t new_length;
+
+ rc = efx_dhcp_walk_tags(&encapp, &following_encap,
+ DHCP_ENCAPSULATOR(opt));
+ if (rc != 0)
+ goto fail6;
+
+ encap_hdrp = (efx_dhcp_tag_hdr_t *)encapp;
+
+ new_length = ((size_t)encap_hdrp->length +
+ value_length - old_length);
+ /* Check encapsulator will not overflow */
+ if (new_length > DHCP_MAX_VALUE) {
+ rc = E2BIG;
+ goto fail7;
+ }
+
+ encap_hdrp->length = (uint8_t)new_length;
+ }
+
+ /*
+ * Move the following data up/down to accomodate the new payload
+ * length.
+ */
+ if (old_length != value_length) {
+ uint8_t *destp = (uint8_t *)DHCP_NEXT_TAG(hdrp) +
+ value_length - old_length;
+ size_t count = &bufferp[buffer_length] -
+ (uint8_t *)DHCP_NEXT_TAG(hdrp);
+
+ memmove(destp, DHCP_NEXT_TAG(hdrp), count);
+ }
+
+ EFSYS_ASSERT(hdrp->tag == DHCP_ENCAPSULATED(opt));
+ efx_dhcp_write_tag(write_pointp, opt, valuep, value_length);
+
+ return (0);
+
+fail7:
+ EFSYS_PROBE(fail7);
+fail6:
+ EFSYS_PROBE(fail6);
+fail5:
+ EFSYS_PROBE(fail5);
+fail4:
+ EFSYS_PROBE(fail4);
+fail3:
+ EFSYS_PROBE(fail3);
+fail2:
+ EFSYS_PROBE(fail2);
+fail1:
+ EFSYS_PROBE1(fail1, efx_rc_t, rc);
+
+ return (rc);
+}
+
+