diff --git a/CHANGELOG b/CHANGELOG index adddf78..9967655 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1123,4 +1123,23 @@ release 2.10 support was added. Thanks to Michael Hamilton for assistance with this. +version 2.11 + Fixed DHCP problem which could result in two leases in the + database with the same address. This looked much more + alarming then it was, since it could only happen when a + machine changes MAC address but kept the same name. The + old lease would persist until it timed out but things + would still work OK. + + Check that IP addresses in all dhcp-host directives are + unique and die horribly if they are not, since otherwise + endless protocol loops can occur. + + Use IPV6_RECVPKTINFO as socket option rather than + IPV6_PKTINFO where available. This keeps late-model FreeBSD + happy. + + Set source interface when replying to IPv6 UDP + queries. This is needed to cope with link-local addresses. + diff --git a/dnsmasq-rh.spec b/dnsmasq-rh.spec index 787886e..4973d42 100644 --- a/dnsmasq-rh.spec +++ b/dnsmasq-rh.spec @@ -5,7 +5,7 @@ ############################################################################### Name: dnsmasq -Version: 2.10 +Version: 2.11 Release: 1 Copyright: GPL Group: System Environment/Daemons diff --git a/dnsmasq-suse.spec b/dnsmasq-suse.spec index bc7c34e..9899ecf 100644 --- a/dnsmasq-suse.spec +++ b/dnsmasq-suse.spec @@ -5,7 +5,7 @@ ############################################################################### Name: dnsmasq -Version: 2.10 +Version: 2.11 Release: 1 Copyright: GPL Group: Productivity/Networking/DNS/Servers diff --git a/doc.html b/doc.html index ae8b205..8416b84 100644 --- a/doc.html +++ b/doc.html @@ -112,7 +112,9 @@ bzip2 dnsmasq-zzz.tar

Links.

-Ulrich Ivens has a nice HOWTO in German on installing dnsmasq at http://howto.linux-hardware-shop.de/dnsmasq.html +Ulrich Ivens has a nice HOWTO in German on installing dnsmasq at http://howto.linux-hardware-shop.de/dnsmasq.html +and Damien Raude-Morvan has one in French at http://www.drazzib.com/docs-dnsmasq.html

License.

Dnsmasq is distributed under the GPL. See the file COPYING in the distribution diff --git a/src/config.h b/src/config.h index b072a48..753dfaa 100644 --- a/src/config.h +++ b/src/config.h @@ -12,7 +12,7 @@ /* Author's email: simon@thekelleys.org.uk */ -#define VERSION "2.10" +#define VERSION "2.11" #define FTABSIZ 150 /* max number of outstanding requests */ #define MAX_PROCS 20 /* max no children for TCP requests */ diff --git a/src/dhcp.c b/src/dhcp.c index be6855f..f667f95 100644 --- a/src/dhcp.c +++ b/src/dhcp.c @@ -14,12 +14,13 @@ #include "dnsmasq.h" -void dhcp_init(int *fdp, int* rfdp) +void dhcp_init(int *fdp, int* rfdp, struct dhcp_config *configs) { int fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); struct sockaddr_in saddr; int opt = 1; - + struct dhcp_config *cp; + if (fd == -1) die ("cannot create DHCP socket : %s", NULL); @@ -57,6 +58,14 @@ void dhcp_init(int *fdp, int* rfdp) #endif *rfdp = fd; + + /* 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. */ + for (; configs; configs = configs->next) + for (cp = configs->next; cp; cp = cp->next) + if ((configs->flags & cp->flags & CONFIG_ADDR) && configs->addr.s_addr == cp->addr.s_addr) + die("Duplicate IP address %s in dhcp-config directive.", inet_ntoa(cp->addr)); } void dhcp_packet(struct dhcp_context *contexts, char *packet, @@ -370,6 +379,17 @@ int address_available(struct dhcp_context *context, struct in_addr taddr) return 1; } + +struct dhcp_config *config_find_by_address(struct dhcp_config *configs, struct in_addr addr) +{ + struct dhcp_config *config; + + for (config = configs; config; config = config->next) + if ((config->flags & CONFIG_ADDR) && config->addr.s_addr == addr.s_addr) + return config; + + return NULL; +} int address_allocate(struct dhcp_context *context, struct dhcp_config *configs, struct in_addr *addrp, unsigned char *hwaddr) @@ -377,7 +397,6 @@ int address_allocate(struct dhcp_context *context, struct dhcp_config *configs, /* Find a free address: exclude anything in use and anything allocated to a particular hwaddr/clientid/hostname in our configuration */ - struct dhcp_config *config; struct in_addr start, addr ; unsigned int i, j; @@ -400,17 +419,10 @@ int address_allocate(struct dhcp_context *context, struct dhcp_config *configs, addr.s_addr = htonl(ntohl(addr.s_addr) + 1); - if (!lease_find_by_addr(addr)) + if (!lease_find_by_addr(addr) && !config_find_by_address(configs, addr)) { - for (config = configs; config; config = config->next) - if ((config->flags & CONFIG_ADDR) && config->addr.s_addr == addr.s_addr) - break; - - if (!config) - { - *addrp = addr; - return 1; - } + *addrp = addr; + return 1; } } while (addr.s_addr != start.s_addr); diff --git a/src/dnsmasq.c b/src/dnsmasq.c index bd2b72a..1b72e16 100644 --- a/src/dnsmasq.c +++ b/src/dnsmasq.c @@ -206,7 +206,7 @@ int main (int argc, char **argv) if (c != 1) die("must set exactly one interface on broken systems without IP_RECVIF", NULL); #endif - dhcp_init(&dhcpfd, &dhcp_raw_fd); + dhcp_init(&dhcpfd, &dhcp_raw_fd, dhcp_configs); leasefd = lease_init(lease_file, domain_suffix, dnamebuff, packet, now, maxleases); } diff --git a/src/dnsmasq.h b/src/dnsmasq.h index 1f0394a..4c50bfb 100644 --- a/src/dnsmasq.h +++ b/src/dnsmasq.h @@ -230,6 +230,7 @@ struct frec { union mysockaddr source; struct all_addr dest; struct server *sentto; + unsigned int iface; unsigned short orig_id, new_id; int fd; time_t time; @@ -412,7 +413,7 @@ struct irec *enumerate_interfaces(struct iname **names, struct listener *create_wildcard_listeners(int port); struct listener *create_bound_listeners(struct irec *interfaces, int port); /* dhcp.c */ -void dhcp_init(int *fdp, int* rfdp); +void dhcp_init(int *fdp, int* rfdp, struct dhcp_config *configs); void dhcp_packet(struct dhcp_context *contexts, char *packet, struct dhcp_opt *dhcp_opts, struct dhcp_config *dhcp_configs, struct dhcp_vendor *vendors, @@ -430,7 +431,7 @@ struct dhcp_config *find_config(struct dhcp_config *configs, struct dhcp_config *read_ethers(struct dhcp_config *configs, char *buff); void dhcp_update_configs(struct dhcp_config *configs); struct dhcp_config *dhcp_read_ethers(struct dhcp_config *configs, char *buff); - +struct dhcp_config *config_find_by_address(struct dhcp_config *configs, struct in_addr addr); /* lease.c */ void lease_update_file(int force, time_t now); void lease_update_dns(void); diff --git a/src/forward.c b/src/forward.c index 4d2354a..b21e9fc 100644 --- a/src/forward.c +++ b/src/forward.c @@ -36,7 +36,8 @@ void forward_init(int first) /* Send a UDP packet with it's source address set as "source" unless nowild is true, when we just send it with the kernel default */ static void send_from(int fd, int nowild, char *packet, int len, - union mysockaddr *to, struct all_addr *source) + union mysockaddr *to, struct all_addr *source, + unsigned int iface) { struct msghdr msg; struct iovec iov[1]; @@ -94,7 +95,7 @@ static void send_from(int fd, int nowild, char *packet, int len, { struct cmsghdr *cmptr = CMSG_FIRSTHDR(&msg); struct in6_pktinfo *pkt = (struct in6_pktinfo *)CMSG_DATA(cmptr); - pkt->ipi6_ifindex = 0; + pkt->ipi6_ifindex = iface; /* Need iface for IPv6 to handle link-local addrs */ pkt->ipi6_addr = source->addr.addr6; msg.msg_controllen = cmptr->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo)); cmptr->cmsg_type = IPV6_PKTINFO; @@ -191,8 +192,8 @@ unsigned short search_servers(struct server *servers, unsigned int options, stru /* returns new last_server */ static struct server *forward_query(int udpfd, union mysockaddr *udpaddr, - struct all_addr *dst_addr, HEADER *header, - int plen, unsigned int options, char *dnamebuff, + struct all_addr *dst_addr, unsigned int dst_iface, + HEADER *header, int plen, unsigned int options, char *dnamebuff, struct server *servers, struct server *last_server, time_t now, unsigned long local_ttl) { @@ -246,6 +247,7 @@ static struct server *forward_query(int udpfd, union mysockaddr *udpaddr, forward->source = *udpaddr; forward->dest = *dst_addr; + forward->iface = dst_iface; forward->new_id = get_id(); forward->fd = udpfd; forward->orig_id = ntohs(header->id); @@ -310,7 +312,7 @@ static struct server *forward_query(int udpfd, union mysockaddr *udpaddr, /* could not send on, return empty answer or address if known for whole domain */ plen = setup_reply(header, (unsigned int)plen, addrp, flags, local_ttl); - send_from(udpfd, options & OPT_NOWILD, (char *)header, plen, udpaddr, dst_addr); + send_from(udpfd, options & OPT_NOWILD, (char *)header, plen, udpaddr, dst_addr, dst_iface); return last_server; } @@ -405,7 +407,7 @@ struct server *reply_query(struct serverfd *sfd, int options, char *packet, time return NULL; header->id = htons(forward->orig_id); - send_from(forward->fd, options & OPT_NOWILD, packet, n, &forward->source, &forward->dest); + send_from(forward->fd, options & OPT_NOWILD, packet, n, &forward->source, &forward->dest, forward->iface); forward->new_id = 0; /* cancel */ } @@ -564,9 +566,9 @@ struct server *receive_query(struct listener *listen, char *packet, struct mx_re m = answer_request (header, ((char *) header) + PACKETSZ, (unsigned int)n, mxnames, mxtarget, options, now, local_ttl, namebuff, edns_pcktsz); if (m >= 1) - send_from(listen->fd, options & OPT_NOWILD, (char *)header, m, &source_addr, &dst_addr); + send_from(listen->fd, options & OPT_NOWILD, (char *)header, m, &source_addr, &dst_addr, if_index); else - last_server = forward_query(listen->fd, &source_addr, &dst_addr, + last_server = forward_query(listen->fd, &source_addr, &dst_addr, if_index, header, n, options, namebuff, servers, last_server, now, local_ttl); return last_server; diff --git a/src/network.c b/src/network.c index 4c8c207..d0d34e0 100644 --- a/src/network.c +++ b/src/network.c @@ -263,7 +263,11 @@ static int create_ipv6_listener(struct listener **link, int port) setsockopt(tcpfd, IPV6_LEVEL, IPV6_V6ONLY, &opt, sizeof(opt)) == -1 || (flags = fcntl(tcpfd, F_GETFL, 0)) == -1 || fcntl(tcpfd, F_SETFL, flags | O_NONBLOCK) == -1 || +#ifdef IPV6_RECVPKTINFO + setsockopt(fd, IPV6_LEVEL, IPV6_RECVPKTINFO, &opt, sizeof(opt)) == -1 || +#else setsockopt(fd, IPV6_LEVEL, IPV6_PKTINFO, &opt, sizeof(opt)) == -1 || +#endif bind(tcpfd, (struct sockaddr *)&addr, sa_len(&addr)) == -1 || listen(tcpfd, 5) == -1 || bind(fd, (struct sockaddr *)&addr, sa_len(&addr)) == -1) diff --git a/src/rfc2131.c b/src/rfc2131.c index b5c25b9..7fb0d1a 100644 --- a/src/rfc2131.c +++ b/src/rfc2131.c @@ -152,8 +152,9 @@ int dhcp_reply(struct dhcp_context *context, clid_len = 0; } - if ((config = find_config(dhcp_configs, context, clid, clid_len, mess->chaddr, NULL)) && - have_config(config, CONFIG_NAME)) + config = find_config(dhcp_configs, context, clid, clid_len, mess->chaddr, NULL); + + if (have_config(config, CONFIG_NAME)) hostname = config->hostname; else if ((opt = option_find(mess, sz, OPTION_HOSTNAME))) { @@ -184,8 +185,16 @@ int dhcp_reply(struct dhcp_context *context, hostname = NULL; /* nothing left */ } } - /* search again now we have a hostname */ - config = find_config(dhcp_configs, context, clid, clid_len, mess->chaddr, hostname); + + /* Search again now we have a hostname. + Only accept configs without CLID and HWADDR here, (they won't match) + to avoid impersonation by name. */ + if (!config) + { + struct dhcp_config *new = find_config(dhcp_configs, context, NULL, 0, mess->chaddr, hostname); + if (!have_config(new, CONFIG_CLID) && !have_config(new, CONFIG_HWADDR)) + config = new; + } } } @@ -347,7 +356,8 @@ int dhcp_reply(struct dhcp_context *context, mess->yiaddr = config->addr; else if (lease && address_available(context, lease->addr)) mess->yiaddr = lease->addr; - else if (opt && address_available(context, addr) && !lease_find_by_addr(addr)) + else if (opt && address_available(context, addr) && !lease_find_by_addr(addr) && + !config_find_by_address(dhcp_configs, addr)) mess->yiaddr = addr; else if (!address_allocate(context, dhcp_configs, &mess->yiaddr, mess->chaddr)) message = "no address available"; @@ -400,12 +410,11 @@ int dhcp_reply(struct dhcp_context *context, if (!lease) { - if ((!address_available(context, mess->yiaddr) || lease_find_by_addr(mess->yiaddr)) && - (!have_config(config, CONFIG_ADDR) || config->addr.s_addr != mess->yiaddr.s_addr)) - message = "address unavailable"; + if (lease_find_by_addr(mess->yiaddr)) + message = "address in use"; else if (!(lease = lease_allocate(clid, clid_len, mess->yiaddr))) message = "no leases left"; - } + } } else { @@ -424,29 +433,38 @@ int dhcp_reply(struct dhcp_context *context, fuzz = fuzz/2; } - /* If a machine moves networks whilst it has a lease, we catch that here. */ - if (!message && !is_same_net(mess->yiaddr, context->start, context->netmask)) - message = "wrong network"; + if (!message) + { + struct dhcp_config *addr_config; + /* If a machine moves networks whilst it has a lease, we catch that here. */ + if (!is_same_net(mess->yiaddr, context->start, context->netmask)) + message = "wrong network"; - /* Check for renewal of a lease which is now outside the allowed range. */ - if (!message && !address_available(context, mess->yiaddr) && - (!have_config(config, CONFIG_ADDR) || config->addr.s_addr != mess->yiaddr.s_addr)) - message = "address no longer available"; + /* Check for renewal of a lease which is now outside the allowed range. */ + else if (!address_available(context, mess->yiaddr) && + (!have_config(config, CONFIG_ADDR) || config->addr.s_addr != mess->yiaddr.s_addr)) + message = "address no longer available"; + + /* Check if a new static address has been configured. Be very sure that + when the client does DISCOVER, it will get the static address, otherwise + an endless protocol loop will ensue. */ + + else if (have_config(config, CONFIG_ADDR) && !lease_find_by_addr(config->addr)) + message = "static lease available"; + + /* Check to see if the address is reserved as a static address for another host */ + else if ((addr_config = config_find_by_address(dhcp_configs, mess->yiaddr)) && addr_config != config) + message ="address reserved"; + } - /* Check if a new static address has been configured. Be very sure that - when the client does DISCOVER, it will get the static address, otherwise - an endless protocol loop will ensue. */ - if (!message && have_config(config, CONFIG_ADDR) && - !have_config(config, CONFIG_DISABLE) && - !lease_find_by_addr(config->addr)) - message = "static lease available"; - log_packet("REQUEST", &mess->yiaddr, mess->chaddr, iface_name, NULL); if (message) { log_packet("NAK", &mess->yiaddr, mess->chaddr, iface_name, message); + lease_prune(lease, now); + mess->siaddr.s_addr = mess->yiaddr.s_addr = mess->ciaddr.s_addr = 0; bootp_option_put(mess, NULL, NULL); p = option_put(p, end, OPTION_MESSAGE_TYPE, 1, DHCPNAK);