/* * DHCPv4 Incoming Messages * * This file implements the message parser object for incoming DHCP4 messages. * It takes a linear data blob as input, and provides accessors for the message * content. * * This wrapper mainly deals with the OPTIONs array. That is, in hides the * different overload-sections the DHCP4 spec defines, it concatenates * duplicate option fields (as described by the spec), and provides a * consistent view to the caller. * * Internally, for every incoming message we linearize its OPTIONs. This means, * we create a copy of the contents, and merge all duplicate options into a * single option entry. We then provide accessors to the caller to easily get * O(1) access to individual fields. */ #include #include #include #include #include #include #include #include #include #include #include #include "n-dhcp4.h" #include "n-dhcp4-private.h" static void n_dhcp4_incoming_prefetch(NDhcp4Incoming *incoming, size_t *offset, uint8_t option, const uint8_t *raw, size_t n_raw) { uint8_t o, l; size_t pos; for (pos = 0; pos < n_raw; ) { o = raw[pos++]; if (o == N_DHCP4_OPTION_PAD) continue; if (o == N_DHCP4_OPTION_END) return; /* bail out if no remaining space for length field */ if (pos >= n_raw) return; /* bail out if length exceeds the available space */ l = raw[pos++]; if (l > n_raw || pos > n_raw - l) return; /* prefetch content if it matches @option */ if (o == option) { memcpy((uint8_t *)&incoming->message + *offset, raw + pos, l); *offset += l; } pos += l; } } static void n_dhcp4_incoming_merge(NDhcp4Incoming *incoming, size_t *offset, uint8_t overload, uint8_t option) { uint8_t *m = (uint8_t *)&incoming->message; size_t pos; /* * Prefetch all options matching @option from the 3 sections, * concatenating their content. Remember the offset and size of the * option in our message state. */ pos = *offset; /* prefetch option from OPTIONS */ n_dhcp4_incoming_prefetch(incoming, offset, option, m + offsetof(NDhcp4Message, options), incoming->n_message - offsetof(NDhcp4Message, options)); /* prefetch option from FILE */ if (overload & N_DHCP4_OVERLOAD_FILE) n_dhcp4_incoming_prefetch(incoming, offset, option, m + offsetof(NDhcp4Message, file), sizeof(incoming->message.file)); /* prefetch option from SNAME */ if (overload & N_DHCP4_OVERLOAD_SNAME) n_dhcp4_incoming_prefetch(incoming, offset, option, m + offsetof(NDhcp4Message, sname), sizeof(incoming->message.sname)); incoming->options[option].value = m + pos; incoming->options[option].size = *offset - pos; } static void n_dhcp4_incoming_linearize(NDhcp4Incoming *incoming) { uint8_t *m, o, l, overload; size_t i, pos, end, offset; /* * Linearize all OPTIONs of the incoming message. We know that * @incoming->message is preallocated to be big enough to hold the * entire linearized message _trailing_ the original copy. All we have * to do is walk the raw message in @incoming->message and for each * option we find, copy it into the trailing space, concatenating all * instances we find. * * Before we can copy the individual options, we must scan for the * OVERLOAD option. This is required so our prefetcher knows which data * arrays to scan for prefetching. * * So far, we require the OVERLOAD option to be present in the * options-array (which is obvious and a given). However, if the option * occurs multiple times outside of the options-array (i.e., SNAME or * FILE), we silently ignore them. The specification does not allow * multiple OVERLOAD options, anyway. Hence, this behavior only defines * what we do when we see broken implementations, and we currently seem * to support all styles we saw in the wild so far. */ m = (uint8_t *)&incoming->message; offset = incoming->n_message; n_dhcp4_incoming_merge(incoming, &offset, 0, N_DHCP4_OPTION_OVERLOAD); if (incoming->options[N_DHCP4_OPTION_OVERLOAD].size >= 1) overload = *incoming->options[N_DHCP4_OPTION_OVERLOAD].value; else overload = 0; for (i = 0; i < 3; ++i) { if (i == 0) { /* walk OPTIONS */ pos = offsetof(NDhcp4Message, options); end = incoming->n_message; } else if (i == 1) { /* walk FILE */ if (!(overload & N_DHCP4_OVERLOAD_FILE)) continue; pos = offsetof(NDhcp4Message, file); end = pos + sizeof(incoming->message.file); } else { /* walk SNAME */ if (!(overload & N_DHCP4_OVERLOAD_SNAME)) continue; pos = offsetof(NDhcp4Message, sname); end = pos + sizeof(incoming->message.sname); } while (pos < end) { o = m[pos++]; if (o == N_DHCP4_OPTION_PAD) continue; if (o == N_DHCP4_OPTION_END) break; if (pos >= end) break; l = m[pos++]; if (l > end || pos > end - l) break; if (!incoming->options[o].value) n_dhcp4_incoming_merge(incoming, &offset, overload, o); pos += l; } } } /** * n_dhcp4_incoming_new() - Allocate new incoming message object * @incomingp: output argument for new object * @raw: raw message blob * @n_raw: length of the raw message blob * * This function allocates a new incoming-message object to wrap a received * message blob. It performs basic verification of the message length and * header, and then linearizes the DHCP4 options. * * The incoming-message object mainly provides accessors for the option-array. * It handles all the different quirks around parsing and concatenating the * options array, and provides the assembled data to the caller. It does not, * however, in any way interpret the data of the individual options. This is up * to the caller to do. * * Return: 0 on success, negative error code on failure, N_DHCP4_E_MALFORMED if * the message is not a valid DHCP4 message. */ int n_dhcp4_incoming_new(NDhcp4Incoming **incomingp, const void *raw, size_t n_raw) { _c_cleanup_(n_dhcp4_incoming_freep) NDhcp4Incoming *incoming = NULL; size_t size; if (n_raw < sizeof(NDhcp4Message) || n_raw > UINT16_MAX) return N_DHCP4_E_MALFORMED; /* * Allocate enough space for book-keeping, a copy of @raw and trailing * space for linearized options. The trailing space must be big enough * to hold the entire options array unmodified (linearizing can only * make it smaller). Hence, just allocate enough space to hold the raw * message without the header. */ size = sizeof(*incoming) + n_raw - sizeof(NDhcp4Message); size += n_raw - sizeof(NDhcp4Header); incoming = calloc(1, size); if (!incoming) return -ENOMEM; *incoming = (NDhcp4Incoming)N_DHCP4_INCOMING_NULL(*incoming); incoming->n_message = n_raw; memcpy(&incoming->message, raw, n_raw); if (incoming->message.magic != htobe32(N_DHCP4_MESSAGE_MAGIC)) return N_DHCP4_E_MALFORMED; /* linearize options */ n_dhcp4_incoming_linearize(incoming); *incomingp = incoming; incoming = NULL; return 0; } /** * n_dhcp4_incoming_free() - Deallocate message object * @incoming: object to operate on, or NULL * * This deallocates and frees the given incoming-message object. If NULL is * passed, this is a no-op. * * Return: NULL is returned. */ NDhcp4Incoming *n_dhcp4_incoming_free(NDhcp4Incoming *incoming) { if (!incoming) return NULL; free(incoming); return NULL; } /** * n_dhcp4_incoming_get_header() - Return message header * @incoming: message to operate on * * This returns a pointer to the message header. Note that modifications to * this header are permanent and will affect the original message. * * Return: A pointer to the message header is returned. */ NDhcp4Header *n_dhcp4_incoming_get_header(NDhcp4Incoming *incoming) { return &incoming->message.header; } /** * n_dhcp4_incoming_get_raw() - Get access to the raw original message * @incoming: message to operate on * @rawp: output argument for the raw blob, or NULL * * This hands out a pointer to the raw message blob to the caller. This will * point to the original message content, rather than the linearized version. * * Note that if the caller queried the contents of the message before, any * modifications done by the caller will not affect the original message. It * only affects the linearized content (which is a duplicate trailing the * original message). However, modifications to the message header *DO* also * appear in the original, since the message header is not duplicated. * * In either case, it is better to never modify the message, if you intend to * forward it further. * * Return: The length of the raw message blob is returned. */ size_t n_dhcp4_incoming_get_raw(NDhcp4Incoming *incoming, const void **rawp) { if (rawp) *rawp = &incoming->message; return incoming->n_message; } /** * n_dhcp4_incoming_query() - Query the contents of a specific option * @incoming: message to query * @option: option to look for * @datap: output argument for the option-data, or NULL * @n_datap: output argument for the length of the option, or NULL * * This returns a pointer to the requested option blob in the message. It * points to a linearized version of all respective option-fields of the same * type. Hence, the caller is not required to deal with multiple occurrences of * the same option. * * If an option was not present in the incoming message, N_DHCP4_E_UNSET is * returned. Note that this is different from an empty option! And empty option * will return a valid pointer and size 0. * * Note that the pointer to the option-blob does not point to the original * message, but a duplicated version. Modifications to the blob will not be * reflected in the original message, but they will be permanent regarding * further queries through this function. * * Note that the original message alignment might no longer be reflected in the * returned blob. You must not alias the content of the blob, but always copy * it out, or consume piecemeal. * * This function runs in O(1). * * Return: 0 on success, negative error code on failure, N_DHCP4_E_UNSET if the * option was not found, */ int n_dhcp4_incoming_query(NDhcp4Incoming *incoming, uint8_t option, uint8_t **datap, size_t *n_datap) { if (!incoming->options[option].value) return N_DHCP4_E_UNSET; if (datap) *datap = incoming->options[option].value; if (n_datap) *n_datap = incoming->options[option].size; return 0; } static int n_dhcp4_incoming_query_u8(NDhcp4Incoming *message, uint8_t option, uint8_t *u8p) { uint8_t *data; size_t n_data; int r; r = n_dhcp4_incoming_query(message, option, &data, &n_data); if (r) return r; else if (n_data < sizeof(*data)) return N_DHCP4_E_MALFORMED; *u8p = *data; return 0; } static int n_dhcp4_incoming_query_u16(NDhcp4Incoming *message, uint8_t option, uint16_t *u16p) { uint8_t *data; size_t n_data; uint16_t be16; int r; r = n_dhcp4_incoming_query(message, option, &data, &n_data); if (r) return r; else if (n_data < sizeof(be16)) return N_DHCP4_E_MALFORMED; memcpy(&be16, data, sizeof(be16)); *u16p = ntohs(be16); return 0; } static int n_dhcp4_incoming_query_u32(NDhcp4Incoming *message, uint8_t option, uint32_t *u32p) { uint8_t *data; size_t n_data; uint32_t be32; int r; r = n_dhcp4_incoming_query(message, option, &data, &n_data); if (r) return r; else if (n_data < sizeof(be32)) return N_DHCP4_E_MALFORMED; memcpy(&be32, data, sizeof(be32)); *u32p = ntohl(be32); return 0; } static int n_dhcp4_incoming_query_in_addr(NDhcp4Incoming *message, uint8_t option, struct in_addr *addrp) { uint8_t *data; size_t n_data; uint32_t be32; int r; r = n_dhcp4_incoming_query(message, option, &data, &n_data); if (r) return r; else if (n_data < sizeof(be32)) return N_DHCP4_E_MALFORMED; memcpy(&be32, data, sizeof(be32)); addrp->s_addr = be32; return 0; } int n_dhcp4_incoming_query_message_type(NDhcp4Incoming *message, uint8_t *typep) { return n_dhcp4_incoming_query_u8(message, N_DHCP4_OPTION_MESSAGE_TYPE, typep); } int n_dhcp4_incoming_query_lifetime(NDhcp4Incoming *message, uint32_t *lifetimep) { return n_dhcp4_incoming_query_u32(message, N_DHCP4_OPTION_IP_ADDRESS_LEASE_TIME, lifetimep); } int n_dhcp4_incoming_query_t2(NDhcp4Incoming *message, uint32_t *t2p) { return n_dhcp4_incoming_query_u32(message, N_DHCP4_OPTION_REBINDING_T2_TIME, t2p); } int n_dhcp4_incoming_query_t1(NDhcp4Incoming *message, uint32_t *t1p) { return n_dhcp4_incoming_query_u32(message, N_DHCP4_OPTION_RENEWAL_T1_TIME, t1p); } int n_dhcp4_incoming_query_server_identifier(NDhcp4Incoming *message, struct in_addr *idp) { return n_dhcp4_incoming_query_in_addr(message, N_DHCP4_OPTION_SERVER_IDENTIFIER, idp); } int n_dhcp4_incoming_query_max_message_size(NDhcp4Incoming *message, uint16_t *max_message_sizep) { return n_dhcp4_incoming_query_u16(message, N_DHCP4_OPTION_MAXIMUM_MESSAGE_SIZE, max_message_sizep); } int n_dhcp4_incoming_query_requested_ip(NDhcp4Incoming *message, struct in_addr *requested_ipp) { return n_dhcp4_incoming_query_in_addr(message, N_DHCP4_OPTION_REQUESTED_IP_ADDRESS, requested_ipp); } void n_dhcp4_incoming_get_xid(NDhcp4Incoming *message, uint32_t *xidp) { NDhcp4Header *header = n_dhcp4_incoming_get_header(message); *xidp = header->xid; } void n_dhcp4_incoming_get_yiaddr(NDhcp4Incoming *message, struct in_addr *yiaddr) { NDhcp4Header *header = n_dhcp4_incoming_get_header(message); yiaddr->s_addr = header->yiaddr; }