--- embedaddon/dnsmasq/src/rfc2131.c 2016/11/02 09:57:01 1.1.1.3 +++ embedaddon/dnsmasq/src/rfc2131.c 2021/03/17 00:56:46 1.1.1.4 @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2016 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2021 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 @@ -30,9 +30,9 @@ static struct in_addr server_id(struct dhcp_context *c static unsigned int calc_time(struct dhcp_context *context, struct dhcp_config *config, unsigned char *opt); static void option_put(struct dhcp_packet *mess, unsigned char *end, int opt, int len, unsigned int val); static void option_put_string(struct dhcp_packet *mess, unsigned char *end, - int opt, char *string, int null_term); + int opt, const 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,38 +42,43 @@ static void clear_packet(struct dhcp_packet *mess, uns 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, unsigned int lease_time, - unsigned short fuzz); + unsigned short fuzz, + const char *pxevendor); 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 void pxe_misc(struct dhcp_packet *mess, unsigned char *end, unsigned char *uuid); +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, const char *pxevendor); 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); +static int is_pxe_client(struct dhcp_packet *mess, size_t sz, const char **pxe_vendor); + 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; struct dhcp_vendor *vendor; struct dhcp_mac *mac; struct dhcp_netid_list *id_list; - int clid_len = 0, ignore = 0, do_classes = 0, selecting = 0, pxearch = -1; + int clid_len = 0, ignore = 0, do_classes = 0, rapid_commit = 0, selecting = 0, pxearch = -1; + const char *pxevendor = NULL; struct dhcp_packet *mess = (struct dhcp_packet *)daemon->dhcp_packet.iov_base; unsigned char *end = (unsigned char *)(mess + 1); unsigned char *real_end = (unsigned char *)(mess + 1); @@ -155,7 +160,7 @@ size_t dhcp_reply(struct dhcp_context *context, char * 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 +191,8 @@ size_t dhcp_reply(struct dhcp_context *context, char * 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; @@ -231,7 +237,7 @@ size_t dhcp_reply(struct dhcp_context *context, char * subnet_addr = option_addr(opt); /* If there is no client identifier option, use the hardware address */ - if ((opt = option_find(mess, sz, OPTION_CLIENT_ID, 1))) + if (!option_bool(OPT_IGNORE_CLID) && (opt = option_find(mess, sz, OPTION_CLIENT_ID, 1))) { clid_len = option_len(opt); clid = option_ptr(opt, 0); @@ -271,8 +277,9 @@ size_t dhcp_reply(struct dhcp_context *context, char * if (mess->giaddr.s_addr || subnet_addr.s_addr || mess->ciaddr.s_addr) { struct dhcp_context *context_tmp, *context_new = NULL; + struct shared_network *share = NULL; struct in_addr addr; - int force = 0; + int force = 0, via_relay = 0; if (subnet_addr.s_addr) { @@ -283,6 +290,7 @@ size_t dhcp_reply(struct dhcp_context *context, char * { addr = mess->giaddr; force = 1; + via_relay = 1; } else { @@ -299,42 +307,65 @@ size_t dhcp_reply(struct dhcp_context *context, char * } if (!context_new) - for (context_tmp = daemon->dhcp; context_tmp; context_tmp = context_tmp->next) - { - struct in_addr netmask = context_tmp->netmask; + { + for (context_tmp = daemon->dhcp; context_tmp; context_tmp = context_tmp->next) + { + struct in_addr netmask = context_tmp->netmask; + + /* guess the netmask for relayed networks */ + if (!(context_tmp->flags & CONTEXT_NETMASK) && context_tmp->netmask.s_addr == 0) + { + if (IN_CLASSA(ntohl(context_tmp->start.s_addr)) && IN_CLASSA(ntohl(context_tmp->end.s_addr))) + netmask.s_addr = htonl(0xff000000); + else if (IN_CLASSB(ntohl(context_tmp->start.s_addr)) && IN_CLASSB(ntohl(context_tmp->end.s_addr))) + netmask.s_addr = htonl(0xffff0000); + else if (IN_CLASSC(ntohl(context_tmp->start.s_addr)) && IN_CLASSC(ntohl(context_tmp->end.s_addr))) + netmask.s_addr = htonl(0xffffff00); + } - /* guess the netmask for relayed networks */ - if (!(context_tmp->flags & CONTEXT_NETMASK) && context_tmp->netmask.s_addr == 0) - { - if (IN_CLASSA(ntohl(context_tmp->start.s_addr)) && IN_CLASSA(ntohl(context_tmp->end.s_addr))) - netmask.s_addr = htonl(0xff000000); - else if (IN_CLASSB(ntohl(context_tmp->start.s_addr)) && IN_CLASSB(ntohl(context_tmp->end.s_addr))) - netmask.s_addr = htonl(0xffff0000); - else if (IN_CLASSC(ntohl(context_tmp->start.s_addr)) && IN_CLASSC(ntohl(context_tmp->end.s_addr))) - netmask.s_addr = htonl(0xffffff00); - } - - /* This section fills in context mainly when a client which is on a remote (relayed) - network renews a lease without using the relay, after dnsmasq has restarted. */ - if (netmask.s_addr != 0 && - is_same_net(addr, context_tmp->start, netmask) && - is_same_net(addr, context_tmp->end, netmask)) - { - context_tmp->netmask = netmask; - if (context_tmp->local.s_addr == 0) - context_tmp->local = fallback; - if (context_tmp->router.s_addr == 0) - context_tmp->router = mess->giaddr; - - /* fill in missing broadcast addresses for relayed ranges */ - if (!(context_tmp->flags & CONTEXT_BRDCAST) && context_tmp->broadcast.s_addr == 0 ) - context_tmp->broadcast.s_addr = context_tmp->start.s_addr | ~context_tmp->netmask.s_addr; - - context_tmp->current = context_new; - context_new = context_tmp; - } - } - + /* check to see is a context is OK because of a shared address on + the relayed subnet. */ + if (via_relay) + for (share = daemon->shared_networks; share; share = share->next) + { +#ifdef HAVE_DHCP6 + if (share->shared_addr.s_addr == 0) + continue; +#endif + if (share->if_index != 0 || + share->match_addr.s_addr != mess->giaddr.s_addr) + continue; + + if (netmask.s_addr != 0 && + is_same_net(share->shared_addr, context_tmp->start, netmask) && + is_same_net(share->shared_addr, context_tmp->end, netmask)) + break; + } + + /* This section fills in context mainly when a client which is on a remote (relayed) + network renews a lease without using the relay, after dnsmasq has restarted. */ + if (share || + (netmask.s_addr != 0 && + is_same_net(addr, context_tmp->start, netmask) && + is_same_net(addr, context_tmp->end, netmask))) + { + context_tmp->netmask = netmask; + if (context_tmp->local.s_addr == 0) + context_tmp->local = fallback; + if (context_tmp->router.s_addr == 0 && !share) + context_tmp->router = mess->giaddr; + + /* fill in missing broadcast addresses for relayed ranges */ + if (!(context_tmp->flags & CONTEXT_BRDCAST) && context_tmp->broadcast.s_addr == 0 ) + context_tmp->broadcast.s_addr = context_tmp->start.s_addr | ~context_tmp->netmask.s_addr; + + context_tmp->current = context_new; + context_new = context_tmp; + } + + } + } + if (context_new || force) context = context_new; } @@ -364,7 +395,7 @@ size_t dhcp_reply(struct dhcp_context *context, char * /* 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,12 +411,12 @@ size_t dhcp_reply(struct dhcp_context *context, char * { 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) { elen = option_uint(opt, o2, 1); - if ((o2 + elen + 1 <= option_len(opt)) && + if ((o2 + elen + 1 <= (unsigned)option_len(opt)) && (match = match_bytes(o, option_ptr(opt, o2 + 1), elen))) break; } @@ -476,7 +507,7 @@ size_t dhcp_reply(struct dhcp_context *context, char * mess->op = BOOTREPLY; config = find_config(daemon->dhcp_conf, context, clid, clid_len, - mess->chaddr, mess->hlen, mess->htype, NULL); + mess->chaddr, mess->hlen, mess->htype, NULL, run_tag_if(netid)); /* set "known" tag for known hosts */ if (config) @@ -485,6 +516,13 @@ size_t dhcp_reply(struct dhcp_context *context, char * 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, run_tag_if(netid))) + { + known_id.net = "known-othernet"; + known_id.next = netid; + netid = &known_id; + } if (mess_type == 0 && !pxe) { @@ -566,7 +604,7 @@ size_t dhcp_reply(struct dhcp_context *context, char * 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 @@ -612,10 +650,11 @@ 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, 0xffffffff, 0); + netid, subnet_addr, 0, 0, -1, NULL, vendor_class_len, now, 0xffffffff, 0, NULL); } } + daemon->metrics[METRIC_BOOTP]++; log_packet("BOOTP", logaddr, mess->chaddr, mess->hlen, iface_name, NULL, message, mess->xid); return message ? 0 : dhcp_packet_size(mess, agent_id, real_end); @@ -689,8 +728,37 @@ size_t dhcp_reply(struct dhcp_context *context, char * client_hostname = daemon->dhcp_buff; } - if (client_hostname && option_bool(OPT_LOG_OPTS)) - my_syslog(MS_DHCP | LOG_INFO, _("%u client provides name: %s"), ntohl(mess->xid), client_hostname); + if (client_hostname) + { + struct dhcp_match_name *m; + size_t nl = strlen(client_hostname); + + if (option_bool(OPT_LOG_OPTS)) + my_syslog(MS_DHCP | LOG_INFO, _("%u client provides name: %s"), ntohl(mess->xid), client_hostname); + for (m = daemon->dhcp_name_match; m; m = m->next) + { + size_t ml = strlen(m->name); + char save = 0; + + if (nl < ml) + continue; + if (nl > ml) + { + save = client_hostname[ml]; + client_hostname[ml] = 0; + } + + if (hostname_isequal(client_hostname, m->name) && + (save == 0 || m->wildcard)) + { + m->netid->next = netid; + netid = m->netid; + } + + if (save != 0) + client_hostname[ml] = save; + } + } if (have_config(config, CONFIG_NAME)) { @@ -708,6 +776,7 @@ size_t dhcp_reply(struct dhcp_context *context, char * if (strlen(client_hostname) != 0) { hostname = client_hostname; + if (!config) { /* Search again now we have a hostname. @@ -715,7 +784,7 @@ size_t dhcp_reply(struct dhcp_context *context, char * to avoid impersonation by name. */ struct dhcp_config *new = find_config(daemon->dhcp_conf, context, NULL, 0, mess->chaddr, mess->hlen, - mess->htype, hostname); + mess->htype, hostname, run_tag_if(netid)); if (new && !have_config(new, CONFIG_CLID) && !new->hwaddr) { config = new; @@ -769,9 +838,8 @@ size_t dhcp_reply(struct dhcp_context *context, char * clid = NULL; /* Check if client is PXE client. */ - if (daemon->enable_pxe && - (opt = option_find(mess, sz, OPTION_VENDOR_ID, 9)) && - strncmp(option_ptr(opt, 0), "PXEClient", 9) == 0) + if (daemon->enable_pxe && + is_pxe_client(mess, sz, &pxevendor)) { if ((opt = option_find(mess, sz, OPTION_PXE_UUID, 17))) { @@ -824,13 +892,16 @@ size_t dhcp_reply(struct dhcp_context *context, char * else mess->siaddr = context->local; - snprintf((char *)mess->file, sizeof(mess->file), - strchr(service->basename, '.') ? "%s" :"%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); + pxe_misc(mess, end, uuid, pxevendor); prune_vendor_opts(tagif_netid); opt71.val = save71; @@ -896,7 +967,7 @@ size_t dhcp_reply(struct dhcp_context *context, char * if (!workaround && boot) { - /* Provide the bootfile here, for gPXE, and in case we have no menu items + /* 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; @@ -904,19 +975,22 @@ size_t dhcp_reply(struct dhcp_context *context, char * mess->siaddr = a_record_from_hosts(boot->tftp_sname, now); if (boot->file) - strncpy((char *)mess->file, boot->file, sizeof(mess->file)-1); + safe_strncpy((char *)mess->file, boot->file, sizeof(mess->file)); } option_put(mess, end, OPTION_MESSAGE_TYPE, 1, mess_type == DHCPDISCOVER ? DHCPOFFER : DHCPACK); option_put(mess, end, OPTION_SERVER_IDENTIFIER, INADDRSZ, htonl(tmp->local.s_addr)); - pxe_misc(mess, end, uuid); + pxe_misc(mess, end, uuid, pxevendor); prune_vendor_opts(tagif_netid); 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); + daemon->metrics[METRIC_PXE]++; 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); } } @@ -947,6 +1021,7 @@ size_t dhcp_reply(struct dhcp_context *context, char * if (!(opt = option_find(mess, sz, OPTION_REQUESTED_IP, INADDRSZ))) return 0; + daemon->metrics[METRIC_DHCPDECLINE]++; log_packet("DHCPDECLINE", option_ptr(opt, 0), emac, emac_len, iface_name, NULL, daemon->dhcp_buff, mess->xid); if (lease && lease->addr.s_addr == option_addr(opt).s_addr) @@ -979,6 +1054,7 @@ size_t dhcp_reply(struct dhcp_context *context, char * else message = _("unknown lease"); + daemon->metrics[METRIC_DHCPRELEASE]++; log_packet("DHCPRELEASE", &mess->ciaddr, emac, emac_len, iface_name, NULL, message, mess->xid); return 0; @@ -1037,14 +1113,15 @@ size_t dhcp_reply(struct dhcp_context *context, char * !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"); } + daemon->metrics[METRIC_DHCPDISCOVER]++; log_packet("DHCPDISCOVER", opt ? option_ptr(opt, 0) : NULL, emac, emac_len, iface_name, NULL, message, mess->xid); if (message || !(context = narrow_context(context, mess->yiaddr, tagif_netid))) @@ -1057,7 +1134,15 @@ size_t dhcp_reply(struct dhcp_context *context, char * } log_tags(tagif_netid, ntohl(mess->xid)); + apply_delay(mess->xid, recvtime, tagif_netid); + + if (option_bool(OPT_RAPID_COMMIT) && option_find(mess, sz, OPTION_RAPID_COMMIT, 0)) + { + rapid_commit = 1; + goto rapid_commit; + } + daemon->metrics[METRIC_DHCPOFFER]++; 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)); @@ -1067,10 +1152,11 @@ size_t dhcp_reply(struct dhcp_context *context, char * option_put(mess, end, OPTION_LEASE_TIME, 4, time); /* T1 and T2 are required in DHCPOFFER by HP's wacky Jetdirect client. */ 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, time, fuzz); + netid, subnet_addr, fqdn_flags, borken_opt, pxearch, uuid, vendor_class_len, now, time, fuzz, pxevendor); return dhcp_packet_size(mess, agent_id, real_end); - + + case DHCPREQUEST: if (ignore || have_config(config, CONFIG_DISABLE)) return 0; @@ -1168,9 +1254,11 @@ size_t dhcp_reply(struct dhcp_context *context, char * fuzz = rand16(); mess->yiaddr = mess->ciaddr; } - + + daemon->metrics[METRIC_DHCPREQUEST]++; log_packet("DHCPREQUEST", &mess->yiaddr, emac, emac_len, iface_name, NULL, NULL, mess->xid); - + + rapid_commit: if (!message) { struct dhcp_config *addr_config; @@ -1241,7 +1329,12 @@ size_t dhcp_reply(struct dhcp_context *context, char * if (message) { - log_packet("DHCPNAK", &mess->yiaddr, emac, emac_len, iface_name, NULL, message, mess->xid); + daemon->metrics[rapid_commit ? METRIC_NOANSWER : METRIC_DHCPNAK]++; + log_packet(rapid_commit ? "NOANSWER" : "DHCPNAK", &mess->yiaddr, emac, emac_len, iface_name, NULL, message, mess->xid); + + /* rapid commit case: lease allocate failed but don't send DHCPNAK */ + if (rapid_commit) + return 0; mess->yiaddr.s_addr = 0; clear_packet(mess, end); @@ -1303,6 +1396,24 @@ size_t dhcp_reply(struct dhcp_context *context, char * 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); @@ -1380,14 +1491,17 @@ size_t dhcp_reply(struct dhcp_context *context, char * else override = lease->override; + daemon->metrics[METRIC_DHCPACK]++; log_packet("DHCPACK", &mess->yiaddr, emac, emac_len, iface_name, hostname, NULL, mess->xid); - + clear_packet(mess, end); 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); - 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, time, fuzz); + if (rapid_commit) + option_put(mess, end, OPTION_RAPID_COMMIT, 0, 0); + 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, time, fuzz, pxevendor); } return dhcp_packet_size(mess, agent_id, real_end); @@ -1396,6 +1510,7 @@ size_t dhcp_reply(struct dhcp_context *context, char * if (ignore || have_config(config, CONFIG_DISABLE)) message = _("ignored"); + daemon->metrics[METRIC_DHCPINFORM]++; log_packet("DHCPINFORM", &mess->ciaddr, emac, emac_len, iface_name, message, NULL, mess->xid); if (message || mess->ciaddr.s_addr == 0) @@ -1422,6 +1537,7 @@ size_t dhcp_reply(struct dhcp_context *context, char * log_tags(tagif_netid, ntohl(mess->xid)); + daemon->metrics[METRIC_DHCPACK]++; log_packet("DHCPACK", &mess->ciaddr, emac, emac_len, iface_name, hostname, NULL, mess->xid); if (lease) @@ -1452,7 +1568,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, 0xffffffff, 0); + netid, subnet_addr, fqdn_flags, borken_opt, pxearch, uuid, vendor_class_len, now, 0xffffffff, 0, pxevendor); *is_inform = 1; /* handle reply differently */ return dhcp_packet_size(mess, agent_id, real_end); @@ -1588,6 +1704,13 @@ static void log_packet(char *type, void *addr, unsigne daemon->namebuff, string ? string : "", err ? err : ""); + +#ifdef HAVE_UBUS + if (!strcmp(type, "DHCPACK")) + ubus_event_bcast("dhcp.ack", daemon->namebuff, addr ? inet_ntoa(a) : NULL, string ? string : NULL, interface); + else if (!strcmp(type, "DHCPRELEASE")) + ubus_event_bcast("dhcp.release", daemon->namebuff, addr ? inet_ntoa(a) : NULL, string ? string : NULL, interface); +#endif } static void log_options(unsigned char *start, u32 xid) @@ -1606,7 +1729,7 @@ static unsigned char *option_find1(unsigned char *p, u { while (1) { - if (p > end) + if (p >= end) return NULL; else if (*p == OPTION_END) return opt == OPTION_END ? p : NULL; @@ -1827,7 +1950,7 @@ static void option_put(struct dhcp_packet *mess, unsig } static void option_put_string(struct dhcp_packet *mess, unsigned char *end, int opt, - char *string, int null_term) + const char *string, int null_term) { unsigned char *p; size_t len = strlen(string); @@ -1905,15 +2028,32 @@ static void match_vendor_opts(unsigned char *opt, stru dopt->flags &= ~DHOPT_VENDOR_MATCH; if (opt && (dopt->flags & DHOPT_VENDOR)) { - int i, len = 0; - if (dopt->u.vendor_class) - len = strlen((char *)dopt->u.vendor_class); - for (i = 0; i <= (option_len(opt) - len); i++) - if (len == 0 || memcmp(dopt->u.vendor_class, option_ptr(opt, i), len) == 0) - { - dopt->flags |= DHOPT_VENDOR_MATCH; - break; - } + const struct dhcp_pxe_vendor *pv; + struct dhcp_pxe_vendor dummy_vendor = { + .data = (char *)dopt->u.vendor_class, + .next = NULL, + }; + if (dopt->flags & DHOPT_VENDOR_PXE) + pv = daemon->dhcp_pxe_vendors; + else + pv = &dummy_vendor; + for (; pv; pv = pv->next) + { + int i, len = 0, matched = 0; + if (pv->data) + len = strlen(pv->data); + for (i = 0; i <= (option_len(opt) - len); i++) + if (len == 0 || memcmp(pv->data, option_ptr(opt, i), len) == 0) + { + matched = 1; + break; + } + if (matched) + { + dopt->flags |= DHOPT_VENDOR_MATCH; + break; + } + } } } } @@ -1966,11 +2106,13 @@ static int do_encap_opts(struct dhcp_opt *opt, int enc return ret; } -static void pxe_misc(struct dhcp_packet *mess, unsigned char *end, unsigned char *uuid) +static void pxe_misc(struct dhcp_packet *mess, unsigned char *end, unsigned char *uuid, const char *pxevendor) { unsigned char *p; - option_put_string(mess, end, OPTION_VENDOR_ID, "PXEClient", 0); + if (!pxevendor) + pxevendor="PXEClient"; + option_put_string(mess, end, OPTION_VENDOR_ID, pxevendor, 0); if (uuid && (p = free_space(mess, end, OPTION_PXE_UUID, 17))) memcpy(p, uuid, 17); } @@ -2187,6 +2329,29 @@ struct dhcp_boot *find_boot(struct dhcp_netid *netid) return boot; } +static int is_pxe_client(struct dhcp_packet *mess, size_t sz, const char **pxe_vendor) +{ + const unsigned char *opt = NULL; + ssize_t conf_len = 0; + const struct dhcp_pxe_vendor *conf = daemon->dhcp_pxe_vendors; + opt = option_find(mess, sz, OPTION_VENDOR_ID, 0); + if (!opt) + return 0; + for (; conf; conf = conf->next) + { + conf_len = strlen(conf->data); + if (option_len(opt) < conf_len) + continue; + if (strncmp(option_ptr(opt, 0), conf->data, conf_len) == 0) + { + if (pxe_vendor) + *pxe_vendor = conf->data; + return 1; + } + } + return 0; +} + static void do_options(struct dhcp_context *context, struct dhcp_packet *mess, unsigned char *end, @@ -2201,7 +2366,8 @@ static void do_options(struct dhcp_context *context, int vendor_class_len, time_t now, unsigned int lease_time, - unsigned short fuzz) + unsigned short fuzz, + const char *pxevendor) { struct dhcp_opt *opt, *config_opts = daemon->dhcp_opts; struct dhcp_boot *boot; @@ -2251,7 +2417,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))) @@ -2263,7 +2429,7 @@ static void do_options(struct dhcp_context *context, in_list(req_options, OPTION_SNAME)) option_put_string(mess, end, OPTION_SNAME, boot->sname, 1); else - strncpy((char *)mess->sname, boot->sname, sizeof(mess->sname)-1); + safe_strncpy((char *)mess->sname, boot->sname, sizeof(mess->sname)); } if (boot->file) @@ -2273,7 +2439,7 @@ static void do_options(struct dhcp_context *context, in_list(req_options, OPTION_FILENAME)) option_put_string(mess, end, OPTION_FILENAME, boot->file, 1); else - strncpy((char *)mess->file, boot->file, sizeof(mess->file)-1); + safe_strncpy((char *)mess->file, boot->file, sizeof(mess->file)); } if (boot->next_server.s_addr) @@ -2290,14 +2456,14 @@ static void do_options(struct dhcp_context *context, if ((!req_options || !in_list(req_options, OPTION_FILENAME)) && (opt = option_find2(OPTION_FILENAME)) && !(opt->flags & DHOPT_FORCE)) { - strncpy((char *)mess->file, (char *)opt->val, sizeof(mess->file)-1); + safe_strncpy((char *)mess->file, (char *)opt->val, sizeof(mess->file)); done_file = 1; } if ((!req_options || !in_list(req_options, OPTION_SNAME)) && (opt = option_find2(OPTION_SNAME)) && !(opt->flags & DHOPT_FORCE)) { - strncpy((char *)mess->sname, (char *)opt->val, sizeof(mess->sname)-1); + safe_strncpy((char *)mess->sname, (char *)opt->val, sizeof(mess->sname)); done_server = 1; } @@ -2419,10 +2585,10 @@ static void do_options(struct dhcp_context *context, if (fqdn_flags & 0x04) { - p = do_rfc1035_name(p, hostname); + p = do_rfc1035_name(p, hostname, NULL); if (domain) { - p = do_rfc1035_name(p, domain); + p = do_rfc1035_name(p, domain, NULL); *p++ = 0; } } @@ -2575,7 +2741,7 @@ static void do_options(struct dhcp_context *context, if (context && pxe_arch != -1) { - pxe_misc(mess, end, uuid); + pxe_misc(mess, end, uuid, pxevendor); if (!pxe_uefi_workaround(pxe_arch, tagif, mess, context->local, now, 0)) config_opts = pxe_opts(pxe_arch, tagif, context->local, now); } @@ -2593,6 +2759,29 @@ static void do_options(struct dhcp_context *context, { mess->file[0] = f0; mess->sname[0] = s0; + } +} + +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); } }