DHCPv4: do ICMP-ping check in all cases other that current lease.
diff --git a/CHANGELOG b/CHANGELOG
index e9112e1..111764f 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -108,6 +108,11 @@
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 @@
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 @@
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 @@
(!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 *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 @@
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 @@
!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");