Commit 5ce3e76f authored by Simon Kelley's avatar Simon Kelley

DHCPv4: do ICMP-ping check in all cases other that current lease.

parent 6ec5f5c4
...@@ -108,6 +108,11 @@ version 2.77 ...@@ -108,6 +108,11 @@ version 2.77
the internal interfaces of a router. Thanks to the internal interfaces of a router. Thanks to
Vladislav Grishenko for the patch. 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 version 2.76
Include 0.0.0.0/8 in DNS rebind checks. This range Include 0.0.0.0/8 in DNS rebind checks. This range
......
...@@ -643,6 +643,66 @@ struct dhcp_config *config_find_by_address(struct dhcp_config *configs, struct i ...@@ -643,6 +643,66 @@ struct dhcp_config *config_find_by_address(struct dhcp_config *configs, struct i
return NULL; 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, int address_allocate(struct dhcp_context *context,
struct in_addr *addrp, unsigned char *hwaddr, int hw_len, struct in_addr *addrp, unsigned char *hwaddr, int hw_len,
struct dhcp_netid *netids, time_t now) struct dhcp_netid *netids, time_t now)
...@@ -660,6 +720,10 @@ int address_allocate(struct dhcp_context *context, ...@@ -660,6 +720,10 @@ int address_allocate(struct dhcp_context *context,
dispersal even with similarly-valued "strings". */ dispersal even with similarly-valued "strings". */
for (j = 0, i = 0; i < hw_len; i++) for (j = 0, i = 0; i < hw_len; i++)
j = hwaddr[i] + (j << 6) + (j << 16) - j; j = hwaddr[i] + (j << 6) + (j << 16) - j;
/* j == 0 is marker */
if (j == 0)
j = 1;
for (pass = 0; pass <= 1; pass++) for (pass = 0; pass <= 1; pass++)
for (c = context; c; c = c->current) for (c = context; c; c = c->current)
...@@ -697,69 +761,27 @@ int address_allocate(struct dhcp_context *context, ...@@ -697,69 +761,27 @@ int address_allocate(struct dhcp_context *context,
(!IN_CLASSC(ntohl(addr.s_addr)) || (!IN_CLASSC(ntohl(addr.s_addr)) ||
((ntohl(addr.s_addr) & 0xff) != 0xff && ((ntohl(addr.s_addr) & 0xff) != 0x0)))) ((ntohl(addr.s_addr) & 0xff) != 0xff && ((ntohl(addr.s_addr) & 0xff) != 0x0))))
{ {
struct ping_result *r, *victim = NULL; struct ping_result *r;
int count, max = (int)(0.6 * (((float)PING_CACHE_TIME)/
((float)PING_WAIT)));
*addrp = addr; if ((r = do_icmp_ping(now, addr, j)))
{
/* check if we failed to ping addr sometime in the last /* consec-ip mode: we offered this address for another client
PING_CACHE_TIME seconds. If so, assume the same situation still exists. (different hash) recently, don't offer it to this one. */
This avoids problems when a stupid client bangs if (!option_bool(OPT_CONSEC_ADDR) || r->hash == j)
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))
{ {
/* address in use: perturb address selection so that we are *addrp = addr;
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;
}
return 1; 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); addr.s_addr = htonl(ntohl(addr.s_addr) + 1);
if (addr.s_addr == htonl(ntohl(c->end.s_addr) + 1)) if (addr.s_addr == htonl(ntohl(c->end.s_addr) + 1))
......
...@@ -1292,6 +1292,8 @@ struct dhcp_context *address_available(struct dhcp_context *context, ...@@ -1292,6 +1292,8 @@ struct dhcp_context *address_available(struct dhcp_context *context,
struct dhcp_context *narrow_context(struct dhcp_context *context, struct dhcp_context *narrow_context(struct dhcp_context *context,
struct in_addr taddr, struct in_addr taddr,
struct dhcp_netid *netids); 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, int address_allocate(struct dhcp_context *context,
struct in_addr *addrp, unsigned char *hwaddr, int hw_len, struct in_addr *addrp, unsigned char *hwaddr, int hw_len,
struct dhcp_netid *netids, time_t now); struct dhcp_netid *netids, time_t now);
......
...@@ -1029,6 +1029,8 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, ...@@ -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) && else if (have_config(config, CONFIG_DECLINED) &&
difftime(now, config->decline_time) < (float)DECLINE_BACKOFF) 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); 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 else
conf = config->addr; conf = config->addr;
} }
...@@ -1041,7 +1043,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index, ...@@ -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)) !config_find_by_address(daemon->dhcp_conf, lease->addr))
mess->yiaddr = lease->addr; mess->yiaddr = lease->addr;
else if (opt && address_available(context, addr, tagif_netid) && !lease_find_by_addr(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; mess->yiaddr = addr;
else if (emac_len == 0) else if (emac_len == 0)
message = _("no unique-id"); message = _("no unique-id");
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment