summaryrefslogtreecommitdiff
path: root/src/dhcp.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/dhcp.c')
-rw-r--r--src/dhcp.c189
1 files changed, 114 insertions, 75 deletions
diff --git a/src/dhcp.c b/src/dhcp.c
index e6fceb1..5a8daec 100644
--- a/src/dhcp.c
+++ b/src/dhcp.c
@@ -1,4 +1,4 @@
-/* dnsmasq is Copyright (c) 2000-2015 Simon Kelley
+/* dnsmasq is Copyright (c) 2000-2018 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
@@ -67,9 +67,9 @@ static int make_fd(int port)
setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &oneopt, sizeof(oneopt)) == -1)
die(_("failed to set options on DHCP socket: %s"), NULL, EC_BADNET);
- /* When bind-interfaces is set, there might be more than one dnmsasq
+ /* When bind-interfaces is set, there might be more than one dnsmasq
instance binding port 67. That's OK if they serve different networks.
- Need to set REUSEADDR|REUSEPORT to make this posible.
+ Need to set REUSEADDR|REUSEPORT to make this possible.
Handle the case that REUSEPORT is defined, but the kernel doesn't
support it. This handles the introduction of REUSEPORT on Linux. */
if (option_bool(OPT_NOWILD) || option_bool(OPT_CLEVERBIND))
@@ -145,11 +145,14 @@ void dhcp_packet(time_t now, int pxe_fd)
struct cmsghdr *cmptr;
struct iovec iov;
ssize_t sz;
- int iface_index = 0, unicast_dest = 0, is_inform = 0;
+ int iface_index = 0, unicast_dest = 0, is_inform = 0, loopback = 0;
+ int rcvd_iface_index;
struct in_addr iface_addr;
struct iface_param parm;
+ time_t recvtime = now;
#ifdef HAVE_LINUX_NETWORK
struct arpreq arp_req;
+ struct timeval tv;
#endif
union {
@@ -176,6 +179,9 @@ void dhcp_packet(time_t now, int pxe_fd)
return;
#if defined (HAVE_LINUX_NETWORK)
+ if (ioctl(fd, SIOCGSTAMP, &tv) == 0)
+ recvtime = tv.tv_sec;
+
if (msg.msg_controllen >= sizeof(struct cmsghdr))
for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr))
if (cmptr->cmsg_level == IPPROTO_IP && cmptr->cmsg_type == IP_PKTINFO)
@@ -217,9 +223,13 @@ void dhcp_packet(time_t now, int pxe_fd)
}
#endif
- if (!indextoname(daemon->dhcpfd, iface_index, ifr.ifr_name))
+ if (!indextoname(daemon->dhcpfd, iface_index, ifr.ifr_name) ||
+ ioctl(daemon->dhcpfd, SIOCGIFFLAGS, &ifr) != 0)
return;
-
+
+ mess = (struct dhcp_packet *)daemon->dhcp_packet.iov_base;
+ loopback = !mess->giaddr.s_addr && (ifr.ifr_flags & IFF_LOOPBACK);
+
#ifdef HAVE_LINUX_NETWORK
/* ARP fiddling uses original interface even if we pretend to use a different one. */
strncpy(arp_req.arp_dev, ifr.ifr_name, 16);
@@ -230,6 +240,7 @@ void dhcp_packet(time_t now, int pxe_fd)
--bridge-interface option), change ifr.ifr_name so that we look
for DHCP contexts associated with the aliased interface instead
of with the aliasing one. */
+ rcvd_iface_index = iface_index;
for (bridge = daemon->bridges; bridge; bridge = bridge->next)
{
for (alias = bridge->alias; alias; alias = alias->next)
@@ -262,8 +273,8 @@ void dhcp_packet(time_t now, int pxe_fd)
if ((relay = relay_reply4((struct dhcp_packet *)daemon->dhcp_packet.iov_base, ifr.ifr_name)))
{
/* Reply from server, using us as relay. */
- iface_index = relay->iface_index;
- if (!indextoname(daemon->dhcpfd, iface_index, ifr.ifr_name))
+ rcvd_iface_index = relay->iface_index;
+ if (!indextoname(daemon->dhcpfd, rcvd_iface_index, ifr.ifr_name))
return;
is_relay_reply = 1;
iov.iov_len = sz;
@@ -278,7 +289,8 @@ void dhcp_packet(time_t now, int pxe_fd)
iface_addr = ((struct sockaddr_in *) &ifr.ifr_addr)->sin_addr;
else
{
- my_syslog(MS_DHCP | LOG_WARNING, _("DHCP packet received on %s which has no address"), ifr.ifr_name);
+ if (iface_check(AF_INET, NULL, ifr.ifr_name, NULL))
+ my_syslog(MS_DHCP | LOG_WARNING, _("DHCP packet received on %s which has no address"), ifr.ifr_name);
return;
}
@@ -323,7 +335,7 @@ void dhcp_packet(time_t now, int pxe_fd)
/* We're relaying this request */
if (parm.relay_local.s_addr != 0 &&
- relay_upstream4(parm.relay, (struct dhcp_packet *)daemon->dhcp_packet.iov_base, (size_t)sz, iface_index))
+ relay_upstream4(parm.relay, mess, (size_t)sz, iface_index))
return;
/* May have configured relay, but not DHCP server */
@@ -332,14 +344,14 @@ void dhcp_packet(time_t now, int pxe_fd)
lease_prune(NULL, now); /* lose any expired leases */
iov.iov_len = dhcp_reply(parm.current, ifr.ifr_name, iface_index, (size_t)sz,
- now, unicast_dest, &is_inform, pxe_fd, iface_addr);
+ now, unicast_dest, loopback, &is_inform, pxe_fd, iface_addr, recvtime);
lease_update_file(now);
lease_update_dns(0);
if (iov.iov_len == 0)
return;
}
-
+
msg.msg_name = &dest;
msg.msg_namelen = sizeof(dest);
msg.msg_control = NULL;
@@ -387,7 +399,7 @@ void dhcp_packet(time_t now, int pxe_fd)
msg.msg_controllen = sizeof(control_u);
cmptr = CMSG_FIRSTHDR(&msg);
pkt = (struct in_pktinfo *)CMSG_DATA(cmptr);
- pkt->ipi_ifindex = iface_index;
+ pkt->ipi_ifindex = rcvd_iface_index;
pkt->ipi_spec_dst.s_addr = 0;
msg.msg_controllen = cmptr->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo));
cmptr->cmsg_level = IPPROTO_IP;
@@ -452,8 +464,13 @@ void dhcp_packet(time_t now, int pxe_fd)
#endif
while(retry_send(sendmsg(fd, &msg, 0)));
+
+ /* This can fail when, eg, iptables DROPS destination 255.255.255.255 */
+ if (errno != 0)
+ my_syslog(MS_DHCP | LOG_WARNING, _("Error sending DHCP packet to %s: %s"),
+ inet_ntoa(dest.sin_addr), strerror(errno));
}
-
+
/* check against secondary interface addresses */
static int check_listen_addrs(struct in_addr local, int if_index, char *label,
struct in_addr netmask, struct in_addr broadcast, void *vparam)
@@ -488,7 +505,7 @@ static int check_listen_addrs(struct in_addr local, int if_index, char *label,
3) Fills in local (this host) and router (this host or relay) addresses.
4) Links contexts which are valid for hosts directly connected to the arrival interface on ->current.
- Note that the current chain may be superceded later for configured hosts or those coming via gateways. */
+ Note that the current chain may be superseded later for configured hosts or those coming via gateways. */
static int complete_context(struct in_addr local, int if_index, char *label,
struct in_addr netmask, struct in_addr broadcast, void *vparam)
@@ -588,7 +605,7 @@ struct dhcp_context *narrow_context(struct dhcp_context *context,
{
/* We start of with a set of possible contexts, all on the current physical interface.
These are chained on ->current.
- Here we have an address, and return the actual context correponding to that
+ Here we have an address, and return the actual context corresponding to that
address. Note that none may fit, if the address came a dhcp-host and is outside
any dhcp-range. In that case we return a static range if possible, or failing that,
any context on the correct subnet. (If there's more than one, this is a dodgy
@@ -630,9 +647,69 @@ 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, int loopback)
+{
+ 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) || loopback)
+ {
+ /* overloaded, or configured not to check, loopback interface, 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)
+ struct dhcp_netid *netids, time_t now, int loopback)
{
/* Find a free address: exclude anything in use and anything allocated to
a particular hwaddr/clientid/hostname in our configuration.
@@ -646,7 +723,11 @@ int address_allocate(struct dhcp_context *context,
/* hash hwaddr: use the SDBM hashing algorithm. Seems to give good
dispersal even with similarly-valued "strings". */
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 (c = context; c; c = c->current)
@@ -684,69 +765,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))
- {
- /* 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
+ if ((r = do_icmp_ping(now, addr, j, loopback)))
+ {
+ /* 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)
{
- /* 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))