--- embedaddon/dnsmasq/src/rfc2131.c 2014/06/15 16:31:38 1.1.1.2 +++ embedaddon/dnsmasq/src/rfc2131.c 2016/11/02 09:57:01 1.1.1.3 @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2014 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2016 Simon Kelley 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 @@ -52,7 +52,9 @@ static void do_options(struct dhcp_context *context, int null_term, int pxearch, unsigned char *uuid, int vendor_class_len, - time_t now); + time_t now, + unsigned int lease_time, + unsigned short fuzz); static void match_vendor_opts(unsigned char *opt, struct dhcp_opt *dopt); @@ -61,7 +63,7 @@ static void pxe_misc(struct dhcp_packet *mess, unsigne static int prune_vendor_opts(struct dhcp_netid *netid); static struct dhcp_opt *pxe_opts(int pxe_arch, struct dhcp_netid *netid, struct in_addr local, time_t now); struct dhcp_boot *find_boot(struct dhcp_netid *netid); - +static int pxe_uefi_workaround(int pxe_arch, struct dhcp_netid *netid, struct dhcp_packet *mess, struct in_addr local, time_t now, int pxe); size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, size_t sz, time_t now, int unicast_dest, int *is_inform, int pxe, struct in_addr fallback) @@ -610,7 +612,7 @@ size_t dhcp_reply(struct dhcp_context *context, char * clear_packet(mess, end); do_options(context, mess, end, NULL, hostname, get_domain(mess->yiaddr), - netid, subnet_addr, 0, 0, -1, NULL, vendor_class_len, now); + netid, subnet_addr, 0, 0, -1, NULL, vendor_class_len, now, 0xffffffff, 0); } } @@ -803,9 +805,14 @@ size_t dhcp_reply(struct dhcp_context *context, char * if (service->type == type) break; - if (!service || !service->basename) - return 0; + for (; context; context = context->current) + if (match_netid(context->filter, tagif_netid, 1) && + is_same_net(mess->ciaddr, context->start, context->netmask)) + break; + if (!service || !service->basename || !context) + return 0; + clear_packet(mess, end); mess->yiaddr = mess->ciaddr; @@ -817,7 +824,10 @@ size_t dhcp_reply(struct dhcp_context *context, char * else mess->siaddr = context->local; - snprintf((char *)mess->file, sizeof(mess->file), "%s.%d", service->basename, layer); + snprintf((char *)mess->file, sizeof(mess->file), + strchr(service->basename, '.') ? "%s" :"%s.%d", + service->basename, layer); + option_put(mess, end, OPTION_MESSAGE_TYPE, 1, DHCPACK); option_put(mess, end, OPTION_SERVER_IDENTIFIER, INADDRSZ, htonl(context->local.s_addr)); pxe_misc(mess, end, uuid); @@ -844,6 +854,7 @@ size_t dhcp_reply(struct dhcp_context *context, char * if ((mess_type == DHCPDISCOVER || (pxe && mess_type == DHCPREQUEST))) { struct dhcp_context *tmp; + int workaround = 0; for (tmp = context; tmp; tmp = tmp->current) if ((tmp->flags & CONTEXT_PROXY) && @@ -853,7 +864,8 @@ size_t dhcp_reply(struct dhcp_context *context, char * if (tmp) { struct dhcp_boot *boot; - + int redirect4011 = 0; + if (tmp->netid.net) { tmp->netid.next = netid; @@ -871,10 +883,21 @@ size_t dhcp_reply(struct dhcp_context *context, char * clear_packet(mess, end); - /* Provide the bootfile here, for gPXE, and in case we have no menu items - and set discovery_control = 8 */ - if (boot) + /* Redirect EFI clients to port 4011 */ + if (pxearch >= 6) { + redirect4011 = 1; + mess->siaddr = tmp->local; + } + + /* Returns true if only one matching service is available. On port 4011, + it also inserts the boot file and server name. */ + workaround = pxe_uefi_workaround(pxearch, tagif_netid, mess, tmp->local, now, pxe); + + if (!workaround && boot) + { + /* Provide the bootfile here, for gPXE, and in case we have no menu items + and set discovery_control = 8 */ if (boot->next_server.s_addr) mess->siaddr = boot->next_server; else if (boot->tftp_sname) @@ -886,11 +909,12 @@ size_t dhcp_reply(struct dhcp_context *context, char * option_put(mess, end, OPTION_MESSAGE_TYPE, 1, mess_type == DHCPDISCOVER ? DHCPOFFER : DHCPACK); - option_put(mess, end, OPTION_SERVER_IDENTIFIER, INADDRSZ, htonl(context->local.s_addr)); + option_put(mess, end, OPTION_SERVER_IDENTIFIER, INADDRSZ, htonl(tmp->local.s_addr)); pxe_misc(mess, end, uuid); prune_vendor_opts(tagif_netid); - do_encap_opts(pxe_opts(pxearch, tagif_netid, context->local, now), OPTION_VENDOR_CLASS_OPT, DHOPT_VENDOR_MATCH, mess, end, 0); - + if ((pxe && !workaround) || !redirect4011) + do_encap_opts(pxe_opts(pxearch, tagif_netid, tmp->local, now), OPTION_VENDOR_CLASS_OPT, DHOPT_VENDOR_MATCH, mess, end, 0); + log_packet("PXE", NULL, emac, emac_len, iface_name, ignore ? "proxy-ignored" : "proxy", NULL, mess->xid); log_tags(tagif_netid, ntohl(mess->xid)); return ignore ? 0 : dhcp_packet_size(mess, agent_id, real_end); @@ -1042,13 +1066,8 @@ size_t dhcp_reply(struct dhcp_context *context, char * option_put(mess, end, OPTION_SERVER_IDENTIFIER, INADDRSZ, ntohl(server_id(context, override, fallback).s_addr)); option_put(mess, end, OPTION_LEASE_TIME, 4, time); /* T1 and T2 are required in DHCPOFFER by HP's wacky Jetdirect client. */ - if (time != 0xffffffff) - { - option_put(mess, end, OPTION_T1, 4, (time/2)); - option_put(mess, end, OPTION_T2, 4, (time*7)/8); - } do_options(context, mess, end, req_options, offer_hostname, get_domain(mess->yiaddr), - netid, subnet_addr, fqdn_flags, borken_opt, pxearch, uuid, vendor_class_len, now); + netid, subnet_addr, fqdn_flags, borken_opt, pxearch, uuid, vendor_class_len, now, time, fuzz); return dhcp_packet_size(mess, agent_id, real_end); @@ -1306,7 +1325,7 @@ size_t dhcp_reply(struct dhcp_context *context, char * /* If the user-class option started as counted strings, the first byte will be zero. */ if (len != 0 && ucp[0] == 0) ucp++, len--; - lease_add_extradata(lease, ucp, len, 0); + lease_add_extradata(lease, ucp, len, -1); } } #endif @@ -1367,15 +1386,8 @@ size_t dhcp_reply(struct dhcp_context *context, char * option_put(mess, end, OPTION_MESSAGE_TYPE, 1, DHCPACK); option_put(mess, end, OPTION_SERVER_IDENTIFIER, INADDRSZ, ntohl(server_id(context, override, fallback).s_addr)); option_put(mess, end, OPTION_LEASE_TIME, 4, time); - if (time != 0xffffffff) - { - while (fuzz > (time/16)) - fuzz = fuzz/2; - option_put(mess, end, OPTION_T1, 4, (time/2) - fuzz); - option_put(mess, end, OPTION_T2, 4, ((time/8)*7) - fuzz); - } do_options(context, mess, end, req_options, hostname, get_domain(mess->yiaddr), - netid, subnet_addr, fqdn_flags, borken_opt, pxearch, uuid, vendor_class_len, now); + netid, subnet_addr, fqdn_flags, borken_opt, pxearch, uuid, vendor_class_len, now, time, fuzz); } return dhcp_packet_size(mess, agent_id, real_end); @@ -1440,7 +1452,7 @@ size_t dhcp_reply(struct dhcp_context *context, char * } do_options(context, mess, end, req_options, hostname, get_domain(mess->ciaddr), - netid, subnet_addr, fqdn_flags, borken_opt, pxearch, uuid, vendor_class_len, now); + netid, subnet_addr, fqdn_flags, borken_opt, pxearch, uuid, vendor_class_len, now, 0xffffffff, 0); *is_inform = 1; /* handle reply differently */ return dhcp_packet_size(mess, agent_id, real_end); @@ -1980,6 +1992,56 @@ static int prune_vendor_opts(struct dhcp_netid *netid) return force; } + +/* Many UEFI PXE implementations have badly broken menu code. + If there's exactly one relevant menu item, we abandon the menu system, + and jamb the data direct into the DHCP file, siaddr and sname fields. + Note that in this case, we have to assume that layer zero would be requested + by the client PXE stack. */ +static int pxe_uefi_workaround(int pxe_arch, struct dhcp_netid *netid, struct dhcp_packet *mess, struct in_addr local, time_t now, int pxe) +{ + struct pxe_service *service, *found; + + /* Only workaround UEFI archs. */ + if (pxe_arch < 6) + return 0; + + for (found = NULL, service = daemon->pxe_services; service; service = service->next) + if (pxe_arch == service->CSA && service->basename && match_netid(service->netid, netid, 1)) + { + if (found) + return 0; /* More than one relevant menu item */ + + found = service; + } + + if (!found) + return 0; /* No relevant menu items. */ + + if (!pxe) + return 1; + + if (found->sname) + { + mess->siaddr = a_record_from_hosts(found->sname, now); + snprintf((char *)mess->sname, sizeof(mess->sname), "%s", found->sname); + } + else + { + if (found->server.s_addr != 0) + mess->siaddr = found->server; + else + mess->siaddr = local; + + inet_ntop(AF_INET, &mess->siaddr, (char *)mess->sname, INET_ADDRSTRLEN); + } + + snprintf((char *)mess->file, sizeof(mess->file), + strchr(found->basename, '.') ? "%s" : "%s.0", found->basename); + + return 1; +} + static struct dhcp_opt *pxe_opts(int pxe_arch, struct dhcp_netid *netid, struct in_addr local, time_t now) { #define NUM_OPTS 4 @@ -2137,7 +2199,9 @@ static void do_options(struct dhcp_context *context, int null_term, int pxe_arch, unsigned char *uuid, int vendor_class_len, - time_t now) + time_t now, + unsigned int lease_time, + unsigned short fuzz) { struct dhcp_opt *opt, *config_opts = daemon->dhcp_opts; struct dhcp_boot *boot; @@ -2261,7 +2325,42 @@ static void do_options(struct dhcp_context *context, /* rfc3011 says this doesn't need to be in the requested options list. */ if (subnet_addr.s_addr) option_put(mess, end, OPTION_SUBNET_SELECT, INADDRSZ, ntohl(subnet_addr.s_addr)); - + + if (lease_time != 0xffffffff) + { + unsigned int t1val = lease_time/2; + unsigned int t2val = (lease_time*7)/8; + unsigned int hval; + + /* If set by user, sanity check, so not longer than lease. */ + if ((opt = option_find2(OPTION_T1))) + { + hval = ntohl(*((unsigned int *)opt->val)); + if (hval < lease_time && hval > 2) + t1val = hval; + } + + if ((opt = option_find2(OPTION_T2))) + { + hval = ntohl(*((unsigned int *)opt->val)); + if (hval < lease_time && hval > 2) + t2val = hval; + } + + /* ensure T1 is still < T2 */ + if (t2val <= t1val) + t1val = t2val - 1; + + while (fuzz > (t1val/8)) + fuzz = fuzz/2; + + t1val -= fuzz; + t2val -= fuzz; + + option_put(mess, end, OPTION_T1, 4, t1val); + option_put(mess, end, OPTION_T2, 4, t2val); + } + /* replies to DHCPINFORM may not have a valid context */ if (context) { @@ -2356,12 +2455,14 @@ static void do_options(struct dhcp_context *context, if (!(opt->flags & DHOPT_FORCE) && !in_list(req_options, optno)) continue; - /* prohibit some used-internally options */ + /* prohibit some used-internally options. T1 and T2 already handled. */ if (optno == OPTION_CLIENT_FQDN || optno == OPTION_MAXMESSAGE || optno == OPTION_OVERLOAD || optno == OPTION_PAD || - optno == OPTION_END) + optno == OPTION_END || + optno == OPTION_T1 || + optno == OPTION_T2) continue; if (optno == OPTION_SNAME && done_server) @@ -2475,7 +2576,8 @@ static void do_options(struct dhcp_context *context, if (context && pxe_arch != -1) { pxe_misc(mess, end, uuid); - config_opts = pxe_opts(pxe_arch, tagif, context->local, now); + if (!pxe_uefi_workaround(pxe_arch, tagif, mess, context->local, now, 0)) + config_opts = pxe_opts(pxe_arch, tagif, context->local, now); } if ((force_encap || in_list(req_options, OPTION_VENDOR_CLASS_OPT)) &&