summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorSeonah Moon <seonah1.moon@samsung.com>2021-01-27 20:12:33 +0900
committerSeonah Moon <seonah1.moon@samsung.com>2021-01-27 20:19:26 +0900
commit9a9940fac7c8116d02571a84692e25396a4cfcfc (patch)
tree067040a4aeebf5642c6a8b1e0ad14feee80a1c46 /src
parent09d50377ce7e37f2719227f03687b5286d9a82a5 (diff)
parent219639ccc0b123b3770c799a8f050bce7836378e (diff)
downloaddnsmasq-9a9940fac7c8116d02571a84692e25396a4cfcfc.tar.gz
dnsmasq-9a9940fac7c8116d02571a84692e25396a4cfcfc.tar.bz2
dnsmasq-9a9940fac7c8116d02571a84692e25396a4cfcfc.zip
Change-Id: Ib59076a278201769b7b309fc0aa320804c60bbbe
Diffstat (limited to 'src')
-rw-r--r--src/config.h8
-rw-r--r--src/crypto.c7
-rw-r--r--src/dnsmasq.h44
-rw-r--r--src/dnssec.c332
-rw-r--r--src/edns0.c75
-rw-r--r--src/forward.c271
-rw-r--r--src/hash_questions.c281
-rw-r--r--src/network.c37
-rw-r--r--src/option.c27
-rw-r--r--src/rfc1035.c51
-rw-r--r--src/rfc2131.c97
11 files changed, 866 insertions, 364 deletions
diff --git a/src/config.h b/src/config.h
index 4298c6a..665754c 100644
--- a/src/config.h
+++ b/src/config.h
@@ -122,6 +122,9 @@ HAVE_AUTH
define this to include the facility to act as an authoritative DNS
server for one or more zones.
+HAVE_NETTLEHASH
+ include just hash function from nettle, but no DNSSEC.
+
HAVE_DNSSEC
include DNSSEC validator.
@@ -189,6 +192,7 @@ RESOLVFILE
/* #define HAVE_IDN */
/* #define HAVE_LIBIDN2 */
/* #define HAVE_CONNTRACK */
+/* #define HAVE_NETTLEHASH */
/* #define HAVE_DNSSEC */
@@ -422,6 +426,10 @@ static char *compile_opts =
"no-"
#endif
"auth "
+#if !defined(HAVE_NETTLEHASH) && !defined(HAVE_DNSSEC)
+"no-"
+#endif
+"nettlehash "
#ifndef HAVE_DNSSEC
"no-"
#endif
diff --git a/src/crypto.c b/src/crypto.c
index ca63111..09525d2 100644
--- a/src/crypto.c
+++ b/src/crypto.c
@@ -25,6 +25,9 @@
#if NETTLE_VERSION_MAJOR == 3 && NETTLE_VERSION_MINOR >= 6
# include <nettle/gostdsa.h>
#endif
+#endif
+
+#if defined(HAVE_DNSSEC) || defined(HAVE_NETTLEHASH)
#include <nettle/nettle-meta.h>
#include <nettle/bignum.h>
@@ -167,6 +170,10 @@ int hash_init(const struct nettle_hash *hash, void **ctxp, unsigned char **diges
return 1;
}
+
+#endif
+
+#ifdef HAVE_DNSSEC
static int dnsmasq_rsa_verify(struct blockdata *key_data, unsigned int key_len, unsigned char *sig, size_t sig_len,
unsigned char *digest, size_t digest_len, int algo)
diff --git a/src/dnsmasq.h b/src/dnsmasq.h
index 4220798..914f469 100644
--- a/src/dnsmasq.h
+++ b/src/dnsmasq.h
@@ -157,7 +157,7 @@ extern int capget(cap_user_header_t header, cap_user_data_t data);
#include <priv.h>
#endif
-#ifdef HAVE_DNSSEC
+#if defined(HAVE_DNSSEC) || defined(HAVE_NETTLEHASH)
# include <nettle/nettle-meta.h>
#endif
@@ -653,23 +653,25 @@ struct hostsfile {
#define FREC_DO_QUESTION 64
#define FREC_ADDED_PHEADER 128
#define FREC_TEST_PKTSZ 256
-#define FREC_HAS_EXTRADATA 512
+#define FREC_HAS_EXTRADATA 512
+#define FREC_HAS_PHEADER 1024
+#define FREC_NO_CACHE 2048
-#ifdef HAVE_DNSSEC
-#define HASH_SIZE 20 /* SHA-1 digest size */
-#else
-#define HASH_SIZE sizeof(int)
-#endif
+#define HASH_SIZE 32 /* SHA-256 digest size */
struct frec {
- union mysockaddr source;
- union all_addr dest;
+ struct frec_src {
+ union mysockaddr source;
+ union all_addr dest;
+ unsigned int iface, log_id;
+ unsigned short orig_id;
+ struct frec_src *next;
+ } frec_src;
struct server *sentto; /* NULL means free */
struct randfd *rfd4;
struct randfd *rfd6;
- unsigned int iface;
- unsigned short orig_id, new_id;
- int log_id, fd, forwardall, flags;
+ unsigned short new_id;
+ int fd, forwardall, flags;
time_t time;
unsigned char *hash[HASH_SIZE];
#ifdef HAVE_DNSSEC
@@ -829,6 +831,7 @@ struct dhcp_opt {
#define DHOPT_RFC3925 2048
#define DHOPT_TAGOK 4096
#define DHOPT_ADDR6 8192
+#define DHOPT_VENDOR_PXE 16384
struct dhcp_boot {
char *file, *sname, *tftp_sname;
@@ -852,6 +855,8 @@ struct pxe_service {
struct pxe_service *next;
};
+#define DHCP_PXE_DEF_VENDOR "PXEClient"
+
#define MATCH_VENDOR 1
#define MATCH_USER 2
#define MATCH_CIRCUIT 3
@@ -867,6 +872,11 @@ struct dhcp_vendor {
struct dhcp_vendor *next;
};
+struct dhcp_pxe_vendor {
+ char *data;
+ struct dhcp_pxe_vendor *next;
+};
+
struct dhcp_mac {
unsigned int mask;
int hwaddr_len, hwaddr_type;
@@ -1040,6 +1050,7 @@ extern struct daemon {
struct dhcp_config *dhcp_conf;
struct dhcp_opt *dhcp_opts, *dhcp_match, *dhcp_opts6, *dhcp_match6;
struct dhcp_match_name *dhcp_name_match;
+ struct dhcp_pxe_vendor *dhcp_pxe_vendors;
struct dhcp_vendor *dhcp_vendors;
struct dhcp_mac *dhcp_macs;
struct dhcp_boot *boot_config;
@@ -1088,6 +1099,8 @@ extern struct daemon {
int back_to_the_future;
#endif
struct frec *frec_list;
+ struct frec_src *free_frec_src;
+ int frec_src_count;
struct serverfd *sfds;
struct irec *interfaces;
struct listener *listeners;
@@ -1220,7 +1233,6 @@ int check_for_bogus_wildcard(struct dns_header *header, size_t qlen, char *name,
struct bogus_addr *baddr, time_t now);
int check_for_ignored_address(struct dns_header *header, size_t qlen, struct bogus_addr *baddr);
int check_for_local_domain(char *name, time_t now);
-unsigned int questions_crc(struct dns_header *header, size_t plen, char *name);
size_t resize_packet(struct dns_header *header, size_t plen,
unsigned char *pheader, size_t hlen);
int add_resource_record(struct dns_header *header, char *limit, int *truncp,
@@ -1245,9 +1257,11 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
int check_unsigned, int *neganswer, int *nons, int *nsec_ttl);
int dnskey_keytag(int alg, int flags, unsigned char *key, int keylen);
size_t filter_rrsigs(struct dns_header *header, size_t plen);
-unsigned char* hash_questions(struct dns_header *header, size_t plen, char *name);
int setup_timestamp(void);
+/* hash_questions.c */
+unsigned char *hash_questions(struct dns_header *header, size_t plen, char *name);
+
/* crypto.c */
const struct nettle_hash *hash_find(char *name);
int hash_init(const struct nettle_hash *hash, void **ctxp, unsigned char **digestp);
@@ -1645,7 +1659,7 @@ size_t add_pseudoheader(struct dns_header *header, size_t plen, unsigned char *l
unsigned short udp_sz, int optno, unsigned char *opt, size_t optlen, int set_do, int replace);
size_t add_do_bit(struct dns_header *header, size_t plen, unsigned char *limit);
size_t add_edns0_config(struct dns_header *header, size_t plen, unsigned char *limit,
- union mysockaddr *source, time_t now, int *check_subnet);
+ union mysockaddr *source, time_t now, int *check_subnet, int *cacheable);
int check_source(struct dns_header *header, size_t plen, unsigned char *pseudoheader, union mysockaddr *peer);
/* arp.c */
diff --git a/src/dnssec.c b/src/dnssec.c
index db5c2d1..93cc7bf 100644
--- a/src/dnssec.c
+++ b/src/dnssec.c
@@ -223,138 +223,147 @@ static int check_date_range(unsigned long curtime, u32 date_start, u32 date_end)
&& serial_compare_32(curtime, date_end) == SERIAL_LT;
}
-/* Return bytes of canonicalised rdata, when the return value is zero, the remaining
- data, pointed to by *p, should be used raw. */
-static int get_rdata(struct dns_header *header, size_t plen, unsigned char *end, char *buff, int bufflen,
- unsigned char **p, u16 **desc)
+/* Return bytes of canonicalised rrdata one by one.
+ Init state->ip with the RR, and state->end with the end of same.
+ Init state->op to NULL.
+ Init state->desc to RR descriptor.
+ Init state->buff with a MAXDNAME * 2 buffer.
+
+ After each call which returns 1, state->op points to the next byte of data.
+ On returning 0, the end has been reached.
+*/
+struct rdata_state {
+ u16 *desc;
+ size_t c;
+ unsigned char *end, *ip, *op;
+ char *buff;
+};
+
+static int get_rdata(struct dns_header *header, size_t plen, struct rdata_state *state)
{
- int d = **desc;
+ int d;
- /* No more data needs mangling */
- if (d == (u16)-1)
+ if (state->op && state->c != 1)
{
- /* If there's more data than we have space for, just return what fits,
- we'll get called again for more chunks */
- if (end - *p > bufflen)
- {
- memcpy(buff, *p, bufflen);
- *p += bufflen;
- return bufflen;
- }
-
- return 0;
+ state->op++;
+ state->c--;
+ return 1;
}
-
- (*desc)++;
-
- if (d == 0 && extract_name(header, plen, p, buff, 1, 0))
- /* domain-name, canonicalise */
- return to_wire(buff);
- else
- {
- /* plain data preceding a domain-name, don't run off the end of the data */
- if ((end - *p) < d)
- d = end - *p;
+
+ while (1)
+ {
+ d = *(state->desc);
- if (d != 0)
+ if (d == (u16)-1)
{
- memcpy(buff, *p, d);
- *p += d;
+ /* all the bytes to the end. */
+ if ((state->c = state->end - state->ip) != 0)
+ {
+ state->op = state->ip;
+ state->ip = state->end;;
+ }
+ else
+ return 0;
+ }
+ else
+ {
+ state->desc++;
+
+ if (d == (u16)0)
+ {
+ /* domain-name, canonicalise */
+ int len;
+
+ if (!extract_name(header, plen, &state->ip, state->buff, 1, 0) ||
+ (len = to_wire(state->buff)) == 0)
+ continue;
+
+ state->c = len;
+ state->op = (unsigned char *)state->buff;
+ }
+ else
+ {
+ /* plain data preceding a domain-name, don't run off the end of the data */
+ if ((state->end - state->ip) < d)
+ d = state->end - state->ip;
+
+ if (d == 0)
+ continue;
+
+ state->op = state->ip;
+ state->c = d;
+ state->ip += d;
+ }
}
- return d;
+ return 1;
}
}
-/* Bubble sort the RRset into the canonical order.
- Note that the byte-streams from two RRs may get unsynced: consider
- RRs which have two domain-names at the start and then other data.
- The domain-names may have different lengths in each RR, but sort equal
-
- ------------
- |abcde|fghi|
- ------------
- |abcd|efghi|
- ------------
-
- leaving the following bytes as deciding the order. Hence the nasty left1 and left2 variables.
-*/
+/* Bubble sort the RRset into the canonical order. */
static int sort_rrset(struct dns_header *header, size_t plen, u16 *rr_desc, int rrsetidx,
unsigned char **rrset, char *buff1, char *buff2)
{
- int swap, quit, i, j;
+ int swap, i, j;
do
{
for (swap = 0, i = 0; i < rrsetidx-1; i++)
{
- int rdlen1, rdlen2, left1, left2, len1, len2, len, rc;
- u16 *dp1, *dp2;
- unsigned char *end1, *end2;
+ int rdlen1, rdlen2;
+ struct rdata_state state1, state2;
+
/* Note that these have been determined to be OK previously,
so we don't need to check for NULL return here. */
- unsigned char *p1 = skip_name(rrset[i], header, plen, 10);
- unsigned char *p2 = skip_name(rrset[i+1], header, plen, 10);
-
- p1 += 8; /* skip class, type, ttl */
- GETSHORT(rdlen1, p1);
- end1 = p1 + rdlen1;
+ state1.ip = skip_name(rrset[i], header, plen, 10);
+ state2.ip = skip_name(rrset[i+1], header, plen, 10);
+ state1.op = state2.op = NULL;
+ state1.buff = buff1;
+ state2.buff = buff2;
+ state1.desc = state2.desc = rr_desc;
- p2 += 8; /* skip class, type, ttl */
- GETSHORT(rdlen2, p2);
- end2 = p2 + rdlen2;
-
- dp1 = dp2 = rr_desc;
+ state1.ip += 8; /* skip class, type, ttl */
+ GETSHORT(rdlen1, state1.ip);
+ if (!CHECK_LEN(header, state1.ip, plen, rdlen1))
+ return rrsetidx; /* short packet */
+ state1.end = state1.ip + rdlen1;
- for (quit = 0, left1 = 0, left2 = 0, len1 = 0, len2 = 0; !quit;)
+ state2.ip += 8; /* skip class, type, ttl */
+ GETSHORT(rdlen2, state2.ip);
+ if (!CHECK_LEN(header, state2.ip, plen, rdlen2))
+ return rrsetidx; /* short packet */
+ state2.end = state2.ip + rdlen2;
+
+ while (1)
{
- if (left1 != 0)
- memmove(buff1, buff1 + len1 - left1, left1);
+ int ok1, ok2;
- if ((len1 = get_rdata(header, plen, end1, buff1 + left1, (MAXDNAME * 2) - left1, &p1, &dp1)) == 0)
- {
- quit = 1;
- len1 = end1 - p1;
- memcpy(buff1 + left1, p1, len1);
- }
- len1 += left1;
-
- if (left2 != 0)
- memmove(buff2, buff2 + len2 - left2, left2);
-
- if ((len2 = get_rdata(header, plen, end2, buff2 + left2, (MAXDNAME *2) - left2, &p2, &dp2)) == 0)
- {
- quit = 1;
- len2 = end2 - p2;
- memcpy(buff2 + left2, p2, len2);
- }
- len2 += left2;
-
- if (len1 > len2)
- left1 = len1 - len2, left2 = 0, len = len2;
- else
- left2 = len2 - len1, left1 = 0, len = len1;
-
- rc = (len == 0) ? 0 : memcmp(buff1, buff2, len);
-
- if (rc > 0 || (rc == 0 && quit && len1 > len2))
- {
- unsigned char *tmp = rrset[i+1];
- rrset[i+1] = rrset[i];
- rrset[i] = tmp;
- swap = quit = 1;
- }
- else if (rc == 0 && quit && len1 == len2)
+ ok1 = get_rdata(header, plen, &state1);
+ ok2 = get_rdata(header, plen, &state2);
+
+ if (!ok1 && !ok2)
{
/* Two RRs are equal, remove one copy. RFC 4034, para 6.3 */
for (j = i+1; j < rrsetidx-1; j++)
rrset[j] = rrset[j+1];
rrsetidx--;
i--;
+ break;
}
- else if (rc < 0)
- quit = 1;
+ else if (ok1 && (!ok2 || *state1.op > *state2.op))
+ {
+ unsigned char *tmp = rrset[i+1];
+ rrset[i+1] = rrset[i];
+ rrset[i] = tmp;
+ swap = 1;
+ break;
+ }
+ else if (ok2 && (!ok1 || *state2.op > *state1.op))
+ break;
+
+ /* arrive here when bytes are equal, go round the loop again
+ and compare the next ones. */
}
}
} while (swap);
@@ -569,15 +578,18 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
wire_len = to_wire(keyname);
hash->update(ctx, (unsigned int)wire_len, (unsigned char*)keyname);
from_wire(keyname);
+
+#define RRBUFLEN 128 /* Most RRs are smaller than this. */
for (i = 0; i < rrsetidx; ++i)
{
- int seg;
- unsigned char *end, *cp;
- u16 len, *dp;
+ int j;
+ struct rdata_state state;
+ u16 len;
+ unsigned char rrbuf[RRBUFLEN];
p = rrset[i];
-
+
if (!extract_name(header, plen, &p, name, 1, 10))
return STAT_BOGUS;
@@ -586,12 +598,11 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
/* if more labels than in RRsig name, hash *.<no labels in rrsig labels field> 4035 5.3.2 */
if (labels < name_labels)
{
- int k;
- for (k = name_labels - labels; k != 0; k--)
+ for (j = name_labels - labels; j != 0; j--)
{
while (*name_start != '.' && *name_start != 0)
name_start++;
- if (k != 1 && *name_start == '.')
+ if (j != 1 && *name_start == '.')
name_start++;
}
@@ -606,30 +617,66 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
hash->update(ctx, (unsigned int)wire_len, (unsigned char *)name_start);
hash->update(ctx, 4, p); /* class and type */
hash->update(ctx, 4, (unsigned char *)&nsigttl);
-
- p += 8; /* skip class, type, ttl */
+
+ p += 8; /* skip type, class, ttl */
GETSHORT(rdlen, p);
if (!CHECK_LEN(header, p, plen, rdlen))
return STAT_BOGUS;
-
- end = p + rdlen;
-
- /* canonicalise rdata and calculate length of same, use name buffer as workspace.
- Note that name buffer is twice MAXDNAME long in DNSSEC mode. */
- cp = p;
- dp = rr_desc;
- for (len = 0; (seg = get_rdata(header, plen, end, name, MAXDNAME * 2, &cp, &dp)) != 0; len += seg);
- len += end - cp;
- len = htons(len);
- hash->update(ctx, 2, (unsigned char *)&len);
-
- /* Now canonicalise again and digest. */
- cp = p;
- dp = rr_desc;
- while ((seg = get_rdata(header, plen, end, name, MAXDNAME * 2, &cp, &dp)))
- hash->update(ctx, seg, (unsigned char *)name);
- if (cp != end)
- hash->update(ctx, end - cp, cp);
+
+ /* Optimisation for RR types which need no cannonicalisation.
+ This includes DNSKEY DS NSEC and NSEC3, which are also long, so
+ it saves lots of calls to get_rdata, and avoids the pessimal
+ segmented insertion, even with a small rrbuf[].
+
+ If canonicalisation is not needed, a simple insertion into the hash works.
+ */
+ if (*rr_desc == (u16)-1)
+ {
+ len = htons(rdlen);
+ hash->update(ctx, 2, (unsigned char *)&len);
+ hash->update(ctx, rdlen, p);
+ }
+ else
+ {
+ /* canonicalise rdata and calculate length of same, use
+ name buffer as workspace for get_rdata. */
+ state.ip = p;
+ state.op = NULL;
+ state.desc = rr_desc;
+ state.buff = name;
+ state.end = p + rdlen;
+
+ for (j = 0; get_rdata(header, plen, &state); j++)
+ if (j < RRBUFLEN)
+ rrbuf[j] = *state.op;
+
+ len = htons((u16)j);
+ hash->update(ctx, 2, (unsigned char *)&len);
+
+ /* If the RR is shorter than RRBUFLEN (most of them, in practice)
+ then we can just digest it now. If it exceeds RRBUFLEN we have to
+ go back to the start and do it in chunks. */
+ if (j >= RRBUFLEN)
+ {
+ state.ip = p;
+ state.op = NULL;
+ state.desc = rr_desc;
+
+ for (j = 0; get_rdata(header, plen, &state); j++)
+ {
+ rrbuf[j] = *state.op;
+
+ if (j == RRBUFLEN - 1)
+ {
+ hash->update(ctx, RRBUFLEN, rrbuf);
+ j = -1;
+ }
+ }
+ }
+
+ if (j != 0)
+ hash->update(ctx, j, rrbuf);
+ }
}
hash->digest(ctx, hash->digest_size, digest);
@@ -2056,35 +2103,4 @@ size_t dnssec_generate_query(struct dns_header *header, unsigned char *end, char
return ret;
}
-unsigned char* hash_questions(struct dns_header *header, size_t plen, char *name)
-{
- int q;
- unsigned int len;
- unsigned char *p = (unsigned char *)(header+1);
- const struct nettle_hash *hash;
- void *ctx;
- unsigned char *digest;
-
- if (!(hash = hash_find("sha1")) || !hash_init(hash, &ctx, &digest))
- return NULL;
-
- for (q = ntohs(header->qdcount); q != 0; q--)
- {
- if (!extract_name(header, plen, &p, name, 1, 4))
- break; /* bad packet */
-
- len = to_wire(name);
- hash->update(ctx, len, (unsigned char *)name);
- /* CRC the class and type as well */
- hash->update(ctx, 4, p);
-
- p += 4;
- if (!CHECK_LEN(header, p, plen, 0))
- break; /* bad packet */
- }
-
- hash->digest(ctx, hash->digest_size, digest);
- return digest;
-}
-
#endif /* HAVE_DNSSEC */
diff --git a/src/edns0.c b/src/edns0.c
index d75d3cc..53cfe24 100644
--- a/src/edns0.c
+++ b/src/edns0.c
@@ -264,7 +264,8 @@ static void encoder(unsigned char *in, char *out)
out[3] = char64(in[2]);
}
-static size_t add_dns_client(struct dns_header *header, size_t plen, unsigned char *limit, union mysockaddr *l3, time_t now)
+static size_t add_dns_client(struct dns_header *header, size_t plen, unsigned char *limit,
+ union mysockaddr *l3, time_t now, int *cacheablep)
{
int maclen, replace = 2; /* can't get mac address, just delete any incoming. */
unsigned char mac[DHCP_CHADDR_MAX];
@@ -273,6 +274,7 @@ static size_t add_dns_client(struct dns_header *header, size_t plen, unsigned ch
if ((maclen = find_mac(l3, mac, 1, now)) == 6)
{
replace = 1;
+ *cacheablep = 0;
if (option_bool(OPT_MAC_HEX))
print_mac(encode, mac, maclen);
@@ -288,14 +290,18 @@ static size_t add_dns_client(struct dns_header *header, size_t plen, unsigned ch
}
-static size_t add_mac(struct dns_header *header, size_t plen, unsigned char *limit, union mysockaddr *l3, time_t now)
+static size_t add_mac(struct dns_header *header, size_t plen, unsigned char *limit,
+ union mysockaddr *l3, time_t now, int *cacheablep)
{
int maclen;
unsigned char mac[DHCP_CHADDR_MAX];
if ((maclen = find_mac(l3, mac, 1, now)) != 0)
- plen = add_pseudoheader(header, plen, limit, PACKETSZ, EDNS0_OPTION_MAC, mac, maclen, 0, 0);
-
+ {
+ *cacheablep = 0;
+ plen = add_pseudoheader(header, plen, limit, PACKETSZ, EDNS0_OPTION_MAC, mac, maclen, 0, 0);
+ }
+
return plen;
}
@@ -313,17 +319,18 @@ static void *get_addrp(union mysockaddr *addr, const short family)
return &addr->in.sin_addr;
}
-static size_t calc_subnet_opt(struct subnet_opt *opt, union mysockaddr *source)
+static size_t calc_subnet_opt(struct subnet_opt *opt, union mysockaddr *source, int *cacheablep)
{
/* http://tools.ietf.org/html/draft-vandergaast-edns-client-subnet-02 */
int len;
void *addrp = NULL;
int sa_family = source->sa.sa_family;
-
+ int cacheable = 0;
+
opt->source_netmask = 0;
opt->scope_netmask = 0;
-
+
if (source->sa.sa_family == AF_INET6 && daemon->add_subnet6)
{
opt->source_netmask = daemon->add_subnet6->mask;
@@ -331,6 +338,7 @@ static size_t calc_subnet_opt(struct subnet_opt *opt, union mysockaddr *source)
{
sa_family = daemon->add_subnet6->addr.sa.sa_family;
addrp = get_addrp(&daemon->add_subnet6->addr, sa_family);
+ cacheable = 1;
}
else
addrp = &source->in6.sin6_addr;
@@ -343,6 +351,7 @@ static size_t calc_subnet_opt(struct subnet_opt *opt, union mysockaddr *source)
{
sa_family = daemon->add_subnet4->addr.sa.sa_family;
addrp = get_addrp(&daemon->add_subnet4->addr, sa_family);
+ cacheable = 1; /* Address is constant */
}
else
addrp = &source->in.sin_addr;
@@ -350,8 +359,6 @@ static size_t calc_subnet_opt(struct subnet_opt *opt, union mysockaddr *source)
opt->family = htons(sa_family == AF_INET6 ? 2 : 1);
- len = 0;
-
if (addrp && opt->source_netmask != 0)
{
len = ((opt->source_netmask - 1) >> 3) + 1;
@@ -359,18 +366,26 @@ static size_t calc_subnet_opt(struct subnet_opt *opt, union mysockaddr *source)
if (opt->source_netmask & 7)
opt->addr[len-1] &= 0xff << (8 - (opt->source_netmask & 7));
}
+ else
+ {
+ cacheable = 1; /* No address ever supplied. */
+ len = 0;
+ }
+
+ if (cacheablep)
+ *cacheablep = cacheable;
return len + 4;
}
-static size_t add_source_addr(struct dns_header *header, size_t plen, unsigned char *limit, union mysockaddr *source)
+static size_t add_source_addr(struct dns_header *header, size_t plen, unsigned char *limit, union mysockaddr *source, int *cacheable)
{
/* http://tools.ietf.org/html/draft-vandergaast-edns-client-subnet-02 */
int len;
struct subnet_opt opt;
- len = calc_subnet_opt(&opt, source);
+ len = calc_subnet_opt(&opt, source, cacheable);
return add_pseudoheader(header, plen, (unsigned char *)limit, PACKETSZ, EDNS0_OPTION_CLIENT_SUBNET, (unsigned char *)&opt, len, 0, 0);
}
@@ -383,18 +398,18 @@ int check_source(struct dns_header *header, size_t plen, unsigned char *pseudohe
unsigned char *p;
int code, i, rdlen;
- calc_len = calc_subnet_opt(&opt, peer);
-
- if (!(p = skip_name(pseudoheader, header, plen, 10)))
- return 1;
-
- p += 8; /* skip UDP length and RCODE */
+ calc_len = calc_subnet_opt(&opt, peer, NULL);
- GETSHORT(rdlen, p);
- if (!CHECK_LEN(header, p, plen, rdlen))
- return 1; /* bad packet */
-
- /* check if option there */
+ if (!(p = skip_name(pseudoheader, header, plen, 10)))
+ return 1;
+
+ p += 8; /* skip UDP length and RCODE */
+
+ GETSHORT(rdlen, p);
+ if (!CHECK_LEN(header, p, plen, rdlen))
+ return 1; /* bad packet */
+
+ /* check if option there */
for (i = 0; i + 4 < rdlen; i += len + 4)
{
GETSHORT(code, p);
@@ -412,24 +427,28 @@ int check_source(struct dns_header *header, size_t plen, unsigned char *pseudohe
return 1;
}
+/* Set *check_subnet if we add a client subnet option, which needs to checked
+ in the reply. Set *cacheable to zero if we add an option which the answer
+ may depend on. */
size_t add_edns0_config(struct dns_header *header, size_t plen, unsigned char *limit,
- union mysockaddr *source, time_t now, int *check_subnet)
+ union mysockaddr *source, time_t now, int *check_subnet, int *cacheable)
{
*check_subnet = 0;
-
+ *cacheable = 1;
+
if (option_bool(OPT_ADD_MAC))
- plen = add_mac(header, plen, limit, source, now);
+ plen = add_mac(header, plen, limit, source, now, cacheable);
if (option_bool(OPT_MAC_B64) || option_bool(OPT_MAC_HEX))
- plen = add_dns_client(header, plen, limit, source, now);
-
+ plen = add_dns_client(header, plen, limit, source, now, cacheable);
+
if (daemon->dns_client_id)
plen = add_pseudoheader(header, plen, limit, PACKETSZ, EDNS0_OPTION_NOMCPEID,
(unsigned char *)daemon->dns_client_id, strlen(daemon->dns_client_id), 0, 1);
if (option_bool(OPT_CLIENT_SUBNET))
{
- plen = add_source_addr(header, plen, limit, source);
+ plen = add_source_addr(header, plen, limit, source, cacheable);
*check_subnet = 1;
}
diff --git a/src/forward.c b/src/forward.c
index 9c2b2c6..7a95ddf 100644
--- a/src/forward.c
+++ b/src/forward.c
@@ -16,10 +16,12 @@
#include "dnsmasq.h"
-static struct frec *lookup_frec(unsigned short id, void *hash);
+static struct frec *lookup_frec(unsigned short id, int fd, int family, void *hash);
static struct frec *lookup_frec_by_sender(unsigned short id,
union mysockaddr *addr,
void *hash);
+static struct frec *lookup_frec_by_query(void *hash, unsigned int flags);
+
static unsigned short get_id(void);
static void free_frec(struct frec *f);
@@ -255,20 +257,29 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
int type = SERV_DO_DNSSEC, norebind = 0;
union all_addr *addrp = NULL;
unsigned int flags = 0;
+ unsigned int fwd_flags = 0;
struct server *start = NULL;
-#ifdef HAVE_DNSSEC
void *hash = hash_questions(header, plen, daemon->namebuff);
+#ifdef HAVE_DNSSEC
int do_dnssec = 0;
-#else
- unsigned int crc = questions_crc(header, plen, daemon->namebuff);
- void *hash = &crc;
#endif
unsigned int gotname = extract_request(header, plen, daemon->namebuff, NULL);
unsigned char *oph = find_pseudoheader(header, plen, NULL, NULL, NULL, NULL);
(void)do_bit;
-
+
+ if (header->hb4 & HB4_CD)
+ fwd_flags |= FREC_CHECKING_DISABLED;
+ if (ad_reqd)
+ fwd_flags |= FREC_AD_QUESTION;
+ if (oph)
+ fwd_flags |= FREC_HAS_PHEADER;
+#ifdef HAVE_DNSSEC
+ if (do_bit)
+ fwd_flags |= FREC_DO_QUESTION;
+#endif
+
/* may be no servers available. */
- if (forward || (hash && (forward = lookup_frec_by_sender(ntohs(header->id), udpaddr, hash))))
+ if (forward || (forward = lookup_frec_by_sender(ntohs(header->id), udpaddr, hash)))
{
/* If we didn't get an answer advertising a maximal packet in EDNS,
fall back to 1280, which should work everywhere on IPv6.
@@ -339,6 +350,39 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
}
else
{
+ /* Query from new source, but the same query may be in progress
+ from another source. If so, just add this client to the
+ list that will get the reply.*/
+
+ if (!option_bool(OPT_ADD_MAC) && !option_bool(OPT_MAC_B64) &&
+ (forward = lookup_frec_by_query(hash, fwd_flags)))
+ {
+ /* Note whine_malloc() zeros memory. */
+ if (!daemon->free_frec_src &&
+ daemon->frec_src_count < daemon->ftabsize &&
+ (daemon->free_frec_src = whine_malloc(sizeof(struct frec_src))))
+ {
+ daemon->frec_src_count++;
+ daemon->free_frec_src->next = NULL;
+ }
+
+ /* If we've been spammed with many duplicates, just drop the query. */
+ if (daemon->free_frec_src)
+ {
+ struct frec_src *new = daemon->free_frec_src;
+ daemon->free_frec_src = new->next;
+ new->next = forward->frec_src.next;
+ forward->frec_src.next = new;
+ new->orig_id = ntohs(header->id);
+ new->source = *udpaddr;
+ new->dest = *dst_addr;
+ new->log_id = daemon->log_id;
+ new->iface = dst_iface;
+ }
+
+ return 1;
+ }
+
if (gotname)
flags = search_servers(now, &addrp, gotname, daemon->namebuff, &type, &domain, &norebind);
@@ -346,22 +390,23 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
do_dnssec = type & SERV_DO_DNSSEC;
#endif
type &= ~SERV_DO_DNSSEC;
-
+
if (daemon->servers && !flags)
forward = get_new_frec(now, NULL, NULL);
/* table full - flags == 0, return REFUSED */
if (forward)
{
- forward->source = *udpaddr;
- forward->dest = *dst_addr;
- forward->iface = dst_iface;
- forward->orig_id = ntohs(header->id);
+ forward->frec_src.source = *udpaddr;
+ forward->frec_src.orig_id = ntohs(header->id);
+ forward->frec_src.dest = *dst_addr;
+ forward->frec_src.iface = dst_iface;
+ forward->frec_src.next = NULL;
forward->new_id = get_id();
forward->fd = udpfd;
memcpy(forward->hash, hash, HASH_SIZE);
forward->forwardall = 0;
- forward->flags = 0;
+ forward->flags = fwd_flags;
if (norebind)
forward->flags |= FREC_NOREBIND;
if (header->hb4 & HB4_CD)
@@ -411,18 +456,21 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
if (!flags && forward)
{
struct server *firstsentto = start;
- int subnet, forwarded = 0;
+ int subnet, cacheable, forwarded = 0;
size_t edns0_len;
unsigned char *pheader;
/* If a query is retried, use the log_id for the retry when logging the answer. */
- forward->log_id = daemon->log_id;
+ forward->frec_src.log_id = daemon->log_id;
- plen = add_edns0_config(header, plen, ((unsigned char *)header) + PACKETSZ, &forward->source, now, &subnet);
+ plen = add_edns0_config(header, plen, ((unsigned char *)header) + PACKETSZ, &forward->frec_src.source, now, &subnet, &cacheable);
if (subnet)
forward->flags |= FREC_HAS_SUBNET;
-
+
+ if (!cacheable)
+ forward->flags |= FREC_NO_CACHE;
+
#ifdef HAVE_DNSSEC
if (option_bool(OPT_DNSSEC_VALID) && do_dnssec)
{
@@ -490,7 +538,7 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
if (option_bool(OPT_CONNTRACK))
{
unsigned int mark;
- if (get_incoming_mark(&forward->source, &forward->dest, 0, &mark))
+ if (get_incoming_mark(&forward->frec_src.source, &forward->frec_src.dest, 0, &mark))
setsockopt(fd, SOL_SOCKET, SO_MARK, &mark, sizeof(unsigned int));
}
#endif
@@ -555,7 +603,7 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
return 1;
/* could not send on, prepare to return */
- header->id = htons(forward->orig_id);
+ header->id = htons(forward->frec_src.orig_id);
free_frec(forward); /* cancel */
}
@@ -606,7 +654,7 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server
}
}
#endif
-
+
if ((pheader = find_pseudoheader(header, n, &plen, &sizep, &is_sign, NULL)))
{
/* Get extended RCODE. */
@@ -769,9 +817,6 @@ void reply_query(int fd, int family, time_t now)
size_t nn;
struct server *server;
void *hash;
-#ifndef HAVE_DNSSEC
- unsigned int crc;
-#endif
/* packet buffer overwritten */
daemon->srv_save = NULL;
@@ -798,14 +843,9 @@ void reply_query(int fd, int family, time_t now)
if (difftime(now, server->pktsz_reduced) > UDP_TEST_TIME)
server->edns_pktsz = daemon->edns_pktsz;
-#ifdef HAVE_DNSSEC
hash = hash_questions(header, n, daemon->namebuff);
-#else
- hash = &crc;
- crc = questions_crc(header, n, daemon->namebuff);
-#endif
- if (!(forward = lookup_frec(ntohs(header->id), hash)))
+ if (!(forward = lookup_frec(ntohs(header->id), fd, family, hash)))
return;
#ifdef HAVE_DUMPFILE
@@ -815,8 +855,8 @@ void reply_query(int fd, int family, time_t now)
/* log_query gets called indirectly all over the place, so
pass these in global variables - sorry. */
- daemon->log_display_id = forward->log_id;
- daemon->log_source_addr = &forward->source;
+ daemon->log_display_id = forward->frec_src.log_id;
+ daemon->log_source_addr = &forward->frec_src.source;
if (daemon->ignore_addr && RCODE(header) == NOERROR &&
check_for_ignored_address(header, n, daemon->ignore_addr))
@@ -834,7 +874,6 @@ void reply_query(int fd, int family, time_t now)
int is_sign;
#ifdef HAVE_DNSSEC
- /* For DNSSEC originated queries, just retry the query to the same server. */
if (forward->flags & (FREC_DNSKEY_QUERY | FREC_DS_QUERY))
{
struct server *start;
@@ -860,6 +899,8 @@ void reply_query(int fd, int family, time_t now)
}
+ fd = -1;
+
if (start->sfd)
fd = start->sfd->fd;
else
@@ -867,19 +908,21 @@ void reply_query(int fd, int family, time_t now)
if (start->addr.sa.sa_family == AF_INET6)
{
/* may have changed family */
- if (!forward->rfd6)
- forward->rfd6 = allocate_rfd(AF_INET6);
- fd = forward->rfd6->fd;
+ if (forward->rfd6 || (forward->rfd6 = allocate_rfd(AF_INET6)))
+ fd = forward->rfd6->fd;
}
else
{
/* may have changed family */
- if (!forward->rfd4)
- forward->rfd4 = allocate_rfd(AF_INET);
- fd = forward->rfd4->fd;
+ if (forward->rfd4 || (forward->rfd4 = allocate_rfd(AF_INET)))
+ fd = forward->rfd4->fd;
}
}
+ /* Can't get socket. */
+ if (fd == -1)
+ return;
+
#ifdef HAVE_DUMPFILE
dump_packet(DUMP_SEC_QUERY, (void *)header, (size_t)plen, NULL, &start->addr);
#endif
@@ -1088,6 +1131,7 @@ void reply_query(int fd, int family, time_t now)
new->sentto = server;
new->rfd4 = NULL;
new->rfd6 = NULL;
+ new->frec_src.next = NULL;
new->flags &= ~(FREC_DNSKEY_QUERY | FREC_DS_QUERY | FREC_HAS_EXTRADATA);
new->forwardall = 0;
@@ -1115,8 +1159,7 @@ void reply_query(int fd, int family, time_t now)
log_query(F_NOEXTRA | F_DNSSEC | F_IPV6, daemon->keyname, (union all_addr *)&(server->addr.in6.sin6_addr),
querystr("dnssec-query", querytype));
- if ((hash = hash_questions(header, nn, daemon->namebuff)))
- memcpy(new->hash, hash, HASH_SIZE);
+ memcpy(new->hash, hash_questions(header, nn, daemon->namebuff), HASH_SIZE);
new->new_id = get_id();
header->id = htons(new->new_id);
/* Save query for retransmission */
@@ -1150,7 +1193,7 @@ void reply_query(int fd, int family, time_t now)
if (option_bool(OPT_CONNTRACK))
{
unsigned int mark;
- if (get_incoming_mark(&orig->source, &orig->dest, 0, &mark))
+ if (get_incoming_mark(&orig->frec_src.source, &orig->frec_src.dest, 0, &mark))
setsockopt(fd, SOL_SOCKET, SO_MARK, &mark, sizeof(unsigned int));
}
#endif
@@ -1221,12 +1264,19 @@ void reply_query(int fd, int family, time_t now)
header->hb4 |= HB4_CD;
else
header->hb4 &= ~HB4_CD;
+
+ /* Never cache answers which are contingent on the source or MAC address EDSN0 option,
+ since the cache is ignorant of such things. */
+ if (forward->flags & FREC_NO_CACHE)
+ no_cache_dnssec = 1;
if ((nn = process_reply(header, now, forward->sentto, (size_t)n, check_rebind, no_cache_dnssec, cache_secure, bogusanswer,
forward->flags & FREC_AD_QUESTION, forward->flags & FREC_DO_QUESTION,
- forward->flags & FREC_ADDED_PHEADER, forward->flags & FREC_HAS_SUBNET, &forward->source)))
+ forward->flags & FREC_ADDED_PHEADER, forward->flags & FREC_HAS_SUBNET, &forward->frec_src.source)))
{
- header->id = htons(forward->orig_id);
+ struct frec_src *src;
+
+ header->id = htons(forward->frec_src.orig_id);
header->hb4 |= HB4_RA; /* recursion if available */
#ifdef HAVE_DNSSEC
/* We added an EDNSO header for the purpose of getting DNSSEC RRs, and set the value of the UDP payload size
@@ -1242,13 +1292,26 @@ void reply_query(int fd, int family, time_t now)
}
#endif
+ for (src = &forward->frec_src; src; src = src->next)
+ {
+ header->id = htons(src->orig_id);
+
#ifdef HAVE_DUMPFILE
- dump_packet(DUMP_REPLY, daemon->packet, (size_t)nn, NULL, &forward->source);
+ dump_packet(DUMP_REPLY, daemon->packet, (size_t)nn, NULL, &src->source);
#endif
-
- send_from(forward->fd, option_bool(OPT_NOWILD) || option_bool (OPT_CLEVERBIND), daemon->packet, nn,
- &forward->source, &forward->dest, forward->iface);
+
+ send_from(forward->fd, option_bool(OPT_NOWILD) || option_bool (OPT_CLEVERBIND), daemon->packet, nn,
+ &src->source, &src->dest, src->iface);
+
+ if (option_bool(OPT_EXTRALOG) && src != &forward->frec_src)
+ {
+ daemon->log_display_id = src->log_id;
+ daemon->log_source_addr = &src->source;
+ log_query(F_UPSTREAM, "query", NULL, "duplicate");
+ }
+ }
}
+
free_frec(forward); /* cancel */
}
}
@@ -1767,7 +1830,7 @@ unsigned char *tcp_request(int confd, time_t now,
int local_auth = 0;
#endif
int checking_disabled, do_bit, added_pheader = 0, have_pseudoheader = 0;
- int check_subnet, no_cache_dnssec = 0, cache_secure = 0, bogusanswer = 0;
+ int check_subnet, cacheable, no_cache_dnssec = 0, cache_secure = 0, bogusanswer = 0;
size_t m;
unsigned short qtype;
unsigned int gotname;
@@ -1938,7 +2001,7 @@ unsigned char *tcp_request(int confd, time_t now,
char *domain = NULL;
unsigned char *oph = find_pseudoheader(header, size, NULL, NULL, NULL, NULL);
- size = add_edns0_config(header, size, ((unsigned char *) header) + 65536, &peer_addr, now, &check_subnet);
+ size = add_edns0_config(header, size, ((unsigned char *) header) + 65536, &peer_addr, now, &check_subnet, &cacheable);
if (gotname)
flags = search_servers(now, &addrp, gotname, daemon->namebuff, &type, &domain, &norebind);
@@ -1970,15 +2033,9 @@ unsigned char *tcp_request(int confd, time_t now,
if (!flags && last_server)
{
struct server *firstsendto = NULL;
-#ifdef HAVE_DNSSEC
- unsigned char *newhash, hash[HASH_SIZE];
- if ((newhash = hash_questions(header, (unsigned int)size, daemon->namebuff)))
- memcpy(hash, newhash, HASH_SIZE);
- else
- memset(hash, 0, HASH_SIZE);
-#else
- unsigned int crc = questions_crc(header, (unsigned int)size, daemon->namebuff);
-#endif
+ unsigned char hash[HASH_SIZE];
+ memcpy(hash, hash_questions(header, (unsigned int)size, daemon->namebuff), HASH_SIZE);
+
/* Loop round available servers until we succeed in connecting to one.
Note that this code subtly ensures that consecutive queries on this connection
which can go to the same server, do so. */
@@ -2117,21 +2174,17 @@ unsigned char *tcp_request(int confd, time_t now,
/* If the crc of the question section doesn't match the crc we sent, then
someone might be attempting to insert bogus values into the cache by
sending replies containing questions and bogus answers. */
-#ifdef HAVE_DNSSEC
- newhash = hash_questions(header, (unsigned int)m, daemon->namebuff);
- if (!newhash || memcmp(hash, newhash, HASH_SIZE) != 0)
+ if (memcmp(hash, hash_questions(header, (unsigned int)m, daemon->namebuff), HASH_SIZE) != 0)
{
m = 0;
break;
}
-#else
- if (crc != questions_crc(header, (unsigned int)m, daemon->namebuff))
- {
- m = 0;
- break;
- }
-#endif
+ /* Never cache answers which are contingent on the source or MAC address EDSN0 option,
+ since the cache is ignorant of such things. */
+ if (!cacheable)
+ no_cache_dnssec = 1;
+
m = process_reply(header, now, last_server, (unsigned int)m,
option_bool(OPT_NO_REBIND) && !norebind, no_cache_dnssec, cache_secure, bogusanswer,
ad_reqd, do_bit, added_pheader, check_subnet, &peer_addr);
@@ -2226,6 +2279,17 @@ void free_rfd(struct randfd *rfd)
static void free_frec(struct frec *f)
{
+ struct frec_src *last;
+
+ /* add back to freelist if not the record builtin to every frec. */
+ for (last = f->frec_src.next; last && last->next; last = last->next) ;
+ if (last)
+ {
+ last->next = daemon->free_frec_src;
+ daemon->free_frec_src = f->frec_src.next;
+ }
+
+ f->frec_src.next = NULL;
free_rfd(f->rfd4);
f->rfd4 = NULL;
f->sentto = NULL;
@@ -2338,15 +2402,25 @@ struct frec *get_new_frec(time_t now, int *wait, struct frec *force)
return f; /* OK if malloc fails and this is NULL */
}
-/* crc is all-ones if not known. */
-static struct frec *lookup_frec(unsigned short id, void *hash)
+static struct frec *lookup_frec(unsigned short id, int fd, int family, void *hash)
{
struct frec *f;
for(f = daemon->frec_list; f; f = f->next)
if (f->sentto && f->new_id == id &&
- (!hash || memcmp(hash, f->hash, HASH_SIZE) == 0))
- return f;
+ (memcmp(hash, f->hash, HASH_SIZE) == 0))
+ {
+ /* sent from random port */
+ if (family == AF_INET && f->rfd4 && f->rfd4->fd == fd)
+ return f;
+
+ if (family == AF_INET6 && f->rfd6 && f->rfd6->fd == fd)
+ return f;
+
+ /* sent to upstream from bound socket. */
+ if (f->sentto->sfd && f->sentto->sfd->fd == fd)
+ return f;
+ }
return NULL;
}
@@ -2356,17 +2430,42 @@ static struct frec *lookup_frec_by_sender(unsigned short id,
void *hash)
{
struct frec *f;
+ struct frec_src *src;
+
+ for (f = daemon->frec_list; f; f = f->next)
+ if (f->sentto &&
+ !(f->flags & (FREC_DNSKEY_QUERY | FREC_DS_QUERY)) &&
+ memcmp(hash, f->hash, HASH_SIZE) == 0)
+ for (src = &f->frec_src; src; src = src->next)
+ if (src->orig_id == id &&
+ sockaddr_isequal(&src->source, addr))
+ return f;
+
+ return NULL;
+}
+
+static struct frec *lookup_frec_by_query(void *hash, unsigned int flags)
+{
+ struct frec *f;
+
+ /* FREC_DNSKEY and FREC_DS_QUERY are never set in flags, so the test below
+ ensures that no frec created for internal DNSSEC query can be returned here.
+
+ Similarly FREC_NO_CACHE is never set in flags, so a query which is
+ contigent on a particular source address EDNS0 option will never be matched. */
+
+#define FLAGMASK (FREC_CHECKING_DISABLED | FREC_AD_QUESTION | FREC_DO_QUESTION \
+ | FREC_HAS_PHEADER | FREC_DNSKEY_QUERY | FREC_DS_QUERY | FREC_NO_CACHE)
for(f = daemon->frec_list; f; f = f->next)
if (f->sentto &&
- f->orig_id == id &&
- memcmp(hash, f->hash, HASH_SIZE) == 0 &&
- sockaddr_isequal(&f->source, addr))
+ (f->flags & FLAGMASK) == flags &&
+ memcmp(hash, f->hash, HASH_SIZE) == 0)
return f;
-
+
return NULL;
}
-
+
/* Send query packet again, if we can. */
void resend_query()
{
@@ -2407,12 +2506,20 @@ void server_gone(struct server *server)
static unsigned short get_id(void)
{
unsigned short ret = 0;
+ struct frec *f;
- do
- ret = rand16();
- while (lookup_frec(ret, NULL));
-
- return ret;
+ while (1)
+ {
+ ret = rand16();
+
+ /* ensure id is unique. */
+ for (f = daemon->frec_list; f; f = f->next)
+ if (f->sentto && f->new_id == ret)
+ break;
+
+ if (!f)
+ return ret;
+ }
}
diff --git a/src/hash_questions.c b/src/hash_questions.c
new file mode 100644
index 0000000..51d88c2
--- /dev/null
+++ b/src/hash_questions.c
@@ -0,0 +1,281 @@
+/* Copyright (c) 2012-2020 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 <http://www.gnu.org/licenses/>.
+*/
+
+
+/* Hash the question section. This is used to safely detect query
+ retransmission and to detect answers to questions we didn't ask, which
+ might be poisoning attacks. Note that we decode the name rather
+ than CRC the raw bytes, since replies might be compressed differently.
+ We ignore case in the names for the same reason.
+
+ The hash used is SHA-256. If we're building with DNSSEC support,
+ we use the Nettle cypto library. If not, we prefer not to
+ add a dependency on Nettle, and use a stand-alone implementaion.
+*/
+
+#include "dnsmasq.h"
+
+#if defined(HAVE_DNSSEC) || defined(HAVE_NETTLEHASH)
+unsigned char *hash_questions(struct dns_header *header, size_t plen, char *name)
+{
+ int q;
+ unsigned char *p = (unsigned char *)(header+1);
+ const struct nettle_hash *hash;
+ void *ctx;
+ unsigned char *digest;
+
+ if (!(hash = hash_find("sha256")) || !hash_init(hash, &ctx, &digest))
+ {
+ /* don't think this can ever happen. */
+ static unsigned char dummy[HASH_SIZE];
+ static int warned = 0;
+
+ if (!warned)
+ my_syslog(LOG_ERR, _("Failed to create SHA-256 hash object"));
+ warned = 1;
+
+ return dummy;
+ }
+
+ for (q = ntohs(header->qdcount); q != 0; q--)
+ {
+ char *cp, c;
+
+ if (!extract_name(header, plen, &p, name, 1, 4))
+ break; /* bad packet */
+
+ for (cp = name; (c = *cp); cp++)
+ if (c >= 'A' && c <= 'Z')
+ *cp += 'a' - 'A';
+
+ hash->update(ctx, cp - name, (unsigned char *)name);
+ /* CRC the class and type as well */
+ hash->update(ctx, 4, p);
+
+ p += 4;
+ if (!CHECK_LEN(header, p, plen, 0))
+ break; /* bad packet */
+ }
+
+ hash->digest(ctx, hash->digest_size, digest);
+ return digest;
+}
+
+#else /* HAVE_DNSSEC */
+
+#define SHA256_BLOCK_SIZE 32 // SHA256 outputs a 32 byte digest
+typedef unsigned char BYTE; // 8-bit byte
+typedef unsigned int WORD; // 32-bit word, change to "long" for 16-bit machines
+
+typedef struct {
+ BYTE data[64];
+ WORD datalen;
+ unsigned long long bitlen;
+ WORD state[8];
+} SHA256_CTX;
+
+static void sha256_init(SHA256_CTX *ctx);
+static void sha256_update(SHA256_CTX *ctx, const BYTE data[], size_t len);
+static void sha256_final(SHA256_CTX *ctx, BYTE hash[]);
+
+
+unsigned char *hash_questions(struct dns_header *header, size_t plen, char *name)
+{
+ int q;
+ unsigned char *p = (unsigned char *)(header+1);
+ SHA256_CTX ctx;
+ static BYTE digest[SHA256_BLOCK_SIZE];
+
+ sha256_init(&ctx);
+
+ for (q = ntohs(header->qdcount); q != 0; q--)
+ {
+ char *cp, c;
+
+ if (!extract_name(header, plen, &p, name, 1, 4))
+ break; /* bad packet */
+
+ for (cp = name; (c = *cp); cp++)
+ if (c >= 'A' && c <= 'Z')
+ *cp += 'a' - 'A';
+
+ sha256_update(&ctx, (BYTE *)name, cp - name);
+ /* CRC the class and type as well */
+ sha256_update(&ctx, (BYTE *)p, 4);
+
+ p += 4;
+ if (!CHECK_LEN(header, p, plen, 0))
+ break; /* bad packet */
+ }
+
+ sha256_final(&ctx, digest);
+ return (unsigned char *)digest;
+}
+
+/* Code from here onwards comes from https://github.com/B-Con/crypto-algorithms
+ and was written by Brad Conte (brad@bradconte.com), to whom all credit is given.
+
+ This code is in the public domain, and the copyright notice at the head of this
+ file does not apply to it.
+*/
+
+
+/****************************** MACROS ******************************/
+#define ROTLEFT(a,b) (((a) << (b)) | ((a) >> (32-(b))))
+#define ROTRIGHT(a,b) (((a) >> (b)) | ((a) << (32-(b))))
+
+#define CH(x,y,z) (((x) & (y)) ^ (~(x) & (z)))
+#define MAJ(x,y,z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z)))
+#define EP0(x) (ROTRIGHT(x,2) ^ ROTRIGHT(x,13) ^ ROTRIGHT(x,22))
+#define EP1(x) (ROTRIGHT(x,6) ^ ROTRIGHT(x,11) ^ ROTRIGHT(x,25))
+#define SIG0(x) (ROTRIGHT(x,7) ^ ROTRIGHT(x,18) ^ ((x) >> 3))
+#define SIG1(x) (ROTRIGHT(x,17) ^ ROTRIGHT(x,19) ^ ((x) >> 10))
+
+/**************************** VARIABLES *****************************/
+static const WORD k[64] = {
+ 0x428a2f98,0x71374491,0xb5c0fbcf,0xe9b5dba5,0x3956c25b,0x59f111f1,0x923f82a4,0xab1c5ed5,
+ 0xd807aa98,0x12835b01,0x243185be,0x550c7dc3,0x72be5d74,0x80deb1fe,0x9bdc06a7,0xc19bf174,
+ 0xe49b69c1,0xefbe4786,0x0fc19dc6,0x240ca1cc,0x2de92c6f,0x4a7484aa,0x5cb0a9dc,0x76f988da,
+ 0x983e5152,0xa831c66d,0xb00327c8,0xbf597fc7,0xc6e00bf3,0xd5a79147,0x06ca6351,0x14292967,
+ 0x27b70a85,0x2e1b2138,0x4d2c6dfc,0x53380d13,0x650a7354,0x766a0abb,0x81c2c92e,0x92722c85,
+ 0xa2bfe8a1,0xa81a664b,0xc24b8b70,0xc76c51a3,0xd192e819,0xd6990624,0xf40e3585,0x106aa070,
+ 0x19a4c116,0x1e376c08,0x2748774c,0x34b0bcb5,0x391c0cb3,0x4ed8aa4a,0x5b9cca4f,0x682e6ff3,
+ 0x748f82ee,0x78a5636f,0x84c87814,0x8cc70208,0x90befffa,0xa4506ceb,0xbef9a3f7,0xc67178f2
+};
+
+/*********************** FUNCTION DEFINITIONS ***********************/
+static void sha256_transform(SHA256_CTX *ctx, const BYTE data[])
+{
+ WORD a, b, c, d, e, f, g, h, i, j, t1, t2, m[64];
+
+ for (i = 0, j = 0; i < 16; ++i, j += 4)
+ m[i] = (data[j] << 24) | (data[j + 1] << 16) | (data[j + 2] << 8) | (data[j + 3]);
+ for ( ; i < 64; ++i)
+ m[i] = SIG1(m[i - 2]) + m[i - 7] + SIG0(m[i - 15]) + m[i - 16];
+
+ a = ctx->state[0];
+ b = ctx->state[1];
+ c = ctx->state[2];
+ d = ctx->state[3];
+ e = ctx->state[4];
+ f = ctx->state[5];
+ g = ctx->state[6];
+ h = ctx->state[7];
+
+ for (i = 0; i < 64; ++i)
+ {
+ t1 = h + EP1(e) + CH(e,f,g) + k[i] + m[i];
+ t2 = EP0(a) + MAJ(a,b,c);
+ h = g;
+ g = f;
+ f = e;
+ e = d + t1;
+ d = c;
+ c = b;
+ b = a;
+ a = t1 + t2;
+ }
+
+ ctx->state[0] += a;
+ ctx->state[1] += b;
+ ctx->state[2] += c;
+ ctx->state[3] += d;
+ ctx->state[4] += e;
+ ctx->state[5] += f;
+ ctx->state[6] += g;
+ ctx->state[7] += h;
+}
+
+static void sha256_init(SHA256_CTX *ctx)
+{
+ ctx->datalen = 0;
+ ctx->bitlen = 0;
+ ctx->state[0] = 0x6a09e667;
+ ctx->state[1] = 0xbb67ae85;
+ ctx->state[2] = 0x3c6ef372;
+ ctx->state[3] = 0xa54ff53a;
+ ctx->state[4] = 0x510e527f;
+ ctx->state[5] = 0x9b05688c;
+ ctx->state[6] = 0x1f83d9ab;
+ ctx->state[7] = 0x5be0cd19;
+}
+
+static void sha256_update(SHA256_CTX *ctx, const BYTE data[], size_t len)
+{
+ WORD i;
+
+ for (i = 0; i < len; ++i)
+ {
+ ctx->data[ctx->datalen] = data[i];
+ ctx->datalen++;
+ if (ctx->datalen == 64) {
+ sha256_transform(ctx, ctx->data);
+ ctx->bitlen += 512;
+ ctx->datalen = 0;
+ }
+ }
+}
+
+static void sha256_final(SHA256_CTX *ctx, BYTE hash[])
+{
+ WORD i;
+
+ i = ctx->datalen;
+
+ // Pad whatever data is left in the buffer.
+ if (ctx->datalen < 56)
+ {
+ ctx->data[i++] = 0x80;
+ while (i < 56)
+ ctx->data[i++] = 0x00;
+ }
+ else
+ {
+ ctx->data[i++] = 0x80;
+ while (i < 64)
+ ctx->data[i++] = 0x00;
+ sha256_transform(ctx, ctx->data);
+ memset(ctx->data, 0, 56);
+ }
+
+ // Append to the padding the total message's length in bits and transform.
+ ctx->bitlen += ctx->datalen * 8;
+ ctx->data[63] = ctx->bitlen;
+ ctx->data[62] = ctx->bitlen >> 8;
+ ctx->data[61] = ctx->bitlen >> 16;
+ ctx->data[60] = ctx->bitlen >> 24;
+ ctx->data[59] = ctx->bitlen >> 32;
+ ctx->data[58] = ctx->bitlen >> 40;
+ ctx->data[57] = ctx->bitlen >> 48;
+ ctx->data[56] = ctx->bitlen >> 56;
+ sha256_transform(ctx, ctx->data);
+
+ // Since this implementation uses little endian byte ordering and SHA uses big endian,
+ // reverse all the bytes when copying the final state to the output hash.
+ for (i = 0; i < 4; ++i)
+ {
+ hash[i] = (ctx->state[0] >> (24 - i * 8)) & 0x000000ff;
+ hash[i + 4] = (ctx->state[1] >> (24 - i * 8)) & 0x000000ff;
+ hash[i + 8] = (ctx->state[2] >> (24 - i * 8)) & 0x000000ff;
+ hash[i + 12] = (ctx->state[3] >> (24 - i * 8)) & 0x000000ff;
+ hash[i + 16] = (ctx->state[4] >> (24 - i * 8)) & 0x000000ff;
+ hash[i + 20] = (ctx->state[5] >> (24 - i * 8)) & 0x000000ff;
+ hash[i + 24] = (ctx->state[6] >> (24 - i * 8)) & 0x000000ff;
+ hash[i + 28] = (ctx->state[7] >> (24 - i * 8)) & 0x000000ff;
+ }
+}
+
+#endif
diff --git a/src/network.c b/src/network.c
index c7d002b..7cf2546 100644
--- a/src/network.c
+++ b/src/network.c
@@ -1262,17 +1262,46 @@ int random_sock(int family)
int local_bind(int fd, union mysockaddr *addr, char *intname, unsigned int ifindex, int is_tcp)
{
union mysockaddr addr_copy = *addr;
+ unsigned short port;
+ int tries = 1, done = 0;
+ unsigned int ports_avail = ((unsigned short)daemon->max_port - (unsigned short)daemon->min_port) + 1;
+
+ if (addr_copy.sa.sa_family == AF_INET)
+ port = addr_copy.in.sin_port;
+ else
+ port = addr_copy.in6.sin6_port;
/* cannot set source _port_ for TCP connections. */
if (is_tcp)
+ port = 0;
+
+ /* Bind a random port within the range given by min-port and max-port */
+ if (port == 0)
+ {
+ tries = ports_avail < 30 ? 3 * ports_avail : 100;
+ port = htons(daemon->min_port + (rand16() % ((unsigned short)ports_avail)));
+ }
+
+ while (tries--)
{
if (addr_copy.sa.sa_family == AF_INET)
- addr_copy.in.sin_port = 0;
+ addr_copy.in.sin_port = port;
else
- addr_copy.in6.sin6_port = 0;
+ addr_copy.in6.sin6_port = port;
+
+ if (bind(fd, (struct sockaddr *)&addr_copy, sa_len(&addr_copy)) != -1)
+ {
+ done = 1;
+ break;
+ }
+
+ if (errno != EADDRINUSE && errno != EACCES)
+ return 0;
+
+ port = htons(daemon->min_port + (rand16() % ((unsigned short)ports_avail)));
}
-
- if (bind(fd, (struct sockaddr *)&addr_copy, sa_len(&addr_copy)) == -1)
+
+ if (!done)
return 0;
if (!is_tcp && ifindex > 0)
diff --git a/src/option.c b/src/option.c
index dbe5f90..316d9c8 100644
--- a/src/option.c
+++ b/src/option.c
@@ -167,6 +167,7 @@ struct myoption {
#define LOPT_IGNORE_CLID 358
#define LOPT_SINGLE_PORT 359
#define LOPT_SCRIPT_TIME 360
+#define LOPT_PXE_VENDOR 361
#ifdef HAVE_GETOPT_LONG
static const struct option opts[] =
@@ -270,6 +271,7 @@ static const struct myoption opts[] =
{ "dhcp-circuitid", 1, 0, LOPT_CIRCUIT },
{ "dhcp-remoteid", 1, 0, LOPT_REMOTE },
{ "dhcp-subscrid", 1, 0, LOPT_SUBSCR },
+ { "dhcp-pxe-vendor", 1, 0, LOPT_PXE_VENDOR },
{ "interface-name", 1, 0, LOPT_INTNAME },
{ "dhcp-hostsfile", 1, 0, LOPT_DHCP_HOST },
{ "dhcp-optsfile", 1, 0, LOPT_DHCP_OPTS },
@@ -383,6 +385,7 @@ static struct {
{ LOPT_CIRCUIT, ARG_DUP, "set:<tag>,<circuit>", gettext_noop("Map RFC3046 circuit-id to tag."), NULL },
{ LOPT_REMOTE, ARG_DUP, "set:<tag>,<remote>", gettext_noop("Map RFC3046 remote-id to tag."), NULL },
{ LOPT_SUBSCR, ARG_DUP, "set:<tag>,<remote>", gettext_noop("Map RFC3993 subscriber-id to tag."), NULL },
+ { LOPT_PXE_VENDOR, ARG_DUP, "<vendor>[,...]", gettext_noop("Specify vendor class to match for PXE requests."), NULL },
{ 'J', ARG_DUP, "tag:<tag>...", gettext_noop("Don't do DHCP for hosts with tag set."), NULL },
{ LOPT_BROADCAST, ARG_DUP, "[=tag:<tag>...]", gettext_noop("Force broadcast replies for hosts with tag set."), NULL },
{ 'k', OPT_NO_FORK, NULL, gettext_noop("Do NOT fork into the background, do NOT run in debug mode."), NULL },
@@ -3672,8 +3675,8 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
new->val = opt_malloc(new->len);
memcpy(new->val + 1, arg, new->len - 1);
- new->u.vendor_class = (unsigned char *)"PXEClient";
- new->flags = DHOPT_VENDOR;
+ new->u.vendor_class = NULL;
+ new->flags = DHOPT_VENDOR | DHOPT_VENDOR_PXE;
if (comma && atoi_check(comma, &timeout))
*(new->val) = timeout;
@@ -3935,6 +3938,19 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
new->next = daemon->override_relays;
daemon->override_relays = new;
arg = comma;
+ }
+ break;
+
+ case LOPT_PXE_VENDOR: /* --dhcp-pxe-vendor */
+ {
+ while (arg) {
+ struct dhcp_pxe_vendor *new = opt_malloc(sizeof(struct dhcp_pxe_vendor));
+ comma = split(arg);
+ new->data = opt_string_alloc(arg);
+ new->next = daemon->dhcp_pxe_vendors;
+ daemon->dhcp_pxe_vendors = new;
+ arg = comma;
+ }
}
break;
@@ -5212,6 +5228,13 @@ void read_opts(int argc, char **argv, char *compile_opts)
strcat(buff, daemon->authserver);
daemon->hostmaster = opt_string_alloc(buff);
}
+
+ if (!daemon->dhcp_pxe_vendors)
+ {
+ daemon->dhcp_pxe_vendors = opt_malloc(sizeof(struct dhcp_pxe_vendor));
+ daemon->dhcp_pxe_vendors->data = opt_string_alloc(DHCP_PXE_DEF_VENDOR);
+ daemon->dhcp_pxe_vendors->next = NULL;
+ }
/* only one of these need be specified: the other defaults to the host-name */
if (option_bool(OPT_LOCALMX) || daemon->mxnames || daemon->mxtarget)
diff --git a/src/rfc1035.c b/src/rfc1035.c
index 7fc4b2f..3b404a0 100644
--- a/src/rfc1035.c
+++ b/src/rfc1035.c
@@ -333,55 +333,6 @@ unsigned char *skip_section(unsigned char *ansp, int count, struct dns_header *h
return ansp;
}
-/* CRC the question section. This is used to safely detect query
- retransmission and to detect answers to questions we didn't ask, which
- might be poisoning attacks. Note that we decode the name rather
- than CRC the raw bytes, since replies might be compressed differently.
- We ignore case in the names for the same reason. Return all-ones
- if there is not question section. */
-#ifndef HAVE_DNSSEC
-unsigned int questions_crc(struct dns_header *header, size_t plen, char *name)
-{
- int q;
- unsigned int crc = 0xffffffff;
- unsigned char *p1, *p = (unsigned char *)(header+1);
-
- for (q = ntohs(header->qdcount); q != 0; q--)
- {
- if (!extract_name(header, plen, &p, name, 1, 4))
- return crc; /* bad packet */
-
- for (p1 = (unsigned char *)name; *p1; p1++)
- {
- int i = 8;
- char c = *p1;
-
- if (c >= 'A' && c <= 'Z')
- c += 'a' - 'A';
-
- crc ^= c << 24;
- while (i--)
- crc = crc & 0x80000000 ? (crc << 1) ^ 0x04c11db7 : crc << 1;
- }
-
- /* CRC the class and type as well */
- for (p1 = p; p1 < p+4; p1++)
- {
- int i = 8;
- crc ^= *p1 << 24;
- while (i--)
- crc = crc & 0x80000000 ? (crc << 1) ^ 0x04c11db7 : crc << 1;
- }
-
- p += 4;
- if (!CHECK_LEN(header, p, plen, 0))
- return crc; /* bad packet */
- }
-
- return crc;
-}
-#endif
-
size_t resize_packet(struct dns_header *header, size_t plen, unsigned char *pheader, size_t hlen)
{
unsigned char *ansp = skip_questions(header, plen);
@@ -1408,6 +1359,8 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
}
}
+ else
+ return 0; /* give up if any cached CNAME in chain can't be used for DNSSEC reasons. */
strcpy(name, cname_target);
}
diff --git a/src/rfc2131.c b/src/rfc2131.c
index fc54aab..d678068 100644
--- a/src/rfc2131.c
+++ b/src/rfc2131.c
@@ -30,7 +30,7 @@ static struct in_addr server_id(struct dhcp_context *context, struct in_addr ove
static unsigned int calc_time(struct dhcp_context *context, struct dhcp_config *config, unsigned char *opt);
static void option_put(struct dhcp_packet *mess, unsigned char *end, int opt, int len, unsigned int val);
static void option_put_string(struct dhcp_packet *mess, unsigned char *end,
- int opt, char *string, int null_term);
+ int opt, const char *string, int null_term);
static struct in_addr option_addr(unsigned char *opt);
static unsigned int option_uint(unsigned char *opt, int offset, int size);
static void log_packet(char *type, void *addr, unsigned char *ext_mac,
@@ -54,17 +54,19 @@ static void do_options(struct dhcp_context *context,
int vendor_class_len,
time_t now,
unsigned int lease_time,
- unsigned short fuzz);
+ unsigned short fuzz,
+ const char *pxevendor);
static void match_vendor_opts(unsigned char *opt, struct dhcp_opt *dopt);
static int do_encap_opts(struct dhcp_opt *opt, int encap, int flag, struct dhcp_packet *mess, unsigned char *end, int null_term);
-static void pxe_misc(struct dhcp_packet *mess, unsigned char *end, unsigned char *uuid);
+static void pxe_misc(struct dhcp_packet *mess, unsigned char *end, unsigned char *uuid, const char *pxevendor);
static int prune_vendor_opts(struct dhcp_netid *netid);
static struct dhcp_opt *pxe_opts(int pxe_arch, struct dhcp_netid *netid, struct in_addr local, time_t now);
struct dhcp_boot *find_boot(struct dhcp_netid *netid);
static int pxe_uefi_workaround(int pxe_arch, struct dhcp_netid *netid, struct dhcp_packet *mess, struct in_addr local, time_t now, int pxe);
static void apply_delay(u32 xid, time_t recvtime, struct dhcp_netid *netid);
+static int is_pxe_client(struct dhcp_packet *mess, size_t sz, const char **pxe_vendor);
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,
@@ -76,6 +78,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
struct dhcp_mac *mac;
struct dhcp_netid_list *id_list;
int clid_len = 0, ignore = 0, do_classes = 0, rapid_commit = 0, selecting = 0, pxearch = -1;
+ const char *pxevendor = NULL;
struct dhcp_packet *mess = (struct dhcp_packet *)daemon->dhcp_packet.iov_base;
unsigned char *end = (unsigned char *)(mess + 1);
unsigned char *real_end = (unsigned char *)(mess + 1);
@@ -647,7 +650,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);
+ netid, subnet_addr, 0, 0, -1, NULL, vendor_class_len, now, 0xffffffff, 0, NULL);
}
}
@@ -835,9 +838,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 &&
- (opt = option_find(mess, sz, OPTION_VENDOR_ID, 9)) &&
- strncmp(option_ptr(opt, 0), "PXEClient", 9) == 0)
+ if (daemon->enable_pxe &&
+ is_pxe_client(mess, sz, &pxevendor))
{
if ((opt = option_find(mess, sz, OPTION_PXE_UUID, 17)))
{
@@ -899,7 +901,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
option_put(mess, end, OPTION_MESSAGE_TYPE, 1, DHCPACK);
option_put(mess, end, OPTION_SERVER_IDENTIFIER, INADDRSZ, htonl(context->local.s_addr));
- pxe_misc(mess, end, uuid);
+ pxe_misc(mess, end, uuid, pxevendor);
prune_vendor_opts(tagif_netid);
opt71.val = save71;
@@ -979,7 +981,7 @@ size_t dhcp_reply(struct dhcp_context *context, char *iface_name, int int_index,
option_put(mess, end, OPTION_MESSAGE_TYPE, 1,
mess_type == DHCPDISCOVER ? DHCPOFFER : DHCPACK);
option_put(mess, end, OPTION_SERVER_IDENTIFIER, INADDRSZ, htonl(tmp->local.s_addr));
- pxe_misc(mess, end, uuid);
+ pxe_misc(mess, end, uuid, pxevendor);
prune_vendor_opts(tagif_netid);
if ((pxe && !workaround) || !redirect4011)
do_encap_opts(pxe_opts(pxearch, tagif_netid, tmp->local, now), OPTION_VENDOR_CLASS_OPT, DHOPT_VENDOR_MATCH, mess, end, 0);
@@ -1150,7 +1152,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);
+ netid, subnet_addr, fqdn_flags, borken_opt, pxearch, uuid, vendor_class_len, now, time, fuzz, pxevendor);
return dhcp_packet_size(mess, agent_id, real_end);
@@ -1499,7 +1501,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);
+ netid, subnet_addr, fqdn_flags, borken_opt, pxearch, uuid, vendor_class_len, now, time, fuzz, pxevendor);
}
return dhcp_packet_size(mess, agent_id, real_end);
@@ -1566,7 +1568,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);
+ netid, subnet_addr, fqdn_flags, borken_opt, pxearch, uuid, vendor_class_len, now, 0xffffffff, 0, pxevendor);
*is_inform = 1; /* handle reply differently */
return dhcp_packet_size(mess, agent_id, real_end);
@@ -1948,7 +1950,7 @@ static void option_put(struct dhcp_packet *mess, unsigned char *end, int opt, in
}
static void option_put_string(struct dhcp_packet *mess, unsigned char *end, int opt,
- char *string, int null_term)
+ const char *string, int null_term)
{
unsigned char *p;
size_t len = strlen(string);
@@ -2026,15 +2028,32 @@ static void match_vendor_opts(unsigned char *opt, struct dhcp_opt *dopt)
dopt->flags &= ~DHOPT_VENDOR_MATCH;
if (opt && (dopt->flags & DHOPT_VENDOR))
{
- int i, len = 0;
- if (dopt->u.vendor_class)
- len = strlen((char *)dopt->u.vendor_class);
- for (i = 0; i <= (option_len(opt) - len); i++)
- if (len == 0 || memcmp(dopt->u.vendor_class, option_ptr(opt, i), len) == 0)
- {
- dopt->flags |= DHOPT_VENDOR_MATCH;
- break;
- }
+ const struct dhcp_pxe_vendor *pv;
+ struct dhcp_pxe_vendor dummy_vendor = {
+ .data = (char *)dopt->u.vendor_class,
+ .next = NULL,
+ };
+ if (dopt->flags & DHOPT_VENDOR_PXE)
+ pv = daemon->dhcp_pxe_vendors;
+ else
+ pv = &dummy_vendor;
+ for (; pv; pv = pv->next)
+ {
+ int i, len = 0, matched = 0;
+ if (pv->data)
+ len = strlen(pv->data);
+ for (i = 0; i <= (option_len(opt) - len); i++)
+ if (len == 0 || memcmp(pv->data, option_ptr(opt, i), len) == 0)
+ {
+ matched = 1;
+ break;
+ }
+ if (matched)
+ {
+ dopt->flags |= DHOPT_VENDOR_MATCH;
+ break;
+ }
+ }
}
}
}
@@ -2087,11 +2106,13 @@ static int do_encap_opts(struct dhcp_opt *opt, int encap, int flag,
return ret;
}
-static void pxe_misc(struct dhcp_packet *mess, unsigned char *end, unsigned char *uuid)
+static void pxe_misc(struct dhcp_packet *mess, unsigned char *end, unsigned char *uuid, const char *pxevendor)
{
unsigned char *p;
- option_put_string(mess, end, OPTION_VENDOR_ID, "PXEClient", 0);
+ if (!pxevendor)
+ pxevendor="PXEClient";
+ option_put_string(mess, end, OPTION_VENDOR_ID, pxevendor, 0);
if (uuid && (p = free_space(mess, end, OPTION_PXE_UUID, 17)))
memcpy(p, uuid, 17);
}
@@ -2308,6 +2329,29 @@ struct dhcp_boot *find_boot(struct dhcp_netid *netid)
return boot;
}
+static int is_pxe_client(struct dhcp_packet *mess, size_t sz, const char **pxe_vendor)
+{
+ const unsigned char *opt = NULL;
+ ssize_t conf_len = 0;
+ const struct dhcp_pxe_vendor *conf = daemon->dhcp_pxe_vendors;
+ opt = option_find(mess, sz, OPTION_VENDOR_ID, 0);
+ if (!opt)
+ return 0;
+ for (; conf; conf = conf->next)
+ {
+ conf_len = strlen(conf->data);
+ if (option_len(opt) < conf_len)
+ continue;
+ if (strncmp(option_ptr(opt, 0), conf->data, conf_len) == 0)
+ {
+ if (pxe_vendor)
+ *pxe_vendor = conf->data;
+ return 1;
+ }
+ }
+ return 0;
+}
+
static void do_options(struct dhcp_context *context,
struct dhcp_packet *mess,
unsigned char *end,
@@ -2322,7 +2366,8 @@ static void do_options(struct dhcp_context *context,
int vendor_class_len,
time_t now,
unsigned int lease_time,
- unsigned short fuzz)
+ unsigned short fuzz,
+ const char *pxevendor)
{
struct dhcp_opt *opt, *config_opts = daemon->dhcp_opts;
struct dhcp_boot *boot;
@@ -2696,7 +2741,7 @@ static void do_options(struct dhcp_context *context,
if (context && pxe_arch != -1)
{
- pxe_misc(mess, end, uuid);
+ pxe_misc(mess, end, uuid, pxevendor);
if (!pxe_uefi_workaround(pxe_arch, tagif, mess, context->local, now, 0))
config_opts = pxe_opts(pxe_arch, tagif, context->local, now);
}