/* * * Embedded Linux library * * Copyright (C) 2020 Intel Corporation. All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ #include #include #include #include #include #include #include #include "dhcp-private.h" #include "private.h" #include "useful.h" #define BITS_PER_LONG (sizeof(unsigned long) * 8) static void dhcp_init_header(struct dhcp_message *message, uint8_t type) { switch (type) { case DHCP_MESSAGE_TYPE_OFFER: case DHCP_MESSAGE_TYPE_NAK: case DHCP_MESSAGE_TYPE_ACK: message->op = DHCP_OP_CODE_BOOTREPLY; break; default: message->op = DHCP_OP_CODE_BOOTREQUEST; break; } message->htype = 1; message->hlen = 6; message->magic = htonl(DHCP_MAGIC); message->options[0] = DHCP_OPTION_END; } #define LEN_CHECK(builder, next) \ if ((builder)->pos - (builder)->start + (next) > (builder)->max) \ return false; bool _dhcp_message_builder_append(struct dhcp_message_builder *builder, uint8_t code, size_t optlen, const void *optval) { switch (code) { case DHCP_OPTION_PAD: case DHCP_OPTION_END: LEN_CHECK(builder, 1U); builder->pos[0] = code; builder->pos += 1; break; default: LEN_CHECK(builder, optlen + 2); builder->pos[0] = code; builder->pos[1] = optlen; memcpy(builder->pos + 2, optval, optlen); builder->pos += optlen + 2; break; } return true; } bool _dhcp_message_builder_append_prl(struct dhcp_message_builder *builder, const unsigned long *reqopts) { uint8_t optlen = 0; unsigned int i; unsigned int j; for (i = 0; i < 256 / BITS_PER_LONG; i++) optlen += __builtin_popcountl(reqopts[i]); /* * This function assumes that there's enough space to put the PRL * into the buffer without resorting to file or sname overloading */ LEN_CHECK(builder, optlen + 2U); i = 0; builder->pos[i++] = DHCP_OPTION_PARAMETER_REQUEST_LIST; builder->pos[i++] = optlen; for (j = 0; j < 256; j++) { if (reqopts[j / BITS_PER_LONG] & 1UL << (j % BITS_PER_LONG)) { builder->pos[i++] = j; } } builder->pos += optlen + 2; return true; } bool _dhcp_message_builder_init(struct dhcp_message_builder *builder, struct dhcp_message *message, size_t len, uint8_t type) { if (!builder || !message || !len) return false; builder->max = len; builder->pos = (uint8_t *) message->options; builder->start = (uint8_t *) message; dhcp_init_header(message, type); return _dhcp_message_builder_append(builder, DHCP_OPTION_MESSAGE_TYPE, 1, &type); } static inline int dhcp_message_optimize(struct dhcp_message *message, const uint8_t *end) { /* * Don't bother sending a full sized dhcp_message as it is most likely * mostly zeros. Instead truncate it at DHCP_OPTION_END and align to * the nearest 4 byte boundary. Many implementations expect a packet * of a certain size or it is filtered, so we cap the length in * accordance to RFC 1542: * "The IP Total Length and UDP Length must be large enough to contain * the minimal BOOTP header of 300 octets" */ size_t len = align_len(end - (uint8_t *) message, 4); if (len < 300) len = 300; return len; } uint8_t *_dhcp_message_builder_finalize(struct dhcp_message_builder *builder, size_t *outlen) { _dhcp_message_builder_append(builder, DHCP_OPTION_END, 0, NULL); *outlen = dhcp_message_optimize((struct dhcp_message *)builder->start, builder->pos); return builder->start; } bool _dhcp_message_iter_init(struct dhcp_message_iter *iter, const struct dhcp_message *message, size_t len) { if (!message) return false; if (len < sizeof(struct dhcp_message)) return false; if (L_BE32_TO_CPU(message->magic) != DHCP_MAGIC) return false; memset(iter, 0, sizeof(*iter)); iter->message = message; iter->message_len = len; iter->max = len - sizeof(struct dhcp_message); iter->options = message->options; iter->can_overload = true; return true; } static bool next_option(struct dhcp_message_iter *iter, uint8_t *t, uint8_t *l, const void **v) { uint8_t type; uint8_t len; while (iter->pos < iter->max) { type = iter->options[iter->pos]; switch (type) { case DHCP_OPTION_PAD: iter->pos += 1; continue; case DHCP_OPTION_END: return false; default: break; } if (iter->pos + 2 >= iter->max) return false; len = iter->options[iter->pos + 1]; if (iter->pos + 2 + len > iter->max) return false; *t = type; *l = len; *v = &iter->options[iter->pos + 2]; iter->pos += 2 + len; return true; } return false; } bool _dhcp_message_iter_next(struct dhcp_message_iter *iter, uint8_t *type, uint8_t *len, const void **data) { bool r; uint8_t t, l; const void *v; do { r = next_option(iter, &t, &l, &v); if (!r) { iter->can_overload = false; if (iter->overload_file) { iter->options = iter->message->file; iter->pos = 0; iter->max = sizeof(iter->message->file); iter->overload_file = false; continue; } if (iter->overload_sname) { iter->options = iter->message->sname; iter->pos = 0; iter->max = sizeof(iter->message->sname); iter->overload_sname = false; continue; } return r; } switch (t) { case DHCP_OPTION_OVERLOAD: if (l != 1) continue; if (!iter->can_overload) continue; if (l_get_u8(v) & DHCP_OVERLOAD_FILE) iter->overload_file = true; if (l_get_u8(v) & DHCP_OVERLOAD_SNAME) iter->overload_sname = true; continue; default: if (type) *type = t; if (len) *len = l; if (data) *data = v; return r; } } while (true); return false; } int _dhcp_option_append(uint8_t **buf, size_t *buflen, uint8_t code, size_t optlen, const void *optval) { if (!buf || !buflen) return -EINVAL; switch (code) { case DHCP_OPTION_PAD: case DHCP_OPTION_END: if (*buflen < 1) return -ENOBUFS; (*buf)[0] = code; *buf += 1; *buflen -= 1; break; default: if (*buflen < optlen + 2) return -ENOBUFS; if (!optval) return -EINVAL; (*buf)[0] = code; (*buf)[1] = optlen; memcpy(&(*buf)[2], optval, optlen); *buf += optlen + 2; *buflen -= (optlen + 2); break; } return 0; }