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);