diff options
Diffstat (limited to 'src/rfc2131.c')
-rw-r--r-- | src/rfc2131.c | 178 |
1 files changed, 151 insertions, 27 deletions
diff --git a/src/rfc2131.c b/src/rfc2131.c index bcfa5d6..c08a8ab 100644 --- a/src/rfc2131.c +++ b/src/rfc2131.c @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2015 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2018 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 @@ -32,7 +32,7 @@ static void option_put(struct dhcp_packet *mess, unsigned char *end, int opt, in static void option_put_string(struct dhcp_packet *mess, unsigned char *end, int opt, char *string, int null_term); static struct in_addr option_addr(unsigned char *opt); -static unsigned int option_uint(unsigned char *opt, int i, int size); +static unsigned int option_uint(unsigned char *opt, int offset, int size); static void log_packet(char *type, void *addr, unsigned char *ext_mac, int mac_len, char *interface, char *string, char *err, u32 xid); static unsigned char *option_find(struct dhcp_packet *mess, size_t size, int opt_type, int minsize); @@ -42,14 +42,14 @@ static void clear_packet(struct dhcp_packet *mess, unsigned char *end); static int in_list(unsigned char *list, int opt); static void do_options(struct dhcp_context *context, struct dhcp_packet *mess, - unsigned char *real_end, + unsigned char *end, unsigned char *req_options, char *hostname, - char *config_domain, + char *domain, struct dhcp_netid *netid, struct in_addr subnet_addr, unsigned char fqdn_flags, - int null_term, int pxearch, + int null_term, int pxe_arch, unsigned char *uuid, int vendor_class_len, time_t now, @@ -58,15 +58,17 @@ static void do_options(struct dhcp_context *context, static void match_vendor_opts(unsigned char *opt, struct dhcp_opt *dopt); -static int do_encap_opts(struct dhcp_opt *opts, int encap, int flag, struct dhcp_packet *mess, unsigned char *end, int null_term); +static int do_encap_opts(struct dhcp_opt *opt, int encap, int flag, struct dhcp_packet *mess, unsigned char *end, int null_term); static void pxe_misc(struct dhcp_packet *mess, unsigned char *end, unsigned char *uuid); 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); +static void apply_delay(u32 xid, time_t recvtime, struct dhcp_netid *netid); - 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) + size_t sz, time_t now, int unicast_dest, int loopback, + int *is_inform, int pxe, struct in_addr fallback, time_t recvtime) { unsigned char *opt, *clid = NULL; struct dhcp_lease *ltmp, *lease = NULL; @@ -155,7 +157,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, for (offset = 0; offset < (len - 5); offset += elen + 5) { elen = option_uint(opt, offset + 4 , 1); - if (option_uint(opt, offset, 4) == BRDBAND_FORUM_IANA) + if (option_uint(opt, offset, 4) == BRDBAND_FORUM_IANA && offset + elen + 5 <= len) { unsigned char *x = option_ptr(opt, offset + 5); unsigned char *y = option_ptr(opt, offset + elen + 5); @@ -186,7 +188,8 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, be enough free space at the end of the packet to copy the option. */ unsigned char *sopt; unsigned int total = option_len(opt) + 2; - unsigned char *last_opt = option_find(mess, sz, OPTION_END, 0); + unsigned char *last_opt = option_find1(&mess->options[0] + sizeof(u32), ((unsigned char *)mess) + sz, + OPTION_END, 0); if (last_opt && last_opt < end - total) { end -= total; @@ -364,7 +367,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, /* dhcp-match. If we have hex-and-wildcards, look for a left-anchored match. Otherwise assume the option is an array, and look for a matching element. - If no data given, existance of the option is enough. This code handles + If no data given, existence of the option is enough. This code handles rfc3925 V-I classes too. */ for (o = daemon->dhcp_match; o; o = o->next) { @@ -380,7 +383,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, { len = option_uint(opt, offset + 4 , 1); /* Need to take care that bad data can't run us off the end of the packet */ - if ((offset + len + 5 <= (option_len(opt))) && + if ((offset + len + 5 <= (unsigned)(option_len(opt))) && (option_uint(opt, offset, 4) == (unsigned int)o->u.encap)) for (o2 = offset + 5; o2 < offset + len + 5; o2 += elen + 1) { @@ -485,6 +488,13 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, known_id.next = netid; netid = &known_id; } + else if (find_config(daemon->dhcp_conf, NULL, clid, clid_len, + mess->chaddr, mess->hlen, mess->htype, NULL)) + { + known_id.net = "known-othernet"; + known_id.next = netid; + netid = &known_id; + } if (mess_type == 0 && !pxe) { @@ -566,7 +576,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, lease_prune(lease, now); lease = NULL; } - if (!address_allocate(context, &mess->yiaddr, mess->chaddr, mess->hlen, tagif_netid, now)) + if (!address_allocate(context, &mess->yiaddr, mess->chaddr, mess->hlen, tagif_netid, now, loopback)) message = _("no address available"); } else @@ -824,7 +834,13 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, else mess->siaddr = context->local; - snprintf((char *)mess->file, sizeof(mess->file), "%s.%d", service->basename, layer); + if (strchr(service->basename, '.')) + snprintf((char *)mess->file, sizeof(mess->file), + "%s", service->basename); + else + snprintf((char *)mess->file, sizeof(mess->file), + "%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); @@ -851,6 +867,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, 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) && @@ -860,7 +877,8 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, if (tmp) { struct dhcp_boot *boot; - + int redirect4011 = 0; + if (tmp->netid.net) { tmp->netid.next = netid; @@ -878,10 +896,21 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, 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 iPXE, 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) @@ -896,10 +925,13 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, 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, tmp->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)); + if (!ignore) + apply_delay(mess->xid, recvtime, tagif_netid); return ignore ? 0 : dhcp_packet_size(mess, agent_id, real_end); } } @@ -1020,11 +1052,11 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, !config_find_by_address(daemon->dhcp_conf, lease->addr)) mess->yiaddr = lease->addr; else if (opt && address_available(context, addr, tagif_netid) && !lease_find_by_addr(addr) && - !config_find_by_address(daemon->dhcp_conf, addr)) + !config_find_by_address(daemon->dhcp_conf, addr) && do_icmp_ping(now, addr, 0, loopback)) mess->yiaddr = addr; else if (emac_len == 0) message = _("no unique-id"); - else if (!address_allocate(context, &mess->yiaddr, emac, emac_len, tagif_netid, now)) + else if (!address_allocate(context, &mess->yiaddr, emac, emac_len, tagif_netid, now, loopback)) message = _("no address available"); } @@ -1040,7 +1072,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, } log_tags(tagif_netid, ntohl(mess->xid)); - + apply_delay(mess->xid, recvtime, tagif_netid); log_packet("DHCPOFFER" , &mess->yiaddr, emac, emac_len, iface_name, NULL, NULL, mess->xid); time = calc_time(context, config, option_find(mess, sz, OPTION_LEASE_TIME, 4)); @@ -1286,6 +1318,24 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, add_extradata_opt(lease, NULL); } + /* DNSMASQ_REQUESTED_OPTIONS */ + if ((opt = option_find(mess, sz, OPTION_REQUESTED_OPTIONS, 1))) + { + int len = option_len(opt); + unsigned char *rop = option_ptr(opt, 0); + char *q = daemon->namebuff; + int i; + for (i = 0; i < len; i++) + { + q += snprintf(q, MAXDNAME - (q - daemon->namebuff), "%d%s", rop[i], i + 1 == len ? "" : ","); + } + lease_add_extradata(lease, (unsigned char *)daemon->namebuff, (q - daemon->namebuff), 0); + } + else + { + add_extradata_opt(lease, NULL); + } + /* space-concat tag set */ if (!tagif_netid) add_extradata_opt(lease, NULL); @@ -1308,7 +1358,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, /* 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 @@ -1589,7 +1639,7 @@ static unsigned char *option_find1(unsigned char *p, unsigned char *end, int opt { while (1) { - if (p > end) + if (p >= end) return NULL; else if (*p == OPTION_END) return opt == OPTION_END ? p : NULL; @@ -1975,6 +2025,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 @@ -2184,7 +2284,7 @@ static void do_options(struct dhcp_context *context, /* See if we can send the boot stuff as options. To do this we need a requested option list, BOOTP and very old DHCP clients won't have this, we also - provide an manual option to disable it. + provide a manual option to disable it. Some PXE ROMs have bugs (surprise!) and need zero-terminated names, so we always send those. */ if ((boot = find_boot(tagif))) @@ -2509,7 +2609,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)) && @@ -2528,6 +2629,29 @@ static void do_options(struct dhcp_context *context, } } +static void apply_delay(u32 xid, time_t recvtime, struct dhcp_netid *netid) +{ + struct delay_config *delay_conf; + + /* Decide which delay_config option we're using */ + for (delay_conf = daemon->delay_conf; delay_conf; delay_conf = delay_conf->next) + if (match_netid(delay_conf->netid, netid, 0)) + break; + + if (!delay_conf) + /* No match, look for one without a netid */ + for (delay_conf = daemon->delay_conf; delay_conf; delay_conf = delay_conf->next) + if (match_netid(delay_conf->netid, netid, 1)) + break; + + if (delay_conf) + { + if (!option_bool(OPT_QUIET_DHCP)) + my_syslog(MS_DHCP | LOG_INFO, _("%u reply delay: %d"), ntohl(xid), delay_conf->delay); + delay_dhcp(recvtime, delay_conf->delay, -1, 0, 0); + } +} + #endif |