comment, a '#' character must now be a the start of a
line or preceded by whitespace. Thanks to Christian
Haggstrom for the bug report.
-
-
+release 2.7
+ Allow the dhcp-host specification of id:* which makes
+ dnsmasq ignore any client-id. This is useful to ensure
+ that a dual-boot machine sees the same lease when one OS
+ gives a client-id and the other doesn't. It's also useful
+ when PXE boot DHCP does not use client IDs but the OS it boots
+ does. Thanks to Grzegorz Nosek for suggesting this enhancement.
+ No longer assume that ciaddr is zero in received DHCPDISCOVER
+ messages, just for security against broken clients.
+ Set default of siaddr field to the address of the machine running
+ dnsmasq when not explicitly set using dhcp-boot
+ option. This is the ISC dhcpd behaviour.
-
+ Send T1 and T2 options in DHCPOFFER packets. This is required
+ by the DHCP client in some JetDirect printers. Thanks
+ to Paul Mattal for work on this.
+
+ Fixed bug with DHCP on OpenBSD reported by Dominique Jacquel.
+ The code which added loopback interfaces to the list
+ was confusing the DHCP code, which expected one interface only.
+ Solved by adding loopback interfaces to address list instead.
+ Add dhcp-vendorclass option to allow options to be sent only
+ to certain classes of clients.
+
+ Tweaked option search code so that if a netid-qualified
+ option is used, any unqualified option is ignored.
+ Changed the method of picking new dynamic IP
+ addresses. This used to use the next consecutive
+ address as long it was free, now it uses a hash
+ from the client hardware address. This reduces the amount
+ of address movement for clients which let their lease
+ expire and allows consecutive DHCPOFFERS to the same host
+ to (almost always) be for the same address, without
+ storing state before a lease is granted.
+
+ Tweaked option handling code to return all possible
+ options rather than none when DHCP "requested options"
+ field is missing. This fixes interoperability with
+ ancient IBM LANMAN DHCP clients. Thanks to Jim Louvau for
+ help with this.
+
###############################################################################
Name: dnsmasq
-Version: 2.6
+Version: 2.7
Release: 1
Copyright: GPL
Group: System Environment/Daemons
###############################################################################
Name: dnsmasq
-Version: 2.6
+Version: 2.7
Release: 1
Copyright: GPL
Group: Productivity/Networking/DNS/Servers
.B dhcp-host
or from /etc/ethers will be served.
.TP
-.B \-G, --dhcp-host=[[<hwaddr>]|[id:<client_id>]][net:<netid>][,<ipaddr>][,<hostname>][,<lease_time>][,ignore]
+.B \-G, --dhcp-host=[[<hwaddr>]|[id:[<client_id>][*]]][net:<netid>][,<ipaddr>][,<hostname>][,<lease_time>][,ignore]
Specify per host parameters for the DHCP server. This allows a machine
with a particular hardware address to be always allocated the same
hostname, IP address and lease time. A hostname specified like this
.B --dhcp-host=id:01:02:03:04,.....
refers to the host with client identifier 01:02:03:04. It is also
allowed to specify the client ID as text, like this:
-.B --dhcp-host=id:clientidastext,.....
+.B --dhcp-host=id:clientidastext,.....
+The special option id:* means "ignore any client-id
+and use MAC addresses only." This is useful when a client presents a client-id sometimes
+but not others.
If a name appears in /etc/hosts, the associated address can be
allocated to a DHCP lease, but only if a
.B --dhcp-host
192.168.4.4, do
.B --dhcp-option=3,192.168.4.4
and to set the time-server address to 192.168.0.4, do
-.B dhcp-option=42,192.168.0.4
+.B --dhcp-option=42,192.168.0.4
The special address 0.0.0.0 is taken to mean "the address of the
machine running dnsmasq". Data types allowed are comma seperated
dotted-quad IP addresses, a decimal number, colon-seperated hex digits
persuade dnsmasq to generate illegal DHCP packets with injudicious use
of this flag.
.TP
+.B \-U, --dhcp-vendorclass=<network-id>,<vendor-class>
+Map from a vendor-class string to a network id. Most DHCP clients provide a
+"vendor class" which represents, in some sense, the type of host. This options
+maps vendor classes to network ids, so that DHCP options may be selectively delivered
+to different classes of hosts. For example
+.B dhcp-vendorclass=printers,Hewlett-Packard JetDirect
+will allow options to be set only for HP printers like so:
+.B --dhcp-option=printers,3,192.168.4.4
+.TP
.B \-M, --dhcp-boot=<filename>,[<servername>[,<server address>]]
Set BOOTP options to be returned by the DHCP server. These are needed
for machines which network boot, and tell the machine where to collect
# address is 11:22:33:44:55:66
#dhcp-host=11:22:33:44:55:66,ignore
+# Ignore any client-id presented by the machine with ethernet
+# address 11:22:33:44:55:66. This is useful to prevent a machine
+# being treated differently when running under different OS's or
+# between PXE boot and OS boot.
+#dhcp-host=11:22:33:44:55:66,id:*
+
# Send extra options which are tagged as "red" to
# the machine with ethernet address 11:22:33:44:55:66
#dhcp-host=11:22:33:44:55:66,net:red
/* Author's email: simon@thekelleys.org.uk */
-#define VERSION "2.6"
+#define VERSION "2.7"
#define FTABSIZ 150 /* max number of outstanding requests */
#define TIMEOUT 20 /* drop queries after TIMEOUT seconds */
}
void dhcp_packet(struct dhcp_context *contexts, char *packet,
- struct dhcp_opt *dhcp_opts, struct dhcp_config *dhcp_configs,
+ struct dhcp_opt *dhcp_opts, struct dhcp_config *dhcp_configs,
+ struct dhcp_vendor *vendors,
time_t now, char *namebuff, char *domain_suffix,
char *dhcp_file, char *dhcp_sname,
struct in_addr dhcp_next_server, int dhcp_fd, int raw_fd,
/* we can use the interface netmask if either the packet came direct,
or it came via a relay listening on the same network. This sounds unlikely,
but it happens with win4lin. */
- if ((source.s_addr & iface_netmask.s_addr) != (iface_addr.s_addr & iface_netmask.s_addr))
+ if (!is_same_net(source, iface_addr, iface_netmask))
iface_netmask.s_addr = 0;
else if (ioctl(dhcp_fd, SIOCGIFBRDADDR, &ifr) != -1)
iface_broadcast = ((struct sockaddr_in *) &ifr.ifr_addr)->sin_addr;
struct in_addr netmask = context->netmask.s_addr ? context->netmask : iface_netmask;
if (netmask.s_addr &&
- (source.s_addr & netmask.s_addr) == (context->start.s_addr & netmask.s_addr) &&
- (source.s_addr & netmask.s_addr) == (context->end.s_addr & netmask.s_addr))
+ is_same_net(source, context->start, netmask) &&
+ is_same_net(source, context->end, netmask))
break;
}
DHCP broadcast, either this machine or a relay. In the special case that the relay
is on the same network as us, we set the default route to us, not the relay.
This is the win4lin scenario again. */
- if ((source.s_addr & context->netmask.s_addr) == (iface_addr.s_addr & context->netmask.s_addr))
+ if (is_same_net(source, iface_addr, context->netmask))
router = iface_addr;
else
router = source;
lease_prune(NULL, now); /* lose any expired leases */
newlen = dhcp_reply(context, iface_addr, ifr.ifr_name, ifr.ifr_mtu,
rawpacket, sz, now, namebuff,
- dhcp_opts, dhcp_configs, domain_suffix, dhcp_file,
- dhcp_sname, dhcp_next_server, router);
+ dhcp_opts, dhcp_configs, vendors, domain_suffix,
+ dhcp_file, dhcp_sname, dhcp_next_server, router);
lease_update_file(0, now);
lease_update_dns();
}
}
-
int address_available(struct dhcp_context *context, struct in_addr taddr)
{
/* Check is an address is OK for this network, ie
}
int address_allocate(struct dhcp_context *context, struct dhcp_config *configs,
- struct in_addr *addrp)
+ struct in_addr *addrp, unsigned char *hwaddr)
{
/* Find a free address: exlude anything in use and anything allocated to
a particular hwaddr/clientid/hostname in our configuration */
struct dhcp_config *config;
- struct in_addr start = context->last;
+ struct in_addr start, addr ;
+ int i, j;
/* start == end means no dynamic leases. */
if (context->end.s_addr == context->start.s_addr)
return 0;
-
+
+ /* pick a seed based on hwaddr then iterate until we find a free address. */
+ for (j = 0, i = 0; i < ETHER_ADDR_LEN; i++)
+ j += hwaddr[i] + (hwaddr[i] << 8) + (hwaddr[i] << 16);
+
+ start.s_addr = addr.s_addr =
+ htonl(ntohl(context->start.s_addr) +
+ (j % (ntohl(context->end.s_addr) - ntohl(context->start.s_addr))));
+
do {
- if (context->last.s_addr == context->end.s_addr)
- context->last = context->start;
+ if (addr.s_addr == context->end.s_addr)
+ addr = context->start;
else
- context->last.s_addr = htonl(ntohl(context->last.s_addr) + 1);
+ addr.s_addr = htonl(ntohl(addr.s_addr) + 1);
- if (!lease_find_by_addr(context->last))
+ if (!lease_find_by_addr(addr))
{
for (config = configs; config; config = config->next)
- if ((config->flags & CONFIG_ADDR) && config->addr.s_addr == context->last.s_addr)
+ if ((config->flags & CONFIG_ADDR) && config->addr.s_addr == addr.s_addr)
break;
if (!config)
{
- *addrp = context->last;
+ *addrp = addr;
return 1;
}
}
- } while (context->last.s_addr != start.s_addr);
+ } while (addr.s_addr != start.s_addr);
return 0;
}
return 1;
if (!(config->flags & CONFIG_ADDR))
return 1;
- if ((config->addr.s_addr & context->netmask.s_addr) == (context->start.s_addr & context->netmask.s_addr))
+ if (is_same_net(config->addr, context->start, context->netmask))
return 1;
return 0;
struct dhcp_context *dhcp_tmp, *dhcp = NULL;
struct dhcp_config *dhcp_configs = NULL;
struct dhcp_opt *dhcp_options = NULL;
+ struct dhcp_vendor *dhcp_vendors = NULL;
char *dhcp_file = NULL, *dhcp_sname = NULL;
struct in_addr dhcp_next_server;
int leasefd = -1, dhcpfd = -1, dhcp_raw_fd = -1;
&username, &groupname, &domain_suffix, &runfile,
&if_names, &if_addrs, &if_except, &bogus_addr,
&serv_addrs, &cachesize, &port, &query_port, &local_ttl, &addn_hosts,
- &dhcp, &dhcp_configs, &dhcp_options,
+ &dhcp, &dhcp_configs, &dhcp_options, &dhcp_vendors,
&dhcp_file, &dhcp_sname, &dhcp_next_server, &maxleases, &min_leasetime,
&doctors);
- /* if we cannot support binding the wildcard address, set the "bind only
- interfaces in use" option */
-#ifndef HAVE_UDP_SRC_DST
- options |= OPT_NOWILD;
-#endif
-
if (!lease_file)
{
if (dhcp)
die("ISC dhcpd integration not available: set HAVE_ISC_READER in src/config.h", NULL);
#endif
- interfaces = enumerate_interfaces(if_names, if_addrs, if_except, port);
+#ifndef HAVE_UDP_SRC_DST
+ /* if we cannot support binding the wildcard address, set the "bind only
+ interfaces in use" option */
+ options |= OPT_NOWILD;
+#endif
+
+ interfaces = enumerate_interfaces(&if_names, &if_addrs, if_except, port);
if (options & OPT_NOWILD)
listeners = create_bound_listeners(interfaces);
else
dnamebuff, last_server, bogus_addr, doctors);
if (dhcp && FD_ISSET(dhcpfd, &rset))
- dhcp_packet(dhcp, packet, dhcp_options, dhcp_configs,
+ dhcp_packet(dhcp, packet, dhcp_options, dhcp_configs, dhcp_vendors,
now, dnamebuff, domain_suffix, dhcp_file,
dhcp_sname, dhcp_next_server, dhcpfd, dhcp_raw_fd,
if_names, if_addrs, if_except);
};
#define F_IMMORTAL 1
-#define F_CONFIG 2
+#define F_CONFIG 2
#define F_REVERSE 4
#define F_FORWARD 8
#define F_DHCP 16
#define CONFIG_NAME 16
#define CONFIG_ADDR 32
#define CONFIG_NETID 64
+#define CONFIG_NOCLID 128
struct dhcp_opt {
int opt, len, is_addr;
unsigned char *val;
char *netid;
struct dhcp_opt *next;
- };
+};
+
+struct dhcp_vendor {
+ int len;
+ char *data, *net;
+ struct dhcp_vendor *next;
+};
struct dhcp_context {
unsigned int lease_time;
struct in_addr netmask, broadcast;
- struct in_addr start, end, last; /* range of available addresses */
+ struct in_addr start, end; /* range of available addresses */
char *netid;
struct dhcp_context *next;
};
int sockaddr_isequal(union mysockaddr *s1, union mysockaddr *s2);
int hostname_isequal(unsigned char *a, unsigned char *b);
time_t dnsmasq_time(int fd);
+int is_same_net(struct in_addr a, struct in_addr b, struct in_addr mask);
+
/* option.c */
unsigned int read_opts(int argc, char **argv, char *buff, struct resolvc **resolv_file,
char **mxname, char **mxtarget, char **lease_file,
struct iname **if_names, struct iname **if_addrs, struct iname **if_except,
struct bogus_addr **bogus_addr, struct server **serv_addrs, int *cachesize,
int *port, int *query_port, unsigned long *local_ttl, char **addn_hosts,
- struct dhcp_context **dhcp, struct dhcp_config **dhcp_conf, struct dhcp_opt **opts,
+ struct dhcp_context **dhcp, struct dhcp_config **dhcp_conf,
+ struct dhcp_opt **opts, struct dhcp_vendor **dhcp_vendors,
char **dhcp_file, char **dhcp_sname, struct in_addr *dhcp_next_server,
int *maxleases, unsigned int *min_leasetime, struct doctor **doctors);
/* network.c */
struct server *reload_servers(char *fname, char *buff, struct server *servers, int query_port);
struct server *check_servers(struct server *new, struct irec *interfaces, struct serverfd **sfds);
-struct irec *enumerate_interfaces(struct iname *names,
- struct iname *addrs,
+struct irec *enumerate_interfaces(struct iname **names,
+ struct iname **addrs,
struct iname *except,
int port);
struct listener *create_wildcard_listeners(int port);
void dhcp_init(int *fdp, int* rfdp);
void dhcp_packet(struct dhcp_context *contexts, char *packet,
struct dhcp_opt *dhcp_opts, struct dhcp_config *dhcp_configs,
+ struct dhcp_vendor *vendors,
time_t now, char *namebuff, char *domain_suffix,
char *dhcp_file, char *dhcp_sname,
struct in_addr dhcp_next_server, int dhcp_fd, int raw_fd,
struct iname *names, struct iname *addrs, struct iname *except);
int address_available(struct dhcp_context *context, struct in_addr addr);
int address_allocate(struct dhcp_context *context, struct dhcp_config *configs,
- struct in_addr *addrp);
+ struct in_addr *addrp, unsigned char *hwaddr);
struct dhcp_config *find_config(struct dhcp_config *configs,
struct dhcp_context *context,
unsigned char *clid, int clid_len,
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);
+
/* lease.c */
void lease_update_file(int force, time_t now);
void lease_update_dns(void);
struct dhcp_lease *lease_find_by_addr(struct in_addr addr);
void lease_prune(struct dhcp_lease *target, time_t now);
void lease_update_from_configs(struct dhcp_config *dhcp_configs, char *domain);
+
/* rfc2131.c */
int dhcp_reply(struct dhcp_context *context,
struct in_addr iface_addr,
struct udp_dhcp_packet *rawpacket,
unsigned int sz, time_t now, char *namebuff,
struct dhcp_opt *dhcp_opts, struct dhcp_config *dhcp_configs,
+ struct dhcp_vendor *vendors,
char *domain_suffix, char *dhcp_file, char *dhcp_sname,
struct in_addr dhcp_next_server, struct in_addr router);
if (except)
for (tmp = except; tmp; tmp = tmp->next)
if (tmp->name && strcmp(tmp->name, name) == 0)
- return NULL;
+ return list;
/* we may need to check the whitelist */
if (names || addrs)
if (sockaddr_isequal(&tmp->addr, addr))
break;
if (!tmp)
- return NULL;
+ return list;
}
/* check whether the interface IP has been added already
it is possible to have multiple interfaces with the same address */
- for (; list; list = list->next)
- if (sockaddr_isequal(&list->addr, addr))
+ for (iface = list; iface; iface = iface->next)
+ if (sockaddr_isequal(&iface->addr, addr))
break;
- if (list)
- return NULL;
+ if (iface)
+ return list;
/* If OK, add it to the head of the list */
iface = safe_malloc(sizeof(struct irec));
iface->addr = *addr;
-
+ iface->next = list;
return iface;
}
-struct irec *enumerate_interfaces(struct iname *names,
- struct iname *addrs,
+struct irec *enumerate_interfaces(struct iname **names,
+ struct iname **addrs,
struct iname *except,
int port)
{
- struct irec *iface = NULL, *new;
+ struct irec *iface = NULL;
char *buf, *ptr;
struct ifreq *ifr = NULL;
struct ifconf ifc;
die("ioctl error getting interface flags: %m", NULL);
/* If we are restricting the set of interfaces to use, make
- sure that loopback interfaces are in that set. */
- if (names && (ifr->ifr_flags & IFF_LOOPBACK))
+ sure that loopback interfaces are in that set. Note that
+ this is done as addresses rather than interface names so
+ as not to confuse the no-IPRECVIF workaround on the DHCP code */
+ if (*names && (ifr->ifr_flags & IFF_LOOPBACK))
{
struct iname *lo = safe_malloc(sizeof(struct iname));
- lo->name = safe_string_alloc(ifr->ifr_name);
- lo->next = names->next;
- names->next = lo;
- }
-
- if ((new = add_iface(iface, ifr->ifr_name,
- &addr, names, addrs, except)))
- {
- new->next = iface;
- iface = new;
+ lo->addr = addr;
+ lo->next = *addrs;
+ *addrs = lo;
}
+ iface = add_iface(iface, ifr->ifr_name, &addr, *names, *addrs, except);
+
#if defined(HAVE_LINUX_IPV6_PROC) && defined(HAVE_IPV6)
/* IPv6 addresses don't seem to work with SIOCGIFCONF. Barf */
/* This code snarfed from net-tools 1.60 and certainly linux specific, though
fclose(f);
}
- if (found && (new = add_iface(iface, ifr->ifr_name,
- &addr6, names, addrs, except)))
+ if (found)
{
- new->next = iface;
- iface = new;
+ if (*names && (ifr->ifr_flags & IFF_LOOPBACK))
+ {
+ struct iname *lo = safe_malloc(sizeof(struct iname));
+ lo->addr = addr6;
+ lo->next = *addrs;
+ *addrs = lo;
+ }
+
+ iface = add_iface(iface, ifr->ifr_name, &addr6, *names, *addrs, except);
}
}
#endif /* LINUX */
-/* dnsmasq is Copyright (c) 2000 - 2003 Simon Kelley
+/* dnsmasq is Copyright (c) 2000 - 2004 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
int val;
};
-#define OPTSTRING "ZDNLERzowefnbvhdqr:m:p:c:l:s:i:t:u:g:a:x:S:C:A:T:H:Q:I:B:F:G:O:M:X:V:"
+#define OPTSTRING "ZDNLERzowefnbvhdqr:m:p:c:l:s:i:t:u:g:a:x:S:C:A:T:H:Q:I:B:F:G:O:M:X:V:U:"
static struct myoption opts[] = {
{"version", 0, 0, 'v'},
{"bind-interfaces", 0, 0, 'z'},
{"read-ethers", 0, 0, 'Z' },
{"alias", 1, 0, 'V' },
+ {"dhcp-vendorclass", 1, 0, 'U'},
{0, 0, 0, 0}
};
"-t, --mx-target=host_name Specify the host in an MX reply.\n"
"-T, --local-ttl=time Specify time-to-live in seconds for replies from /etc/hosts.\n"
"-u, --user=username Change to this user after startup. (defaults to " CHUSER ").\n"
+"-U, --dhcp-vendorclass=<id>,<class> Map DHCP vendor class to option set.\n"
"-v, --version Display dnsmasq version.\n"
"-V, --alias=addr,addr,mask Translate IPv4 addresses from upstream servers.\n"
"-w, --help Display this message.\n"
struct iname **if_names, struct iname **if_addrs, struct iname **if_except,
struct bogus_addr **bogus_addr, struct server **serv_addrs, int *cachesize, int *port,
int *query_port, unsigned long *local_ttl, char **addn_hosts, struct dhcp_context **dhcp,
- struct dhcp_config **dhcp_conf, struct dhcp_opt **dhcp_opts, char **dhcp_file,
+ struct dhcp_config **dhcp_conf, struct dhcp_opt **dhcp_opts, struct dhcp_vendor **dhcp_vendors, char **dhcp_file,
char **dhcp_sname, struct in_addr *dhcp_next_server, int *dhcp_max,
unsigned int *min_leasetime, struct doctor **doctors)
{
}
}
- new->last = new->start;
if (new->lease_time < *min_leasetime)
*min_leasetime = new->lease_time;
break;
(arg[1] == 'd' || arg[1] == 'D') &&
arg[2] == ':')
{
- int len;
- arg += 3; /* dump id: */
- if (strchr(arg, ':'))
+ if (arg[3] == '*')
+ new->flags |= CONFIG_NOCLID;
+ else
{
- /* decode hex in place */
- char *p = arg, *q = arg, *r;
- while (*p)
+ int len;
+ arg += 3; /* dump id: */
+ if (strchr(arg, ':'))
{
- for (r = p; *r && *r != ':'; r++);
- if (*r)
+ /* decode hex in place */
+ char *p = arg, *q = arg, *r;
+ while (*p)
{
- if (r != p)
+ for (r = p; *r && *r != ':'; r++);
+ if (*r)
{
- *r = 0;
- *(q++) = strtol(p, NULL, 16);
+ if (r != p)
+ {
+ *r = 0;
+ *(q++) = strtol(p, NULL, 16);
+ }
+ p = r+1;
+ }
+ else
+ {
+ if (*p)
+ *(q++) = strtol(p, NULL, 16);
+ break;
}
- p = r+1;
- }
- else
- {
- if (*p)
- *(q++) = strtol(p, NULL, 16);
- break;
}
+ len = q - arg;
}
- len = q - arg;
+ else
+ len = strlen(arg);
+
+ new->flags |= CONFIG_CLID;
+ new->clid_len = len;
+ new->clid = safe_malloc(len);
+ memcpy(new->clid, arg, len);
}
- else
- len = strlen(arg);
-
- new->flags |= CONFIG_CLID;
- new->clid_len = len;
- new->clid = safe_malloc(len);
- memcpy(new->clid, arg, len);
}
else if ((arg[0] == 'n' || arg[0] == 'N') &&
(arg[1] == 'e' || arg[1] == 'E') &&
break;
}
+ case 'U':
+ {
+ char *comma;
+
+ if (!(comma = strchr(optarg, ',')))
+ option = '?';
+ else
+ {
+ struct dhcp_vendor *new = safe_malloc(sizeof(struct dhcp_vendor));
+ *comma = 0;
+ new->net = safe_string_alloc(optarg);
+ new->len = strlen(comma+1);
+ new->data = safe_malloc(new->len);
+ memcpy(new->data, comma+1, new->len);
+ new->next = *dhcp_vendors;
+ *dhcp_vendors = new;
+ }
+ break;
+ }
+
case 'V':
{
char *a[3] = { NULL, NULL, NULL };
static void dns_doctor(struct doctor *doctor, struct in_addr *addr)
{
for (; doctor; doctor = doctor->next)
- if ((doctor->in.s_addr & doctor->mask.s_addr) == (addr->s_addr & doctor->mask.s_addr))
+ if (is_same_net(doctor->in, *addr, doctor->mask))
{
addr->s_addr &= ~doctor->mask.s_addr;
addr->s_addr |= (doctor->out.s_addr & doctor->mask.s_addr);
#define OPTION_MAXMESSAGE 57
#define OPTION_T1 58
#define OPTION_T2 59
+#define OPTION_VENDOR_ID 60
#define OPTION_CLIENT_ID 61
#define OPTION_END 255
struct udp_dhcp_packet *rawpacket,
unsigned int sz, time_t now, char *namebuff,
struct dhcp_opt *dhcp_opts, struct dhcp_config *dhcp_configs,
+ struct dhcp_vendor *vendors,
char *domain_suffix, char *dhcp_file, char *dhcp_sname,
struct in_addr dhcp_next_server, struct in_addr router)
{
unsigned int renewal_time, expires_time, def_time;
struct dhcp_config *config;
char *netid;
-
+ struct in_addr addr;
+ unsigned short fuzz = 0;
+
if (mess->op != BOOTREQUEST ||
mess->hlen != ETHER_ADDR_LEN ||
mess->cookie != htonl(DHCP_COOKIE))
clid = mess->chaddr;
clid_len = 0;
}
-
- /* do we have a lease in store? */
- lease = lease_find_by_client(clid, clid_len);
-
- if ((opt = option_find(mess, sz, OPTION_REQUESTED_OPTIONS)))
- {
- int len = option_len(opt);
- req_options = namebuff;
- memcpy(req_options, option_ptr(opt), len);
- req_options[len] = OPTION_END;
- }
-
+
if ((config = find_config(dhcp_configs, context, clid, clid_len, mess->chaddr, NULL)) &&
have_config(config, CONFIG_NAME))
hostname = config->hostname;
else
*dot = 0; /* truncate */
}
+ /* 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 */
- config = find_config(dhcp_configs, context, clid, clid_len, mess->chaddr, hostname);
+
def_time = have_config(config, CONFIG_TIME) ? config->lease_time : context->lease_time;
- netid = have_config(config, CONFIG_NETID) ? config->netid : context->netid;
+
+ netid = context->netid;
+ if (have_config(config, CONFIG_NETID))
+ netid = config->netid;
+ else if ((opt = option_find(mess, sz, OPTION_VENDOR_ID)))
+ {
+ struct dhcp_vendor *vendor;
+ for (vendor = vendors; vendor; vendor = vendor->next)
+ if (vendor->len == option_len(opt) &&
+ memcmp(vendor->data, option_ptr(opt), vendor->len) == 0)
+ netid = vendor->net;
+ }
+
+ /* Can have setting to ignore the client ID for a particular MAC address or hostname */
+ if (have_config(config, CONFIG_NOCLID))
+ {
+ clid = mess->chaddr;
+ clid_len = 0;
+ }
+
+ /* do we have a lease in store? */
+ lease = lease_find_by_client(clid, clid_len);
if ((opt = option_find(mess, sz, OPTION_LEASE_TIME)))
{
else
expires_time = def_time;
}
-
+
+ if ((opt = option_find(mess, sz, OPTION_REQUESTED_OPTIONS)))
+ {
+ int len = option_len(opt);
+ req_options = namebuff;
+ memcpy(req_options, option_ptr(opt), len);
+ req_options[len] = OPTION_END;
+ }
+
if (!(opt = option_find(mess, sz, OPTION_MESSAGE_TYPE)))
return 0;
- switch (opt[2])
+ switch (option_uint(opt, 1))
{
case DHCPDECLINE:
if (!(opt = option_find(mess, sz, OPTION_SERVER_IDENTIFIER)) ||
case DHCPDISCOVER:
if ((opt = option_find(mess, sz, OPTION_REQUESTED_IP)))
- mess->yiaddr = option_addr(opt);
+ addr = option_addr(opt);
if (have_config(config, CONFIG_DISABLE))
message = "ignored";
else if (have_config(config, CONFIG_ADDR) && !lease_find_by_addr(config->addr))
mess->yiaddr = config->addr;
- else if (lease &&
- ((lease->addr.s_addr & context->netmask.s_addr) ==
- (context->start.s_addr & context->netmask.s_addr)))
+ else if (lease && is_same_net(lease->addr, context->start, context->netmask))
mess->yiaddr = lease->addr;
- else if ((!opt || !address_available(context, mess->yiaddr)) &&
- !address_allocate(context, dhcp_configs, &mess->yiaddr))
+ else if (opt && address_available(context, addr))
+ mess->yiaddr = addr;
+ else if (!address_allocate(context, dhcp_configs, &mess->yiaddr, mess->chaddr))
message = "no address available";
-
- log_packet("DISCOVER", opt ? &mess->yiaddr : NULL, mess->chaddr, iface_name, message);
+
+ log_packet("DISCOVER", opt ? &addr : NULL, mess->chaddr, iface_name, message);
+
if (message)
return 0;
-
+
+ /* ensure that we send the reply by steam even if a buggy client sets this. */
+ mess->ciaddr.s_addr = 0;
bootp_option_put(mess, dhcp_file, dhcp_sname);
- mess->siaddr = dhcp_next_server;
+ mess->siaddr = dhcp_next_server.s_addr ? dhcp_next_server : iface_addr;
p = option_put(p, end, OPTION_MESSAGE_TYPE, 1, DHCPOFFER);
p = option_put(p, end, OPTION_SERVER_IDENTIFIER, INADDRSZ, ntohl(iface_addr.s_addr));
p = option_put(p, end, OPTION_LEASE_TIME, 4, expires_time);
+ /* T1 and T2 are required in DHCPOFFER by HP's wacky Jetdirect client. */
+ if (expires_time != 0xffffffff)
+ {
+ p = option_put(p, end, OPTION_T1, 4, (expires_time/2));
+ p = option_put(p, end, OPTION_T2, 4, ((expires_time * 7)/8));
+ }
p = do_req_options(context, p, end, req_options, dhcp_opts, domain_suffix,
NULL, router, iface_addr, iface_mtu, netid);
p = option_put(p, end, OPTION_END, 0, 0);
log_packet("OFFER" , &mess->yiaddr, mess->chaddr, iface_name, NULL);
return p - (unsigned char *)mess;
-
case DHCPREQUEST:
- if ((opt = option_find(mess, sz, OPTION_REQUESTED_IP)))
+ if (have_config(config, CONFIG_DISABLE))
+ message = "disabled";
+ else if ((opt = option_find(mess, sz, OPTION_REQUESTED_IP)))
{
/* SELECTING or INIT_REBOOT */
mess->yiaddr = option_addr(opt);
mess->yiaddr = mess->ciaddr;
if (!lease || mess->ciaddr.s_addr != lease->addr.s_addr)
message = "lease not found";
+
+ /* desynchronise renewals */
+ fuzz = rand16();
+ while (fuzz > (renewal_time/16))
+ fuzz = fuzz/2;
}
/* If a machine moves networks whilst it has a lease, we catch that here. */
- if ((mess->yiaddr.s_addr & context->netmask.s_addr) != (context->start.s_addr & context->netmask.s_addr))
+ if (!message && !is_same_net(mess->yiaddr, context->start, context->netmask))
message = "wrong network";
- if (have_config(config, CONFIG_DISABLE))
- message = "disabled";
-
log_packet("REQUEST", &mess->yiaddr, mess->chaddr, iface_name, NULL);
if (message)
lease_set_expires(lease, renewal_time == 0xffffffff ? 0 : now + (time_t)renewal_time);
bootp_option_put(mess, dhcp_file, dhcp_sname);
- mess->siaddr = dhcp_next_server;
+ mess->siaddr = dhcp_next_server.s_addr ? dhcp_next_server : iface_addr;
p = option_put(p, end, OPTION_MESSAGE_TYPE, 1, DHCPACK);
p = option_put(p, end, OPTION_SERVER_IDENTIFIER, INADDRSZ, ntohl(iface_addr.s_addr));
p = option_put(p, end, OPTION_LEASE_TIME, 4, renewal_time);
if (renewal_time != 0xffffffff)
{
- unsigned short fuzz = rand16();
- while (fuzz > (renewal_time/16))
- fuzz = fuzz/2;
p = option_put(p, end, OPTION_T1, 4, (renewal_time/2) - fuzz);
p = option_put(p, end, OPTION_T2, 4, ((renewal_time * 7)/8) - fuzz);
}
case DHCPINFORM:
if (have_config(config, CONFIG_DISABLE))
- {
- log_packet("INFORM", &mess->ciaddr, mess->chaddr, iface_name, "ignored");
- return 0;
- }
+ message = "ignored";
- log_packet("INFORM", &mess->ciaddr, mess->chaddr, iface_name, NULL);
+ log_packet("INFORM", &mess->ciaddr, mess->chaddr, iface_name, message);
+
+ if (message || mess->ciaddr.s_addr == 0)
+ return 0;
p = option_put(p, end, OPTION_MESSAGE_TYPE, 1, DHCPACK);
p = option_put(p, end, OPTION_SERVER_IDENTIFIER, INADDRSZ, ntohl(iface_addr.s_addr));
{
int i;
+ /* If no requested options, send everything, not nothing. */
+ if (!list)
+ return 1;
+
for (i = 0; list[i] != OPTION_END; i++)
if (opt == list[i])
return 1;
static struct dhcp_opt *option_find2(char *netid, struct dhcp_opt *opts, int opt)
{
- for (; opts; opts = opts->next)
- if (opts->opt == opt &&
- (!opts->netid || (netid && strcmp(opts->netid, netid) == 0)))
- return opts;
- return NULL;
+ struct dhcp_opt *tmp;
+
+ for (tmp = opts; tmp; tmp = tmp->next)
+ if (tmp->opt == opt &&
+ ((!netid && !tmp->netid) ||
+ (netid && tmp->netid && strcmp(tmp->netid, netid) == 0)))
+ return tmp;
+
+ return netid ? option_find2(NULL, opts, opt) : NULL;
}
static unsigned char *do_req_options(struct dhcp_context *context,
struct in_addr iface_addr,
int iface_mtu, char *netid)
{
- int i;
-
- if (!req_options)
- return p;
-
+ struct dhcp_opt *opt;
+
if (in_list(req_options, OPTION_MAXMESSAGE))
p = option_put(p, end, OPTION_MAXMESSAGE, 2,
DNSMASQ_PACKETSZ > iface_mtu ?
if (hostname && in_list(req_options, OPTION_HOSTNAME))
p = option_put_string(p, end, OPTION_HOSTNAME, hostname);
- for (i = 0; req_options[i] != OPTION_END; i++)
+ for (opt=config_opts; opt; opt = opt->next)
{
- struct dhcp_opt *opt;
-
- if (req_options[i] == OPTION_HOSTNAME ||
- req_options[i] == OPTION_MAXMESSAGE ||
- !(opt = option_find2(netid, config_opts, req_options[i])) ||
- (p + opt->len + 3 >= end))
+ if (opt->opt == OPTION_HOSTNAME ||
+ opt->opt == OPTION_MAXMESSAGE ||
+ !in_list(req_options, opt->opt) ||
+ opt != option_find2(netid, config_opts, opt->opt) ||
+ p + opt->len + 3 >= end)
continue;
/* For the options we have default values on
*(p++) = opt->len;
if (opt->len == 0)
continue;
-
+
if (opt->is_addr)
{
int j;
return time(NULL);
#endif
}
+
+int is_same_net(struct in_addr a, struct in_addr b, struct in_addr mask)
+{
+ return (a.s_addr & mask.s_addr) == (b.s_addr & mask.s_addr);
+}