diff --git a/CHANGELOG b/CHANGELOG
index 518b0bf..f99da12 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -51,7 +51,10 @@ version 2.92
server configured, queries are never sent upstream so they are never
validated and the new behaviour is moot.
+ Add support for leasequery to the dnsmasq DHCPv4 server.
+ This has to be specifically enabled with the --leasequery option.
+
version 2.91
Fix spurious "resource limit exceeded messages". Thanks to
Dominik Derigs for the bug report.
diff --git a/contrib/leasequery/leasequery.c b/contrib/leasequery/leasequery.c
new file mode 100644
index 0000000..928312e
--- /dev/null
+++ b/contrib/leasequery/leasequery.c
@@ -0,0 +1,692 @@
+/* leasequery is Copyright (c) 2025 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
+ the Free Software Foundation; version 2 dated June, 1991, or
+ (at your option) version 3 dated 29 June, 2007.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+
+/* Author's email: simon@thekelleys.org.uk */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+typedef unsigned char u8;
+typedef unsigned short u16;
+typedef unsigned int u32;
+typedef unsigned long long u64;
+
+#define INADDRSZ 4
+#define ADDRSTRLEN 46
+
+#define option_len(opt) ((int)(((unsigned char *)(opt))[1]))
+#define option_ptr(opt, i) ((void *)&(((unsigned char *)(opt))[2u+(unsigned int)(i)]))
+
+#define DHCP_CHADDR_MAX 16
+#define DHCP_SERVER_PORT 67
+#define DHCP_CLIENT_PORT 68
+#define BOOTREQUEST 1
+#define BOOTREPLY 2
+
+#define DHCP_COOKIE 0x63825363
+
+#define DHCPLEASEQUERY 10
+#define DHCPLEASEUNASSIGNED 11
+#define DHCPLEASEUNKNOWN 12
+#define DHCPLEASEACTIVE 13
+
+#define OPTION_END 255
+#define OPTION_ROUTER 3
+#define OPTION_VENDOR_CLASS_OPT 43
+#define OPTION_LEASE_TIME 51
+#define OPTION_MESSAGE_TYPE 53
+#define OPTION_SERVER_IDENTIFIER 54
+#define OPTION_REQUESTED_OPTIONS 55
+#define OPTION_VENDOR_ID 60
+#define OPTION_CLIENT_ID 61
+
+/* flags in top of length field for DHCP-option tables */
+#define OT_ADDR_LIST 0x8000
+#define OT_RFC1035_NAME 0x4000
+#define OT_INTERNAL 0x2000
+#define OT_NAME 0x1000
+#define OT_CSTRING 0x0800
+#define OT_DEC 0x0400
+#define OT_TIME 0x0200
+
+#define GETSHORT(s, cp) do { \
+ unsigned char *t_cp = (unsigned char *)(cp); \
+ (s) = ((u16)t_cp[0] << 8) \
+ | ((u16)t_cp[1]) \
+ ; \
+ (cp) += 2; \
+ } while(0)
+
+#define GETLONG(l, cp) do { \
+ unsigned char *t_cp = (unsigned char *)(cp); \
+ (l) = ((u32)t_cp[0] << 24) \
+ | ((u32)t_cp[1] << 16) \
+ | ((u32)t_cp[2] << 8) \
+ | ((u32)t_cp[3]) \
+ ; \
+ (cp) += 4; \
+ } while (0)
+
+#define PUTSHORT(s, cp) do { \
+ u16 t_s = (u16)(s); \
+ unsigned char *t_cp = (unsigned char *)(cp); \
+ *t_cp++ = t_s >> 8; \
+ *t_cp = t_s; \
+ (cp) += 2; \
+ } while(0)
+
+#define PUTLONG(l, cp) do { \
+ u32 t_l = (u32)(l); \
+ unsigned char *t_cp = (unsigned char *)(cp); \
+ *t_cp++ = t_l >> 24; \
+ *t_cp++ = t_l >> 16; \
+ *t_cp++ = t_l >> 8; \
+ *t_cp = t_l; \
+ (cp) += 4; \
+ } while (0)
+
+union all_addr {
+ struct in_addr addr4;
+ struct in6_addr addr6;
+};
+
+struct dhcp_packet_with_opts{
+ struct dhcp_packet {
+ unsigned char op, htype, hlen, hops;
+ unsigned int xid;
+ unsigned short secs, flags;
+ struct in_addr ciaddr, yiaddr, siaddr, giaddr;
+ unsigned char chaddr[DHCP_CHADDR_MAX], sname[64], file[128];
+ } header;
+ unsigned char options[312];
+};
+
+static const struct opttab_t {
+ char *name;
+ u16 val, size;
+} opttab[] = {
+ { "netmask", 1, OT_ADDR_LIST },
+ { "time-offset", 2, 4 },
+ { "router", 3, OT_ADDR_LIST },
+ { "dns-server", 6, OT_ADDR_LIST },
+ { "log-server", 7, OT_ADDR_LIST },
+ { "lpr-server", 9, OT_ADDR_LIST },
+ { "hostname", 12, OT_INTERNAL | OT_NAME },
+ { "boot-file-size", 13, 2 | OT_DEC },
+ { "domain-name", 15, OT_NAME },
+ { "swap-server", 16, OT_ADDR_LIST },
+ { "root-path", 17, OT_NAME },
+ { "extension-path", 18, OT_NAME },
+ { "ip-forward-enable", 19, 1 },
+ { "non-local-source-routing", 20, 1 },
+ { "policy-filter", 21, OT_ADDR_LIST },
+ { "max-datagram-reassembly", 22, 2 | OT_DEC },
+ { "default-ttl", 23, 1 | OT_DEC },
+ { "mtu", 26, 2 | OT_DEC },
+ { "all-subnets-local", 27, 1 },
+ { "broadcast", 28, OT_INTERNAL | OT_ADDR_LIST },
+ { "router-discovery", 31, 1 },
+ { "router-solicitation", 32, OT_ADDR_LIST },
+ { "static-route", 33, OT_ADDR_LIST },
+ { "trailer-encapsulation", 34, 1 },
+ { "arp-timeout", 35, 4 | OT_DEC },
+ { "ethernet-encap", 36, 1 },
+ { "tcp-ttl", 37, 1 },
+ { "tcp-keepalive", 38, 4 | OT_DEC },
+ { "nis-domain", 40, OT_NAME },
+ { "nis-server", 41, OT_ADDR_LIST },
+ { "ntp-server", 42, OT_ADDR_LIST },
+ { "vendor-encap", 43, OT_INTERNAL },
+ { "netbios-ns", 44, OT_ADDR_LIST },
+ { "netbios-dd", 45, OT_ADDR_LIST },
+ { "netbios-nodetype", 46, 1 },
+ { "netbios-scope", 47, 0 },
+ { "x-windows-fs", 48, OT_ADDR_LIST },
+ { "x-windows-dm", 49, OT_ADDR_LIST },
+ { "requested-address", 50, OT_INTERNAL | OT_ADDR_LIST },
+ { "lease-time", 51, OT_INTERNAL | OT_TIME },
+ { "option-overload", 52, OT_INTERNAL },
+ { "message-type", 53, OT_INTERNAL | OT_DEC },
+ { "server-identifier", 54, OT_INTERNAL | OT_ADDR_LIST },
+ { "parameter-request", 55, OT_INTERNAL },
+ { "message", 56, OT_INTERNAL },
+ { "max-message-size", 57, OT_INTERNAL },
+ { "T1", 58, OT_TIME},
+ { "T2", 59, OT_TIME},
+ { "vendor-class", 60, OT_NAME },
+ { "client-id", 61, OT_INTERNAL },
+ { "nis+-domain", 64, OT_NAME },
+ { "nis+-server", 65, OT_ADDR_LIST },
+ { "tftp-server", 66, OT_NAME },
+ { "bootfile-name", 67, OT_NAME },
+ { "mobile-ip-home", 68, OT_ADDR_LIST },
+ { "smtp-server", 69, OT_ADDR_LIST },
+ { "pop3-server", 70, OT_ADDR_LIST },
+ { "nntp-server", 71, OT_ADDR_LIST },
+ { "irc-server", 74, OT_ADDR_LIST },
+ { "user-class", 77, 0 },
+ { "rapid-commit", 80, 0 },
+ { "FQDN", 81, OT_INTERNAL },
+ { "agent-info", 82, OT_INTERNAL },
+ { "last-transaction", 91, 4 | OT_TIME },
+ { "associated-ip", 92, OT_ADDR_LIST },
+ { "client-arch", 93, 2 | OT_DEC },
+ { "client-interface-id", 94, 0 },
+ { "client-machine-id", 97, 0 },
+ { "posix-timezone", 100, OT_NAME }, /* RFC 4833, Sec. 2 */
+ { "tzdb-timezone", 101, OT_NAME }, /* RFC 4833, Sec. 2 */
+ { "ipv6-only", 108, 4 | OT_DEC }, /* RFC 8925 */
+ { "subnet-select", 118, OT_INTERNAL },
+ { "domain-search", 119, OT_RFC1035_NAME },
+ { "sip-server", 120, 0 },
+ { "classless-static-route", 121, 0 },
+ { "vendor-id-encap", 125, 0 },
+ { "tftp-server-address", 150, OT_ADDR_LIST },
+ { "server-ip-address", 255, OT_ADDR_LIST }, /* special, internal only, sets siaddr */
+ { NULL, 0, 0 }
+};
+
+static void prettyprint_time(char *buf, unsigned int t);
+static char *print_mac(char *buff, unsigned char *mac, int len);
+
+int lookup_dhcp_opt(char *name)
+{
+ const struct opttab_t *t = opttab;
+ int i;
+
+ for (i = 0; t[i].name; i++)
+ if (strcasecmp(t[i].name, name) == 0)
+ return t[i].val;
+
+ return -1;
+}
+
+char *option_string(unsigned int opt, unsigned char *val, int opt_len, char *buf, int buf_len)
+{
+ int o, i, j, nodecode = 0;
+ const struct opttab_t *ot = opttab;
+ char addrbuff[ADDRSTRLEN];
+
+ for (o = 0; ot[o].name; o++)
+ if (ot[o].val == opt)
+ {
+ if (buf)
+ {
+ memset(buf, 0, buf_len);
+
+ if (ot[o].size & OT_ADDR_LIST)
+ {
+ union all_addr addr;
+ int addr_len = INADDRSZ;
+
+ for (buf[0]= 0, i = 0; i <= opt_len - addr_len; i += addr_len)
+ {
+ if (i != 0)
+ strncat(buf, ", ", buf_len - strlen(buf));
+ /* align */
+ memcpy(&addr, &val[i], addr_len);
+ inet_ntop(AF_INET, &val[i], addrbuff, ADDRSTRLEN);
+ strncat(buf, addrbuff, buf_len - strlen(buf));
+ }
+ }
+ else if (ot[o].size & OT_NAME)
+ for (i = 0, j = 0; i < opt_len && j < buf_len ; i++)
+ {
+ char c = val[i];
+ if (isprint((unsigned char)c))
+ buf[j++] = c;
+ }
+ else if ((ot[o].size & (OT_DEC | OT_TIME)) && opt_len != 0)
+ {
+ unsigned int dec = 0;
+
+ for (i = 0; i < opt_len; i++)
+ dec = (dec << 8) | val[i];
+
+ if (ot[o].size & OT_TIME)
+ prettyprint_time(buf, dec);
+ else
+ sprintf(buf, "%u", dec);
+ }
+ else
+ nodecode = 1;
+ }
+ break;
+ }
+
+ if (opt_len != 0 && buf && (!ot[o].name || nodecode))
+ {
+ int trunc = 0;
+ if (opt_len > 14)
+ {
+ trunc = 1;
+ opt_len = 14;
+ }
+ print_mac(buf, val, opt_len);
+ if (trunc)
+ strncat(buf, "...", buf_len - strlen(buf));
+
+
+ }
+
+ return ot[o].name ? ot[o].name : "";
+
+}
+
+static void prettyprint_time(char *buf, unsigned int t)
+{
+ if (t == 0xffffffff)
+ sprintf(buf, "infinite");
+ else
+ {
+ unsigned int x, p = 0;
+ if ((x = t/86400))
+ p += sprintf(&buf[p], "%ud", x);
+ if ((x = (t/3600)%24))
+ p += sprintf(&buf[p], "%uh", x);
+ if ((x = (t/60)%60))
+ p += sprintf(&buf[p], "%um", x);
+ if ((x = t%60))
+ sprintf(&buf[p], "%us", x);
+ }
+}
+
+static char *print_mac(char *buff, unsigned char *mac, int len)
+{
+ char *p = buff;
+ int i;
+
+ if (len == 0)
+ sprintf(p, "");
+ else
+ for (i = 0; i < len; i++)
+ p += sprintf(p, "%.2x%s", mac[i], (i == len - 1) ? "" : ":");
+
+ return buff;
+}
+
+static unsigned char *dhcp_skip_opts(struct dhcp_packet_with_opts *pktp)
+{
+ unsigned char *start = (unsigned char *)(((unsigned int *)&pktp->options[0]) + 1);
+ while (*start != 0)
+ start += start[1] + 2;
+ return start;
+}
+
+static unsigned char *dhcp_find_opt(struct dhcp_packet_with_opts *pktp, int optno)
+{
+ unsigned char *start = (unsigned char *)(((unsigned int *)&pktp->options[0]) + 1);
+ while (*start != OPTION_END)
+ {
+ if (*start == optno)
+ return start;
+ start += start[1] + 2;
+ }
+
+ return NULL;
+}
+
+static void option_put(struct dhcp_packet_with_opts *pktp, int opt, int len, unsigned int val)
+{
+ int i;
+ unsigned char *p = dhcp_skip_opts(pktp);
+
+ if (p)
+ {
+ *p++ = opt;
+ *p++ = len;
+ for (i = 0; i < len; i++)
+ *(p++) = val >> (8 * (len - (i + 1)));
+ }
+}
+
+static void option_put_string(struct dhcp_packet_with_opts *pktp, int opt,
+ const char *string, int null_term)
+{
+ unsigned char *p;
+ size_t len = strlen(string);
+
+ if (null_term && len != 255)
+ len++;
+
+ if ((p = dhcp_skip_opts(pktp)))
+ {
+ *p++ = opt;
+ *p++ = len;
+ memcpy(p, string, len);
+ }
+}
+
+
+/* in may equal out, when maxlen may be -1 (No max len).
+ Return -1 for extraneous no-hex chars found. */
+int parse_hex(char *in, unsigned char *out, int maxlen,
+ unsigned int *wildcard_mask, int *mac_type)
+{
+ int done = 0, mask = 0, i = 0;
+ char *r;
+
+ if (mac_type)
+ *mac_type = 0;
+
+ while (!done && (maxlen == -1 || i < maxlen))
+ {
+ for (r = in; *r != 0 && *r != ':' && *r != '-' && *r != ' '; r++)
+ if (*r != '*' && !isxdigit((unsigned char)*r))
+ return -1;
+
+ if (*r == 0)
+ done = 1;
+
+ if (r != in )
+ {
+ if (*r == '-' && i == 0 && mac_type)
+ {
+ *r = 0;
+ *mac_type = strtol(in, NULL, 16);
+ mac_type = NULL;
+ }
+ else
+ {
+ *r = 0;
+ if (strcmp(in, "*") == 0)
+ {
+ mask = (mask << 1) | 1;
+ i++;
+ }
+ else
+ {
+ int j, bytes = (1 + (r - in))/2;
+ for (j = 0; j < bytes; j++)
+ {
+ char sav;
+ if (j < bytes - 1)
+ {
+ sav = in[(j+1)*2];
+ in[(j+1)*2] = 0;
+ }
+ /* checks above allow mix of hexdigit and *, which
+ is illegal. */
+ if (strchr(&in[j*2], '*'))
+ return -1;
+ out[i] = strtol(&in[j*2], NULL, 16);
+ mask = mask << 1;
+ if (++i == maxlen)
+ break;
+ if (j < bytes - 1)
+ in[(j+1)*2] = sav;
+ }
+ }
+ }
+ }
+ in = r+1;
+ }
+
+ if (wildcard_mask)
+ *wildcard_mask = mask;
+
+ return i;
+}
+
+static char *split_chr(char *s, char c)
+{
+ char *comma, *p;
+
+ if (!s || !(comma = strchr(s, c)))
+ return NULL;
+
+ p = comma;
+ *comma = ' ';
+
+ for (; *comma == ' '; comma++);
+
+ for (; (p >= s) && *p == ' '; p--)
+ *p = 0;
+
+ return comma;
+}
+
+static char *split(char *s)
+{
+ return split_chr(s, ',');
+}
+
+
+int main(int argc, char **argv)
+{
+ int fd;
+ struct ifreq ifr;
+ struct in_addr iface_addr, server_addr, lease_addr;
+ struct dhcp_packet_with_opts pkt;
+ struct sockaddr_in saddr;
+ unsigned char *p;
+ ssize_t sz;
+ unsigned char mac[DHCP_CHADDR_MAX], clid[256], req_options[256];
+ char buff[500];
+ int clid_len = 0, mac_len = 0, mac_type = 0, opts_len = 0;
+ unsigned short port = DHCP_SERVER_PORT;
+ unsigned int xid;
+ struct timeval tv;
+
+ gettimeofday(&tv, NULL);
+ srand(tv.tv_usec);
+ xid = rand();
+ server_addr.s_addr = lease_addr.s_addr = iface_addr.s_addr = 0;
+
+ if ((fd = socket(PF_INET, SOCK_DGRAM, 0)) == -1)
+ {
+ perror("leasequery: cannot create socket");
+ exit(1);
+ }
+
+ while (1)
+ {
+ int option = getopt(argc, argv, "i:s:l:m:c:p:r:");
+
+ if (option == -1)
+ break;
+
+ switch (option)
+ {
+ default:
+ fprintf(stderr,
+ "-p port on DHCP server.\n"
+ "-i exit interface to DHCP server.\n"
+ "-s DHCP server address.\n"
+ "-l Query lease by IP address.\n"
+ "-m Query lease by MAC address.\n"
+ "-c Query lease by client-id.\n"
+ "-r |[,...] List of options to return.\n");
+ exit(1);
+
+ case 'p':
+ port = atoi(optarg);
+ break;
+
+ case 'i':
+ strncpy(ifr.ifr_name, optarg, IF_NAMESIZE);
+ ifr.ifr_addr.sa_family = AF_INET;
+ if (ioctl(fd, SIOCGIFADDR, &ifr) == -1)
+ {
+ perror("leasequery: cannot get interface address");
+ exit(1);
+ }
+
+ iface_addr = ((struct sockaddr_in *) &ifr.ifr_addr)->sin_addr;
+ break;
+
+ case 's':
+ if (inet_pton(AF_INET, optarg, &server_addr) <= 0)
+ {
+ fprintf(stderr, "leasequery: bad server address\n");
+ exit(1);
+ }
+ break;
+
+ case 'l':
+ if (inet_pton(AF_INET, optarg, &lease_addr) <= 0)
+ {
+ fprintf(stderr, "leasequery: bad lease address\n");
+ exit(1);
+ }
+ break;
+
+ case 'm':
+ mac_type = 1; /* default ethernet */
+ mac_len = parse_hex(optarg, mac, DHCP_CHADDR_MAX, NULL, &mac_type);
+ if (!mac_type)
+ mac_type = 1; /* default ethernet */
+ break;
+
+ case 'c':
+ clid_len = parse_hex(optarg, clid, 256, NULL, NULL);
+ break;
+
+ case 'r':
+ {
+ char *comma;
+ int opt;
+
+ while (optarg)
+ {
+ comma = split(optarg);
+
+ if ((opt = lookup_dhcp_opt(optarg)) != -1 || (opt = atoi(optarg)) != 0)
+ req_options[opts_len++] = opt;
+
+ optarg = comma;
+ }
+
+ break;
+ }
+ }
+ }
+
+ if (!server_addr.s_addr)
+ {
+ fprintf(stderr, "leasequery: no server address\n");
+ exit(1);
+ }
+
+ memset(&pkt, 0, sizeof pkt);
+ pkt.header.op = BOOTREQUEST;
+ pkt.header.xid = xid;
+ pkt.header.ciaddr = lease_addr;
+ pkt.header.hlen = mac_len;
+ pkt.header.htype = mac_type;
+ memcpy(pkt.header.chaddr, mac, mac_len);
+ *((unsigned int *)&pkt.options[0]) = htonl(DHCP_COOKIE);
+
+ /* Dnsmasq extension. */
+ pkt.header.giaddr.s_addr = iface_addr.s_addr ? iface_addr.s_addr : INADDR_BROADCAST;
+
+ option_put(&pkt, OPTION_MESSAGE_TYPE, 1, DHCPLEASEQUERY);
+
+ if (clid_len != 0)
+ {
+ p = dhcp_skip_opts(&pkt);
+ *p++ = OPTION_CLIENT_ID;
+ *p++ = clid_len;
+ memcpy(p, clid, clid_len);
+ }
+
+ if (opts_len != 0)
+ {
+ p = dhcp_skip_opts(&pkt);
+ *p++ = OPTION_REQUESTED_OPTIONS;
+ *p++ = opts_len;
+ memcpy(p, req_options, opts_len);
+ }
+
+ if (iface_addr.s_addr)
+ {
+ saddr.sin_family = AF_INET;
+ saddr.sin_port = htons(port);
+ saddr.sin_addr.s_addr = iface_addr.s_addr;
+
+ if (bind(fd, (struct sockaddr *)&saddr, sizeof(struct sockaddr_in)))
+ {
+ perror("leasequery: cannot bind DHCP server socket");
+ exit(1);
+ }
+ }
+
+ saddr.sin_family = AF_INET;
+ saddr.sin_port = htons(port);
+ saddr.sin_addr = server_addr;
+
+ while((sz = sendto(fd, &pkt, dhcp_skip_opts(&pkt) - ((unsigned char *)&pkt), 0, (struct sockaddr *)&saddr, sizeof(saddr))) == -1 &&
+ errno == EINTR);
+
+ if (sz == -1)
+ {
+ perror("leasequery: sendto()");
+ exit(1);
+ }
+
+ sz = recv(fd, &pkt, sizeof(pkt), 0);
+
+ if (sz == -1)
+ {
+ perror("leasequery: recv()");
+ exit(1);
+ }
+
+ if (sz >= sizeof(pkt.header) &&
+ pkt.header.op == BOOTREPLY &&
+ (p = dhcp_find_opt(&pkt, OPTION_MESSAGE_TYPE)) &&
+ pkt.header.xid == xid)
+ {
+ if (p[2] == DHCPLEASEUNASSIGNED)
+ printf("UNASSIGNED\n");
+ else if (p[2] == DHCPLEASEUNKNOWN)
+ printf("UNKNOWN\n");
+ else if (p[2] == DHCPLEASEACTIVE)
+ {
+ print_mac(buff, pkt.header.chaddr, pkt.header.hlen);
+ printf("ACTIVE %s %s\n", inet_ntoa(pkt.header.ciaddr), buff);
+
+ p = (unsigned char *)(((unsigned int *)&pkt.options[0]) + 1);
+
+ while (*p != OPTION_END)
+ {
+ char *optname = option_string(p[0], option_ptr(p, 0), option_len(p), buff, 500);
+
+ printf("size:%3d option:%3d %s %s\n", option_len(p), p[0], optname, buff);
+ p += p[1] + 2;
+ }
+ }
+ }
+}
diff --git a/man/dnsmasq.8 b/man/dnsmasq.8
index 1eba6fa..b2c3d9b 100644
--- a/man/dnsmasq.8
+++ b/man/dnsmasq.8
@@ -1438,6 +1438,16 @@ DHCP options. This make extra space available in the DHCP packet for
options but can, rarely, confuse old or broken clients. This flag
forces "simple and safe" behaviour to avoid problems in such a case.
.TP
+.B --leasequery
+Enable RFC 4388 leasequery. The dnsmasq DHCP server will answer LEASEQUERY messages from DHCP relays
+when this option is given. To correctly answer lease queries it is necessary to store extra data in
+the lease database, and this is also enabled by the \fB--leasequery\fP option. The extra fields
+(agent-info and vendorclass) are stored in the leases file in a somewhat backwards compatible manner.
+Enabling and then disabling leasequery will not cause problems; the extra information will be aged
+out of the database. However, enabling leasequery in release 2.92 or later, storing leases
+which come via DHCP relays which add option-82 agent-info data, and then moving back to a pre-2.92 dnsmasq
+binary may cause problems reading the leases file. As of release 2.92, leasequery is only supported for DHCPv4.
+.TP
.B --dhcp-relay=[,[#]][,dhcp_packet.iov_base;
loopback = !mess->giaddr.s_addr && (ifr.ifr_flags & IFF_LOOPBACK);
-
+
+ /* Non-standard extension:
+ If giaddr == 255.255.255.255 we reply to the source
+ address in the request packet header. This makes
+ stand-alone leasequery clients easier, as they
+ can leave source address determination to the kernel. */
+ if (mess->giaddr.s_addr == INADDR_BROADCAST)
+ {
+ mess->giaddr.s_addr = 0;
+ is_relay_use_source = 1;
+ }
+
#ifdef HAVE_LINUX_NETWORK
/* ARP fiddling uses original interface even if we pretend to use a different one. */
safe_strncpy(arp_req.arp_dev, ifr.ifr_name, sizeof(arp_req.arp_dev));
@@ -337,8 +348,8 @@ void dhcp_packet(time_t now, int pxe_fd)
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, loopback, &is_inform, pxe_fd, iface_addr, recvtime);
+ iov.iov_len = dhcp_reply(parm.current, ifr.ifr_name, iface_index, (size_t)sz, now, unicast_dest,
+ loopback, &is_inform, pxe_fd, iface_addr, recvtime, is_relay_use_source);
lease_update_file(now);
lease_update_dns(0);
@@ -365,11 +376,16 @@ void dhcp_packet(time_t now, int pxe_fd)
if (mess->ciaddr.s_addr != 0)
dest.sin_addr = mess->ciaddr;
}
- else if (mess->giaddr.s_addr && !is_relay_reply)
+ if ((is_relay_use_source || mess->giaddr.s_addr) && !is_relay_reply)
{
- /* Send to BOOTP relay */
- dest.sin_port = htons(daemon->dhcp_server_port);
- dest.sin_addr = mess->giaddr;
+ /* Send to BOOTP relay. */
+ if (is_relay_use_source)
+ mess->giaddr.s_addr = INADDR_BROADCAST;
+ else
+ {
+ dest.sin_addr = mess->giaddr;
+ dest.sin_port = htons(daemon->dhcp_server_port);
+ }
}
else if (mess->ciaddr.s_addr)
{
diff --git a/src/dnsmasq.h b/src/dnsmasq.h
index 5171d8b..dbb9fbb 100644
--- a/src/dnsmasq.h
+++ b/src/dnsmasq.h
@@ -282,7 +282,8 @@ struct event_desc {
#define OPT_NO_0x20 74
#define OPT_DO_0x20 75
#define OPT_AUTH_LOG 76
-#define OPT_LAST 77
+#define OPT_LEASEQUERY 77
+#define OPT_LAST 78
#define OPTION_BITS (sizeof(unsigned int)*8)
#define OPTION_SIZE ( (OPT_LAST/OPTION_BITS)+((OPT_LAST%OPTION_BITS)!=0) )
@@ -853,6 +854,7 @@ struct dhcp_lease {
char *old_hostname; /* hostname before it moved to another lease */
int flags;
time_t expires; /* lease expiry */
+ time_t last_transaction;
#ifdef HAVE_BROKEN_RTC
unsigned int length;
#endif
@@ -864,6 +866,8 @@ struct dhcp_lease {
int last_interface;
int new_interface; /* save possible originated interface */
int new_prefixlen; /* and its prefix length */
+ unsigned char *agent_id, *vendorclass;
+ int agent_id_len, vendorclass_len;
#ifdef HAVE_DHCP6
struct in6_addr addr6;
unsigned int iaid;
@@ -1619,6 +1623,7 @@ void lease6_reset(void);
struct dhcp_lease *lease6_find_by_client(struct dhcp_lease *first, int lease_type,
unsigned char *clid, int clid_len, unsigned int iaid);
struct dhcp_lease *lease6_find_by_addr(struct in6_addr *net, int prefix, u64 addr);
+struct dhcp_lease *lease6_find_by_plain_addr(struct in6_addr *addr);
u64 lease_find_max_addr6(struct dhcp_context *context);
void lease_ping_reply(struct in6_addr *sender, unsigned char *packet, char *interface);
void lease_update_slaac(time_t now);
@@ -1631,6 +1636,8 @@ void lease_set_hwaddr(struct dhcp_lease *lease, const unsigned char *hwaddr,
void lease_set_hostname(struct dhcp_lease *lease, const char *name, int auth, char *domain, char *config_domain);
void lease_set_expires(struct dhcp_lease *lease, unsigned int len, time_t now);
void lease_set_interface(struct dhcp_lease *lease, int interface, time_t now);
+void lease_set_agent_id(struct dhcp_lease *lease, unsigned char *new, int len);
+void lease_set_vendorclass(struct dhcp_lease *lease, unsigned char *new, int len);
struct dhcp_lease *lease_find_by_client(unsigned char *hwaddr, int hw_len, int hw_type,
unsigned char *clid, int clid_len);
struct dhcp_lease *lease_find_by_addr(struct in_addr addr);
@@ -1651,7 +1658,8 @@ void lease_add_extradata(struct dhcp_lease *lease, unsigned char *data,
#ifdef HAVE_DHCP
size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
size_t sz, time_t now, int unicast_dest, int loopback,
- int *is_inform, int pxe, struct in_addr fallback, time_t recvtime);
+ int *is_inform, int pxe, struct in_addr fallback,
+ time_t recvtime, int is_relay_use_source);
unsigned char *extended_hwaddr(int hwtype, int hwlen, unsigned char *hwaddr,
int clid_len, unsigned char *clid, int *len_out);
#endif
diff --git a/src/lease.c b/src/lease.c
index 7a7179f..00748ad 100644
--- a/src/lease.c
+++ b/src/lease.c
@@ -25,7 +25,7 @@ static int read_leases(time_t now, FILE *leasestream)
unsigned long ei;
union all_addr addr;
struct dhcp_lease *lease;
- int clid_len, hw_len, hw_type;
+ int opt_len, clid_len, hw_len, hw_type;
int items;
*daemon->dhcp_buff3 = *daemon->dhcp_buff2 = '\0';
@@ -55,6 +55,34 @@ static int read_leases(time_t now, FILE *leasestream)
continue;
}
#endif
+
+ /* Weird backwards compatible way of adding extra fields to leases */
+ if ((strcmp(daemon->dhcp_buff3, "vendorclass") == 0 || strcmp(daemon->dhcp_buff3, "agent-info") == 0))
+ {
+ if (fscanf(leasestream, " %764s", daemon->packet) == 1)
+ {
+ if (inet_pton(AF_INET, daemon->dhcp_buff2, &addr.addr4))
+ lease = lease_find_by_addr(addr.addr4);
+#ifdef HAVE_DHCP6
+ else if (inet_pton(AF_INET6, daemon->dhcp_buff2, &addr.addr6))
+ lease = lease6_find_by_plain_addr(&addr.addr6);
+#endif
+ else
+ continue;
+
+ if (lease)
+ {
+ opt_len = parse_hex(daemon->packet, (unsigned char *)daemon->packet, 255, NULL, NULL);
+
+ if (strcmp(daemon->dhcp_buff3, "vendorclass") == 0)
+ lease_set_vendorclass(lease, (unsigned char *)daemon->packet, opt_len);
+ else if (strcmp(daemon->dhcp_buff3, "agent-info") == 0)
+ lease_set_agent_id(lease, (unsigned char *)daemon->packet, opt_len);
+ }
+ }
+
+ continue;
+ }
if (fscanf(leasestream, " %64s %255s %764s",
daemon->namebuff, daemon->dhcp_buff, daemon->packet) != 3)
@@ -251,8 +279,8 @@ void lease_update_file(time_t now)
{
struct dhcp_lease *lease;
time_t next_event;
- int i, err = 0;
-
+ int i, err = 0, extras;
+
if (file_dirty != 0 && daemon->lease_stream)
{
errno = 0;
@@ -260,9 +288,11 @@ void lease_update_file(time_t now)
if (errno != 0 || ftruncate(fileno(daemon->lease_stream), 0) != 0)
err = errno;
- for (lease = leases; lease; lease = lease->next)
+ for (extras = 0, lease = leases; lease; lease = lease->next)
{
-
+ if (lease->agent_id || lease->vendorclass)
+ extras = 1;
+
#ifdef HAVE_DHCP6
if (lease->flags & (LEASE_TA | LEASE_NA))
continue;
@@ -335,7 +365,37 @@ void lease_update_file(time_t now)
}
}
#endif
-
+
+ if (extras)
+ {
+ /* Dump this at the end for least confusion with older parsing code. */
+ for (lease = leases; lease; lease = lease->next)
+ {
+#ifdef HAVE_DHCP6
+ if (lease->flags & (LEASE_TA | LEASE_NA))
+ inet_ntop(AF_INET6, &lease->addr6, daemon->addrbuff, ADDRSTRLEN);
+ else
+#endif
+ inet_ntop(AF_INET, &lease->addr, daemon->addrbuff, ADDRSTRLEN);
+
+ if (lease->agent_id)
+ {
+ ourprintf(&err, "agent-info %s ", daemon->addrbuff);
+ for (i = 0; i < lease->agent_id_len - 1; i++)
+ ourprintf(&err, "%.2x:", lease->agent_id[i]);
+ ourprintf(&err, "%.2x\n", lease->agent_id[i]);
+ }
+
+ if (lease->vendorclass)
+ {
+ ourprintf(&err, "vendorclass %s ", daemon->addrbuff);
+ for (i = 0; i < lease->vendorclass_len - 1; i++)
+ ourprintf(&err, "%.2x:", lease->vendorclass[i]);
+ ourprintf(&err, "%.2x\n", lease->vendorclass[i]);
+ }
+ }
+ }
+
if (fflush(daemon->lease_stream) != 0 ||
fsync(fileno(daemon->lease_stream)) < 0)
err = errno;
@@ -713,6 +773,22 @@ struct dhcp_lease *lease6_find_by_addr(struct in6_addr *net, int prefix, u64 add
return NULL;
}
+struct dhcp_lease *lease6_find_by_plain_addr(struct in6_addr *addr)
+{
+ struct dhcp_lease *lease;
+
+ for (lease = leases; lease; lease = lease->next)
+ {
+ if (!(lease->flags & (LEASE_TA | LEASE_NA)))
+ continue;
+
+ if (IN6_ARE_ADDR_EQUAL(&lease->addr6, addr))
+ return lease;
+ }
+
+ return NULL;
+}
+
/* Find largest assigned address in context */
u64 lease_find_max_addr6(struct dhcp_context *context)
{
@@ -1084,6 +1160,46 @@ void lease_set_interface(struct dhcp_lease *lease, int interface, time_t now)
#endif
}
+void lease_set_agent_id(struct dhcp_lease *lease, unsigned char *new, int len)
+{
+
+ if (!lease->agent_id && !new)
+ return;
+
+ if (lease->agent_id && new && lease->agent_id_len == len && memcmp(lease->agent_id, new, len) == 0)
+ return;
+
+ file_dirty = 1;
+ free(lease->agent_id);
+ lease->agent_id = NULL;
+
+ if (new && (lease->agent_id = whine_malloc(len)))
+ {
+ memcpy(lease->agent_id, new, len);
+ lease->agent_id_len = len;
+ }
+}
+
+void lease_set_vendorclass(struct dhcp_lease *lease, unsigned char *new, int len)
+{
+ if (!lease->vendorclass && !new)
+ return;
+
+ if (lease->vendorclass && new && lease->vendorclass_len == len && memcmp(lease->vendorclass, new, len) == 0)
+ return;
+
+ file_dirty = 1;
+ free(lease->vendorclass);
+ lease->vendorclass = NULL;
+
+ if (new && (lease->vendorclass = whine_malloc(len)))
+ {
+ memcpy(lease->vendorclass, new, len);
+ lease->vendorclass_len = len;
+ }
+}
+
+
void rerun_scripts(void)
{
struct dhcp_lease *lease;
@@ -1143,9 +1259,11 @@ int do_script_run(time_t now)
#endif
old_leases = lease->next;
- free(lease->old_hostname);
+ free(lease->hostname);
free(lease->clid);
free(lease->extradata);
+ free(lease->agent_id);
+ free(lease->vendorclass);
free(lease);
return 1;
diff --git a/src/metrics.c b/src/metrics.c
index ccc1d56..864b34c 100644
--- a/src/metrics.c
+++ b/src/metrics.c
@@ -43,6 +43,10 @@ const char * metric_names[] = {
"leases_allocated_6",
"leases_pruned_6",
"tcp_connections",
+ "dhcp_leasequery",
+ "dhcp_lease_unassigned",
+ "dhcp_lease_actve",
+ "dhcp_lease_unknown"
};
const char* get_metric_name(int i) {
diff --git a/src/metrics.h b/src/metrics.h
index f84581e..79017e2 100644
--- a/src/metrics.h
+++ b/src/metrics.h
@@ -42,6 +42,10 @@ enum {
METRIC_LEASES_ALLOCATED_6,
METRIC_LEASES_PRUNED_6,
METRIC_TCP_CONNECTIONS,
+ METRIC_DHCPLEASEQUERY,
+ METRIC_DHCPLEASEUNASSIGNED,
+ METRIC_DHCPLEASEACTIVE,
+ METRIC_DHCPLEASEUNKNOWN,
__METRIC_MAX,
};
diff --git a/src/option.c b/src/option.c
index 7d8d726..a021a2c 100644
--- a/src/option.c
+++ b/src/option.c
@@ -195,6 +195,7 @@ struct myoption {
#define LOPT_PXE_OPT 386
#define LOPT_NO_ENCODE 387
#define LOPT_DO_ENCODE 388
+#define LOPT_LEASEQUERY 389
#ifdef HAVE_GETOPT_LONG
static const struct option opts[] =
@@ -394,6 +395,7 @@ static const struct myoption opts[] =
{ "use-stale-cache", 2, 0 , LOPT_STALE_CACHE },
{ "no-ident", 0, 0, LOPT_NO_IDENT },
{ "max-tcp-connections", 1, 0, LOPT_MAX_PROCS },
+ { "leasequery", 0, 0, LOPT_LEASEQUERY },
{ NULL, 0, 0, 0 }
};
@@ -499,6 +501,7 @@ static struct {
{ '4', ARG_DUP, "set:,", gettext_noop("Map MAC address (with wildcards) to option set."), NULL },
{ LOPT_BRIDGE, ARG_DUP, ",..", gettext_noop("Treat DHCP requests on aliases as arriving from interface."), NULL },
{ LOPT_SHARED_NET, ARG_DUP, "|,", gettext_noop("Specify extra networks sharing a broadcast domain for DHCP"), NULL},
+ { LOPT_LEASEQUERY, OPT_LEASEQUERY, NULL, gettext_noop("Enable RFC 4388 leasequery functions for DHCPv4"), NULL},
{ '5', OPT_NO_PING, NULL, gettext_noop("Disable ICMP echo address checking in the DHCP server."), NULL },
{ '6', ARG_ONE, "", gettext_noop("Shell script to run on DHCP lease creation and destruction."), NULL },
{ LOPT_LUASCRIPT, ARG_DUP, "path", gettext_noop("Lua script to run on DHCP lease creation and destruction."), NULL },
diff --git a/src/rfc2131.c b/src/rfc2131.c
index 5c5c90d..9f5adf8 100644
--- a/src/rfc2131.c
+++ b/src/rfc2131.c
@@ -56,7 +56,8 @@ static void do_options(struct dhcp_context *context,
time_t now,
unsigned int lease_time,
unsigned short fuzz,
- const char *pxevendor);
+ const char *pxevendor,
+ int leasequery);
static void match_vendor_opts(unsigned char *opt, struct dhcp_opt *dopt);
@@ -73,7 +74,7 @@ static void handle_encap(struct dhcp_packet *mess, unsigned char *end, unsigned
size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
size_t sz, time_t now, int unicast_dest, int loopback,
- int *is_inform, int pxe, struct in_addr fallback, time_t recvtime)
+ int *is_inform, int pxe, struct in_addr fallback, time_t recvtime, int is_relay_use_source)
{
unsigned char *opt, *clid = NULL;
struct dhcp_lease *ltmp, *lease = NULL;
@@ -185,7 +186,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
}
}
- if ((opt = option_find(mess, sz, OPTION_AGENT_ID, 1)))
+ if (mess_type != DHCPLEASEQUERY && (opt = option_find(mess, sz, OPTION_AGENT_ID, 1)))
{
/* Any agent-id needs to be copied back out, verbatim, as the last option
in the packet. Here, we shift it to the very end of the buffer, if it doesn't
@@ -211,7 +212,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
if ((sopt = option_find1(option_ptr(opt, 0), option_ptr(opt, option_len(opt)), SUBOPT_SERVER_OR, INADDRSZ)))
override = option_addr(sopt);
- /* if a circuit-id or remote-is option is provided, exact-match to options. */
+ /* if a circuit-id or remote-is option is provided, exact-match to options. */
for (vendor = daemon->dhcp_vendors; vendor; vendor = vendor->next)
{
int search;
@@ -224,7 +225,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
search = SUBOPT_SUBSCR_ID;
else
continue;
-
+
if ((sopt = option_find1(option_ptr(opt, 0), option_ptr(opt, option_len(opt)), search, 1)) &&
vendor->len == option_len(sopt) &&
memcmp(option_ptr(sopt, 0), vendor->data, vendor->len) == 0)
@@ -234,7 +235,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
}
}
}
-
+
/* Check for RFC3011 subnet selector - only if RFC3527 one not present */
if (subnet_addr.s_addr == 0 && (opt = option_find(mess, sz, OPTION_SUBNET_SELECT, INADDRSZ)))
subnet_addr = option_addr(opt);
@@ -373,44 +374,47 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
context = context_new;
}
- if (!context)
+ if (mess_type != DHCPLEASEQUERY)
{
- const char *via;
- if (subnet_addr.s_addr)
+ if (!context)
{
- via = _("with subnet selector");
- inet_ntop(AF_INET, &subnet_addr, daemon->addrbuff, ADDRSTRLEN);
- }
- else
- {
- via = _("via");
- if (mess->giaddr.s_addr)
- inet_ntop(AF_INET, &mess->giaddr, daemon->addrbuff, ADDRSTRLEN);
- else
- safe_strncpy(daemon->addrbuff, iface_name, ADDRSTRLEN);
- }
- my_syslog(MS_DHCP | LOG_WARNING, _("no address range available for DHCP request %s %s"),
- via, daemon->addrbuff);
- return 0;
- }
-
- if (option_bool(OPT_LOG_OPTS))
- {
- struct dhcp_context *context_tmp;
- for (context_tmp = context; context_tmp; context_tmp = context_tmp->current)
- {
- inet_ntop(AF_INET, &context_tmp->start, daemon->namebuff, MAXDNAME);
- if (context_tmp->flags & (CONTEXT_STATIC | CONTEXT_PROXY))
+ const char *via;
+ if (subnet_addr.s_addr)
{
- inet_ntop(AF_INET, &context_tmp->netmask, daemon->addrbuff, ADDRSTRLEN);
- my_syslog(MS_DHCP | LOG_INFO, _("%u available DHCP subnet: %s/%s"),
- ntohl(mess->xid), daemon->namebuff, daemon->addrbuff);
+ via = _("with subnet selector");
+ inet_ntop(AF_INET, &subnet_addr, daemon->addrbuff, ADDRSTRLEN);
}
else
{
- inet_ntop(AF_INET, &context_tmp->end, daemon->addrbuff, ADDRSTRLEN);
- my_syslog(MS_DHCP | LOG_INFO, _("%u available DHCP range: %s -- %s"),
- ntohl(mess->xid), daemon->namebuff, daemon->addrbuff);
+ via = _("via");
+ if (mess->giaddr.s_addr)
+ inet_ntop(AF_INET, &mess->giaddr, daemon->addrbuff, ADDRSTRLEN);
+ else
+ safe_strncpy(daemon->addrbuff, iface_name, ADDRSTRLEN);
+ }
+ my_syslog(MS_DHCP | LOG_WARNING, _("no address range available for DHCP request %s %s"),
+ via, daemon->addrbuff);
+ return 0;
+ }
+
+ if (option_bool(OPT_LOG_OPTS))
+ {
+ struct dhcp_context *context_tmp;
+ for (context_tmp = context; context_tmp; context_tmp = context_tmp->current)
+ {
+ inet_ntop(AF_INET, &context_tmp->start, daemon->namebuff, MAXDNAME);
+ if (context_tmp->flags & (CONTEXT_STATIC | CONTEXT_PROXY))
+ {
+ inet_ntop(AF_INET, &context_tmp->netmask, daemon->addrbuff, ADDRSTRLEN);
+ my_syslog(MS_DHCP | LOG_INFO, _("%u available DHCP subnet: %s/%s"),
+ ntohl(mess->xid), daemon->namebuff, daemon->addrbuff);
+ }
+ else
+ {
+ inet_ntop(AF_INET, &context_tmp->end, daemon->addrbuff, ADDRSTRLEN);
+ my_syslog(MS_DHCP | LOG_INFO, _("%u available DHCP range: %s -- %s"),
+ ntohl(mess->xid), daemon->namebuff, daemon->addrbuff);
+ }
}
}
}
@@ -672,7 +676,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
clear_packet(mess, end);
do_options(context, mess, end, NULL, hostname, get_domain(mess->yiaddr),
- netid, subnet_addr, 0, 0, -1, NULL, vendor_class_len, now, 0xffffffff, 0, NULL);
+ netid, subnet_addr, 0, 0, -1, NULL, vendor_class_len, now, 0xffffffff, 0, NULL, 0);
}
}
@@ -818,8 +822,8 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
}
}
}
-
- if (config)
+
+ if (mess_type != DHCPLEASEQUERY && config)
{
struct dhcp_netid_list *list;
@@ -860,7 +864,8 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
clid = NULL;
/* Check if client is PXE client. */
- if (daemon->enable_pxe &&
+ if (mess_type != DHCPLEASEQUERY &&
+ daemon->enable_pxe &&
is_pxe_client(mess, sz, &pxevendor))
{
if ((opt = option_find(mess, sz, OPTION_PXE_UUID, 17)))
@@ -1035,7 +1040,8 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
}
/* if we're just a proxy server, go no further */
- if ((context->flags & CONTEXT_PROXY) || pxe)
+ if (mess_type != DHCPLEASEQUERY &&
+ ((context->flags & CONTEXT_PROXY) || pxe))
return 0;
if ((opt = option_find(mess, sz, OPTION_REQUESTED_OPTIONS, 0)))
@@ -1047,6 +1053,158 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
switch (mess_type)
{
+ case DHCPLEASEQUERY:
+ mess_type = DHCPLEASEUNKNOWN;
+
+ if (!option_bool(OPT_LEASEQUERY))
+ return 0;
+
+ if (mess->giaddr.s_addr == 0 && !is_relay_use_source)
+ return 0;
+
+ daemon->metrics[METRIC_DHCPLEASEQUERY]++;
+ log_packet("DHCPLEASEQUERY", mess->ciaddr.s_addr ? &mess->ciaddr : NULL, emac_len != 0 ? emac : NULL, emac_len, iface_name, NULL, NULL, mess->xid);
+
+ /* Put all the contexts on the ->current list for the next stages. */
+ for (context = daemon->dhcp; context; context = context->next)
+ context->current = context->next;
+
+ /* Have maybe already found the lease by MAC or clid. */
+ if (mess->ciaddr.s_addr != 0 &&
+ !(lease = lease_find_by_addr(mess->ciaddr)) &&
+ address_available(daemon->dhcp, mess->ciaddr, tagif_netid))
+ {
+ mess_type = DHCPLEASEUNASSIGNED;
+ daemon->metrics[METRIC_DHCPLEASEUNASSIGNED]++;
+ }
+
+ if (lease)
+ {
+ /* RFC4388 para 6.4.2 */
+ if (lease->agent_id)
+ {
+ unsigned char *sopt ;
+
+ for (vendor = daemon->dhcp_vendors; vendor; vendor = vendor->next)
+ {
+ int search;
+
+ if (vendor->match_type == MATCH_CIRCUIT)
+ search = SUBOPT_CIRCUIT_ID;
+ else if (vendor->match_type == MATCH_REMOTE)
+ search = SUBOPT_REMOTE_ID;
+ else if (vendor->match_type == MATCH_SUBSCRIBER)
+ search = SUBOPT_SUBSCR_ID;
+ else
+ continue;
+
+ if ((sopt = option_find1(lease->agent_id, lease->agent_id + lease->agent_id_len, search, 1)) &&
+ vendor->len == option_len(sopt) &&
+ memcmp(option_ptr(sopt, 0), vendor->data, vendor->len) == 0)
+ {
+ vendor->netid.next = netid;
+ netid = &vendor->netid;
+ }
+ }
+
+ tagif_netid = run_tag_if(netid);
+ }
+
+ /* Now find the context for this lease and config for this host. */
+ if ((context = narrow_context(daemon->dhcp, lease->addr, tagif_netid)))
+ {
+ if ((config = find_config(daemon->dhcp_conf, context, lease->clid, lease->clid_len,
+ lease->hwaddr, lease->hwaddr_len, lease->hwaddr_type, lease->hostname, tagif_netid)))
+ {
+ struct dhcp_netid_list *list;
+
+ for (list = config->netid; list; list = list->next)
+ {
+ list->list->next = netid;
+ netid = list->list;
+ }
+
+ tagif_netid = run_tag_if(netid);
+ }
+
+ if (context->netid.net)
+ {
+ context->netid.next = netid;
+ tagif_netid = run_tag_if(&context->netid);
+ }
+
+ log_tags(tagif_netid, ntohl(mess->xid));
+ emac = extended_hwaddr(lease->hwaddr_type, lease->hwaddr_len, lease->hwaddr, lease->clid_len, lease->clid, &emac_len);
+ mess_type = DHCPLEASEACTIVE;
+ daemon->metrics[METRIC_DHCPLEASEACTIVE]++;
+ }
+ }
+
+ log_packet(mess_type == DHCPLEASEACTIVE ? "DHCPLEASEACTIVE" : (mess_type == DHCPLEASEUNASSIGNED ? "DHCPLEASEUNASSIGNED" : "DHCPLEASEUNKNOWN"),
+ mess_type == DHCPLEASEACTIVE ? &lease->addr : (mess->ciaddr.s_addr != 0 ? &mess->ciaddr : NULL),
+ emac_len != 0 ? emac : NULL, emac_len,
+ iface_name, mess_type == DHCPLEASEACTIVE ? lease->hostname : NULL, NULL, mess->xid);
+
+ clear_packet(mess, end);
+ option_put(mess, end, OPTION_MESSAGE_TYPE, 1, mess_type);
+
+ if (mess_type == DHCPLEASEUNKNOWN)
+ {
+ daemon->metrics[METRIC_DHCPLEASEUNKNOWN]++;
+ mess->ciaddr.s_addr = 0;
+ }
+
+ if (mess_type == DHCPLEASEACTIVE)
+ {
+ unsigned char *p;
+
+ mess->ciaddr = lease->addr;
+ mess->hlen = lease->hwaddr_len;
+ mess->htype = lease->hwaddr_type;
+ memcpy(mess->chaddr, lease->hwaddr, lease->hwaddr_len);
+
+ if (lease->clid && in_list(req_options, OPTION_CLIENT_ID) &&
+ (p = free_space(mess, end, OPTION_CLIENT_ID, lease->clid_len)))
+ memcpy(p, lease->clid, lease->clid_len);
+
+ if (in_list(req_options, OPTION_LEASE_TIME))
+ {
+ if (lease->expires == 0) /* infinite lease */
+ option_put(mess, end, OPTION_LEASE_TIME, 4, 0xffffffff);
+ else
+ option_put(mess, end, OPTION_LEASE_TIME, 4, (unsigned int)(lease->expires - now));
+ }
+
+ if (lease->expires != 0)
+ {
+ time = calc_time(context, config, NULL);
+
+ if (in_list(req_options, OPTION_T1) && (lease->expires - now) > time/2)
+ option_put(mess, end, OPTION_T1, 4, ((unsigned int)(lease->expires - now)) - time/2);
+ if (in_list(req_options, OPTION_T2) && (lease->expires - now) > time/8)
+ option_put(mess, end, OPTION_T2, 4, ((unsigned int)(lease->expires - now)) - time/8);
+ if (in_list(req_options, OPTION_LAST_TRANSACTION) && (lease->expires - now) < time)
+ option_put(mess, end, OPTION_LAST_TRANSACTION, 4, time - ((unsigned int)(lease->expires - now)));
+ }
+
+ if (lease->vendorclass)
+ {
+ memcpy(daemon->dhcp_buff3, lease->vendorclass, lease->vendorclass_len);
+ vendor_class_len = lease->vendorclass_len;
+ }
+
+ subnet_addr.s_addr = 0;
+ do_options(context, mess, end, req_options, lease->hostname, get_domain(lease->addr), netid, subnet_addr,
+ 0, 0, -1, NULL, vendor_class_len, now, 0xffffffff, 0, NULL, 1);
+
+ /* Does this have to be last for leasequery replies also? RFC 4388 is silent on the subject. */
+ if (lease->agent_id && in_list(req_options, OPTION_AGENT_ID) &&
+ (p = free_space(mess, end, OPTION_AGENT_ID, lease->agent_id_len)))
+ memcpy(p, lease->agent_id, lease->agent_id_len);
+ }
+
+ return dhcp_packet_size(mess, NULL, real_end);
+
case DHCPDECLINE:
if (!(opt = option_find(mess, sz, OPTION_SERVER_IDENTIFIER, INADDRSZ)) ||
option_addr(opt).s_addr != server_id(context, override, fallback).s_addr)
@@ -1197,7 +1355,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
option_put(mess, end, OPTION_LEASE_TIME, 4, time);
/* T1 and T2 are required in DHCPOFFER by HP's wacky Jetdirect client. */
do_options(context, mess, end, req_options, offer_hostname, get_domain(mess->yiaddr),
- netid, subnet_addr, fqdn_flags, borken_opt, pxearch, uuid, vendor_class_len, now, time, fuzz, pxevendor);
+ netid, subnet_addr, fqdn_flags, borken_opt, pxearch, uuid, vendor_class_len, now, time, fuzz, pxevendor, 0);
return dhcp_packet_size(mess, agent_id, real_end);
@@ -1528,7 +1686,21 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
lease_set_expires(lease, time, now);
lease_set_interface(lease, int_index, now);
-
+
+ if (option_bool(OPT_LEASEQUERY))
+ {
+ if (agent_id)
+ lease_set_agent_id(lease, option_ptr(agent_id, 0), option_len(agent_id));
+ if (vendor_class_len != 0)
+ lease_set_vendorclass(lease, (unsigned char *)daemon->dhcp_buff3, vendor_class_len);
+ }
+ else
+ {
+ /* if leasequery no longer enabled, remove stuff that may have been stored when it was. */
+ lease_set_agent_id(lease, NULL, 0);
+ lease_set_vendorclass(lease, NULL, 0);
+ }
+
if (override.s_addr != 0)
lease->override = override;
else
@@ -1544,7 +1716,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
if (rapid_commit)
option_put(mess, end, OPTION_RAPID_COMMIT, 0, 0);
do_options(context, mess, end, req_options, hostname, get_domain(mess->yiaddr),
- netid, subnet_addr, fqdn_flags, borken_opt, pxearch, uuid, vendor_class_len, now, time, fuzz, pxevendor);
+ netid, subnet_addr, fqdn_flags, borken_opt, pxearch, uuid, vendor_class_len, now, time, fuzz, pxevendor, 0);
}
return dhcp_packet_size(mess, agent_id, real_end);
@@ -1611,7 +1783,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
}
do_options(context, mess, end, req_options, hostname, get_domain(mess->ciaddr),
- netid, subnet_addr, fqdn_flags, borken_opt, pxearch, uuid, vendor_class_len, now, 0xffffffff, 0, pxevendor);
+ netid, subnet_addr, fqdn_flags, borken_opt, pxearch, uuid, vendor_class_len, now, 0xffffffff, 0, pxevendor, 0);
*is_inform = 1; /* handle reply differently */
return dhcp_packet_size(mess, agent_id, real_end);
@@ -1720,11 +1892,13 @@ static void log_packet(char *type, void *addr, unsigned char *ext_mac,
if (!err && !option_bool(OPT_LOG_OPTS) && option_bool(OPT_QUIET_DHCP))
return;
- daemon->addrbuff[0] = 0;
+ daemon->addrbuff[0] = daemon->namebuff[0] = 0;
+
if (addr)
inet_ntop(AF_INET, addr, daemon->addrbuff, ADDRSTRLEN);
- print_mac(daemon->namebuff, ext_mac, mac_len);
+ if (ext_mac)
+ print_mac(daemon->namebuff, ext_mac, mac_len);
if (option_bool(OPT_LOG_OPTS))
my_syslog(MS_DHCP | LOG_INFO, "%u %s(%s) %s%s%s %s%s",
@@ -2412,7 +2586,8 @@ static void do_options(struct dhcp_context *context,
time_t now,
unsigned int lease_time,
unsigned short fuzz,
- const char *pxevendor)
+ const char *pxevendor,
+ int leasequery)
{
struct dhcp_opt *opt, *config_opts = daemon->dhcp_opts;
struct dhcp_boot *boot;
@@ -2450,70 +2625,73 @@ static void do_options(struct dhcp_context *context,
}
}
- for (id_list = daemon->force_broadcast; id_list; id_list = id_list->next)
- if ((!id_list->list) || match_netid(id_list->list, netid, 0))
- break;
- if (id_list)
- mess->flags |= htons(0x8000); /* force broadcast */
-
- if (context)
- mess->siaddr = context->local;
-
- /* See if we can send the boot stuff as options.
- To do this we need a requested option list, BOOTP
- and very old DHCP clients won't have this, we also
- provide a manual option to disable it.
- Some PXE ROMs have bugs (surprise!) and need zero-terminated
- names, so we always send those. */
- if ((boot = find_boot(tagif)))
+ if (!leasequery)
{
- if (boot->sname)
- {
- if (!option_bool(OPT_NO_OVERRIDE) &&
- req_options &&
- in_list(req_options, OPTION_SNAME))
- option_put_string(mess, end, OPTION_SNAME, boot->sname, 1);
- else
- safe_strncpy((char *)mess->sname, boot->sname, sizeof(mess->sname));
- }
+ for (id_list = daemon->force_broadcast; id_list; id_list = id_list->next)
+ if ((!id_list->list) || match_netid(id_list->list, netid, 0))
+ break;
+ if (id_list)
+ mess->flags |= htons(0x8000); /* force broadcast */
- if (boot->file)
+ if (context)
+ mess->siaddr = context->local;
+
+ /* See if we can send the boot stuff as options.
+ To do this we need a requested option list, BOOTP
+ and very old DHCP clients won't have this, we also
+ provide a manual option to disable it.
+ Some PXE ROMs have bugs (surprise!) and need zero-terminated
+ names, so we always send those. */
+ if ((boot = find_boot(tagif)))
{
- if (!option_bool(OPT_NO_OVERRIDE) &&
- req_options &&
- in_list(req_options, OPTION_FILENAME))
- option_put_string(mess, end, OPTION_FILENAME, boot->file, 1);
- else
- safe_strncpy((char *)mess->file, boot->file, sizeof(mess->file));
+ if (boot->sname)
+ {
+ if (!option_bool(OPT_NO_OVERRIDE) &&
+ req_options &&
+ in_list(req_options, OPTION_SNAME))
+ option_put_string(mess, end, OPTION_SNAME, boot->sname, 1);
+ else
+ safe_strncpy((char *)mess->sname, boot->sname, sizeof(mess->sname));
+ }
+
+ if (boot->file)
+ {
+ if (!option_bool(OPT_NO_OVERRIDE) &&
+ req_options &&
+ in_list(req_options, OPTION_FILENAME))
+ option_put_string(mess, end, OPTION_FILENAME, boot->file, 1);
+ else
+ safe_strncpy((char *)mess->file, boot->file, sizeof(mess->file));
+ }
+
+ if (boot->next_server.s_addr)
+ mess->siaddr = boot->next_server;
+ else if (boot->tftp_sname)
+ mess->siaddr = a_record_from_hosts(boot->tftp_sname, now);
}
-
- if (boot->next_server.s_addr)
- mess->siaddr = boot->next_server;
- else if (boot->tftp_sname)
- mess->siaddr = a_record_from_hosts(boot->tftp_sname, now);
- }
- else
- /* Use the values of the relevant options if no dhcp-boot given and
- they're not explicitly asked for as options. OPTION_END is used
- as an internal way to specify siaddr without using dhcp-boot, for use in
- dhcp-optsfile. */
- {
- if ((!req_options || !in_list(req_options, OPTION_FILENAME)) &&
- (opt = option_find2(OPTION_FILENAME)) && !(opt->flags & DHOPT_FORCE))
+ else
+ /* Use the values of the relevant options if no dhcp-boot given and
+ they're not explicitly asked for as options. OPTION_END is used
+ as an internal way to specify siaddr without using dhcp-boot, for use in
+ dhcp-optsfile. */
{
- safe_strncpy((char *)mess->file, (char *)opt->val, sizeof(mess->file));
- done_file = 1;
+ if ((!req_options || !in_list(req_options, OPTION_FILENAME)) &&
+ (opt = option_find2(OPTION_FILENAME)) && !(opt->flags & DHOPT_FORCE))
+ {
+ safe_strncpy((char *)mess->file, (char *)opt->val, sizeof(mess->file));
+ done_file = 1;
+ }
+
+ if ((!req_options || !in_list(req_options, OPTION_SNAME)) &&
+ (opt = option_find2(OPTION_SNAME)) && !(opt->flags & DHOPT_FORCE))
+ {
+ safe_strncpy((char *)mess->sname, (char *)opt->val, sizeof(mess->sname));
+ done_server = 1;
+ }
+
+ if ((opt = option_find2(OPTION_END)))
+ mess->siaddr.s_addr = ((struct in_addr *)opt->val)->s_addr;
}
-
- if ((!req_options || !in_list(req_options, OPTION_SNAME)) &&
- (opt = option_find2(OPTION_SNAME)) && !(opt->flags & DHOPT_FORCE))
- {
- safe_strncpy((char *)mess->sname, (char *)opt->val, sizeof(mess->sname));
- done_server = 1;
- }
-
- if ((opt = option_find2(OPTION_END)))
- mess->siaddr.s_addr = ((struct in_addr *)opt->val)->s_addr;
}
/* We don't want to do option-overload for BOOTP, so make the file and sname
@@ -2534,7 +2712,7 @@ static void do_options(struct dhcp_context *context,
end -= 3;
/* rfc3011 says this doesn't need to be in the requested options list. */
- if (subnet_addr.s_addr)
+ if (!leasequery && subnet_addr.s_addr)
option_put(mess, end, OPTION_SUBNET_SELECT, INADDRSZ, ntohl(subnet_addr.s_addr));
if (lease_time != 0xffffffff)
@@ -2575,14 +2753,17 @@ static void do_options(struct dhcp_context *context,
/* replies to DHCPINFORM may not have a valid context */
if (context)
{
- if (!option_find2(OPTION_NETMASK))
+ /* Netmask and broadcast always sent, except leasequery. */
+ if (!option_find2(OPTION_NETMASK) &&
+ (!leasequery || in_list(req_options, OPTION_NETMASK)))
option_put(mess, end, OPTION_NETMASK, INADDRSZ, ntohl(context->netmask.s_addr));
-
+
/* May not have a "guessed" broadcast address if we got no packets via a relay
from this net yet (ie just unicast renewals after a restart */
if (context->broadcast.s_addr &&
- !option_find2(OPTION_BROADCAST))
- option_put(mess, end, OPTION_BROADCAST, INADDRSZ, ntohl(context->broadcast.s_addr));
+ !option_find2(OPTION_BROADCAST) &&
+ (!leasequery || in_list(req_options, OPTION_BROADCAST)))
+ option_put(mess, end, OPTION_BROADCAST, INADDRSZ, ntohl(context->broadcast.s_addr));
/* Same comments as broadcast apply, and also may not be able to get a sensible
default when using subnet select. User must configure by steam in that case. */
@@ -2663,7 +2844,7 @@ static void do_options(struct dhcp_context *context,
continue;
/* was it asked for, or are we sending it anyway? */
- if (!(opt->flags & DHOPT_FORCE) && !in_list(req_options, optno))
+ if ((!(opt->flags & DHOPT_FORCE) || leasequery) && !in_list(req_options, optno))
continue;
/* prohibit some used-internally options. T1 and T2 already handled. */
@@ -2721,7 +2902,7 @@ static void do_options(struct dhcp_context *context,
handle_encap(mess, end, req_options, null_term, tagif, pxe_arch != 1);
force_encap = prune_vendor_opts(tagif);
-
+
if (context && pxe_arch != -1)
{
pxe_misc(mess, end, uuid, pxevendor);
@@ -2729,8 +2910,8 @@ static void do_options(struct dhcp_context *context,
config_opts = pxe_opts(pxe_arch, tagif, context->local, now);
}
- if ((force_encap || in_list(req_options, OPTION_VENDOR_CLASS_OPT)) &&
- do_encap_opts(config_opts, OPTION_VENDOR_CLASS_OPT, DHOPT_VENDOR_MATCH, mess, end, null_term) &&
+ if ((force_encap || in_list(req_options, OPTION_VENDOR_CLASS_OPT) || in_list(req_options, OPTION_VENDOR_ID)) &&
+ (leasequery || do_encap_opts(config_opts, OPTION_VENDOR_CLASS_OPT, DHOPT_VENDOR_MATCH, mess, end, null_term)) &&
pxe_arch == -1 && !done_vendor_class && vendor_class_len != 0 &&
(p = free_space(mess, end, OPTION_VENDOR_ID, vendor_class_len)))
/* If we send vendor encapsulated options, and haven't already sent option 60,