mirror of
https://gitlab.isc.org/isc-projects/dhcp
synced 2025-08-22 09:57:20 +00:00
- Document update process.
- Add database management for tsig keys and DNS zones.
This commit is contained in:
parent
0a7593816b
commit
5b6b5c33b6
654
common/dns.c
654
common/dns.c
@ -3,7 +3,7 @@
|
|||||||
Domain Name Service subroutines. */
|
Domain Name Service subroutines. */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 1996-1999 Internet Software Consortium.
|
* Copyright (c) 2000 Internet Software Consortium.
|
||||||
* Use is subject to license terms which appear in the file named
|
* Use is subject to license terms which appear in the file named
|
||||||
* ISC-LICENSE that should have accompanied this file when you
|
* ISC-LICENSE that should have accompanied this file when you
|
||||||
* received it. If a file named ISC-LICENSE did not accompany this
|
* received it. If a file named ISC-LICENSE did not accompany this
|
||||||
@ -22,486 +22,206 @@
|
|||||||
|
|
||||||
#ifndef lint
|
#ifndef lint
|
||||||
static char copyright[] =
|
static char copyright[] =
|
||||||
"$Id: dns.c,v 1.16 2000/02/03 03:43:51 mellon Exp $ Copyright (c) 1997 The Internet Software Consortium. All rights reserved.\n";
|
"$Id: dns.c,v 1.17 2000/03/06 23:13:35 mellon Exp $ Copyright (c) 2000 The Internet Software Consortium. All rights reserved.\n";
|
||||||
#endif /* not lint */
|
#endif /* not lint */
|
||||||
|
|
||||||
#include "dhcpd.h"
|
#include "dhcpd.h"
|
||||||
#include "arpa/nameser.h"
|
#include "arpa/nameser.h"
|
||||||
|
|
||||||
int dns_protocol_initialized;
|
/* This file is kind of a crutch for the BIND 8 nsupdate code, which has
|
||||||
int dns_protocol_fd;
|
* itself been cruelly hacked from its original state. What this code
|
||||||
|
* does is twofold: first, it maintains a database of zone cuts that can
|
||||||
|
* be used to figure out which server should be contacted to update any
|
||||||
|
* given domain name. Secondly, it maintains a set of named TSIG keys,
|
||||||
|
* and associates those keys with zones. When an update is requested for
|
||||||
|
* a particular zone, the key associated with that zone is used for the
|
||||||
|
* update.
|
||||||
|
*
|
||||||
|
* The way this works is that you define the domain name to which an
|
||||||
|
* SOA corresponds, and the addresses of some primaries for that domain name:
|
||||||
|
*
|
||||||
|
* zone FOO.COM {
|
||||||
|
* primary 10.0.17.1;
|
||||||
|
* secondary 10.0.22.1, 10.0.23.1;
|
||||||
|
* tsig-key "FOO.COM Key";
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* If an update is requested for GAZANGA.TOPANGA.FOO.COM, then the name
|
||||||
|
* server looks in its database for a zone record for "GAZANGA.TOPANGA.FOO.COM",
|
||||||
|
* doesn't find it, looks for one for "TOPANGA.FOO.COM", doesn't find *that*,
|
||||||
|
* looks for "FOO.COM", finds it. So it
|
||||||
|
* attempts the update to the primary for FOO.COM. If that times out, it
|
||||||
|
* tries the secondaries. You can list multiple primaries if you have some
|
||||||
|
* kind of magic name server that supports that. You shouldn't list
|
||||||
|
* secondaries that don't know how to forward updates (e.g., BIND 8 doesn't
|
||||||
|
* support update forwarding, AFAIK). If no TSIG key is listed, the update
|
||||||
|
* is attempted without TSIG.
|
||||||
|
*
|
||||||
|
* The DHCP server tries to find an existing zone for any given name by
|
||||||
|
* trying to look up a local zone structure for each domain containing
|
||||||
|
* that name, all the way up to '.'. If it finds one cached, it tries
|
||||||
|
* to use that one to do the update. That's why it tries to update
|
||||||
|
* "FOO.COM" above, even though theoretically it should try GAZANGA...
|
||||||
|
* and TOPANGA... first.
|
||||||
|
*
|
||||||
|
* If the update fails with a predefined or cached zone (we'll get to
|
||||||
|
* those in a second), then it tries to find a more specific zone. This
|
||||||
|
* is done by looking first for an SOA for GAZANGA.TOPANGA.FOO.COM. Then
|
||||||
|
* an SOA for TOPANGA.FOO.COM is sought. If during this search a predefined
|
||||||
|
* or cached zone is found, the update fails - there's something wrong
|
||||||
|
* somewhere.
|
||||||
|
*
|
||||||
|
* If a more specific zone _is_ found, that zone is cached for the length of
|
||||||
|
* its TTL in the same database as that described above. TSIG updates are
|
||||||
|
* never done for cached zones - if you want TSIG updates you _must_
|
||||||
|
* write a zone definition linking the key to the zone. In cases where you
|
||||||
|
* know for sure what the key is but do not want to hardcode the IP addresses
|
||||||
|
* of the primary or secondaries, a zone declaration can be made that doesn't
|
||||||
|
* include any primary or secondary declarations. When the DHCP server
|
||||||
|
* encounters this while hunting up a matching zone for a name, it looks up
|
||||||
|
* the SOA, fills in the IP addresses, and uses that record for the update.
|
||||||
|
* If the SOA lookup returns NXRRSET, a warning is printed and the zone is
|
||||||
|
* discarded, TSIG key and all. The search for the zone then continues as if
|
||||||
|
* the zone record hadn't been found. Zones without IP addresses don't
|
||||||
|
* match when initially hunting for a predefined or cached zone to update.
|
||||||
|
*
|
||||||
|
* When an update is attempted and no predefined or cached zone is found
|
||||||
|
* that matches any enclosing domain of the domain being updated, the DHCP
|
||||||
|
* server goes through the same process that is done when the update to a
|
||||||
|
* predefined or cached zone fails - starting with the most specific domain
|
||||||
|
* name (GAZANGA.TOPANGA.FOO.COM) and moving to the least specific (the root),
|
||||||
|
* it tries to look up an SOA record. When it finds one, it creates a cached
|
||||||
|
* zone and attempts an update, and gives up if the update fails.
|
||||||
|
*
|
||||||
|
* TSIG keys are defined like this:
|
||||||
|
*
|
||||||
|
* tsig-key "FOO.COM Key" HMAC-MD5.SIG-ALG.REG.INT <CSHL>;
|
||||||
|
*
|
||||||
|
* CSHL is a colon-seperated list of hexadecimal bytes that make up
|
||||||
|
* the key. It's also permissible to use a quoted string here - this will
|
||||||
|
* be translated as the ASCII bytes making up the string, and will not include
|
||||||
|
* any NUL termination. The key name can be any text string, and the
|
||||||
|
* key type must be one of the key types defined in the draft or by the IANA.
|
||||||
|
* Currently only the HMAC-MD5... key type is supported.
|
||||||
|
*/
|
||||||
|
|
||||||
static unsigned addlabel PROTO ((u_int8_t *, const char *));
|
struct hash_table *tsig_key_hash;
|
||||||
static int skipname PROTO ((u_int8_t *));
|
struct hash_table *dns_zone_hash;
|
||||||
static int copy_out_name PROTO ((u_int8_t *, u_int8_t *, char *));
|
|
||||||
static int nslookup PROTO ((u_int8_t, char *, int, u_int16_t, u_int16_t));
|
|
||||||
static int zonelookup PROTO ((u_int8_t, char *, int, u_int16_t));
|
|
||||||
u_int16_t dns_port;
|
|
||||||
|
|
||||||
#define DNS_QUERY_HASH_SIZE 293
|
isc_result_t enter_dns_zone (struct dns_zone *zone)
|
||||||
struct dns_query *dns_query_hash [DNS_QUERY_HASH_SIZE];
|
|
||||||
|
|
||||||
/* Initialize the DNS protocol. */
|
|
||||||
|
|
||||||
void dns_startup ()
|
|
||||||
{
|
{
|
||||||
struct servent *srv;
|
struct dns_zone *tz;
|
||||||
struct sockaddr_in from;
|
|
||||||
|
|
||||||
/* Only initialize icmp once. */
|
if (dns_zone_hash) {
|
||||||
if (dns_protocol_initialized)
|
tz = hash_lookup (dns_zone_hash, zone -> name, 0);
|
||||||
log_fatal ("attempted to reinitialize dns protocol");
|
if (tz == zone)
|
||||||
dns_protocol_initialized = 1;
|
return ISC_R_SUCCESS;
|
||||||
|
if (tz)
|
||||||
|
delete_hash_entry (dns_zone_hash, zone -> name, 0);
|
||||||
|
} else {
|
||||||
|
dns_zone_hash =
|
||||||
|
new_hash ((hash_reference)dns_zone_reference,
|
||||||
|
(hash_dereference)dns_zone_dereference);
|
||||||
|
if (!dns_zone_hash)
|
||||||
|
return ISC_R_NOMEMORY;
|
||||||
|
}
|
||||||
|
add_hash (dns_zone_hash, zone -> name, 0, zone);
|
||||||
|
return ISC_R_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
/* Get the protocol number (should be 1). */
|
isc_result_t dns_zone_lookup (struct dns_zone **zone, const char *name) {
|
||||||
srv = getservbyname ("domain", "tcp");
|
struct dns_zone *tz;
|
||||||
if (srv)
|
|
||||||
dns_port = srv -> s_port;
|
|
||||||
else
|
|
||||||
dns_port = htons (53);
|
|
||||||
|
|
||||||
/* Get a socket for the DNS protocol. */
|
if (!dns_zone_hash)
|
||||||
dns_protocol_fd = socket (AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
return ISC_R_NOTFOUND;
|
||||||
if (dns_protocol_fd < 0)
|
tz = hash_lookup (dns_zone_hash, name, 0);
|
||||||
log_fatal ("unable to create dns socket: %m");
|
if (!tz)
|
||||||
|
return ISC_R_NOTFOUND;
|
||||||
|
if (!dns_zone_reference (zone, tz, MDL))
|
||||||
|
return ISC_R_UNEXPECTED;
|
||||||
|
return ISC_R_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
#if defined (HAVE_SETFD)
|
isc_result_t enter_tsig_key (struct tsig_key *tkey)
|
||||||
if (fcntl (dns_protocol_fd, F_SETFD, 1) < 0)
|
{
|
||||||
log_fatal ("unable to set close-on-exec on dns fd: %m");
|
struct tsig_key *tk;
|
||||||
|
|
||||||
|
if (tsig_key_hash) {
|
||||||
|
tk = hash_lookup (tsig_key_hash, tkey -> name, 0);
|
||||||
|
if (tk == tkey)
|
||||||
|
return ISC_R_SUCCESS;
|
||||||
|
if (tk)
|
||||||
|
delete_hash_entry (tsig_key_hash, tkey -> name, 0);
|
||||||
|
} else {
|
||||||
|
tsig_key_hash =
|
||||||
|
new_hash ((hash_reference)tsig_key_reference,
|
||||||
|
(hash_dereference)tsig_key_dereference);
|
||||||
|
if (!tsig_key_hash)
|
||||||
|
return ISC_R_NOMEMORY;
|
||||||
|
}
|
||||||
|
add_hash (tsig_key_hash, tkey -> name, 0, tkey);
|
||||||
|
return ISC_R_SUCCESS;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
isc_result_t tsig_key_lookup (struct tsig_key **tkey, const char *name) {
|
||||||
|
struct tsig_key *tk;
|
||||||
|
|
||||||
|
if (!tsig_key_hash)
|
||||||
|
return ISC_R_NOTFOUND;
|
||||||
|
tk = hash_lookup (tsig_key_hash, name, 0);
|
||||||
|
if (!tk)
|
||||||
|
return ISC_R_NOTFOUND;
|
||||||
|
if (!tsig_key_reference (tkey, tk, MDL))
|
||||||
|
return ISC_R_UNEXPECTED;
|
||||||
|
return ISC_R_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
int dns_zone_dereference (ptr, file, line)
|
||||||
|
struct dns_zone **ptr;
|
||||||
|
const char *file;
|
||||||
|
int line;
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
struct dns_zone *dns_zone;
|
||||||
|
|
||||||
|
if (!ptr || !*ptr) {
|
||||||
|
log_error ("%s(%d): null pointer", file, line);
|
||||||
|
#if defined (POINTER_DEBUG)
|
||||||
|
abort ();
|
||||||
|
#else
|
||||||
|
return 0;
|
||||||
#endif
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
first_name_server ();
|
dns_zone = *ptr;
|
||||||
|
*ptr = (struct dns_zone *)0;
|
||||||
add_protocol ("dns", dns_protocol_fd, dns_packet, 0);
|
--dns_zone -> refcnt;
|
||||||
}
|
rc_register (file, line, ptr, dns_zone, dns_zone -> refcnt);
|
||||||
|
if (dns_zone -> refcnt > 0)
|
||||||
/* Label manipulation stuff; see RFC1035, page 28 section 4.1.2 and
|
|
||||||
page 30, section 4.1.4. */
|
|
||||||
|
|
||||||
/* addlabel copies a label into the specified buffer, putting the length of
|
|
||||||
the label in the first character, the contents of the label in subsequent
|
|
||||||
characters, and returning the length of the conglomeration. */
|
|
||||||
|
|
||||||
static unsigned addlabel (buf, label)
|
|
||||||
u_int8_t *buf;
|
|
||||||
const char *label;
|
|
||||||
{
|
|
||||||
*buf = strlen (label);
|
|
||||||
memcpy (buf + 1, label, (unsigned)*buf);
|
|
||||||
return (unsigned)(*buf + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* skipname skips over all of the labels in a single domain name,
|
|
||||||
returning the length of the domain name. */
|
|
||||||
|
|
||||||
static int skipname (label)
|
|
||||||
u_int8_t *label;
|
|
||||||
{
|
|
||||||
if (*label & INDIR_MASK)
|
|
||||||
return 2;
|
|
||||||
if (*label == 0)
|
|
||||||
return 1;
|
return 1;
|
||||||
return *label + 1 + skipname (label + *label + 1);
|
|
||||||
|
if (dns_zone -> refcnt < 0) {
|
||||||
|
log_error ("%s(%d): negative refcnt!", file, line);
|
||||||
|
#if defined (DEBUG_RC_HISTORY)
|
||||||
|
dump_rc_history ();
|
||||||
|
#endif
|
||||||
|
#if defined (POINTER_DEBUG)
|
||||||
|
abort ();
|
||||||
|
#else
|
||||||
|
return 0;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dns_zone -> name)
|
||||||
|
dfree (dns_zone -> name, file, line);
|
||||||
|
if (dns_zone -> key)
|
||||||
|
tsig_key_dereference (&dns_zone -> key, file, line);
|
||||||
|
if (dns_zone -> primary)
|
||||||
|
option_cache_dereference (&dns_zone -> primary, file, line);
|
||||||
|
if (dns_zone -> secondary)
|
||||||
|
option_cache_dereference (&dns_zone -> secondary, file, line);
|
||||||
|
dfree (dns_zone, file, line);
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* copy_out_name copies out the name appearing at the specified location
|
|
||||||
into a string, stored as fields seperated by dots rather than lengths
|
|
||||||
and labels. The length of the label-formatted name is returned. */
|
|
||||||
|
|
||||||
static int copy_out_name (base, name, buf)
|
|
||||||
u_int8_t *base;
|
|
||||||
u_int8_t *name;
|
|
||||||
char *buf;
|
|
||||||
{
|
|
||||||
if (*name & INDIR_MASK) {
|
|
||||||
int offset = (*name & ~INDIR_MASK) + (*name + 1);
|
|
||||||
return copy_out_name (base, base + offset, buf);
|
|
||||||
}
|
|
||||||
if (!*name) {
|
|
||||||
*buf = 0;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
memcpy (buf, name + 1, *name);
|
|
||||||
*(buf + *name) = '.';
|
|
||||||
return (*name + 1
|
|
||||||
+ copy_out_name (base, name + *name + 1, buf + *name + 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Compute a hash on the question. */
|
|
||||||
|
|
||||||
static INLINE u_int32_t dns_hash_question (struct dns_question *question)
|
|
||||||
{
|
|
||||||
u_int32_t sum;
|
|
||||||
u_int32_t remainder;
|
|
||||||
u_int32_t *p = (u_int32_t *)question;
|
|
||||||
u_int8_t *s;
|
|
||||||
|
|
||||||
/* First word. */
|
|
||||||
sum = *p++;
|
|
||||||
s = (u_int8_t *)p;
|
|
||||||
|
|
||||||
remainder = 0;
|
|
||||||
while (s [0]) {
|
|
||||||
remainder = s [0];
|
|
||||||
if (s [1]) {
|
|
||||||
remainder = (remainder << 8) + s [1];
|
|
||||||
if (s [2]) {
|
|
||||||
remainder = (remainder << 8) + s [2];
|
|
||||||
if (s [3])
|
|
||||||
remainder = (remainder << 8) + s [3];
|
|
||||||
else
|
|
||||||
goto done;
|
|
||||||
} else
|
|
||||||
goto done;
|
|
||||||
} else {
|
|
||||||
done:
|
|
||||||
sum += remainder;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if ((sum & 0x80000000) && (remainder & 0x80000000))
|
|
||||||
++sum;
|
|
||||||
sum += remainder;
|
|
||||||
s += 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (sum > DNS_QUERY_HASH_SIZE) {
|
|
||||||
remainder = sum / DNS_QUERY_HASH_SIZE;
|
|
||||||
sum = sum % DNS_QUERY_HASH_SIZE;
|
|
||||||
while (remainder) {
|
|
||||||
sum += remainder % DNS_QUERY_HASH_SIZE;
|
|
||||||
remainder /= DNS_QUERY_HASH_SIZE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return sum;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Find a query that matches the specified name. If one can't be
|
|
||||||
found, and new is nonzero, allocate one, hash it in, and save the
|
|
||||||
question. Otherwise, if new is nonzero, free() the question.
|
|
||||||
Return the query if one was found or allocated. */
|
|
||||||
|
|
||||||
struct dns_query *find_dns_query (question, new)
|
|
||||||
struct dns_question *question;
|
|
||||||
int new;
|
|
||||||
{
|
|
||||||
int hash = dns_hash_question (question);
|
|
||||||
struct dns_query *q;
|
|
||||||
|
|
||||||
for (q = dns_query_hash [hash]; q; q = q -> next) {
|
|
||||||
if (q -> question -> type == question -> type &&
|
|
||||||
q -> question -> class == question -> class &&
|
|
||||||
!strcmp ((char *)q -> question -> data,
|
|
||||||
(char *)question -> data))
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (q || !new) {
|
|
||||||
if (new)
|
|
||||||
dfree (question, MDL);
|
|
||||||
return q;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Allocate and zap a new query. */
|
|
||||||
q = (struct dns_query *)dmalloc (sizeof (struct dns_query), MDL);
|
|
||||||
memset (q, 0, sizeof *q);
|
|
||||||
|
|
||||||
/* All we need to set up is the question and the hash. */
|
|
||||||
q -> question = question;
|
|
||||||
q -> next = dns_query_hash [hash];
|
|
||||||
dns_query_hash [hash] = q;
|
|
||||||
q -> hash = hash;
|
|
||||||
return q;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Free up all memory associated with a DNS query and remove it from the
|
|
||||||
query hash. */
|
|
||||||
|
|
||||||
void destroy_dns_query (query)
|
|
||||||
struct dns_query *query;
|
|
||||||
{
|
|
||||||
struct dns_query *q;
|
|
||||||
|
|
||||||
/* Free up attached free data. */
|
|
||||||
if (query -> question)
|
|
||||||
dfree (query -> question, MDL);
|
|
||||||
if (query -> answer)
|
|
||||||
dfree (query -> answer, MDL);
|
|
||||||
if (query -> query)
|
|
||||||
dfree (query -> query, MDL);
|
|
||||||
|
|
||||||
/* Remove query from hash table. */
|
|
||||||
if (dns_query_hash [query -> hash] == query)
|
|
||||||
dns_query_hash [query -> hash] = query -> next;
|
|
||||||
else {
|
|
||||||
for (q = dns_query_hash [query -> hash];
|
|
||||||
q -> next && q -> next != query; q = q -> next)
|
|
||||||
;
|
|
||||||
if (q -> next)
|
|
||||||
q -> next = query -> next;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Free the query structure. */
|
|
||||||
dfree (query, MDL);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ns_inaddr_lookup constructs a PTR lookup query for an internet address -
|
|
||||||
e.g., 1.200.9.192.in-addr.arpa. It then passes it on to ns_query for
|
|
||||||
completion. */
|
|
||||||
|
|
||||||
struct dns_query *ns_inaddr_lookup (inaddr, wakeup)
|
|
||||||
struct iaddr inaddr;
|
|
||||||
struct dns_wakeup *wakeup;
|
|
||||||
{
|
|
||||||
unsigned char query [512];
|
|
||||||
unsigned char *s;
|
|
||||||
unsigned char *label;
|
|
||||||
int i;
|
|
||||||
unsigned char c;
|
|
||||||
struct dns_question *question;
|
|
||||||
|
|
||||||
/* First format the query in the internal format. */
|
|
||||||
sprintf ((char *)query, "%d.%d.%d.%d.in-addr.arpa.",
|
|
||||||
inaddr.iabuf [0], inaddr.iabuf [1],
|
|
||||||
inaddr.iabuf [2], inaddr.iabuf [3]);
|
|
||||||
|
|
||||||
question = (struct dns_question *)dmalloc (strlen ((char *)query) +
|
|
||||||
sizeof *question, MDL);
|
|
||||||
if (!question)
|
|
||||||
return (struct dns_query *)-1;
|
|
||||||
question -> type = T_PTR;
|
|
||||||
question -> class = C_IN;
|
|
||||||
strcpy ((char *)question -> data, (char *)query);
|
|
||||||
|
|
||||||
/* Now format the query for the name server. */
|
|
||||||
s = query;
|
|
||||||
|
|
||||||
/* Copy out the digits. */
|
|
||||||
for (i = 3; i >= 0; --i) {
|
|
||||||
label = s++;
|
|
||||||
sprintf ((char *)s, "%d", inaddr.iabuf [i]);
|
|
||||||
*label = strlen ((char *)s);
|
|
||||||
s += *label;
|
|
||||||
}
|
|
||||||
s += addlabel (s, "in-addr");
|
|
||||||
s += addlabel (s, "arpa");
|
|
||||||
*s++ = 0;
|
|
||||||
|
|
||||||
/* Set the query type. */
|
|
||||||
putUShort (s, T_PTR);
|
|
||||||
s += sizeof (u_int16_t);
|
|
||||||
|
|
||||||
/* Set the query class. */
|
|
||||||
putUShort (s, C_IN);
|
|
||||||
s += sizeof (u_int16_t);
|
|
||||||
|
|
||||||
return ns_query (question, query, (unsigned)(s - query), wakeup);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Try to satisfy a query out of the local cache. If no answer has
|
|
||||||
been cached, and if there isn't already a query pending on this
|
|
||||||
question, send it. If the query can be immediately satisfied,
|
|
||||||
a pointer to the dns_query structure is returned. If the query
|
|
||||||
can't even be made for some reason, (struct dns_query *)-1 is
|
|
||||||
returned. Otherwise, the null pointer is returned, indicating that
|
|
||||||
a wakeup will be performed later when the answer comes back. */
|
|
||||||
|
|
||||||
struct dns_query *ns_query (question, formatted_query, len, wakeup)
|
|
||||||
struct dns_question *question;
|
|
||||||
unsigned char *formatted_query;
|
|
||||||
unsigned len;
|
|
||||||
struct dns_wakeup *wakeup;
|
|
||||||
{
|
|
||||||
HEADER *hdr;
|
|
||||||
struct dns_query *query;
|
|
||||||
unsigned char *s;
|
|
||||||
unsigned char buf [512];
|
|
||||||
|
|
||||||
/* If the query won't fit, don't bother setting it up. */
|
|
||||||
if (len > 255) {
|
|
||||||
dfree (question, MDL);
|
|
||||||
return (struct dns_query *)-1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* See if there's already a query for this name, and allocate a
|
|
||||||
query if none exists. */
|
|
||||||
query = find_dns_query (question, 1);
|
|
||||||
|
|
||||||
/* If we can't allocate a query, report that the query failed. */
|
|
||||||
if (!query)
|
|
||||||
return (struct dns_query *)-1;
|
|
||||||
|
|
||||||
/* If the query has already been answered, return it. */
|
|
||||||
if (query -> expiry > cur_time)
|
|
||||||
return query;
|
|
||||||
|
|
||||||
/* The query hasn't yet been answered, so we have to wait, one
|
|
||||||
way or another. Put the wakeup on the list. */
|
|
||||||
if (wakeup) {
|
|
||||||
wakeup -> next = query -> wakeups;
|
|
||||||
query -> wakeups = wakeup;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* If the query has already been sent, but we don't yet have
|
|
||||||
an answer, we're done. */
|
|
||||||
if (query -> sent)
|
|
||||||
return (struct dns_query *)0;
|
|
||||||
|
|
||||||
/* Construct a header... */
|
|
||||||
hdr = (HEADER *)buf;
|
|
||||||
memset (hdr, 0, sizeof *hdr);
|
|
||||||
hdr -> id = query -> id;
|
|
||||||
hdr -> rd = 1;
|
|
||||||
hdr -> opcode = QUERY;
|
|
||||||
hdr -> qdcount = htons (1);
|
|
||||||
|
|
||||||
/* Copy the formatted name into the buffer. */
|
|
||||||
s = (unsigned char *)hdr + 1;
|
|
||||||
memcpy (s, formatted_query, len);
|
|
||||||
|
|
||||||
/* Figure out how long the whole message is */
|
|
||||||
s += len;
|
|
||||||
query -> len = s - buf;
|
|
||||||
|
|
||||||
/* Save the raw query data. */
|
|
||||||
query -> query = dmalloc (len, MDL);
|
|
||||||
if (!query -> query) {
|
|
||||||
destroy_dns_query (query);
|
|
||||||
return (struct dns_query *)-1;
|
|
||||||
}
|
|
||||||
memcpy (query -> query, buf, query -> len);
|
|
||||||
|
|
||||||
/* Flag the query as having been sent. */
|
|
||||||
query -> sent = 1;
|
|
||||||
|
|
||||||
/* Send the query. */
|
|
||||||
dns_timeout (query);
|
|
||||||
|
|
||||||
/* No answer yet, obviously. */
|
|
||||||
return (struct dns_query *)0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Retransmit a DNS query. */
|
|
||||||
|
|
||||||
void dns_timeout (qv)
|
|
||||||
void *qv;
|
|
||||||
{
|
|
||||||
struct dns_query *query = qv;
|
|
||||||
int status;
|
|
||||||
|
|
||||||
/* Choose the server to send to. */
|
|
||||||
if (!query -> next_server)
|
|
||||||
query -> next_server = first_name_server ();
|
|
||||||
|
|
||||||
/* Send the query. */
|
|
||||||
if (query -> next_server)
|
|
||||||
status = sendto (dns_protocol_fd,
|
|
||||||
(char *)query -> query, query -> len, 0,
|
|
||||||
((struct sockaddr *)&query ->
|
|
||||||
next_server -> addr),
|
|
||||||
sizeof query -> next_server -> addr);
|
|
||||||
else
|
|
||||||
status = -1;
|
|
||||||
|
|
||||||
/* Look for the next server... */
|
|
||||||
query -> next_server = query -> next_server -> next;
|
|
||||||
|
|
||||||
/* If this is our first time, backoff one second. */
|
|
||||||
if (!query -> backoff)
|
|
||||||
query -> backoff = 1;
|
|
||||||
|
|
||||||
/* If the send failed, don't advance the backoff. */
|
|
||||||
else if (status < 0)
|
|
||||||
;
|
|
||||||
|
|
||||||
/* If we haven't run out of servers to try, don't backoff. */
|
|
||||||
else if (query -> next_server)
|
|
||||||
;
|
|
||||||
|
|
||||||
/* If we haven't backed off enough yet, back off some more. */
|
|
||||||
else if (query -> backoff < 30)
|
|
||||||
query -> backoff += random() % query -> backoff;
|
|
||||||
|
|
||||||
/* Set up the timeout. */
|
|
||||||
add_timeout (cur_time + query -> backoff, dns_timeout, query);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Process a reply from a name server. */
|
|
||||||
|
|
||||||
void dns_packet (protocol)
|
|
||||||
struct protocol *protocol;
|
|
||||||
{
|
|
||||||
HEADER *ns_header;
|
|
||||||
struct sockaddr_in from;
|
|
||||||
struct dns_wakeup *wakeup;
|
|
||||||
unsigned char buf [512];
|
|
||||||
union {
|
|
||||||
unsigned char u [512];
|
|
||||||
struct dns_question q;
|
|
||||||
} qbuf;
|
|
||||||
unsigned char *base;
|
|
||||||
unsigned char *dptr, *name;
|
|
||||||
u_int16_t type;
|
|
||||||
u_int16_t class;
|
|
||||||
TIME ttl;
|
|
||||||
u_int16_t rdlength;
|
|
||||||
SOCKLEN_T len;
|
|
||||||
int status;
|
|
||||||
int i;
|
|
||||||
struct dns_query *query;
|
|
||||||
|
|
||||||
len = sizeof from;
|
|
||||||
status = recvfrom (protocol -> fd, (char *)buf, sizeof buf, 0,
|
|
||||||
(struct sockaddr *)&from, &len);
|
|
||||||
if (status < 0) {
|
|
||||||
log_error ("dns_packet: %m");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Response is too long? */
|
|
||||||
if (len > 512) {
|
|
||||||
log_error ("dns_packet: dns message too long (%d)", len);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ns_header = (HEADER *)buf;
|
|
||||||
base = (unsigned char *)(ns_header + 1);
|
|
||||||
|
|
||||||
/* Parse the response... */
|
|
||||||
dptr = base;
|
|
||||||
|
|
||||||
/* If this is a response to a query from us, there should have
|
|
||||||
been only one query. */
|
|
||||||
if (ntohs (ns_header -> qdcount) != 1) {
|
|
||||||
log_error ("Bogus DNS answer packet from %s claims %d queries.\n",
|
|
||||||
inet_ntoa (from.sin_addr),
|
|
||||||
ntohs (ns_header -> qdcount));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Find the start of the name in the query. */
|
|
||||||
name = dptr;
|
|
||||||
|
|
||||||
/* Skip over the name. */
|
|
||||||
dptr += copy_out_name (name, name, (char *)qbuf.q.data);
|
|
||||||
|
|
||||||
/* Skip over the query type and query class. */
|
|
||||||
qbuf.q.type = getUShort (dptr);
|
|
||||||
dptr += sizeof (u_int16_t);
|
|
||||||
qbuf.q.class = getUShort (dptr);
|
|
||||||
dptr += sizeof (u_int16_t);
|
|
||||||
|
|
||||||
/* See if we asked this question. */
|
|
||||||
query = find_dns_query (&qbuf.q, 0);
|
|
||||||
if (!query) {
|
|
||||||
log_error ("got answer for question %s from DNS, which we didn't ask.",
|
|
||||||
qbuf.q.data);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
log_info ("got answer for question %s from DNS", qbuf.q.data);
|
|
||||||
|
|
||||||
/* Wake up everybody who's waiting. */
|
|
||||||
for (wakeup = query -> wakeups; wakeup; wakeup = wakeup -> next) {
|
|
||||||
(*wakeup -> func) (query);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user