diff --git a/CHANGELOG b/CHANGELOG index e9112e1..111764f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -108,6 +108,11 @@ version 2.77 the internal interfaces of a router. Thanks to Vladislav Grishenko for the patch. + Do ICMP-ping check for address-in-use for DHCPv4 when + the client specifies an address in DHCPDISCOVER, and when + an address in configured locally. Thanks to Alin Năstac + for spotting the problem. + version 2.76 Include 0.0.0.0/8 in DNS rebind checks. This range diff --git a/src/dhcp.c b/src/dhcp.c index ada1be8..5b8c319 100644 --- a/src/dhcp.c +++ b/src/dhcp.c @@ -643,6 +643,66 @@ struct dhcp_config *config_find_by_address(struct dhcp_config *configs, struct i return NULL; } +/* Check if and address is in use by sending ICMP ping. + This wrapper handles a cache and load-limiting. + Return is NULL is address in use, or a pointer to a cache entry + recording that it isn't. */ +struct ping_result *do_icmp_ping(time_t now, struct in_addr addr, unsigned int hash) +{ + static struct ping_result dummy; + struct ping_result *r, *victim = NULL; + int count, max = (int)(0.6 * (((float)PING_CACHE_TIME)/ + ((float)PING_WAIT))); + + /* check if we failed to ping addr sometime in the last + PING_CACHE_TIME seconds. If so, assume the same situation still exists. + This avoids problems when a stupid client bangs + on us repeatedly. As a final check, if we did more + than 60% of the possible ping checks in the last + PING_CACHE_TIME, we are in high-load mode, so don't do any more. */ + for (count = 0, r = daemon->ping_results; r; r = r->next) + if (difftime(now, r->time) > (float)PING_CACHE_TIME) + victim = r; /* old record */ + else + { + count++; + if (r->addr.s_addr == addr.s_addr) + return r; + } + + /* didn't find cached entry */ + if ((count >= max) || option_bool(OPT_NO_PING)) + { + /* overloaded, or configured not to check, return "not in use" */ + dummy.hash = 0; + return &dummy; + } + else if (icmp_ping(addr)) + return NULL; /* address in use. */ + else + { + /* at this point victim may hold an expired record */ + if (!victim) + { + if ((victim = whine_malloc(sizeof(struct ping_result)))) + { + victim->next = daemon->ping_results; + daemon->ping_results = victim; + } + } + + /* record that this address is OK for 30s + without more ping checks */ + if (victim) + { + victim->addr = addr; + victim->time = now; + victim->hash = hash; + } + return victim; + } +} + int address_allocate(struct dhcp_context *context, struct in_addr *addrp, unsigned char *hwaddr, int hw_len, struct dhcp_netid *netids, time_t now) @@ -660,6 +720,10 @@ int address_allocate(struct dhcp_context *context, dispersal even with similarly-valued "strings". */ for (j = 0, i = 0; i < hw_len; i++) j = hwaddr[i] + (j << 6) + (j << 16) - j; + + /* j == 0 is marker */ + if (j == 0) + j = 1; for (pass = 0; pass <= 1; pass++) for (c = context; c; c = c->current) @@ -697,69 +761,27 @@ int address_allocate(struct dhcp_context *context, (!IN_CLASSC(ntohl(addr.s_addr)) || ((ntohl(addr.s_addr) & 0xff) != 0xff && ((ntohl(addr.s_addr) & 0xff) != 0x0)))) { - struct ping_result *r, *victim = NULL; - int count, max = (int)(0.6 * (((float)PING_CACHE_TIME)/ - ((float)PING_WAIT))); + struct ping_result *r; - *addrp = addr; - - /* check if we failed to ping addr sometime in the last - PING_CACHE_TIME seconds. If so, assume the same situation still exists. - This avoids problems when a stupid client bangs - on us repeatedly. As a final check, if we did more - than 60% of the possible ping checks in the last - PING_CACHE_TIME, we are in high-load mode, so don't do any more. */ - for (count = 0, r = daemon->ping_results; r; r = r->next) - if (difftime(now, r->time) > (float)PING_CACHE_TIME) - victim = r; /* old record */ - else - { - count++; - if (r->addr.s_addr == addr.s_addr) - { - /* consec-ip mode: we offered this address for another client - (different hash) recently, don't offer it to this one. */ - if (option_bool(OPT_CONSEC_ADDR) && r->hash != j) - break; - - return 1; - } - } - - if (!r) - { - if ((count < max) && !option_bool(OPT_NO_PING) && icmp_ping(addr)) + if ((r = do_icmp_ping(now, addr, j))) + { + /* consec-ip mode: we offered this address for another client + (different hash) recently, don't offer it to this one. */ + if (!option_bool(OPT_CONSEC_ADDR) || r->hash == j) { - /* address in use: perturb address selection so that we are - less likely to try this address again. */ - if (!option_bool(OPT_CONSEC_ADDR)) - c->addr_epoch++; - } - else - { - /* at this point victim may hold an expired record */ - if (!victim) - { - if ((victim = whine_malloc(sizeof(struct ping_result)))) - { - victim->next = daemon->ping_results; - daemon->ping_results = victim; - } - } - - /* record that this address is OK for 30s - without more ping checks */ - if (victim) - { - victim->addr = addr; - victim->time = now; - victim->hash = j; - } + *addrp = addr; return 1; } } + else + { + /* address in use: perturb address selection so that we are + less likely to try this address again. */ + if (!option_bool(OPT_CONSEC_ADDR)) + c->addr_epoch++; + } } - + addr.s_addr = htonl(ntohl(addr.s_addr) + 1); if (addr.s_addr == htonl(ntohl(c->end.s_addr) + 1)) diff --git a/src/dnsmasq.h b/src/dnsmasq.h index ace6b1e..ee2cd4e 100644 --- a/src/dnsmasq.h +++ b/src/dnsmasq.h @@ -1292,6 +1292,8 @@ struct dhcp_context *address_available(struct dhcp_context *context, struct dhcp_context *narrow_context(struct dhcp_context *context, struct in_addr taddr, struct dhcp_netid *netids); +struct ping_result *do_icmp_ping(time_t now, struct in_addr addr, + unsigned int hash); int address_allocate(struct dhcp_context *context, struct in_addr *addrp, unsigned char *hwaddr, int hw_len, struct dhcp_netid *netids, time_t now); diff --git a/src/rfc2131.c b/src/rfc2131.c index 6a8f0db..dfe25da 100644 --- a/src/rfc2131.c +++ b/src/rfc2131.c @@ -1029,6 +1029,8 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, else if (have_config(config, CONFIG_DECLINED) && difftime(now, config->decline_time) < (float)DECLINE_BACKOFF) my_syslog(MS_DHCP | LOG_WARNING, _("not using configured address %s because it was previously declined"), addrs); + else if (!do_icmp_ping(now, config->addr, 0)) + my_syslog(MS_DHCP | LOG_WARNING, _("not using configured address %s because it is in use by another host"), addrs); else conf = config->addr; } @@ -1041,7 +1043,7 @@ 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)) mess->yiaddr = addr; else if (emac_len == 0) message = _("no unique-id");