--- embedaddon/dnsmasq/src/dhcp.c 2013/07/29 19:37:40 1.1 +++ embedaddon/dnsmasq/src/dhcp.c 2023/09/27 11:02:07 1.1.1.5 @@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2013 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2022 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 @@ -28,10 +28,12 @@ struct match_param { struct in_addr netmask, broadcast, addr; }; -static int complete_context(struct in_addr local, int if_index, +static int complete_context(struct in_addr local, int if_index, char *label, struct in_addr netmask, struct in_addr broadcast, void *vparam); -static int check_listen_addrs(struct in_addr local, int if_index, +static int check_listen_addrs(struct in_addr local, int if_index, char *label, struct in_addr netmask, struct in_addr broadcast, void *vparam); +static int relay_upstream4(int iface_index, struct dhcp_packet *mess, size_t sz); +static struct dhcp_relay *relay_reply4(struct dhcp_packet *mess, char *arrival_interface); static int make_fd(int port) { @@ -63,22 +65,22 @@ 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)) { - int rc = -1, porterr = 0; + int rc = 0; #ifdef SO_REUSEPORT if ((rc = setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &oneopt, sizeof(oneopt))) == -1 && - errno != ENOPROTOOPT) - porterr = 1; + errno == ENOPROTOOPT) + rc = 0; #endif - if (rc == -1 && !porterr) + if (rc != -1) rc = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &oneopt, sizeof(oneopt)); if (rc == -1) @@ -132,6 +134,8 @@ void dhcp_packet(time_t now, int pxe_fd) int fd = pxe_fd ? daemon->pxefd : daemon->dhcpfd; struct dhcp_packet *mess; struct dhcp_context *context; + struct dhcp_relay *relay; + int is_relay_reply = 0; struct iname *tmp; struct ifreq ifr; struct msghdr msg; @@ -139,11 +143,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 { @@ -168,8 +175,15 @@ void dhcp_packet(time_t now, int pxe_fd) if ((sz = recv_dhcp_packet(fd, &msg)) == -1 || (sz < (ssize_t)(sizeof(*mess) - sizeof(mess->options)))) return; - - #if defined (HAVE_LINUX_NETWORK) + +#ifdef HAVE_DUMPFILE + dump_packet_udp(DUMP_DHCP, (void *)daemon->dhcp_packet.iov_base, sz, (union mysockaddr *)&dest, NULL, fd); +#endif + +#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) @@ -211,31 +225,39 @@ 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); + safe_strncpy(arp_req.arp_dev, ifr.ifr_name, sizeof(arp_req.arp_dev)); #endif - /* One form of bridging on BSD has the property that packets - can be recieved on bridge interfaces which do not have an IP address. - We allow these to be treated as aliases of another interface which does have - an IP address with --dhcp-bridge=interface,alias,alias */ + /* If the interface on which the DHCP request was received is an + alias of some other interface (as specified by the + --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) - if (strncmp(ifr.ifr_name, alias->iface, IF_NAMESIZE) == 0) + if (wildcard_matchn(alias->iface, ifr.ifr_name, IF_NAMESIZE)) { if (!(iface_index = if_nametoindex(bridge->iface))) { - my_syslog(LOG_WARNING, _("unknown interface %s in bridge-interface"), ifr.ifr_name); + my_syslog(MS_DHCP | LOG_WARNING, + _("unknown interface %s in bridge-interface"), + bridge->iface); return; } else { - strncpy(ifr.ifr_name, bridge->iface, IF_NAMESIZE); + safe_strncpy(ifr.ifr_name, bridge->iface, sizeof(ifr.ifr_name)); break; } } @@ -250,58 +272,87 @@ void dhcp_packet(time_t now, int pxe_fd) unicast_dest = 1; #endif - ifr.ifr_addr.sa_family = AF_INET; - if (ioctl(daemon->dhcpfd, SIOCGIFADDR, &ifr) != -1 ) - iface_addr = ((struct sockaddr_in *) &ifr.ifr_addr)->sin_addr; - else + if ((relay = relay_reply4((struct dhcp_packet *)daemon->dhcp_packet.iov_base, ifr.ifr_name))) { - my_syslog(MS_DHCP | LOG_WARNING, _("DHCP packet received on %s which has no address"), ifr.ifr_name); - return; + /* Reply from server, using us as relay. */ + 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; +#ifdef HAVE_LINUX_NETWORK + safe_strncpy(arp_req.arp_dev, ifr.ifr_name, sizeof(arp_req.arp_dev)); +#endif } - - for (tmp = daemon->dhcp_except; tmp; tmp = tmp->next) - if (tmp->name && wildcard_match(tmp->name, ifr.ifr_name)) - return; - - /* unlinked contexts are marked by context->current == context */ - for (context = daemon->dhcp; context; context = context->next) - context->current = context; - - parm.current = NULL; - parm.ind = iface_index; - - if (!iface_check(AF_INET, (struct all_addr *)&iface_addr, ifr.ifr_name, NULL)) + else { - /* If we failed to match the primary address of the interface, see if we've got a --listen-address - for a secondary */ - struct match_param match; + ifr.ifr_addr.sa_family = AF_INET; + if (ioctl(daemon->dhcpfd, SIOCGIFADDR, &ifr) != -1 ) + iface_addr = ((struct sockaddr_in *) &ifr.ifr_addr)->sin_addr; + else + { + 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; + } - match.matched = 0; - match.ind = iface_index; + for (tmp = daemon->dhcp_except; tmp; tmp = tmp->next) + if (tmp->name && wildcard_match(tmp->name, ifr.ifr_name)) + return; - if (!daemon->if_addrs || - !iface_enumerate(AF_INET, &match, check_listen_addrs) || - !match.matched) + /* unlinked contexts/relays are marked by context->current == context */ + for (context = daemon->dhcp; context; context = context->next) + context->current = context; + + parm.current = NULL; + parm.ind = iface_index; + + if (!iface_check(AF_INET, (union all_addr *)&iface_addr, ifr.ifr_name, NULL)) + { + /* If we failed to match the primary address of the interface, see if we've got a --listen-address + for a secondary */ + struct match_param match; + + match.matched = 0; + match.ind = iface_index; + + if (!daemon->if_addrs || + !iface_enumerate(AF_INET, &match, check_listen_addrs) || + !match.matched) + return; + + iface_addr = match.addr; + /* make sure secondary address gets priority in case + there is more than one address on the interface in the same subnet */ + complete_context(match.addr, iface_index, NULL, match.netmask, match.broadcast, &parm); + } + + if (relay_upstream4(iface_index, mess, (size_t)sz)) return; + + if (!iface_enumerate(AF_INET, &parm, complete_context)) + return; - iface_addr = match.addr; - /* make sure secondary address gets priority in case - there is more than one address on the interface in the same subnet */ - complete_context(match.addr, iface_index, match.netmask, match.broadcast, &parm); - } + /* Check for a relay again after iface_enumerate/complete_context has had + chance to fill in relay->iface_index fields. This handles first time through + and any changes in interface config. */ + if (relay_upstream4(iface_index, mess, (size_t)sz)) + return; + + /* May have configured relay, but not DHCP server */ + if (!daemon->dhcp) + return; + + 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, loopback, &is_inform, pxe_fd, iface_addr, recvtime); + lease_update_file(now); + lease_update_dns(0); - if (!iface_enumerate(AF_INET, &parm, complete_context)) - return; - - 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); - lease_update_file(now); - lease_update_dns(0); - - if (iov.iov_len == 0) - return; - + if (iov.iov_len == 0) + return; + } + msg.msg_name = &dest; msg.msg_namelen = sizeof(dest); msg.msg_control = NULL; @@ -321,7 +372,7 @@ void dhcp_packet(time_t now, int pxe_fd) if (mess->ciaddr.s_addr != 0) dest.sin_addr = mess->ciaddr; } - else if (mess->giaddr.s_addr) + else if (mess->giaddr.s_addr && !is_relay_reply) { /* Send to BOOTP relay */ dest.sin_port = htons(daemon->dhcp_server_port); @@ -334,43 +385,50 @@ void dhcp_packet(time_t now, int pxe_fd) source port too, and send back to that. If we're replying to a DHCPINFORM, trust the source address always. */ if ((!is_inform && dest.sin_addr.s_addr != mess->ciaddr.s_addr) || - dest.sin_port == 0 || dest.sin_addr.s_addr == 0) + dest.sin_port == 0 || dest.sin_addr.s_addr == 0 || is_relay_reply) { dest.sin_port = htons(daemon->dhcp_client_port); dest.sin_addr = mess->ciaddr; } } #if defined(HAVE_LINUX_NETWORK) - else if ((ntohs(mess->flags) & 0x8000) || mess->hlen == 0 || - mess->hlen > sizeof(ifr.ifr_addr.sa_data) || mess->htype == 0) + else { - /* broadcast to 255.255.255.255 (or mac address invalid) */ + /* fill cmsg for outbound interface (both broadcast & unicast) */ struct in_pktinfo *pkt; msg.msg_control = control_u.control; 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)); + msg.msg_controllen = CMSG_SPACE(sizeof(struct in_pktinfo)); + cmptr->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo)); cmptr->cmsg_level = IPPROTO_IP; - cmptr->cmsg_type = IP_PKTINFO; - dest.sin_addr.s_addr = INADDR_BROADCAST; - dest.sin_port = htons(daemon->dhcp_client_port); + cmptr->cmsg_type = IP_PKTINFO; + + if ((ntohs(mess->flags) & 0x8000) || mess->hlen == 0 || + mess->hlen > sizeof(ifr.ifr_addr.sa_data) || mess->htype == 0) + { + /* broadcast to 255.255.255.255 (or mac address invalid) */ + dest.sin_addr.s_addr = INADDR_BROADCAST; + dest.sin_port = htons(daemon->dhcp_client_port); + } + else + { + /* unicast to unconfigured client. Inject mac address direct into ARP cache. + struct sockaddr limits size to 14 bytes. */ + dest.sin_addr = mess->yiaddr; + dest.sin_port = htons(daemon->dhcp_client_port); + memcpy(&arp_req.arp_pa, &dest, sizeof(struct sockaddr_in)); + arp_req.arp_ha.sa_family = mess->htype; + memcpy(arp_req.arp_ha.sa_data, mess->chaddr, mess->hlen); + /* interface name already copied in */ + arp_req.arp_flags = ATF_COM; + if (ioctl(daemon->dhcpfd, SIOCSARP, &arp_req) == -1) + my_syslog(MS_DHCP | LOG_ERR, _("ARP-cache injection failed: %s"), strerror(errno)); + } } - else - { - /* unicast to unconfigured client. Inject mac address direct into ARP cache. - struct sockaddr limits size to 14 bytes. */ - dest.sin_addr = mess->yiaddr; - dest.sin_port = htons(daemon->dhcp_client_port); - memcpy(&arp_req.arp_pa, &dest, sizeof(struct sockaddr_in)); - arp_req.arp_ha.sa_family = mess->htype; - memcpy(arp_req.arp_ha.sa_data, mess->chaddr, mess->hlen); - /* interface name already copied in */ - arp_req.arp_flags = ATF_COM; - ioctl(daemon->dhcpfd, SIOCSARP, &arp_req); - } #elif defined(HAVE_SOLARIS_NETWORK) else if ((ntohs(mess->flags) & 0x8000) || mess->hlen != ETHER_ADDR_LEN || mess->htype != ARPHRD_ETHER) { @@ -398,6 +456,17 @@ void dhcp_packet(time_t now, int pxe_fd) #elif defined(HAVE_BSD_NETWORK) else { +#ifdef HAVE_DUMPFILE + if (ntohs(mess->flags) & 0x8000) + dest.sin_addr.s_addr = INADDR_BROADCAST; + else + dest.sin_addr = mess->yiaddr; + dest.sin_port = htons(daemon->dhcp_client_port); + + dump_packet_udp(DUMP_DHCP, (void *)iov.iov_base, iov.iov_len, NULL, + (union mysockaddr *)&dest, fd); +#endif + send_via_bpf(mess, iov.iov_len, iface_addr, &ifr); return; } @@ -406,17 +475,32 @@ void dhcp_packet(time_t now, int pxe_fd) #ifdef HAVE_SOLARIS_NETWORK setsockopt(fd, IPPROTO_IP, IP_BOUND_IF, &iface_index, sizeof(iface_index)); #endif + +#ifdef HAVE_DUMPFILE + dump_packet_udp(DUMP_DHCP, (void *)iov.iov_base, iov.iov_len, NULL, + (union mysockaddr *)&dest, fd); +#endif - while(sendmsg(fd, &msg, 0) == -1 && retry_send()); + while(retry_send(sendmsg(fd, &msg, 0))); + + /* This can fail when, eg, iptables DROPS destination 255.255.255.255 */ + if (errno != 0) + { + inet_ntop(AF_INET, &dest.sin_addr, daemon->addrbuff, ADDRSTRLEN); + my_syslog(MS_DHCP | LOG_WARNING, _("Error sending DHCP packet to %s: %s"), + daemon->addrbuff, strerror(errno)); + } } - + /* check against secondary interface addresses */ -static int check_listen_addrs(struct in_addr local, int if_index, +static int check_listen_addrs(struct in_addr local, int if_index, char *label, struct in_addr netmask, struct in_addr broadcast, void *vparam) { struct match_param *param = vparam; struct iname *tmp; + (void) label; + if (if_index == param->ind) { for (tmp = daemon->if_addrs; tmp; tmp = tmp->next) @@ -442,32 +526,86 @@ static int check_listen_addrs(struct in_addr local, in 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, - struct in_addr netmask, struct in_addr broadcast, void *vparam) +static void guess_range_netmask(struct in_addr addr, struct in_addr netmask) { struct dhcp_context *context; - struct iface_param *param = vparam; - + for (context = daemon->dhcp; context; context = context->next) - { - if (!(context->flags & CONTEXT_NETMASK) && - (is_same_net(local, context->start, netmask) || - is_same_net(local, context->end, netmask))) + if (!(context->flags & CONTEXT_NETMASK) && + (is_same_net(addr, context->start, netmask) || + is_same_net(addr, context->end, netmask))) { if (context->netmask.s_addr != netmask.s_addr && - !(is_same_net(local, context->start, netmask) && - is_same_net(local, context->end, netmask))) + !(is_same_net(addr, context->start, netmask) && + is_same_net(addr, context->end, netmask))) { - strcpy(daemon->dhcp_buff, inet_ntoa(context->start)); - strcpy(daemon->dhcp_buff2, inet_ntoa(context->end)); + inet_ntop(AF_INET, &context->start, daemon->dhcp_buff, DHCP_BUFF_SZ); + inet_ntop(AF_INET, &context->end, daemon->dhcp_buff2, DHCP_BUFF_SZ); + inet_ntop(AF_INET, &netmask, daemon->addrbuff, ADDRSTRLEN); my_syslog(MS_DHCP | LOG_WARNING, _("DHCP range %s -- %s is not consistent with netmask %s"), - daemon->dhcp_buff, daemon->dhcp_buff2, inet_ntoa(netmask)); + daemon->dhcp_buff, daemon->dhcp_buff2, daemon->addrbuff); } - context->netmask = netmask; + context->netmask = netmask; } +} + +static int complete_context(struct in_addr local, int if_index, char *label, + struct in_addr netmask, struct in_addr broadcast, void *vparam) +{ + struct dhcp_context *context; + struct dhcp_relay *relay; + struct iface_param *param = vparam; + struct shared_network *share; + + (void)label; + + for (share = daemon->shared_networks; share; share = share->next) + { +#ifdef HAVE_DHCP6 + if (share->shared_addr.s_addr == 0) + continue; +#endif + + if (share->if_index != 0) + { + if (share->if_index != if_index) + continue; + } + else + { + if (share->match_addr.s_addr != local.s_addr) + continue; + } + + for (context = daemon->dhcp; context; context = context->next) + { + if (context->netmask.s_addr != 0 && + is_same_net(share->shared_addr, context->start, context->netmask) && + is_same_net(share->shared_addr, context->end, context->netmask)) + { + /* link it onto the current chain if we've not seen it before */ + if (context->current == context) + { + /* For a shared network, we have no way to guess what the default route should be. */ + context->router.s_addr = 0; + context->local = local; /* Use configured address for Server Identifier */ + context->current = param->current; + param->current = context; + } + + if (!(context->flags & CONTEXT_BRDCAST)) + context->broadcast.s_addr = context->start.s_addr | ~context->netmask.s_addr; + } + } + } + + guess_range_netmask(local, netmask); + + for (context = daemon->dhcp; context; context = context->next) + { if (context->netmask.s_addr != 0 && is_same_net(local, context->start, context->netmask) && is_same_net(local, context->end, context->netmask)) @@ -491,6 +629,10 @@ static int complete_context(struct in_addr local, int } } + for (relay = daemon->relay4; relay; relay = relay->next) + if (relay->local.addr4.s_addr == local.s_addr) + relay->iface_index = if_index; + return 1; } @@ -530,7 +672,7 @@ struct dhcp_context *narrow_context(struct dhcp_contex { /* 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 @@ -572,9 +714,69 @@ struct dhcp_config *config_find_by_address(struct dhcp 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 = hash; + 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. @@ -588,7 +790,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) @@ -626,69 +832,36 @@ 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))); - - *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) + /* in consec-ip mode, skip addresses equal to + the number of addresses rejected by clients. This + should avoid the same client being offered the same + address after it has rjected it. */ + if (option_bool(OPT_CONSEC_ADDR) && c->addr_epoch) + c->addr_epoch--; + else { - if ((count < max) && !option_bool(OPT_NO_PING) && icmp_ping(addr)) + struct ping_result *r; + + 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) + { + *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++; } - 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; - } } } - + addr.s_addr = htonl(ntohl(addr.s_addr) + 1); if (addr.s_addr == htonl(ntohl(c->end.s_addr) + 1)) @@ -700,89 +873,6 @@ int address_allocate(struct dhcp_context *context, return 0; } -static int is_addr_in_context(struct dhcp_context *context, struct dhcp_config *config) -{ - if (!context) /* called via find_config() from lease_update_from_configs() */ - return 1; - if (!(config->flags & CONFIG_ADDR)) - return 1; - for (; context; context = context->current) - if (is_same_net(config->addr, context->start, context->netmask)) - return 1; - - return 0; -} - -int config_has_mac(struct dhcp_config *config, unsigned char *hwaddr, int len, int type) -{ - struct hwaddr_config *conf_addr; - - for (conf_addr = config->hwaddr; conf_addr; conf_addr = conf_addr->next) - if (conf_addr->wildcard_mask == 0 && - conf_addr->hwaddr_len == len && - (conf_addr->hwaddr_type == type || conf_addr->hwaddr_type == 0) && - memcmp(conf_addr->hwaddr, hwaddr, len) == 0) - return 1; - - return 0; -} - -struct dhcp_config *find_config(struct dhcp_config *configs, - struct dhcp_context *context, - unsigned char *clid, int clid_len, - unsigned char *hwaddr, int hw_len, - int hw_type, char *hostname) -{ - int count, new; - struct dhcp_config *config, *candidate; - struct hwaddr_config *conf_addr; - - if (clid) - for (config = configs; config; config = config->next) - if (config->flags & CONFIG_CLID) - { - if (config->clid_len == clid_len && - memcmp(config->clid, clid, clid_len) == 0 && - is_addr_in_context(context, config)) - return config; - - /* dhcpcd prefixes ASCII client IDs by zero which is wrong, but we try and - cope with that here */ - if (*clid == 0 && config->clid_len == clid_len-1 && - memcmp(config->clid, clid+1, clid_len-1) == 0 && - is_addr_in_context(context, config)) - return config; - } - - - for (config = configs; config; config = config->next) - if (config_has_mac(config, hwaddr, hw_len, hw_type) && - is_addr_in_context(context, config)) - return config; - - if (hostname && context) - for (config = configs; config; config = config->next) - if ((config->flags & CONFIG_NAME) && - hostname_isequal(config->hostname, hostname) && - is_addr_in_context(context, config)) - return config; - - /* use match with fewest wildcard octets */ - for (candidate = NULL, count = 0, config = configs; config; config = config->next) - if (is_addr_in_context(context, config)) - for (conf_addr = config->hwaddr; conf_addr; conf_addr = conf_addr->next) - if (conf_addr->wildcard_mask != 0 && - conf_addr->hwaddr_len == hw_len && - (conf_addr->hwaddr_type == hw_type || conf_addr->hwaddr_type == 0) && - (new = memcmp_masked(conf_addr->hwaddr, hwaddr, hw_len, conf_addr->wildcard_mask)) > count) - { - count = new; - candidate = config; - } - - return candidate; -} - void dhcp_read_ethers(void) { FILE *f = fopen(ETHERSFILE, "r"); @@ -826,14 +916,14 @@ void dhcp_read_ethers(void) lineno++; - while (strlen(buff) > 0 && isspace((int)buff[strlen(buff)-1])) + while (strlen(buff) > 0 && isspace((unsigned char)buff[strlen(buff)-1])) buff[strlen(buff)-1] = 0; if ((*buff == '#') || (*buff == '+') || (*buff == 0)) continue; - for (ip = buff; *ip && !isspace((int)*ip); ip++); - for(; *ip && isspace((int)*ip); ip++) + for (ip = buff; *ip && !isspace((unsigned char)*ip); ip++); + for(; *ip && isspace((unsigned char)*ip); ip++) *ip = 0; if (!*ip || parse_hex(buff, hwaddr, ETHER_ADDR_LEN, NULL, NULL) != ETHER_ADDR_LEN) { @@ -848,7 +938,7 @@ void dhcp_read_ethers(void) if (!*cp) { - if ((addr.s_addr = inet_addr(ip)) == (in_addr_t)-1) + if (inet_pton(AF_INET, ip, &addr.s_addr) < 1) { my_syslog(MS_DHCP | LOG_ERR, _("bad address at %s line %d"), ETHERSFILE, lineno); continue; @@ -957,7 +1047,7 @@ char *host_from_dns(struct in_addr addr) if (daemon->port == 0) return NULL; /* DNS disabled. */ - lookup = cache_find_by_addr(NULL, (struct all_addr *)&addr, 0, F_IPV4); + lookup = cache_find_by_addr(NULL, (union all_addr *)&addr, 0, F_IPV4); if (lookup && (lookup->flags & F_HOSTS)) { @@ -974,8 +1064,7 @@ char *host_from_dns(struct in_addr addr) if (!legal_hostname(hostname)) return NULL; - strncpy(daemon->dhcp_buff, hostname, 256); - daemon->dhcp_buff[255] = 0; + safe_strncpy(daemon->dhcp_buff, hostname, 256); strip_hostname(daemon->dhcp_buff); return daemon->dhcp_buff; @@ -984,5 +1073,118 @@ char *host_from_dns(struct in_addr addr) return NULL; } +static int relay_upstream4(int iface_index, struct dhcp_packet *mess, size_t sz) +{ + struct in_addr giaddr = mess->giaddr; + u8 hops = mess->hops; + struct dhcp_relay *relay; + + if (mess->op != BOOTREQUEST) + return 0; + + for (relay = daemon->relay4; relay; relay = relay->next) + if (relay->iface_index != 0 && relay->iface_index == iface_index) + break; + + /* No relay config. */ + if (!relay) + return 0; + + for (; relay; relay = relay->next) + if (relay->iface_index != 0 && relay->iface_index == iface_index) + { + union mysockaddr to; + union all_addr from; + + mess->hops = hops; + mess->giaddr = giaddr; + + if ((mess->hops++) > 20) + continue; + + /* source address == relay address */ + from.addr4 = relay->local.addr4; + + /* already gatewayed ? */ + if (giaddr.s_addr) + { + /* if so check if by us, to stomp on loops. */ + if (giaddr.s_addr == relay->local.addr4.s_addr) + continue; + } + else + { + /* plug in our address */ + mess->giaddr.s_addr = relay->local.addr4.s_addr; + } + + to.sa.sa_family = AF_INET; + to.in.sin_addr = relay->server.addr4; + to.in.sin_port = htons(relay->port); + + /* Broadcasting to server. */ + if (relay->server.addr4.s_addr == 0) + { + struct ifreq ifr; + + if (relay->interface) + safe_strncpy(ifr.ifr_name, relay->interface, IF_NAMESIZE); + + if (!relay->interface || strchr(relay->interface, '*') || + ioctl(daemon->dhcpfd, SIOCGIFBRDADDR, &ifr) == -1) + { + my_syslog(MS_DHCP | LOG_ERR, _("Cannot broadcast DHCP relay via interface %s"), relay->interface); + continue; + } + + to.in.sin_addr = ((struct sockaddr_in *) &ifr.ifr_addr)->sin_addr; + } + +#ifdef HAVE_DUMPFILE + { + union mysockaddr fromsock; + fromsock.in.sin_port = htons(daemon->dhcp_server_port); + fromsock.in.sin_addr = from.addr4; + fromsock.sa.sa_family = AF_INET; + + dump_packet_udp(DUMP_DHCP, (void *)mess, sz, &fromsock, &to, -1); + } #endif + + send_from(daemon->dhcpfd, 0, (char *)mess, sz, &to, &from, 0); + + if (option_bool(OPT_LOG_OPTS)) + { + inet_ntop(AF_INET, &relay->local, daemon->addrbuff, ADDRSTRLEN); + if (relay->server.addr4.s_addr == 0) + snprintf(daemon->dhcp_buff2, DHCP_BUFF_SZ, _("broadcast via %s"), relay->interface); + else + inet_ntop(AF_INET, &relay->server.addr4, daemon->dhcp_buff2, DHCP_BUFF_SZ); + my_syslog(MS_DHCP | LOG_INFO, _("DHCP relay at %s -> %s"), daemon->addrbuff, daemon->dhcp_buff2); + } + } + + return 1; +} + +static struct dhcp_relay *relay_reply4(struct dhcp_packet *mess, char *arrival_interface) +{ + struct dhcp_relay *relay; + + if (mess->giaddr.s_addr == 0 || mess->op != BOOTREPLY) + return NULL; + + for (relay = daemon->relay4; relay; relay = relay->next) + { + if (mess->giaddr.s_addr == relay->local.addr4.s_addr) + { + if (!relay->interface || wildcard_match(relay->interface, arrival_interface)) + return relay->iface_index != 0 ? relay : NULL; + } + } + + return NULL; +} + +#endif