diff options
Diffstat (limited to 'src/dhcp.c')
-rw-r--r-- | src/dhcp.c | 768 |
1 files changed, 353 insertions, 415 deletions
@@ -1,4 +1,4 @@ -/* dnsmasq is Copyright (c) 2000-2011 Simon Kelley +/* dnsmasq is Copyright (c) 2000-2015 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 @@ -19,13 +19,23 @@ #ifdef HAVE_DHCP struct iface_param { - struct in_addr relay, primary; struct dhcp_context *current; + struct dhcp_relay *relay; + struct in_addr relay_local; int ind; }; -static int complete_context(struct in_addr local, int if_index, +struct match_param { + int ind, matched; + struct in_addr netmask, broadcast, addr; +}; + +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, char *label, + struct in_addr netmask, struct in_addr broadcast, void *vparam); +static int relay_upstream4(struct dhcp_relay *relay, struct dhcp_packet *mess, size_t sz, int iface_index); +static struct dhcp_relay *relay_reply4(struct dhcp_packet *mess, char *arrival_interface); static int make_fd(int port) { @@ -35,16 +45,22 @@ static int make_fd(int port) #if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DONT) int mtu = IP_PMTUDISC_DONT; #endif +#if defined(IP_TOS) && defined(IPTOS_CLASS_CS6) + int tos = IPTOS_CLASS_CS6; +#endif if (fd == -1) die (_("cannot create DHCP socket: %s"), NULL, EC_BADNET); if (!fix_fd(fd) || #if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DONT) - setsockopt(fd, SOL_IP, IP_MTU_DISCOVER, &mtu, sizeof(mtu)) == -1 || + setsockopt(fd, IPPROTO_IP, IP_MTU_DISCOVER, &mtu, sizeof(mtu)) == -1 || +#endif +#if defined(IP_TOS) && defined(IPTOS_CLASS_CS6) + setsockopt(fd, IPPROTO_IP, IP_TOS, &tos, sizeof(tos)) == -1 || #endif #if defined(HAVE_LINUX_NETWORK) - setsockopt(fd, SOL_IP, IP_PKTINFO, &oneopt, sizeof(oneopt)) == -1 || + setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &oneopt, sizeof(oneopt)) == -1 || #else setsockopt(fd, IPPROTO_IP, IP_RECVIF, &oneopt, sizeof(oneopt)) == -1 || #endif @@ -53,14 +69,22 @@ static int make_fd(int port) /* When bind-interfaces is set, there might be more than one dnmsasq instance binding port 67. That's OK if they serve different networks. - Need to set REUSEADDR to make this posible, or REUSEPORT on *BSD. */ - if (option_bool(OPT_NOWILD)) + Need to set REUSEADDR|REUSEPORT to make this posible. + 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 = 0; + #ifdef SO_REUSEPORT - int rc = setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &oneopt, sizeof(oneopt)); -#else - int rc = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &oneopt, sizeof(oneopt)); + if ((rc = setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &oneopt, sizeof(oneopt))) == -1 && + errno == ENOPROTOOPT) + rc = 0; #endif + + if (rc != -1) + rc = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &oneopt, sizeof(oneopt)); + if (rc == -1) die(_("failed to set SO_REUSE{ADDR|PORT} on DHCP socket: %s"), NULL, EC_BADNET); } @@ -104,19 +128,16 @@ void dhcp_init(void) /* Make BPF raw send socket */ init_bpf(); -#endif - - check_dhcp_hosts(1); - - daemon->dhcp_packet.iov_len = sizeof(struct dhcp_packet); - daemon->dhcp_packet.iov_base = safe_malloc(daemon->dhcp_packet.iov_len); +#endif } - + 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; @@ -125,7 +146,7 @@ void dhcp_packet(time_t now, int pxe_fd) struct iovec iov; ssize_t sz; int iface_index = 0, unicast_dest = 0, is_inform = 0; - struct in_addr iface_addr, *addrp = NULL; + struct in_addr iface_addr; struct iface_param parm; #ifdef HAVE_LINUX_NETWORK struct arpreq arp_req; @@ -141,56 +162,23 @@ void dhcp_packet(time_t now, int pxe_fd) char control[CMSG_SPACE(sizeof(struct sockaddr_dl))]; #endif } control_u; - - msg.msg_control = NULL; - msg.msg_controllen = 0; - msg.msg_name = NULL; - msg.msg_namelen = 0; - msg.msg_iov = &daemon->dhcp_packet; - msg.msg_iovlen = 1; - - while (1) - { - msg.msg_flags = 0; - while ((sz = recvmsg(fd, &msg, MSG_PEEK | MSG_TRUNC)) == -1 && errno == EINTR); - - if (sz == -1) - return; - - if (!(msg.msg_flags & MSG_TRUNC)) - break; + struct dhcp_bridge *bridge, *alias; - /* Very new Linux kernels return the actual size needed, - older ones always return truncated size */ - if ((size_t)sz == daemon->dhcp_packet.iov_len) - { - if (!expand_buf(&daemon->dhcp_packet, sz + 100)) - return; - } - else - { - expand_buf(&daemon->dhcp_packet, sz); - break; - } - } - - /* expand_buf may have moved buffer */ - mess = (struct dhcp_packet *)daemon->dhcp_packet.iov_base; msg.msg_controllen = sizeof(control_u); msg.msg_control = control_u.control; - msg.msg_flags = 0; msg.msg_name = &dest; msg.msg_namelen = sizeof(dest); - - while ((sz = recvmsg(fd, &msg, 0)) == -1 && errno == EINTR); - - if ((msg.msg_flags & MSG_TRUNC) || sz < (ssize_t)(sizeof(*mess) - sizeof(mess->options))) - return; + msg.msg_iov = &daemon->dhcp_packet; + msg.msg_iovlen = 1; -#if defined (HAVE_LINUX_NETWORK) + if ((sz = recv_dhcp_packet(fd, &msg)) == -1 || + (sz < (ssize_t)(sizeof(*mess) - sizeof(mess->options)))) + return; + + #if defined (HAVE_LINUX_NETWORK) if (msg.msg_controllen >= sizeof(struct cmsghdr)) for (cmptr = CMSG_FIRSTHDR(&msg); cmptr; cmptr = CMSG_NXTHDR(&msg, cmptr)) - if (cmptr->cmsg_level == SOL_IP && cmptr->cmsg_type == IP_PKTINFO) + if (cmptr->cmsg_level == IPPROTO_IP && cmptr->cmsg_type == IP_PKTINFO) { union { unsigned char *c; @@ -237,74 +225,120 @@ void dhcp_packet(time_t now, int pxe_fd) strncpy(arp_req.arp_dev, ifr.ifr_name, 16); #endif + /* 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. */ + for (bridge = daemon->bridges; bridge; bridge = bridge->next) + { + for (alias = bridge->alias; alias; alias = alias->next) + if (wildcard_matchn(alias->iface, ifr.ifr_name, IF_NAMESIZE)) + { + if (!(iface_index = if_nametoindex(bridge->iface))) + { + my_syslog(MS_DHCP | LOG_WARNING, + _("unknown interface %s in bridge-interface"), + bridge->iface); + return; + } + else + { + strncpy(ifr.ifr_name, bridge->iface, IF_NAMESIZE); + break; + } + } + + if (alias) + break; + } + #ifdef MSG_BCAST /* OpenBSD tells us when a packet was broadcast */ if (!(msg.msg_flags & MSG_BCAST)) unicast_dest = 1; #endif - - ifr.ifr_addr.sa_family = AF_INET; - if (ioctl(daemon->dhcpfd, SIOCGIFADDR, &ifr) != -1 ) - { - addrp = &iface_addr; - iface_addr = ((struct sockaddr_in *) &ifr.ifr_addr)->sin_addr; - } - - if (!iface_check(AF_INET, (struct all_addr *)addrp, ifr.ifr_name, &iface_index)) - return; - for (tmp = daemon->dhcp_except; tmp; tmp = tmp->next) - if (tmp->name && (strcmp(tmp->name, ifr.ifr_name) == 0)) - return; - - /* weird libvirt-inspired access control */ - for (context = daemon->dhcp; context; context = context->next) - if (!context->interface || strcmp(context->interface, ifr.ifr_name) == 0) - break; - - if (!context) - return; - - /* unlinked contexts are marked by context->current == context */ - for (context = daemon->dhcp; context; context = context->next) - context->current = context; - - parm.relay = mess->giaddr; - parm.primary = iface_addr; - parm.current = NULL; - parm.ind = iface_index; - - /* interface may have been changed by alias in iface_check, make sure it gets priority in case - there is more than one address on the interface in the same subnet */ - if (ioctl(daemon->dhcpfd, SIOCGIFADDR, &ifr) == -1) + 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. */ + iface_index = relay->iface_index; + if (!indextoname(daemon->dhcpfd, iface_index, ifr.ifr_name)) + return; + is_relay_reply = 1; + iov.iov_len = sz; +#ifdef HAVE_LINUX_NETWORK + strncpy(arp_req.arp_dev, ifr.ifr_name, 16); +#endif } else { - iface_addr = ((struct sockaddr_in *) &ifr.ifr_addr)->sin_addr; - if (ioctl(daemon->dhcpfd, SIOCGIFNETMASK, &ifr) != -1) + 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 { - struct in_addr netmask = ((struct sockaddr_in *) &ifr.ifr_addr)->sin_addr; - if (ioctl(daemon->dhcpfd, SIOCGIFBRDADDR, &ifr) != -1) - { - struct in_addr broadcast = ((struct sockaddr_in *) &ifr.ifr_addr)->sin_addr; - complete_context(iface_addr, iface_index, netmask, broadcast, &parm); - } + my_syslog(MS_DHCP | LOG_WARNING, _("DHCP packet received on %s which has no address"), ifr.ifr_name); + return; } - } + + for (tmp = daemon->dhcp_except; tmp; tmp = tmp->next) + if (tmp->name && wildcard_match(tmp->name, ifr.ifr_name)) + return; + + /* unlinked contexts/relays are marked by context->current == context */ + for (context = daemon->dhcp; context; context = context->next) + context->current = context; + + for (relay = daemon->relay4; relay; relay = relay->next) + relay->current = relay; + + parm.current = NULL; + parm.relay = NULL; + parm.relay_local.s_addr = 0; + parm.ind = iface_index; + + if (!iface_check(AF_INET, (struct 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 (!iface_enumerate(AF_INET, &parm, complete_context)) + return; - 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); - lease_update_file(now); - lease_update_dns(); - - if (iov.iov_len == 0) - return; + /* 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)) + 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, &is_inform, pxe_fd, iface_addr); + lease_update_file(now); + lease_update_dns(0); + + if (iov.iov_len == 0) + return; + } msg.msg_name = &dest; msg.msg_namelen = sizeof(dest); @@ -319,13 +353,13 @@ void dhcp_packet(time_t now, int pxe_fd) #ifdef HAVE_SOCKADDR_SA_LEN dest.sin_len = sizeof(struct sockaddr_in); #endif - + if (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); @@ -338,17 +372,16 @@ 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); @@ -357,23 +390,30 @@ void dhcp_packet(time_t now, int pxe_fd) pkt->ipi_ifindex = iface_index; pkt->ipi_spec_dst.s_addr = 0; msg.msg_controllen = cmptr->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo)); - cmptr->cmsg_level = SOL_IP; - cmptr->cmsg_type = IP_PKTINFO; - 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; - ioctl(daemon->dhcpfd, SIOCSARP, &arp_req); + cmptr->cmsg_level = IPPROTO_IP; + 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)); + } } #elif defined(HAVE_SOLARIS_NETWORK) else if ((ntohs(mess->flags) & 0x8000) || mess->hlen != ETHER_ADDR_LEN || mess->htype != ARPHRD_ETHER) @@ -411,9 +451,35 @@ void dhcp_packet(time_t now, int pxe_fd) setsockopt(fd, IPPROTO_IP, IP_BOUND_IF, &iface_index, sizeof(iface_index)); #endif - while(sendmsg(fd, &msg, 0) == -1 && retry_send()); + while(retry_send(sendmsg(fd, &msg, 0))); } +/* 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) +{ + struct match_param *param = vparam; + struct iname *tmp; + + (void) label; + + if (if_index == param->ind) + { + for (tmp = daemon->if_addrs; tmp; tmp = tmp->next) + if ( tmp->addr.sa.sa_family == AF_INET && + tmp->addr.in.sin_addr.s_addr == local.s_addr) + { + param->matched = 1; + param->addr = local; + param->netmask = netmask; + param->broadcast = broadcast; + break; + } + } + + return 1; +} + /* This is a complex routine: it gets called with each (address,netmask,broadcast) triple of each interface (and any relay address) and does the following things: @@ -424,11 +490,14 @@ void dhcp_packet(time_t now, int pxe_fd) Note that the current chain may be superceded later for configured hosts or those coming via gateways. */ -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) { struct dhcp_context *context; + struct dhcp_relay *relay; struct iface_param *param = vparam; + + (void)label; for (context = daemon->dhcp; context; context = context->next) { @@ -448,40 +517,38 @@ static int complete_context(struct in_addr local, int if_index, context->netmask = netmask; } - if (context->netmask.s_addr) + if (context->netmask.s_addr != 0 && + is_same_net(local, context->start, context->netmask) && + is_same_net(local, context->end, context->netmask)) { - if (is_same_net(local, context->start, context->netmask) && - is_same_net(local, context->end, context->netmask)) + /* link it onto the current chain if we've not seen it before */ + if (if_index == param->ind && context->current == context) { - /* link it onto the current chain if we've not seen it before */ - if (if_index == param->ind && context->current == context) - { - context->router = local; - context->local = local; - context->current = param->current; - param->current = context; - } - - if (!(context->flags & CONTEXT_BRDCAST)) - { - if (is_same_net(broadcast, context->start, context->netmask)) - context->broadcast = broadcast; - else - context->broadcast.s_addr = context->start.s_addr | ~context->netmask.s_addr; - } - } - else if (param->relay.s_addr && is_same_net(param->relay, context->start, context->netmask)) + context->router = local; + context->local = local; + context->current = param->current; + param->current = context; + } + + if (!(context->flags & CONTEXT_BRDCAST)) { - context->router = param->relay; - context->local = param->primary; - /* fill in missing broadcast addresses for relayed ranges */ - if (!(context->flags & CONTEXT_BRDCAST)) + if (is_same_net(broadcast, context->start, context->netmask)) + context->broadcast = broadcast; + else context->broadcast.s_addr = context->start.s_addr | ~context->netmask.s_addr; } - - } + } } + for (relay = daemon->relay4; relay; relay = relay->next) + if (if_index == param->ind && relay->local.addr.addr4.s_addr == local.s_addr && relay->current == relay && + (param->relay_local.s_addr == 0 || param->relay_local.s_addr == local.s_addr)) + { + relay->current = param->relay; + param->relay = relay; + param->relay_local = local; + } + return 1; } @@ -505,7 +572,7 @@ struct dhcp_context *address_available(struct dhcp_context *context, start = ntohl(tmp->start.s_addr); end = ntohl(tmp->end.s_addr); - if (!(tmp->flags & CONTEXT_STATIC) && + if (!(tmp->flags & (CONTEXT_STATIC | CONTEXT_PROXY)) && addr >= start && addr <= end && match_netid(tmp->filter, netids, 1)) @@ -540,7 +607,8 @@ struct dhcp_context *narrow_context(struct dhcp_context *context, if (!tmp) for (tmp = context; tmp; tmp = tmp->current) if (match_netid(tmp->filter, netids, 1) && - is_same_net(taddr, tmp->start, tmp->netmask)) + is_same_net(taddr, tmp->start, tmp->netmask) && + !(tmp->flags & CONTEXT_PROXY)) break; } @@ -562,50 +630,6 @@ struct dhcp_config *config_find_by_address(struct dhcp_config *configs, struct i return NULL; } -/* Is every member of check matched by a member of pool? - If tagnotneeded, untagged is OK */ -int match_netid(struct dhcp_netid *check, struct dhcp_netid *pool, int tagnotneeded) -{ - struct dhcp_netid *tmp1; - - if (!check && !tagnotneeded) - return 0; - - for (; check; check = check->next) - { - /* '#' for not is for backwards compat. */ - if (check->net[0] != '!' && check->net[0] != '#') - { - for (tmp1 = pool; tmp1; tmp1 = tmp1->next) - if (strcmp(check->net, tmp1->net) == 0) - break; - if (!tmp1) - return 0; - } - else - for (tmp1 = pool; tmp1; tmp1 = tmp1->next) - if (strcmp((check->net)+1, tmp1->net) == 0) - return 0; - } - return 1; -} - -struct dhcp_netid *run_tag_if(struct dhcp_netid *tags) -{ - struct tag_if *exprs; - struct dhcp_netid_list *list; - - for (exprs = daemon->tag_if; exprs; exprs = exprs->next) - if (match_netid(exprs->tag, tags, 1)) - for (list = exprs->set; list; list = list->next) - { - list->list->next = tags; - tags = list->list; - } - - return tags; -} - int address_allocate(struct dhcp_context *context, struct in_addr *addrp, unsigned char *hwaddr, int hw_len, struct dhcp_netid *netids, time_t now) @@ -626,16 +650,22 @@ int address_allocate(struct dhcp_context *context, for (pass = 0; pass <= 1; pass++) for (c = context; c; c = c->current) - if (c->flags & CONTEXT_STATIC) + if (c->flags & (CONTEXT_STATIC | CONTEXT_PROXY)) continue; else if (!match_netid(c->filter, netids, pass)) continue; else { - /* pick a seed based on hwaddr then iterate until we find a free address. */ - start.s_addr = addr.s_addr = - htonl(ntohl(c->start.s_addr) + - ((j + c->addr_epoch) % (1 + ntohl(c->end.s_addr) - ntohl(c->start.s_addr)))); + if (option_bool(OPT_CONSEC_ADDR)) + /* seed is largest extant lease addr in this context */ + start = lease_find_max_addr(c); + else + /* pick a seed based on hwaddr */ + start.s_addr = htonl(ntohl(c->start.s_addr) + + ((j + c->addr_epoch) % (1 + ntohl(c->end.s_addr) - ntohl(c->start.s_addr)))); + + /* iterate until we find a free address. */ + addr = start; do { /* eliminate addresses in use by the server. */ @@ -660,9 +690,6 @@ int address_allocate(struct dhcp_context *context, *addrp = addr; - if (option_bool(OPT_NO_PING)) - return 1; - /* 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 @@ -672,33 +699,51 @@ int address_allocate(struct dhcp_context *context, 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 if (++count == max || r->addr.s_addr == addr.s_addr) - return 1; - - if (icmp_ping(addr)) - /* address in use: perturb address selection so that we are - less likely to try this address again. */ - c->addr_epoch++; - else + 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) { - /* at this point victim may hold an expired record */ - if (!victim) + if ((count < max) && !option_bool(OPT_NO_PING) && icmp_ping(addr)) { - if ((victim = whine_malloc(sizeof(struct ping_result)))) - { - victim->next = daemon->ping_results; - daemon->ping_results = victim; - } + /* 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++; } - - /* record that this address is OK for 30s - without more ping checks */ - if (victim) + else { - victim->addr = addr; - victim->time = now; + /* 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; } } @@ -709,92 +754,10 @@ int address_allocate(struct dhcp_context *context, } while (addr.s_addr != start.s_addr); } - 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 wildcast 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"); @@ -956,85 +919,6 @@ void dhcp_read_ethers(void) my_syslog(MS_DHCP | LOG_INFO, _("read %s - %d addresses"), ETHERSFILE, count); } -void check_dhcp_hosts(int fatal) -{ - /* If the same IP appears in more than one host config, then DISCOVER - for one of the hosts will get the address, but REQUEST will be NAKed, - since the address is reserved by the other one -> protocol loop. - Also check that FQDNs match the domain we are using. */ - - struct dhcp_config *configs, *cp; - - for (configs = daemon->dhcp_conf; configs; configs = configs->next) - { - char *domain; - - if ((configs->flags & DHOPT_BANK) || fatal) - { - for (cp = configs->next; cp; cp = cp->next) - if ((configs->flags & cp->flags & CONFIG_ADDR) && configs->addr.s_addr == cp->addr.s_addr) - { - if (fatal) - die(_("duplicate IP address %s in dhcp-config directive."), - inet_ntoa(cp->addr), EC_BADCONF); - else - my_syslog(MS_DHCP | LOG_ERR, _("duplicate IP address %s in %s."), - inet_ntoa(cp->addr), daemon->dhcp_hosts_file); - configs->flags &= ~CONFIG_ADDR; - } - - /* split off domain part */ - if ((configs->flags & CONFIG_NAME) && (domain = strip_hostname(configs->hostname))) - configs->domain = domain; - } - } -} - -void dhcp_update_configs(struct dhcp_config *configs) -{ - /* Some people like to keep all static IP addresses in /etc/hosts. - This goes through /etc/hosts and sets static addresses for any DHCP config - records which don't have an address and whose name matches. - We take care to maintain the invariant that any IP address can appear - in at most one dhcp-host. Since /etc/hosts can be re-read by SIGHUP, - restore the status-quo ante first. */ - - struct dhcp_config *config; - struct crec *crec; - - for (config = configs; config; config = config->next) - if (config->flags & CONFIG_ADDR_HOSTS) - config->flags &= ~(CONFIG_ADDR | CONFIG_ADDR_HOSTS); - - - if (daemon->port != 0) - for (config = configs; config; config = config->next) - if (!(config->flags & CONFIG_ADDR) && - (config->flags & CONFIG_NAME) && - (crec = cache_find_by_name(NULL, config->hostname, 0, F_IPV4)) && - (crec->flags & F_HOSTS)) - { - if (cache_find_by_name(crec, config->hostname, 0, F_IPV4)) - { - /* use primary (first) address */ - while (crec && !(crec->flags & F_REVERSE)) - crec = cache_find_by_name(crec, config->hostname, 0, F_IPV4); - if (!crec) - continue; /* should be never */ - my_syslog(MS_DHCP | LOG_WARNING, _("%s has more than one address in hostsfile, using %s for DHCP"), - config->hostname, inet_ntoa(crec->addr.addr.addr.addr4)); - } - - if (config_find_by_address(configs, crec->addr.addr.addr.addr4)) - my_syslog(MS_DHCP | LOG_WARNING, _("duplicate IP address %s (%s) in dhcp-config directive"), - inet_ntoa(crec->addr.addr.addr.addr4), config->hostname); - else - { - config->addr = crec->addr.addr.addr.addr4; - config->flags |= CONFIG_ADDR | CONFIG_ADDR_HOSTS; - } - } -} /* If we've not found a hostname any other way, try and see if there's one in /etc/hosts for this address. If it has a domain part, that must match the set domain and @@ -1075,20 +959,74 @@ char *host_from_dns(struct in_addr addr) return NULL; } -/* return domain or NULL if none. */ -char *strip_hostname(char *hostname) +static int relay_upstream4(struct dhcp_relay *relay, struct dhcp_packet *mess, size_t sz, int iface_index) { - char *dot = strchr(hostname, '.'); - - if (!dot) - return NULL; + /* ->local is same value for all relays on ->current chain */ + struct all_addr from; - *dot = 0; /* truncate */ - if (strlen(dot+1) != 0) - return dot+1; + if (mess->op != BOOTREQUEST) + return 0; + + /* source address == relay address */ + from.addr.addr4 = relay->local.addr.addr4; - return NULL; + /* already gatewayed ? */ + if (mess->giaddr.s_addr) + { + /* if so check if by us, to stomp on loops. */ + if (mess->giaddr.s_addr == relay->local.addr.addr4.s_addr) + return 1; + } + else + { + /* plug in our address */ + mess->giaddr.s_addr = relay->local.addr.addr4.s_addr; + } + + if ((mess->hops++) > 20) + return 1; + + for (; relay; relay = relay->current) + { + union mysockaddr to; + + to.sa.sa_family = AF_INET; + to.in.sin_addr = relay->server.addr.addr4; + to.in.sin_port = htons(daemon->dhcp_server_port); + + 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); + my_syslog(MS_DHCP | LOG_INFO, _("DHCP relay %s -> %s"), daemon->addrbuff, inet_ntoa(relay->server.addr.addr4)); + } + + /* Save this for replies */ + relay->iface_index = iface_index; + } + + return 1; } -#endif +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.addr.addr4.s_addr) + { + if (!relay->interface || wildcard_match(relay->interface, arrival_interface)) + return relay->iface_index != 0 ? relay : NULL; + } + } + + return NULL; +} + +#endif |