From 64d58aa8061c7c848cf0fd37de3ccbb8582d0fc5 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Mon, 27 Mar 2017 22:22:09 +0200 Subject: [PATCH] udhcp6: fix problems found running against dnsmasq Patch is based on work by tiggerswelt.net. They say: " But when we tried to use dnsmasq on server-side, udhcpc6 was unable to forward the acquired address to its setup-script although the IPv6-Address had been assigned by the server as we could see via tcpdump. We traced this issue down to a problem on how udhcpc6 parses DHCPv6-Options: When moving to next option, a pointer-address is increased and a length buffer is decreased by the length of the option. The problem is that it is done in this order: option += 4 + option[3]; len_m4 -= 4 + option[3]; But this has to be switched as the length is decreased by the length of the *next* option, not the current one. This affected both - internal checks if a required option is present and the function to expose options to the environment of the setup-script. There was also a bug parsing D6_OPT_STATUS_CODE Options, that made dnsmasq not work as udhcpc6 thought it is receiving a non-positive status-code (because it did not parse the status-code as required in RFC 3315). In addition we introduced basic support for RFC 3646 (OPTION_DNS_SERVERS and OPTION_DOMAIN_LIST) and RFC 4704 (OPTION_CLIENT_FQDN). " Signed-off-by: Denys Vlasenko --- networking/udhcp/d6_common.h | 5 +++ networking/udhcp/d6_dhcpc.c | 74 ++++++++++++++++++++++++++++++++++-- 2 files changed, 76 insertions(+), 3 deletions(-) diff --git a/networking/udhcp/d6_common.h b/networking/udhcp/d6_common.h index f7cfa4ab8..335a0927a 100644 --- a/networking/udhcp/d6_common.h +++ b/networking/udhcp/d6_common.h @@ -81,9 +81,14 @@ struct d6_option { #define D6_OPT_RECONF_MSG 19 #define D6_OPT_RECONF_ACCEPT 20 +#define D6_OPT_DNS_SERVERS 23 +#define D6_OPT_DOMAIN_LIST 24 + #define D6_OPT_IA_PD 25 #define D6_OPT_IAPREFIX 26 +#define D6_OPT_CLIENT_FQDN 39 + /*** Other shared functions ***/ struct client6_data_t { diff --git a/networking/udhcp/d6_dhcpc.c b/networking/udhcp/d6_dhcpc.c index 95f8939b4..a0cdded11 100644 --- a/networking/udhcp/d6_dhcpc.c +++ b/networking/udhcp/d6_dhcpc.c @@ -86,6 +86,19 @@ enum { IF_FEATURE_UDHCP_PORT( OPT_P = 1 << OPTBIT_P,) }; +static const char opt_req[] = { + (D6_OPT_ORO >> 8), (D6_OPT_ORO & 0xff), + 0, 6, + (D6_OPT_DNS_SERVERS >> 8), (D6_OPT_DNS_SERVERS & 0xff), + (D6_OPT_DOMAIN_LIST >> 8), (D6_OPT_DOMAIN_LIST & 0xff), + (D6_OPT_CLIENT_FQDN >> 8), (D6_OPT_CLIENT_FQDN & 0xff) +}; + +static const char opt_fqdn_req[] = { + (D6_OPT_CLIENT_FQDN >> 8), (D6_OPT_CLIENT_FQDN & 0xff), + 0, 2, + 0, 0 +}; /*** Utility functions ***/ @@ -107,8 +120,8 @@ static void *d6_find_option(uint8_t *option, uint8_t *option_end, unsigned code) /* Does its code match? */ if (option[1] == code) return option; /* yes! */ - option += option[3] + 4; len_m4 -= option[3] + 4; + option += option[3] + 4; } return NULL; } @@ -140,7 +153,9 @@ static char** new_env(void) static void option_to_env(uint8_t *option, uint8_t *option_end) { /* "length minus 4" */ + char *dlist, *ptr; int len_m4 = option_end - option - 4; + int olen, ooff; while (len_m4 >= 0) { uint32_t v32; char ipv6str[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")]; @@ -217,9 +232,54 @@ static void option_to_env(uint8_t *option, uint8_t *option_end) sprint_nip6(ipv6str, option + 4 + 4 + 1); *new_env() = xasprintf("ipv6prefix=%s/%u", ipv6str, (unsigned)(option[4 + 4])); + break; + case D6_OPT_DNS_SERVERS: + olen = ((option[2] << 8) | option[3]) / 16; + dlist = ptr = malloc (4 + olen * 40 - 1); + + memcpy (ptr, "dns=", 4); + ptr += 4; + ooff = 0; + + while (olen--) { + sprint_nip6(ptr, option + 4 + ooff); + ptr += 39; + ooff += 16; + if (olen) + *ptr++ = ' '; + } + + *new_env() = dlist; + + break; + case D6_OPT_DOMAIN_LIST: + dlist = dname_dec(option + 4, (option[2] << 8) | option[3], "search="); + if (!dlist) + break; + *new_env() = dlist; + break; + case D6_OPT_CLIENT_FQDN: + // Work around broken ISC DHCPD6 + if (option[4] & 0xf8) { + olen = ((option[2] << 8) | option[3]); + dlist = xmalloc(olen); +//fixme: +//- explain +//- add len error check +//- merge two allocs into one + memcpy(dlist, option + 4, olen); + *new_env() = xasprintf("fqdn=%s", dlist); + free(dlist); + break; + } + dlist = dname_dec(option + 5, ((option[2] << 8) | option[3]) - 1, "fqdn="); + if (!dlist) + break; + *new_env() = dlist; + break; } - option += 4 + option[3]; len_m4 -= 4 + option[3]; + option += 4 + option[3]; } } @@ -423,6 +483,10 @@ static NOINLINE int send_d6_discover(uint32_t xid, struct in6_addr *requested_ip } opt_ptr = d6_store_blob(opt_ptr, client6_data.ia_na, len); + /* Request additional options */ + opt_ptr = d6_store_blob(opt_ptr, &opt_req, sizeof(opt_req)); + opt_ptr = d6_store_blob(opt_ptr, &opt_fqdn_req, sizeof(opt_fqdn_req)); + /* Add options: * "param req" option according to -O, options specified with -x */ @@ -476,6 +540,10 @@ static NOINLINE int send_d6_select(uint32_t xid) /* IA NA (contains requested IP) */ opt_ptr = d6_store_blob(opt_ptr, client6_data.ia_na, client6_data.ia_na->len + 2+2); + /* Request additional options */ + opt_ptr = d6_store_blob(opt_ptr, &opt_req, sizeof(opt_req)); + opt_ptr = d6_store_blob(opt_ptr, &opt_fqdn_req, sizeof(opt_fqdn_req)); + /* Add options: * "param req" option according to -O, options specified with -x */ @@ -1306,7 +1374,7 @@ int udhcpc6_main(int argc UNUSED_PARAM, char **argv) struct d6_option *option, *iaaddr; type_is_ok: option = d6_find_option(packet.d6_options, packet_end, D6_OPT_STATUS_CODE); - if (option && option->data[4] != 0) { + if (option && (option->data[0] | option->data[1]) != 0) { /* return to init state */ bb_error_msg("received DHCP NAK (%u)", option->data[4]); d6_run_script(&packet, "nak");