diff options
Diffstat (limited to 'src/dhcp.c')
-rw-r--r-- | src/dhcp.c | 189 |
1 files changed, 114 insertions, 75 deletions
@@ -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)) |