--- embedaddon/dnsmasq/src/dhcp-common.c 2013/07/29 19:37:40 1.1.1.1 +++ embedaddon/dnsmasq/src/dhcp-common.c 2023/09/27 11:02:07 1.1.1.5 @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2013 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2022 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 @@ -20,11 +20,11 @@ void dhcp_common_init(void) { - /* These each hold a DHCP option max size 255 - and get a terminating zero added */ - daemon->dhcp_buff = safe_malloc(256); - daemon->dhcp_buff2 = safe_malloc(256); - daemon->dhcp_buff3 = safe_malloc(256); + /* These each hold a DHCP option max size 255 + and get a terminating zero added */ + daemon->dhcp_buff = safe_malloc(DHCP_BUFF_SZ); + daemon->dhcp_buff2 = safe_malloc(DHCP_BUFF_SZ); + daemon->dhcp_buff3 = safe_malloc(DHCP_BUFF_SZ); /* dhcp_packet is used by v4 and v6, outpacket only by v6 sizeof(struct dhcp_packet) is as good an initial size as any, @@ -38,7 +38,7 @@ void dhcp_common_init(void) ssize_t recv_dhcp_packet(int fd, struct msghdr *msg) { - ssize_t sz; + ssize_t sz, new_sz; while (1) { @@ -65,18 +65,60 @@ ssize_t recv_dhcp_packet(int fd, struct msghdr *msg) } } - while ((sz = recvmsg(fd, msg, 0)) == -1 && errno == EINTR); + while ((new_sz = recvmsg(fd, msg, 0)) == -1 && errno == EINTR); + + /* Some kernels seem to ignore MSG_PEEK, and dequeue the packet anyway. + If that happens we get EAGAIN here because the socket is non-blocking. + Use the result of the original testing recvmsg as long as the buffer + was big enough. There's a small race here that may lose the odd packet, + but it's UDP anyway. */ - return (msg->msg_flags & MSG_TRUNC) ? -1 : sz; + if (new_sz == -1 && (errno == EWOULDBLOCK || errno == EAGAIN)) + new_sz = sz; + + return (msg->msg_flags & MSG_TRUNC) ? -1 : new_sz; } +/* like match_netid() except that the check can have a trailing * for wildcard */ +/* started as a direct copy of match_netid() */ +int match_netid_wild(struct dhcp_netid *check, struct dhcp_netid *pool) +{ + struct dhcp_netid *tmp1; + + for (; check; check = check->next) + { + const int check_len = strlen(check->net); + const int is_wc = (check_len > 0 && check->net[check_len - 1] == '*'); + + /* '#' for not is for backwards compat. */ + if (check->net[0] != '!' && check->net[0] != '#') + { + for (tmp1 = pool; tmp1; tmp1 = tmp1->next) + if (is_wc ? (strncmp(check->net, tmp1->net, check_len-1) == 0) : + (strcmp(check->net, tmp1->net) == 0)) + break; + if (!tmp1) + return 0; + } + else + for (tmp1 = pool; tmp1; tmp1 = tmp1->next) + if (is_wc ? (strncmp((check->net)+1, tmp1->net, check_len-2) == 0) : + (strcmp((check->net)+1, tmp1->net) == 0)) + return 0; + } + return 1; +} + struct dhcp_netid *run_tag_if(struct dhcp_netid *tags) { struct tag_if *exprs; struct dhcp_netid_list *list; + /* this now uses match_netid_wild() above so that tag_if can + * be used to set a 'group of interfaces' tag. + */ for (exprs = daemon->tag_if; exprs; exprs = exprs->next) - if (match_netid(exprs->tag, tags, 1)) + if (match_netid_wild(exprs->tag, tags)) for (list = exprs->set; list; list = list->next) { list->list->next = tags; @@ -253,6 +295,139 @@ int match_bytes(struct dhcp_opt *o, unsigned char *p, return 0; } +int config_has_mac(struct dhcp_config *config, unsigned char *hwaddr, int len, int type) +{ + struct hwaddr_config *conf_addr; + + for (conf_addr = config->hwaddr; conf_addr; conf_addr = conf_addr->next) + if (conf_addr->wildcard_mask == 0 && + conf_addr->hwaddr_len == len && + (conf_addr->hwaddr_type == type || conf_addr->hwaddr_type == 0) && + memcmp(conf_addr->hwaddr, hwaddr, len) == 0) + return 1; + + return 0; +} + +static int is_config_in_context(struct dhcp_context *context, struct dhcp_config *config) +{ + if (!context) /* called via find_config() from lease_update_from_configs() */ + return 1; + + if (!(config->flags & (CONFIG_ADDR | CONFIG_ADDR6))) + return 1; + +#ifdef HAVE_DHCP6 + if (context->flags & CONTEXT_V6) + { + struct addrlist *addr_list; + + if (config->flags & CONFIG_ADDR6) + for (; context; context = context->current) + for (addr_list = config->addr6; addr_list; addr_list = addr_list->next) + { + if ((addr_list->flags & ADDRLIST_WILDCARD) && context->prefix == 64) + return 1; + + if (is_same_net6(&addr_list->addr.addr6, &context->start6, context->prefix)) + return 1; + } + } + else +#endif + { + for (; context; context = context->current) + if ((config->flags & CONFIG_ADDR) && is_same_net(config->addr, context->start, context->netmask)) + return 1; + } + + return 0; +} + +static struct dhcp_config *find_config_match(struct dhcp_config *configs, + struct dhcp_context *context, + unsigned char *clid, int clid_len, + unsigned char *hwaddr, int hw_len, + int hw_type, char *hostname, + struct dhcp_netid *tags, int tag_not_needed) +{ + int count, new; + struct dhcp_config *config, *candidate; + struct hwaddr_config *conf_addr; + + if (clid) + for (config = configs; config; config = config->next) + if (config->flags & CONFIG_CLID) + { + if (config->clid_len == clid_len && + memcmp(config->clid, clid, clid_len) == 0 && + is_config_in_context(context, config) && + match_netid(config->filter, tags, tag_not_needed)) + + return config; + + /* dhcpcd prefixes ASCII client IDs by zero which is wrong, but we try and + cope with that here. This is IPv4 only. context==NULL implies IPv4, + see lease_update_from_configs() */ + if ((!context || !(context->flags & CONTEXT_V6)) && *clid == 0 && config->clid_len == clid_len-1 && + memcmp(config->clid, clid+1, clid_len-1) == 0 && + is_config_in_context(context, config) && + match_netid(config->filter, tags, tag_not_needed)) + return config; + } + + + if (hwaddr) + for (config = configs; config; config = config->next) + if (config_has_mac(config, hwaddr, hw_len, hw_type) && + is_config_in_context(context, config) && + match_netid(config->filter, tags, tag_not_needed)) + return config; + + if (hostname && context) + for (config = configs; config; config = config->next) + if ((config->flags & CONFIG_NAME) && + hostname_isequal(config->hostname, hostname) && + is_config_in_context(context, config) && + match_netid(config->filter, tags, tag_not_needed)) + return config; + + + if (!hwaddr) + return NULL; + + /* use match with fewest wildcard octets */ + for (candidate = NULL, count = 0, config = configs; config; config = config->next) + if (is_config_in_context(context, config) && + match_netid(config->filter, tags, tag_not_needed)) + for (conf_addr = config->hwaddr; conf_addr; conf_addr = conf_addr->next) + if (conf_addr->wildcard_mask != 0 && + conf_addr->hwaddr_len == hw_len && + (conf_addr->hwaddr_type == hw_type || conf_addr->hwaddr_type == 0) && + (new = memcmp_masked(conf_addr->hwaddr, hwaddr, hw_len, conf_addr->wildcard_mask)) > count) + { + count = new; + candidate = config; + } + + return candidate; +} + +/* Find tagged configs first. */ +struct dhcp_config *find_config(struct dhcp_config *configs, + struct dhcp_context *context, + unsigned char *clid, int clid_len, + unsigned char *hwaddr, int hw_len, + int hw_type, char *hostname, struct dhcp_netid *tags) +{ + struct dhcp_config *ret = find_config_match(configs, context, clid, clid_len, hwaddr, hw_len, hw_type, hostname, tags, 0); + + if (!ret) + ret = find_config_match(configs, context, clid, clid_len, hwaddr, hw_len, hw_type, hostname, tags, 1); + + return ret; +} + void dhcp_update_configs(struct dhcp_config *configs) { /* Some people like to keep all static IP addresses in /etc/hosts. @@ -267,8 +442,14 @@ void dhcp_update_configs(struct dhcp_config *configs) int prot = AF_INET; for (config = configs; config; config = config->next) + { if (config->flags & CONFIG_ADDR_HOSTS) - config->flags &= ~(CONFIG_ADDR | CONFIG_ADDR6 | CONFIG_ADDR_HOSTS); + config->flags &= ~(CONFIG_ADDR | CONFIG_ADDR_HOSTS); +#ifdef HAVE_DHCP6 + if (config->flags & CONFIG_ADDR6_HOSTS) + config->flags &= ~(CONFIG_ADDR6 | CONFIG_ADDR6_HOSTS); +#endif + } #ifdef HAVE_DHCP6 again: @@ -295,34 +476,45 @@ void dhcp_update_configs(struct dhcp_config *configs) if (cache_find_by_name(crec, config->hostname, 0, cacheflags)) { /* use primary (first) address */ - while (crec && !(crec->flags & F_REVERSE)) - crec = cache_find_by_name(crec, config->hostname, 0, cacheflags); - if (!crec) - continue; /* should be never */ - inet_ntop(prot, &crec->addr.addr, daemon->addrbuff, ADDRSTRLEN); - my_syslog(MS_DHCP | LOG_WARNING, _("%s has more than one address in hostsfile, using %s for DHCP"), - config->hostname, daemon->addrbuff); + while (crec && !(crec->flags & F_REVERSE)) + crec = cache_find_by_name(crec, config->hostname, 0, cacheflags); + if (!crec) + continue; /* should be never */ + inet_ntop(prot, &crec->addr, daemon->addrbuff, ADDRSTRLEN); + my_syslog(MS_DHCP | LOG_WARNING, _("%s has more than one address in hostsfile, using %s for DHCP"), + config->hostname, daemon->addrbuff); } if (prot == AF_INET && - (!(conf_tmp = config_find_by_address(configs, crec->addr.addr.addr.addr4)) || conf_tmp == config)) + (!(conf_tmp = config_find_by_address(configs, crec->addr.addr4)) || conf_tmp == config)) { - config->addr = crec->addr.addr.addr.addr4; + config->addr = crec->addr.addr4; config->flags |= CONFIG_ADDR | CONFIG_ADDR_HOSTS; continue; } #ifdef HAVE_DHCP6 if (prot == AF_INET6 && - (!(conf_tmp = config_find_by_address6(configs, &crec->addr.addr.addr.addr6, 128, 0)) || conf_tmp == config)) + (!(conf_tmp = config_find_by_address6(configs, NULL, 0, &crec->addr.addr6)) || conf_tmp == config)) { - memcpy(&config->addr6, &crec->addr.addr.addr.addr6, IN6ADDRSZ); - config->flags |= CONFIG_ADDR6 | CONFIG_ADDR_HOSTS; + /* host must have exactly one address if comming from /etc/hosts. */ + if (!config->addr6 && (config->addr6 = whine_malloc(sizeof(struct addrlist)))) + { + config->addr6->next = NULL; + config->addr6->flags = 0; + } + + if (config->addr6 && !config->addr6->next && !(config->addr6->flags & (ADDRLIST_WILDCARD|ADDRLIST_PREFIX))) + { + memcpy(&config->addr6->addr.addr6, &crec->addr.addr6, IN6ADDRSZ); + config->flags |= CONFIG_ADDR6 | CONFIG_ADDR6_HOSTS; + } + continue; } #endif - inet_ntop(prot, &crec->addr.addr, daemon->addrbuff, ADDRSTRLEN); + inet_ntop(prot, &crec->addr, daemon->addrbuff, ADDRSTRLEN); my_syslog(MS_DHCP | LOG_WARNING, _("duplicate IP address %s (%s) in dhcp-config directive"), daemon->addrbuff, config->hostname); @@ -341,40 +533,85 @@ void dhcp_update_configs(struct dhcp_config *configs) } #ifdef HAVE_LINUX_NETWORK -void bindtodevice(int fd) +char *whichdevice(void) { /* If we are doing DHCP on exactly one interface, and running linux, do SO_BINDTODEVICE to that device. This is for the use case of (eg) OpenStack, which runs a new dnsmasq instance for each VLAN interface it creates. Without the BINDTODEVICE, individual processes don't always see the packets they should. - SO_BINDTODEVICE is only available Linux. */ + SO_BINDTODEVICE is only available Linux. + + Note that if wildcards are used in --interface, or --interface is not used at all, + or a configured interface doesn't yet exist, then more interfaces may arrive later, + so we can't safely assert there is only one interface and proceed. +*/ struct irec *iface, *found; + struct iname *if_tmp; + if (!daemon->if_names) + return NULL; + + for (if_tmp = daemon->if_names; if_tmp; if_tmp = if_tmp->next) + if (if_tmp->name && (!if_tmp->used || strchr(if_tmp->name, '*'))) + return NULL; + for (found = NULL, iface = daemon->interfaces; iface; iface = iface->next) if (iface->dhcp_ok) { if (!found) found = iface; else if (strcmp(found->name, iface->name) != 0) - { - /* more than one. */ - found = NULL; - break; - } + return NULL; /* more than one. */ } - + if (found) { - struct ifreq ifr; - strcpy(ifr.ifr_name, found->name); - /* only allowed by root. */ - if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, (void *)&ifr, sizeof(ifr)) == -1 && - errno != EPERM) - die(_("failed to set SO_BINDTODEVICE on DHCP socket: %s"), NULL, EC_BADNET); + char *ret = safe_malloc(strlen(found->name)+1); + strcpy(ret, found->name); + return ret; } + + return NULL; } + +static int bindtodevice(char *device, int fd) +{ + size_t len = strlen(device)+1; + if (len > IFNAMSIZ) + len = IFNAMSIZ; + /* only allowed by root. */ + if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, device, len) == -1 && + errno != EPERM) + return 2; + + return 1; +} + +int bind_dhcp_devices(char *bound_device) +{ + int ret = 0; + + if (bound_device) + { + if (daemon->dhcp) + { + if (!daemon->relay4) + ret |= bindtodevice(bound_device, daemon->dhcpfd); + + if (daemon->enable_pxe && daemon->pxefd != -1) + ret |= bindtodevice(bound_device, daemon->pxefd); + } + +#if defined(HAVE_DHCP6) + if (daemon->doing_dhcp6 && !daemon->relay6) + ret |= bindtodevice(bound_device, daemon->dhcp6fd); #endif + } + + return ret; +} +#endif static const struct opttab_t { char *name; @@ -426,8 +663,8 @@ static const struct opttab_t { { "parameter-request", 55, OT_INTERNAL }, { "message", 56, OT_INTERNAL }, { "max-message-size", 57, OT_INTERNAL }, - { "T1", 58, OT_INTERNAL | OT_TIME}, - { "T2", 59, OT_INTERNAL | OT_TIME}, + { "T1", 58, OT_TIME}, + { "T2", 59, OT_TIME}, { "vendor-class", 60, 0 }, { "client-id", 61, OT_INTERNAL }, { "nis+-domain", 64, OT_NAME }, @@ -440,16 +677,21 @@ static const struct opttab_t { { "nntp-server", 71, OT_ADDR_LIST }, { "irc-server", 74, OT_ADDR_LIST }, { "user-class", 77, 0 }, + { "rapid-commit", 80, 0 }, { "FQDN", 81, OT_INTERNAL }, { "agent-id", 82, OT_INTERNAL }, { "client-arch", 93, 2 | OT_DEC }, { "client-interface-id", 94, 0 }, { "client-machine-id", 97, 0 }, + { "posix-timezone", 100, OT_NAME }, /* RFC 4833, Sec. 2 */ + { "tzdb-timezone", 101, OT_NAME }, /* RFC 4833, Sec. 2 */ + { "ipv6-only", 108, 4 | OT_DEC }, /* RFC 8925 */ { "subnet-select", 118, OT_INTERNAL }, { "domain-search", 119, OT_RFC1035_NAME }, { "sip-server", 120, 0 }, { "classless-static-route", 121, 0 }, { "vendor-id-encap", 125, 0 }, + { "tftp-server-address", 150, OT_ADDR_LIST }, { "server-ip-address", 255, OT_ADDR_LIST }, /* special, internal only, sets siaddr */ { NULL, 0, 0 } }; @@ -480,7 +722,9 @@ static const struct opttab_t opttab6[] = { { "sntp-server", 31, OT_ADDR_LIST }, { "information-refresh-time", 32, OT_TIME }, { "FQDN", 39, OT_INTERNAL | OT_RFC1035_NAME }, - { "ntp-server", 56, OT_ADDR_LIST }, + { "posix-timezone", 41, OT_NAME }, /* RFC 4833, Sec. 3 */ + { "tzdb-timezone", 42, OT_NAME }, /* RFC 4833, Sec. 3 */ + { "ntp-server", 56, 0 /* OT_ADDR_LIST | OT_RFC1035_NAME */ }, { "bootfile-url", 59, OT_NAME }, { "bootfile-param", 60, OT_CSTRING }, { NULL, 0, 0 } @@ -512,11 +756,13 @@ void display_opts6(void) } #endif -u16 lookup_dhcp_opt(int prot, char *name) +int lookup_dhcp_opt(int prot, char *name) { const struct opttab_t *t; int i; + (void)prot; + #ifdef HAVE_DHCP6 if (prot == AF_INET6) t = opttab6; @@ -528,14 +774,16 @@ u16 lookup_dhcp_opt(int prot, char *name) if (strcasecmp(t[i].name, name) == 0) return t[i].val; - return 0; + return -1; } -u16 lookup_dhcp_len(int prot, u16 val) +int lookup_dhcp_len(int prot, int val) { const struct opttab_t *t; int i; + (void)prot; + #ifdef HAVE_DHCP6 if (prot == AF_INET6) t = opttab6; @@ -569,7 +817,7 @@ char *option_string(int prot, unsigned int opt, unsign if (ot[o].size & OT_ADDR_LIST) { - struct all_addr addr; + union all_addr addr; int addr_len = INADDRSZ; #ifdef HAVE_DHCP6 @@ -590,7 +838,7 @@ char *option_string(int prot, unsigned int opt, unsign for (i = 0, j = 0; i < opt_len && j < buf_len ; i++) { char c = val[i]; - if (isprint((int)c)) + if (isprint((unsigned char)c)) buf[j++] = c; } #ifdef HAVE_DHCP6 @@ -604,7 +852,7 @@ char *option_string(int prot, unsigned int opt, unsign for (k = i + 1; k < opt_len && k < l && j < buf_len ; k++) { char c = val[k]; - if (isprint((int)c)) + if (isprint((unsigned char)c)) buf[j++] = c; } i = l; @@ -625,7 +873,7 @@ char *option_string(int prot, unsigned int opt, unsign for (k = 0; k < len && j < buf_len; k++) { char c = *p++; - if (isprint((int)c)) + if (isprint((unsigned char)c)) buf[j++] = c; } i += len +2; @@ -713,43 +961,93 @@ void log_context(int family, struct dhcp_context *cont template = p; p += sprintf(p, ", "); - if (indextoname(daemon->doing_dhcp6 ? daemon->dhcp6fd : daemon->icmp6fd, context->if_index, ifrn_name)) - sprintf(p, "constructed for %s", ifrn_name); + if (indextoname(daemon->icmp6fd, context->if_index, ifrn_name)) + sprintf(p, "%s for %s", (context->flags & CONTEXT_OLD) ? "old prefix" : "constructed", ifrn_name); } - else if (context->flags & CONTEXT_TEMPLATE) + else if (context->flags & CONTEXT_TEMPLATE && !(context->flags & CONTEXT_RA_STATELESS)) { template = p; p += sprintf(p, ", "); - + sprintf(p, "template for %s", context->template_interface); } #endif - if ((context->flags & CONTEXT_DHCP) || family == AF_INET) + if (!(context->flags & CONTEXT_OLD) && + ((context->flags & CONTEXT_DHCP) || family == AF_INET)) { - inet_ntop(family, start, daemon->dhcp_buff, 256); - inet_ntop(family, end, daemon->dhcp_buff3, 256); +#ifdef HAVE_DHCP6 + if (context->flags & CONTEXT_RA_STATELESS) + { + if (context->flags & CONTEXT_TEMPLATE) + strncpy(daemon->dhcp_buff, context->template_interface, DHCP_BUFF_SZ); + else + strcpy(daemon->dhcp_buff, daemon->addrbuff); + } + else +#endif + inet_ntop(family, start, daemon->dhcp_buff, DHCP_BUFF_SZ); + inet_ntop(family, end, daemon->dhcp_buff3, DHCP_BUFF_SZ); my_syslog(MS_DHCP | LOG_INFO, - (context->flags & CONTEXT_RA_STATELESS) ? - _("%s stateless on %s%.0s%.0s%s") : - (context->flags & CONTEXT_STATIC) ? - _("%s, static leases only on %.0s%s%s%.0s") : - (context->flags & CONTEXT_PROXY) ? - _("%s, proxy on subnet %.0s%s%.0s%.0s") : - _("%s, IP range %s -- %s%s%.0s"), - (family != AF_INET) ? "DHCPv6" : "DHCP", + (context->flags & CONTEXT_RA_STATELESS) ? + _("%s stateless on %s%.0s%.0s%s") : + (context->flags & CONTEXT_STATIC) ? + _("%s, static leases only on %.0s%s%s%.0s") : + (context->flags & CONTEXT_PROXY) ? + _("%s, proxy on subnet %.0s%s%.0s%.0s") : + _("%s, IP range %s -- %s%s%.0s"), + (family != AF_INET) ? "DHCPv6" : "DHCP", daemon->dhcp_buff, daemon->dhcp_buff3, daemon->namebuff, template); } #ifdef HAVE_DHCP6 - if (context->flags & CONTEXT_RA_NAME) + if (context->flags & CONTEXT_TEMPLATE) + { + strcpy(daemon->addrbuff, context->template_interface); + template = ""; + } + + if ((context->flags & CONTEXT_RA_NAME) && !(context->flags & CONTEXT_OLD)) my_syslog(MS_DHCP | LOG_INFO, _("DHCPv4-derived IPv6 names on %s%s"), daemon->addrbuff, template); - + if ((context->flags & CONTEXT_RA) || (option_bool(OPT_RA) && (context->flags & CONTEXT_DHCP) && family == AF_INET6)) my_syslog(MS_DHCP | LOG_INFO, _("router advertisement on %s%s"), daemon->addrbuff, template); #endif } - +void log_relay(int family, struct dhcp_relay *relay) +{ + int broadcast = relay->server.addr4.s_addr == 0; + inet_ntop(family, &relay->local, daemon->addrbuff, ADDRSTRLEN); + inet_ntop(family, &relay->server, daemon->namebuff, ADDRSTRLEN); + + if (family == AF_INET && relay->port != DHCP_SERVER_PORT) + sprintf(daemon->namebuff + strlen(daemon->namebuff), "#%u", relay->port); + +#ifdef HAVE_DHCP6 + struct in6_addr multicast; + + inet_pton(AF_INET6, ALL_SERVERS, &multicast); + + if (family == AF_INET6) + { + broadcast = IN6_ARE_ADDR_EQUAL(&relay->server.addr6, &multicast); + if (relay->port != DHCPV6_SERVER_PORT) + sprintf(daemon->namebuff + strlen(daemon->namebuff), "#%u", relay->port); + } +#endif + + + if (relay->interface) + { + if (broadcast) + my_syslog(MS_DHCP | LOG_INFO, _("DHCP relay from %s via %s"), daemon->addrbuff, relay->interface); + else + my_syslog(MS_DHCP | LOG_INFO, _("DHCP relay from %s to %s via %s"), daemon->addrbuff, daemon->namebuff, relay->interface); + } + else + my_syslog(MS_DHCP | LOG_INFO, _("DHCP relay from %s to %s"), daemon->addrbuff, daemon->namebuff); +} + #endif