New module dhcp_vendopt.c provides translate_dhcp_vendopt() function to convert command-line DHCP vendor option string into internal DHCP TLV (tag-length- value) uint8_t buffer. The buffer is stored in global Slirp instance.
Signed-off-by: Fedor Lyakhov <fedor.lyak...@gmail.com> --- slirp/Makefile.objs | 1 + slirp/dhcp_vendopt.c | 163 +++++++++++++++++++++++++++++++++++++++++++++++++++ slirp/slirp.c | 5 ++ slirp/slirp.h | 5 ++ 4 files changed, 174 insertions(+) create mode 100644 slirp/dhcp_vendopt.c diff --git a/slirp/Makefile.objs b/slirp/Makefile.objs index 2daa9dc..b8c60c7 100644 --- a/slirp/Makefile.objs +++ b/slirp/Makefile.objs @@ -1,3 +1,4 @@ common-obj-y = cksum.o if.o ip_icmp.o ip_input.o ip_output.o dnssearch.o common-obj-y += slirp.o mbuf.o misc.o sbuf.o socket.o tcp_input.o tcp_output.o common-obj-y += tcp_subr.o tcp_timer.o udp.o bootp.o tftp.o arp_table.o +common-obj-y += dhcp_vendopt.o diff --git a/slirp/dhcp_vendopt.c b/slirp/dhcp_vendopt.c new file mode 100644 index 0000000..274a35f --- /dev/null +++ b/slirp/dhcp_vendopt.c @@ -0,0 +1,163 @@ +/* + * dhcp_vendoropt.c: adds vendor-specific options (option 43) to DHCP Offer/Ack + * + * Copyright (c) 2014 Fedor Lyakhov <fedor.lyak...@gmail.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/> + */ + +#include "bootp_defines.h" +#include "slirp.h" +#include <stdint.h> +#include <stddef.h> +#include <string.h> +#include <stdlib.h> +#include <ctype.h> +#include <glib.h> + +/* Delimiters for vendor-specific options specified from the command-line */ +#define VENDOR_OPTION_DELIMITER ";" +#define VENDOR_TAG_DELIMITER ":" +/* Maximum number of tag digits in null-terminated string form */ +#define TAG_MAX_DIGITS 3 + +/** + * parse_option: + * @option: Single DHCP vendor option null-terminated string (MUST NOT be NULL) + * @ptag: [out] Tag of the option + * @plen: [out] Length of the option value + * @pvalue: [out] Value of the option + * + * Parses DHCP vendor option string in TLV format. + * + * Zero tag is returned in case of non-TLV format, value will be pointing + * to the original option string. + * + * If option string starts with zero tag "0:", it is treated like non-TLV, + * but the tag will be stripped from the value. + */ +static void parse_option(char *option, uint8_t *ptag, size_t *plen, + char **pvalue); + +/** + * translate_dhcp_vendopt: + * @slirp: Pointer to Slirp struct to store the translated DHCP vendor option + * in a binary TLV form. MUST NOT be NULL. + * @dhcp_vendopt_string: Null-terminated string of DHCP vendor options. + * + * Translates DHCP vendor option string to binary representation to be written + * to DHCP packet. This binary representation is uint8_t buffer in TLV format. + * The buffer and its length are stored in the @slirp struct. + * + * Returns: zero if translation succeeded, non-zero otherwise. + */ +int translate_dhcp_vendopt(Slirp *slirp, const char *dhpc_vendopt_string) +{ + uint8_t options_buf[OPT_HEADER_LEN + MAX_OPT_LEN]; + uint8_t *buf = options_buf + OPT_HEADER_LEN; + /* Total length of vendor option field, excluding top TL header */ + size_t total_length = 0; + char *options; + char *current_option; + char *option_value; + size_t option_length; + uint8_t option_tag; + + /* Copying string because strtok will modify it */ + options = g_strdup(dhpc_vendopt_string); + if (!options) { + return 1; + } + + current_option = strtok(options, VENDOR_OPTION_DELIMITER); + if (!current_option) { /* Strtok returns only non-empty strings or NULL */ + goto cleanup; + } + + parse_option(current_option, &option_tag, &option_length, &option_value); + + if (!option_tag) { /* Zero tag - single vendor option case */ + total_length = option_length; + if (total_length > MAX_OPT_LEN) { + DEBUG_MISC((dfd, "Too long DHCP vendor option is specified, " + "skipping it")); + goto cleanup; + } + memcpy(buf, option_value, total_length); + } else { + /* Multiple vendor options are specified, writing them in TLV format */ + do { + size_t new_total_length = total_length + option_length + + OPT_HEADER_LEN; + if (new_total_length > MAX_OPT_LEN) { + DEBUG_MISC((dfd, "Maximum DHCP options size is reached at " + "option: %s, skipping it and the rest\n", + current_option)); + break; + } + total_length = (uint8_t) new_total_length; + + *buf++ = option_tag; + *buf++ = (uint8_t) option_length; + memcpy(buf, option_value, option_length); + buf += option_length; + + current_option = strtok(NULL, VENDOR_OPTION_DELIMITER); + if (current_option) { + parse_option(current_option, &option_tag, &option_length, + &option_value); + } + } while (current_option && option_tag); + } + + if (total_length > 0) { + /* Writing top TLV header */ + options_buf[0] = RFC1533_VENDOR; + options_buf[1] = (uint8_t) total_length; + slirp->dhcp_vendopt_len = total_length + OPT_HEADER_LEN; + slirp->dhcp_vendopt = g_memdup(options_buf, slirp->dhcp_vendopt_len); + } + +cleanup: + g_free(options); + return 0; +} + +static void parse_option(char *option, uint8_t *ptag, size_t *plen, + char **pvalue) +{ + char tag_digits[TAG_MAX_DIGITS + 1] = {0, 0, 0, 0}; + int i; + + *pvalue = option; + *ptag = 0; + + for (i = 0; (i < TAG_MAX_DIGITS) && (*option) + && (*option != VENDOR_TAG_DELIMITER[0]) && (isdigit(*option)); i++) { + tag_digits[i] = *option++; /* Collecting tag digits, if any */ + } + + if ((i > 0) && (i <= TAG_MAX_DIGITS) + && (*option == VENDOR_TAG_DELIMITER[0])) { + int res = atoi(tag_digits); + /* Only 1-254 tags are allowed by RFC2132, but we accept 0 tag below + as an indicator for tag-less value, i.e. we strip initial 0: */ + if ((res >= 0) && (res < UINT8_MAX)) { + *ptag = (uint8_t) res; + ++option; /* removing delimiter from the value */ + *pvalue = option; + } + } + *plen = strlen(*pvalue); +} diff --git a/slirp/slirp.c b/slirp/slirp.c index 3dfce46..c0435d3 100644 --- a/slirp/slirp.c +++ b/slirp/slirp.c @@ -234,6 +234,10 @@ Slirp *slirp_init(int restricted, struct in_addr vnetwork, translate_dnssearch(slirp, vdnssearch); } + if (dhpc_vendopt_string) { + translate_dhcp_vendopt(slirp, dhpc_vendopt_string); + } + slirp->opaque = opaque; register_savevm(NULL, "slirp", 0, 3, @@ -254,6 +258,7 @@ void slirp_cleanup(Slirp *slirp) m_cleanup(slirp); g_free(slirp->vdnssearch); + g_free(slirp->dhcp_vendopt); g_free(slirp->tftp_prefix); g_free(slirp->bootp_filename); g_free(slirp); diff --git a/slirp/slirp.h b/slirp/slirp.h index e4a1bd4..f5882bc 100644 --- a/slirp/slirp.h +++ b/slirp/slirp.h @@ -239,6 +239,8 @@ struct Slirp { char *bootp_filename; size_t vdnssearch_len; uint8_t *vdnssearch; + size_t dhcp_vendopt_len; + uint8_t *dhcp_vendopt; /* tcp states */ struct socket tcb; @@ -301,6 +303,9 @@ void lprint(const char *, ...) GCC_FMT_ATTR(1, 2); /* dnssearch.c */ int translate_dnssearch(Slirp *s, const char ** names); +/* dhcp_vendopt.c */ +int translate_dhcp_vendopt(Slirp *s, const char *dhpc_vendopt_string); + /* cksum.c */ int cksum(struct mbuf *m, int len); -- 1.8.4.5