rfc2136.c revision c16fc6609d9aa72229802524dc1d8c4ead6e9d2a
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2007 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
#include <thread.h>
#include <stdlib.h>
#include <netdb.h>
#include <strings.h>
#include <alloca.h>
#include "res_update.h"
#include <stdio.h>
#include <errno.h>
#include <resolv.h>
#include <assert.h>
#include <stdarg.h>
#include <libnvpair.h>
static unsigned short parse_ushort(const char **);
static unsigned int parse_uint(const char **);
static void freeupdrecs(ns_updque);
extern char *inet_ntoa_r(struct in_addr, char *);
/*
* The parent (calling) thread and the child thread it spawns to do an
* update use this structure to rendezvous. The child thread sets the
* ``done'' variable to B_TRUE when it's completed its work. The nusers
* variable lets us arbitrate to see who has to clean up (via the
* provided childstat_cleanup() function) the dynamically-allocated
* structure - last one to wake up loses, and has to do the work.
*/
struct childstat {
mutex_t m;
int ret;
int nusers;
};
static void childstat_cleanup(struct childstat *);
static void update_thread(void *);
/*
* The given environment variable, if present, will contain the name
* of a file (or the distinguished values "stdout" and "stderr") into
* which we should place the debugging output from this shared object.
*
* The debugging output is basically free-form but uses the dprint()
* function to ensure that each message is tagged with its thread ID,
* so we have some hope of sorting out later what actually happened.
*/
static char env_filetoken[] = "DHCP_DNS_OUTPUT";
static void dprint(char *, ...);
/* CSTYLED */
/*
* This is the shared object startup function, called once when we
* are dlopen()ed.
*/
static void
init(void)
{
char *cp;
struct __res_state res;
else {
}
if (debug_fp)
}
/*
* Use res_ninit(3RESOLV) to see whether DNS has been configured
* on the host running this code. In practice, life must be very
* bad for res_ninit() to fail.
*/
dprint("res_ninit() failed - dns_config_ok FALSE\n");
} else {
dprint("res_ninit() succeeded\n");
}
res_ndestroy(&res);
}
/*
* This is the interface exported to the outside world. Control over
* the hostent structure is assumed to pass to dns_puthostent(); it will
* free the associated space when done.
*/
int
{
timestruc_t t;
int ret;
/*
* Check the consistency of the hostent structure:
* both the name and address fields should be valid,
* h_addrtype must be AF_INET, and h_length must be
* sizeof (struct in_addr);
*/
dprint("hp is NULL - return -1\n");
return (-1);
}
dprint("h_addr_list is NULL - return -1\n");
return (-1);
}
dprint("h_addr_list is zero-length - return -1\n");
return (-1);
}
dprint("h_name is NULL - return -1\n");
return (-1);
}
dprint("h_name[0] is NUL - return -1\n");
return (-1);
}
dprint("h_addrtype (%d) != AF_INET - return -1\n",
hp->h_addrtype);
return (-1);
}
dprint("h_length (%d) != sizeof (struct in_addr) - return -1\n",
return (-1);
}
if (dns_config_ok == B_FALSE) {
dprint("dns_config_ok FALSE - return -1\n");
return (-1);
}
dprint("malloc (sizeof struct childstat) failed\n");
return (-1);
}
/*
* From this point on, both hp and sp are cleaned up and freed via
* childstat_cleanup(), with bookkeeping done to see whether the
* parent thread or the child one should be the one in charge of
* cleaning up.
*/
if (timeout > 0)
else
t.tv_nsec = 0;
return (-1);
}
else
(void) mutex_lock(&sp->m);
/* if asynchronous, and child still working, just return; */
(void) mutex_unlock(&sp->m);
dprint("done 0, timeout 0\n");
return (0);
}
/* otherwise, wait for child to finish or time to expire */
/*
* Child thread did not return before the
* timeout. One might think we could
* assert(sp->nusers > 1);
* here, but we can't: we must protect
* against this sequence of events:
* cond_timedwait() times out
*
* child finishes, grabs mutex,
* decrements nusers, sets done,
* and exits.
*
* cond_timedwait() reacquires the
* mutex and returns ETIME
*
* If this happens, nusers will now be 1,
* even though cond_timedwait() returned
* ETIME.
*/
/* child must have also set done */
break;
else
/* child thread has not returned */
(void) mutex_unlock(&sp->m);
return (0);
}
}
return (ret);
}
/*
* This worker thread, spawned by dns_puthostent(), is responsible for
* seeing that the update work gets done and cleaning up afterward
* if necessary.
*/
static void
update_thread(void *arg)
{
char *p;
int num_updated = 0;
dprint("update_thread running\n");
(void) mutex_lock(&sp->m);
/*
* Paranoia: if nusers was 0 and we were asked to do a
* synchronous update, our parent must have incremented
* it, called cond_timedwait(), timed out, and decremented it,
* all before we got this far. In this case, we do nothing
* except clean up and exit.
*/
thr_exit(0);
}
(void) mutex_unlock(&sp->m);
/*
* h_name should be full-qualified; find the name servers for
* its domain ...
*/
if (*p == '.') {
char ntoab[INET_ADDRSTRLEN];
dprint("update for %s goes to %s\n",
/* ... and send the update to one of them. */
dprint("send_update succeeded\n");
num_updated = 1;
} else {
dprint("send_update failed\n");
num_updated = 0;
}
} else {
dprint("getNS failed\n");
num_updated = -1;
}
break;
}
(void) mutex_lock(&sp->m);
/* parent timed out and abandoned us - our turn to clean up */
} else {
(void) mutex_unlock(&sp->m);
}
thr_exit(0);
}
/*
* Find a name server for the supplied domain and return its IP address.
* Sadly, in order to do this we have to parse the actual DNS reply
* packet - no functions are provided for doing this work for us.
*/
static boolean_t
{
union {
HEADER h;
char buf[NS_PACKETSZ];
} abuf;
int alen;
int count;
int retries;
unsigned char *data;
unsigned char *m_bound;
unsigned char *NS_data;
struct __res_state res;
extern struct hostent *res_gethostbyname(const char *);
dprint("getNS: found cached IP address for domain %s\n",
domain);
return (B_TRUE);
}
return (B_FALSE);
}
sizeof (abuf));
if (alen <= 0) {
/*
* Look for indicators from libresolv:res_nsend()
* that we should retry a request.
*/
if ((errno == ECONNREFUSED) ||
dprint("getNS retry: errno %d, h_errno %d\n",
continue;
} else {
dprint("getNS(\"%s\"): res_nquery failed "
res_ndestroy(&res);
return (B_FALSE);
}
}
}
if (alen <= 0) {
dprint("getNS(\"%s\"): res_nquery failed " "(h_errno %d)\n",
res_ndestroy(&res);
return (B_FALSE);
}
while (--qdcount >= 0) {
if (dlen < 0) {
dprint("dn_skipname returned < 0\n");
res_ndestroy(&res);
return (B_FALSE);
}
}
dprint("dn_expand() dom failed\n");
res_ndestroy(&res);
return (B_FALSE);
}
switch (type) {
case T_NS:
dprint("\ttype T_NS\n");
break;
case T_CNAME:
dprint("\ttype T_CNAME\n");
break;
case T_A:
dprint("\ttype T_A\n");
break;
case T_SOA:
dprint("\ttype T_SOA\n");
break;
case T_MX:
dprint("\ttype T_MX\n");
break;
case T_TXT:
dprint("\ttype T_TXT\n");
break;
default:
}
dprint("\tclass C_IN\n");
else
switch (type) {
case T_A:
res_ndestroy(&res);
return (B_TRUE);
case T_NS:
dprint("\tdn_expand() T_NS failed\n");
res_ndestroy(&res);
return (B_FALSE);
}
break;
}
}
dprint("getNS: fell through res_nquery results - no A records\n");
/*
* The reply contained NS records, but no A records. Use
* res_gethostbyname() to get the name server's address
* via DNS.
*/
if (found_NS) {
dprint("\tdn_expand() T_NS failed\n");
res_ndestroy(&res);
return (B_FALSE);
}
res_ndestroy(&res);
return (B_TRUE);
} else
} else {
dprint("getNS: reply contained no NS records\n");
}
res_ndestroy(&res);
return (B_FALSE);
}
/*
* Cache the <domain, IP address> tuple (which is assumed to not already
* be cached) for ttl seconds.
*/
static void
{
if (ttl > 0) {
dprint("cacheNS: nvlist_alloc failed\n");
return;
}
sizeof (now)) != 0)) {
dprint("cacheNS: nvlist_add failed\n");
}
} else
dprint("cacheNS: ttl 0 - nothing to cache\n");
}
/*
* See whether the <domain, IP address> tuple has been cached.
*/
static boolean_t
{
int32_t i;
&nelem) != 0)
return (B_FALSE);
return (B_TRUE);
} else {
}
}
return (B_FALSE);
}
/*
* Do the work of updating DNS to have the <hp->h_name <-> hp->h_addr>
* pairing.
*/
static boolean_t
{
char *forfqhost;
struct __res_state res;
char revnamebuf[MAXDNAME];
dprint("send_updated res_ninit failed!");
return (B_FALSE);
}
/* If debugging output desired, then ask resolver to do it, too */
res_ndestroy(&res);
return (B_FALSE);
}
/* Construct the fully-qualified name for PTR record updates */
/* LINTED - alignment */
"%u.%u.%u.%u.in-addr.ARPA",
/*
* The steps in doing an update:
* - delete any A records
* - delete any PTR records
* - add an A record
* - add a PTR record
*/
res_ndestroy(&res);
return (B_FALSE);
}
res_ndestroy(&res);
return (B_TRUE);
}
/* delete A records for this fully-qualified name */
static boolean_t
{
ns_updque q;
INIT_LIST(q);
dprint("res_mkupdrec (del A) failed\n");
return (B_FALSE);
}
dprint("res_nupdate (del A) failed - errno %d, h_errno %d\n",
freeupdrecs(q);
return (B_FALSE);
}
freeupdrecs(q);
return (B_TRUE);
}
/* delete PTR records for this address */
static boolean_t
{
ns_updque q;
INIT_LIST(q);
dprint("res_mkupdrec (del PTR) failed\n");
return (B_FALSE);
}
dprint("res_nupdate (del PTR) failed - errno %d, h_errno %d\n",
freeupdrecs(q);
return (B_FALSE);
}
freeupdrecs(q);
return (B_TRUE);
}
/* add an A record for this fqdn <-> addr pair */
static boolean_t
{
ns_updque q;
char ntoab[INET_ADDRSTRLEN];
INIT_LIST(q);
dprint("res_mkupdrec (add A PREREQ) failed\n");
return (B_FALSE);
}
dprint("res_mkupdrec (add A UPDATE) failed\n");
freeupdrecs(q);
return (B_FALSE);
}
dprint("res_nupdate (ADD A) failed - errno %d, h_errno %d\n",
freeupdrecs(q);
return (B_FALSE);
}
freeupdrecs(q);
return (B_TRUE);
}
/* add a PTR record for this fqdn <-> address pair */
static boolean_t
{
ns_updque q;
INIT_LIST(q);
dprint("res_mkupdrec (add PTR DELETE) failed\n");
return (B_FALSE);
}
dprint("res_mkupdrec (add PTR ADD) failed\n");
freeupdrecs(q);
return (B_FALSE);
}
dprint("res_nupdate (ADD PTR) failed - errno %d, h_errno %d\n",
freeupdrecs(q);
return (B_FALSE);
}
freeupdrecs(q);
return (B_TRUE);
}
/* retry an update request when appropriate */
static boolean_t
{
int retries;
return (B_TRUE);
} else {
/*
* Look for indicators from libresolv:res_nsend()
* that we should retry a request.
*/
if ((errno == ECONNREFUSED) ||
dprint("retry_update - errno %d, h_errno %d\n",
continue;
} else
return (B_FALSE);
}
return (B_FALSE);
}
static void
{
while (!EMPTY(q)) {
}
}
/*
* Parse a 16-bit quantity from a DNS reply packet.
*/
static unsigned short
parse_ushort(const char **pp)
{
unsigned short val;
*pp += 2;
return (val);
}
/*
* Parse a 32-bit quantity from a DNS reply packet.
*/
static unsigned int
parse_uint(const char **pp)
{
unsigned int val;
*pp += 4;
return (val);
}
/*
* Clean up a childstat structure's synchronization variables and free
* the allocated memory.
*/
static void
{
(void) mutex_destroy(&sp->m);
}
/*
* Format and print a debug message, prepending the thread ID of the
* thread logging the message.
*/
/* PRINTFLIKE1 */
static void
{
if (debug_fp) {
}
}
static void
{
}