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.
+
###############################################################################
Name: dnsmasq
-Version: 2.10
+Version: 2.11
Release: 1
Copyright: GPL
Group: System Environment/Daemons
###############################################################################
Name: dnsmasq
-Version: 2.10
+Version: 2.11
Release: 1
Copyright: GPL
Group: Productivity/Networking/DNS/Servers
</PRE>
<H2>Links.</H2>
-Ulrich Ivens has a nice HOWTO in German on installing dnsmasq at <A HREF="http://howto.linux-hardware-shop.de/dnsmasq.html">http://howto.linux-hardware-shop.de/dnsmasq.html</A>
+Ulrich Ivens has a nice HOWTO in German on installing dnsmasq at <A
+HREF="http://howto.linux-hardware-shop.de/dnsmasq.html">http://howto.linux-hardware-shop.de/dnsmasq.html</A>
+and Damien Raude-Morvan has one in French at <A HREF="http://www.drazzib.com/docs-dnsmasq.html">http://www.drazzib.com/docs-dnsmasq.html</A>
<H2>License.</H2>
Dnsmasq is distributed under the GPL. See the file COPYING in the distribution
/* 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 */
#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);
#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,
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)
/* 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;
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);
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);
}
union mysockaddr source;
struct all_addr dest;
struct server *sentto;
+ unsigned int iface;
unsigned short orig_id, new_id;
int fd;
time_t time;
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,
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);
/* 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];
{
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;
/* 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)
{
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);
/* 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;
}
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 */
}
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;
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)
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)))
{
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;
+ }
}
}
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";
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
{
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";
-
- /* 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 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";
-
+ 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. */
+ 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";
+ }
+
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);