|
|
/* -*- Mode: C; tab-width: 4 -*-
|
|
|
*
|
|
|
* Copyright (c) 2002-2004 Apple Computer, Inc. All rights reserved.
|
|
|
*
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
* you may not use this file except in compliance with the License.
|
|
|
* You may obtain a copy of the License at
|
|
|
*
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
*
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
* See the License for the specific language governing permissions and
|
|
|
* limitations under the License.
|
|
|
*/
|
|
|
|
|
|
#if __APPLE__
|
|
|
// In Mac OS X 10.5 and later trying to use the daemon function gives a “‘daemon’ is deprecated”
|
|
|
// error, which prevents compilation because we build with "-Werror".
|
|
|
// Since this is supposed to be portable cross-platform code, we don't care that daemon is
|
|
|
// deprecated on Mac OS X 10.5, so we use this preprocessor trick to eliminate the error message.
|
|
|
#define daemon yes_we_know_that_daemon_is_deprecated_in_os_x_10_5_thankyou
|
|
|
#endif
|
|
|
|
|
|
#include <signal.h>
|
|
|
#include <pthread.h>
|
|
|
#include <stdlib.h>
|
|
|
#include <unistd.h>
|
|
|
#include <sys/types.h>
|
|
|
#include <sys/socket.h>
|
|
|
#include <netinet/in.h>
|
|
|
#include <arpa/inet.h>
|
|
|
#include <stdio.h>
|
|
|
#include <syslog.h>
|
|
|
#include <string.h>
|
|
|
#include <sys/time.h>
|
|
|
#include <sys/resource.h>
|
|
|
#include <time.h>
|
|
|
#include <errno.h>
|
|
|
|
|
|
#if __APPLE__
|
|
|
#undef daemon
|
|
|
extern int daemon(int, int);
|
|
|
#endif
|
|
|
|
|
|
// Solaris doesn't have daemon(), so we define it here
|
|
|
#ifdef NOT_HAVE_DAEMON
|
|
|
#include "../mDNSPosix/mDNSUNP.h" // For daemon()
|
|
|
#endif // NOT_HAVE_DAEMON
|
|
|
|
|
|
#include "dnsextd.h"
|
|
|
#include "../mDNSShared/uds_daemon.h"
|
|
|
#include "../mDNSShared/dnssd_ipc.h"
|
|
|
#include "../mDNSCore/uDNS.h"
|
|
|
#include "../mDNSShared/DebugServices.h"
|
|
|
|
|
|
// Compatibility workaround
|
|
|
#ifndef AF_LOCAL
|
|
|
#define AF_LOCAL AF_UNIX
|
|
|
#endif
|
|
|
|
|
|
//
|
|
|
// Constants
|
|
|
//
|
|
|
mDNSexport const char ProgramName[] = "dnsextd";
|
|
|
|
|
|
#define LOOPBACK "127.0.0.1"
|
|
|
#if !defined(LISTENQ)
|
|
|
# define LISTENQ 128 // tcp connection backlog
|
|
|
#endif
|
|
|
#define RECV_BUFLEN 9000
|
|
|
#define LEASETABLE_INIT_NBUCKETS 256 // initial hashtable size (doubles as table fills)
|
|
|
#define EXPIRATION_INTERVAL 300 // check for expired records every 5 minutes
|
|
|
#define SRV_TTL 7200 // TTL For _dns-update SRV records
|
|
|
#define CONFIG_FILE "/etc/dnsextd.conf"
|
|
|
#define TCP_SOCKET_FLAGS kTCPSocketFlags_UseTLS
|
|
|
|
|
|
// LLQ Lease bounds (seconds)
|
|
|
#define LLQ_MIN_LEASE (15 * 60)
|
|
|
#define LLQ_MAX_LEASE (120 * 60)
|
|
|
#define LLQ_LEASE_FUDGE 60
|
|
|
|
|
|
// LLQ SOA poll interval (microseconds)
|
|
|
#define LLQ_MONITOR_ERR_INTERVAL (60 * 1000000)
|
|
|
#define LLQ_MONITOR_INTERVAL 250000
|
|
|
#ifdef SIGINFO
|
|
|
#define INFO_SIGNAL SIGINFO
|
|
|
#else
|
|
|
#define INFO_SIGNAL SIGUSR1
|
|
|
#endif
|
|
|
|
|
|
#define SAME_INADDR(x,y) (*((mDNSu32 *)&x) == *((mDNSu32 *)&y))
|
|
|
|
|
|
//
|
|
|
// Data Structures
|
|
|
// Structs/fields that must be locked for thread safety are explicitly commented
|
|
|
//
|
|
|
|
|
|
// args passed to UDP request handler thread as void*
|
|
|
|
|
|
typedef struct
|
|
|
{
|
|
|
PktMsg pkt;
|
|
|
struct sockaddr_in cliaddr;
|
|
|
DaemonInfo *d;
|
|
|
int sd;
|
|
|
} UDPContext;
|
|
|
|
|
|
// args passed to TCP request handler thread as void*
|
|
|
typedef struct
|
|
|
{
|
|
|
PktMsg pkt;
|
|
|
struct sockaddr_in cliaddr;
|
|
|
TCPSocket *sock; // socket connected to client
|
|
|
DaemonInfo *d;
|
|
|
} TCPContext;
|
|
|
|
|
|
// args passed to UpdateAnswerList thread as void*
|
|
|
typedef struct
|
|
|
{
|
|
|
DaemonInfo *d;
|
|
|
AnswerListElem *a;
|
|
|
} UpdateAnswerListArgs;
|
|
|
|
|
|
//
|
|
|
// Global Variables
|
|
|
//
|
|
|
|
|
|
// booleans to determine runtime output
|
|
|
// read-only after initialization (no mutex protection)
|
|
|
static mDNSBool foreground = 0;
|
|
|
static mDNSBool verbose = 0;
|
|
|
|
|
|
// globals set via signal handler (accessed exclusively by main select loop and signal handler)
|
|
|
static mDNSBool terminate = 0;
|
|
|
static mDNSBool dumptable = 0;
|
|
|
static mDNSBool hangup = 0;
|
|
|
|
|
|
// global for config file location
|
|
|
static char * cfgfile = NULL;
|
|
|
|
|
|
//
|
|
|
// Logging Routines
|
|
|
// Log messages are delivered to syslog unless -f option specified
|
|
|
//
|
|
|
|
|
|
// common message logging subroutine
|
|
|
mDNSlocal void PrintLog(const char *buffer)
|
|
|
{
|
|
|
if (foreground)
|
|
|
{
|
|
|
fprintf(stderr,"%s\n", buffer);
|
|
|
fflush(stderr);
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
openlog("dnsextd", LOG_CONS, LOG_DAEMON);
|
|
|
syslog(LOG_ERR, "%s", buffer);
|
|
|
closelog();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// Verbose Logging (conditional on -v option)
|
|
|
mDNSlocal void VLog(const char *format, ...)
|
|
|
{
|
|
|
char buffer[512];
|
|
|
va_list ptr;
|
|
|
|
|
|
if (!verbose) return;
|
|
|
va_start(ptr,format);
|
|
|
buffer[mDNS_vsnprintf((char *)buffer, sizeof(buffer), format, ptr)] = 0;
|
|
|
va_end(ptr);
|
|
|
PrintLog(buffer);
|
|
|
}
|
|
|
|
|
|
// Unconditional Logging
|
|
|
mDNSlocal void Log(const char *format, ...)
|
|
|
{
|
|
|
char buffer[512];
|
|
|
va_list ptr;
|
|
|
|
|
|
va_start(ptr,format);
|
|
|
buffer[mDNS_vsnprintf((char *)buffer, sizeof(buffer), format, ptr)] = 0;
|
|
|
va_end(ptr);
|
|
|
PrintLog(buffer);
|
|
|
}
|
|
|
|
|
|
// Error Logging
|
|
|
// prints message "dnsextd <function>: <operation> - <error message>"
|
|
|
// must be compiled w/ -D_REENTRANT for thread-safe errno usage
|
|
|
mDNSlocal void LogErr(const char *fn, const char *operation)
|
|
|
{
|
|
|
char buf[512], errbuf[256];
|
|
|
strerror_r(errno, errbuf, sizeof(errbuf));
|
|
|
snprintf(buf, sizeof(buf), "%s: %s - %s", fn, operation, errbuf);
|
|
|
PrintLog(buf);
|
|
|
}
|
|
|
|
|
|
//
|
|
|
// Networking Utility Routines
|
|
|
//
|
|
|
|
|
|
// Convert DNS Message Header from Network to Host byte order
|
|
|
mDNSlocal void HdrNToH(PktMsg *pkt)
|
|
|
{
|
|
|
// Read the integer parts which are in IETF byte-order (MSB first, LSB second)
|
|
|
mDNSu8 *ptr = (mDNSu8 *)&pkt->msg.h.numQuestions;
|
|
|
pkt->msg.h.numQuestions = (mDNSu16)((mDNSu16)ptr[0] << 8 | ptr[1]);
|
|
|
pkt->msg.h.numAnswers = (mDNSu16)((mDNSu16)ptr[2] << 8 | ptr[3]);
|
|
|
pkt->msg.h.numAuthorities = (mDNSu16)((mDNSu16)ptr[4] << 8 | ptr[5]);
|
|
|
pkt->msg.h.numAdditionals = (mDNSu16)((mDNSu16)ptr[6] << 8 | ptr[7]);
|
|
|
}
|
|
|
|
|
|
// Convert DNS Message Header from Host to Network byte order
|
|
|
mDNSlocal void HdrHToN(PktMsg *pkt)
|
|
|
{
|
|
|
mDNSu16 numQuestions = pkt->msg.h.numQuestions;
|
|
|
mDNSu16 numAnswers = pkt->msg.h.numAnswers;
|
|
|
mDNSu16 numAuthorities = pkt->msg.h.numAuthorities;
|
|
|
mDNSu16 numAdditionals = pkt->msg.h.numAdditionals;
|
|
|
mDNSu8 *ptr = (mDNSu8 *)&pkt->msg.h.numQuestions;
|
|
|
|
|
|
// Put all the integer values in IETF byte-order (MSB first, LSB second)
|
|
|
*ptr++ = (mDNSu8)(numQuestions >> 8);
|
|
|
*ptr++ = (mDNSu8)(numQuestions & 0xFF);
|
|
|
*ptr++ = (mDNSu8)(numAnswers >> 8);
|
|
|
*ptr++ = (mDNSu8)(numAnswers & 0xFF);
|
|
|
*ptr++ = (mDNSu8)(numAuthorities >> 8);
|
|
|
*ptr++ = (mDNSu8)(numAuthorities & 0xFF);
|
|
|
*ptr++ = (mDNSu8)(numAdditionals >> 8);
|
|
|
*ptr++ = (mDNSu8)(numAdditionals & 0xFF);
|
|
|
}
|
|
|
|
|
|
|
|
|
// Add socket to event loop
|
|
|
|
|
|
mDNSlocal mStatus AddSourceToEventLoop( DaemonInfo * self, TCPSocket *sock, EventCallback callback, void *context )
|
|
|
{
|
|
|
EventSource * newSource;
|
|
|
mStatus err = mStatus_NoError;
|
|
|
|
|
|
if ( self->eventSources.LinkOffset == 0 )
|
|
|
{
|
|
|
InitLinkedList( &self->eventSources, offsetof( EventSource, next));
|
|
|
}
|
|
|
|
|
|
newSource = ( EventSource*) malloc( sizeof *newSource );
|
|
|
if ( newSource == NULL )
|
|
|
{
|
|
|
err = mStatus_NoMemoryErr;
|
|
|
goto exit;
|
|
|
}
|
|
|
|
|
|
newSource->callback = callback;
|
|
|
newSource->context = context;
|
|
|
newSource->sock = sock;
|
|
|
newSource->fd = mDNSPlatformTCPGetFD( sock );
|
|
|
|
|
|
AddToTail( &self->eventSources, newSource );
|
|
|
|
|
|
exit:
|
|
|
|
|
|
return err;
|
|
|
}
|
|
|
|
|
|
|
|
|
// Remove socket from event loop
|
|
|
|
|
|
mDNSlocal mStatus RemoveSourceFromEventLoop( DaemonInfo * self, TCPSocket *sock )
|
|
|
{
|
|
|
EventSource * source;
|
|
|
mStatus err;
|
|
|
|
|
|
for ( source = ( EventSource* ) self->eventSources.Head; source; source = source->next )
|
|
|
{
|
|
|
if ( source->sock == sock )
|
|
|
{
|
|
|
RemoveFromList( &self->eventSources, source );
|
|
|
|
|
|
free( source );
|
|
|
err = mStatus_NoError;
|
|
|
goto exit;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
err = mStatus_NoSuchNameErr;
|
|
|
|
|
|
exit:
|
|
|
|
|
|
return err;
|
|
|
}
|
|
|
|
|
|
// create a socket connected to nameserver
|
|
|
// caller terminates connection via close()
|
|
|
mDNSlocal TCPSocket *ConnectToServer(DaemonInfo *d)
|
|
|
{
|
|
|
int ntries = 0, retry = 0;
|
|
|
|
|
|
while (1)
|
|
|
{
|
|
|
mDNSIPPort port = zeroIPPort;
|
|
|
int fd;
|
|
|
|
|
|
TCPSocket *sock = mDNSPlatformTCPSocket( NULL, 0, &port );
|
|
|
if ( !sock ) { LogErr("ConnectToServer", "socket"); return NULL; }
|
|
|
fd = mDNSPlatformTCPGetFD( sock );
|
|
|
if (!connect( fd, (struct sockaddr *)&d->ns_addr, sizeof(d->ns_addr))) return sock;
|
|
|
mDNSPlatformTCPCloseConnection( sock );
|
|
|
if (++ntries < 10)
|
|
|
{
|
|
|
LogErr("ConnectToServer", "connect");
|
|
|
Log("ConnectToServer - retrying connection");
|
|
|
if (!retry) retry = 500000 + random() % 500000;
|
|
|
usleep(retry);
|
|
|
retry *= 2;
|
|
|
}
|
|
|
else { Log("ConnectToServer - %d failed attempts. Aborting.", ntries); return NULL; }
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// send an entire block of data over a connected socket
|
|
|
mDNSlocal int MySend(TCPSocket *sock, const void *msg, int len)
|
|
|
{
|
|
|
int selectval, n, nsent = 0;
|
|
|
fd_set wset;
|
|
|
struct timeval timeout = { 3, 0 }; // until we remove all calls from main thread, keep timeout short
|
|
|
|
|
|
while (nsent < len)
|
|
|
{
|
|
|
int fd;
|
|
|
|
|
|
FD_ZERO(&wset);
|
|
|
|
|
|
fd = mDNSPlatformTCPGetFD( sock );
|
|
|
|
|
|
FD_SET( fd, &wset );
|
|
|
selectval = select( fd+1, NULL, &wset, NULL, &timeout);
|
|
|
if (selectval < 0) { LogErr("MySend", "select"); return -1; }
|
|
|
if (!selectval || !FD_ISSET(fd, &wset)) { Log("MySend - timeout"); return -1; }
|
|
|
|
|
|
n = mDNSPlatformWriteTCP( sock, ( char* ) msg + nsent, len - nsent);
|
|
|
|
|
|
if (n < 0) { LogErr("MySend", "send"); return -1; }
|
|
|
nsent += n;
|
|
|
}
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
// Transmit a DNS message, prefixed by its length, over TCP, blocking if necessary
|
|
|
mDNSlocal int SendPacket(TCPSocket *sock, PktMsg *pkt)
|
|
|
{
|
|
|
// send the lenth, in network byte order
|
|
|
mDNSu16 len = htons((mDNSu16)pkt->len);
|
|
|
if (MySend(sock, &len, sizeof(len)) < 0) return -1;
|
|
|
|
|
|
// send the message
|
|
|
VLog("SendPacket Q:%d A:%d A:%d A:%d ",
|
|
|
ntohs(pkt->msg.h.numQuestions),
|
|
|
ntohs(pkt->msg.h.numAnswers),
|
|
|
ntohs(pkt->msg.h.numAuthorities),
|
|
|
ntohs(pkt->msg.h.numAdditionals));
|
|
|
return MySend(sock, &pkt->msg, pkt->len);
|
|
|
}
|
|
|
|
|
|
// Receive len bytes, waiting until we have all of them.
|
|
|
// Returns number of bytes read (which should always be the number asked for).
|
|
|
static int my_recv(TCPSocket *sock, void *const buf, const int len, mDNSBool * closed)
|
|
|
{
|
|
|
// Don't use "MSG_WAITALL"; it returns "Invalid argument" on some Linux versions;
|
|
|
// use an explicit while() loop instead.
|
|
|
// Also, don't try to do '+=' arithmetic on the original "void *" pointer --
|
|
|
// arithmetic on "void *" pointers is compiler-dependent.
|
|
|
|
|
|
fd_set rset;
|
|
|
struct timeval timeout = { 3, 0 }; // until we remove all calls from main thread, keep timeout short
|
|
|
int selectval, remaining = len;
|
|
|
char *ptr = (char *)buf;
|
|
|
ssize_t num_read;
|
|
|
|
|
|
while (remaining)
|
|
|
{
|
|
|
int fd;
|
|
|
|
|
|
fd = mDNSPlatformTCPGetFD( sock );
|
|
|
|
|
|
FD_ZERO(&rset);
|
|
|
FD_SET(fd, &rset);
|
|
|
selectval = select(fd+1, &rset, NULL, NULL, &timeout);
|
|
|
if (selectval < 0) { LogErr("my_recv", "select"); return -1; }
|
|
|
if (!selectval || !FD_ISSET(fd, &rset))
|
|
|
{
|
|
|
Log("my_recv - timeout");
|
|
|
return -1;
|
|
|
}
|
|
|
|
|
|
num_read = mDNSPlatformReadTCP( sock, ptr, remaining, closed );
|
|
|
|
|
|
if (((num_read == 0) && *closed) || (num_read < 0) || (num_read > remaining)) return -1;
|
|
|
if (num_read == 0) return 0;
|
|
|
ptr += num_read;
|
|
|
remaining -= num_read;
|
|
|
}
|
|
|
return(len);
|
|
|
}
|
|
|
|
|
|
// Return a DNS Message read off of a TCP socket, or NULL on failure
|
|
|
// If storage is non-null, result is placed in that buffer. Otherwise,
|
|
|
// returned value is allocated with Malloc, and contains sufficient extra
|
|
|
// storage for a Lease OPT RR
|
|
|
|
|
|
mDNSlocal PktMsg*
|
|
|
RecvPacket
|
|
|
(
|
|
|
TCPSocket * sock,
|
|
|
PktMsg * storage,
|
|
|
mDNSBool * closed
|
|
|
)
|
|
|
{
|
|
|
int nread;
|
|
|
int allocsize;
|
|
|
mDNSu16 msglen = 0;
|
|
|
PktMsg * pkt = NULL;
|
|
|
unsigned int srclen;
|
|
|
int fd;
|
|
|
mStatus err = 0;
|
|
|
|
|
|
fd = mDNSPlatformTCPGetFD( sock );
|
|
|
|
|
|
nread = my_recv( sock, &msglen, sizeof( msglen ), closed );
|
|
|
|
|
|
require_action_quiet( nread != -1, exit, err = mStatus_UnknownErr );
|
|
|
require_action_quiet( nread > 0, exit, err = mStatus_NoError );
|
|
|
|
|
|
msglen = ntohs( msglen );
|
|
|
require_action_quiet( nread == sizeof( msglen ), exit, err = mStatus_UnknownErr; Log( "Could not read length field of message") );
|
|
|
|
|
|
if ( storage )
|
|
|
{
|
|
|
require_action_quiet( msglen <= sizeof( storage->msg ), exit, err = mStatus_UnknownErr; Log( "RecvPacket: provided buffer too small." ) );
|
|
|
pkt = storage;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
// buffer extra space to add an OPT RR
|
|
|
|
|
|
if ( msglen > sizeof(DNSMessage))
|
|
|
{
|
|
|
allocsize = sizeof(PktMsg) - sizeof(DNSMessage) + msglen;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
allocsize = sizeof(PktMsg);
|
|
|
}
|
|
|
|
|
|
pkt = malloc(allocsize);
|
|
|
require_action_quiet( pkt, exit, err = mStatus_NoMemoryErr; LogErr( "RecvPacket", "malloc" ) );
|
|
|
mDNSPlatformMemZero( pkt, sizeof( *pkt ) );
|
|
|
}
|
|
|
|
|
|
pkt->len = msglen;
|
|
|
srclen = sizeof(pkt->src);
|
|
|
|
|
|
if ( getpeername( fd, ( struct sockaddr* ) &pkt->src, &srclen ) || ( srclen != sizeof( pkt->src ) ) )
|
|
|
{
|
|
|
LogErr("RecvPacket", "getpeername");
|
|
|
mDNSPlatformMemZero(&pkt->src, sizeof(pkt->src));
|
|
|
}
|
|
|
|
|
|
nread = my_recv(sock, &pkt->msg, msglen, closed );
|
|
|
require_action_quiet( nread >= 0, exit, err = mStatus_UnknownErr ; LogErr( "RecvPacket", "recv" ) );
|
|
|
require_action_quiet( nread == msglen, exit, err = mStatus_UnknownErr ; Log( "Could not read entire message" ) );
|
|
|
require_action_quiet( pkt->len >= sizeof( DNSMessageHeader ), exit, err = mStatus_UnknownErr ; Log( "RecvPacket: Message too short (%d bytes)", pkt->len ) );
|
|
|
|
|
|
exit:
|
|
|
|
|
|
if ( err && pkt )
|
|
|
{
|
|
|
if ( pkt != storage )
|
|
|
{
|
|
|
free(pkt);
|
|
|
}
|
|
|
|
|
|
pkt = NULL;
|
|
|
}
|
|
|
|
|
|
return pkt;
|
|
|
}
|
|
|
|
|
|
|
|
|
mDNSlocal DNSZone*
|
|
|
FindZone
|
|
|
(
|
|
|
DaemonInfo * self,
|
|
|
domainname * name
|
|
|
)
|
|
|
{
|
|
|
DNSZone * zone;
|
|
|
|
|
|
for ( zone = self->zones; zone; zone = zone->next )
|
|
|
{
|
|
|
if ( SameDomainName( &zone->name, name ) )
|
|
|
{
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return zone;
|
|
|
}
|
|
|
|
|
|
|
|
|
mDNSlocal mDNSBool
|
|
|
ZoneHandlesName
|
|
|
(
|
|
|
const domainname * zname,
|
|
|
const domainname * dname
|
|
|
)
|
|
|
{
|
|
|
mDNSu16 i = DomainNameLength( zname );
|
|
|
mDNSu16 j = DomainNameLength( dname );
|
|
|
|
|
|
if ( ( i == ( MAX_DOMAIN_NAME + 1 ) ) || ( j == ( MAX_DOMAIN_NAME + 1 ) ) || ( i > j ) || ( memcmp( zname->c, dname->c + ( j - i ), i ) != 0 ) )
|
|
|
{
|
|
|
return mDNSfalse;
|
|
|
}
|
|
|
|
|
|
return mDNStrue;
|
|
|
}
|
|
|
|
|
|
|
|
|
mDNSlocal mDNSBool IsQuery( PktMsg * pkt )
|
|
|
{
|
|
|
return ( pkt->msg.h.flags.b[0] & kDNSFlag0_QROP_Mask ) == (mDNSu8) ( kDNSFlag0_QR_Query | kDNSFlag0_OP_StdQuery );
|
|
|
}
|
|
|
|
|
|
|
|
|
mDNSlocal mDNSBool IsUpdate( PktMsg * pkt )
|
|
|
{
|
|
|
return ( pkt->msg.h.flags.b[0] & kDNSFlag0_QROP_Mask ) == (mDNSu8) ( kDNSFlag0_OP_Update );
|
|
|
}
|
|
|
|
|
|
|
|
|
mDNSlocal mDNSBool IsNotify(PktMsg *pkt)
|
|
|
{
|
|
|
return ( pkt->msg.h.flags.b[0] & kDNSFlag0_QROP_Mask ) == ( mDNSu8) ( kDNSFlag0_OP_Notify );
|
|
|
}
|
|
|
|
|
|
|
|
|
mDNSlocal mDNSBool IsLLQRequest(PktMsg *pkt)
|
|
|
{
|
|
|
const mDNSu8 *ptr = NULL, *end = (mDNSu8 *)&pkt->msg + pkt->len;
|
|
|
LargeCacheRecord lcr;
|
|
|
int i;
|
|
|
mDNSBool result = mDNSfalse;
|
|
|
|
|
|
HdrNToH(pkt);
|
|
|
if ((mDNSu8)(pkt->msg.h.flags.b[0] & kDNSFlag0_QROP_Mask) != (mDNSu8)(kDNSFlag0_QR_Query | kDNSFlag0_OP_StdQuery)) goto end;
|
|
|
|
|
|
if (!pkt->msg.h.numAdditionals) goto end;
|
|
|
ptr = LocateAdditionals(&pkt->msg, end);
|
|
|
if (!ptr) goto end;
|
|
|
|
|
|
// find last Additional info.
|
|
|
for (i = 0; i < pkt->msg.h.numAdditionals; i++)
|
|
|
{
|
|
|
ptr = GetLargeResourceRecord(NULL, &pkt->msg, ptr, end, 0, kDNSRecordTypePacketAdd, &lcr);
|
|
|
if (!ptr) { Log("Unable to read additional record"); goto end; }
|
|
|
}
|
|
|
|
|
|
if ( lcr.r.resrec.rrtype == kDNSType_OPT && lcr.r.resrec.rdlength >= DNSOpt_LLQData_Space && lcr.r.resrec.rdata->u.opt[0].opt == kDNSOpt_LLQ )
|
|
|
{
|
|
|
result = mDNStrue;
|
|
|
}
|
|
|
|
|
|
end:
|
|
|
HdrHToN(pkt);
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
// !!!KRS implement properly
|
|
|
mDNSlocal mDNSBool IsLLQAck(PktMsg *pkt)
|
|
|
{
|
|
|
if ((pkt->msg.h.flags.b[0] & kDNSFlag0_QROP_Mask ) == (mDNSu8) ( kDNSFlag0_QR_Response | kDNSFlag0_OP_StdQuery ) &&
|
|
|
pkt->msg.h.numQuestions && !pkt->msg.h.numAnswers && !pkt->msg.h.numAuthorities) return mDNStrue;
|
|
|
return mDNSfalse;
|
|
|
}
|
|
|
|
|
|
|
|
|
mDNSlocal mDNSBool
|
|
|
IsPublicSRV
|
|
|
(
|
|
|
DaemonInfo * self,
|
|
|
DNSQuestion * q
|
|
|
)
|
|
|
{
|
|
|
DNameListElem * elem;
|
|
|
mDNSBool ret = mDNSfalse;
|
|
|
int i = ( int ) DomainNameLength( &q->qname ) - 1;
|
|
|
|
|
|
for ( elem = self->public_names; elem; elem = elem->next )
|
|
|
{
|
|
|
int j = ( int ) DomainNameLength( &elem->name ) - 1;
|
|
|
|
|
|
if ( i > j )
|
|
|
{
|
|
|
for ( ; i >= 0; i--, j-- )
|
|
|
{
|
|
|
if ( q->qname.c[ i ] != elem->name.c[ j ] )
|
|
|
{
|
|
|
ret = mDNStrue;
|
|
|
goto exit;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
exit:
|
|
|
|
|
|
return ret;
|
|
|
}
|
|
|
|
|
|
|
|
|
mDNSlocal void
|
|
|
SetZone
|
|
|
(
|
|
|
DaemonInfo * self,
|
|
|
PktMsg * pkt
|
|
|
)
|
|
|
{
|
|
|
domainname zname;
|
|
|
mDNSu8 QR_OP;
|
|
|
const mDNSu8 * ptr = pkt->msg.data;
|
|
|
mDNSBool exception = mDNSfalse;
|
|
|
|
|
|
// Initialize
|
|
|
|
|
|
pkt->zone = NULL;
|
|
|
pkt->isZonePublic = mDNStrue;
|
|
|
zname.c[0] = '\0';
|
|
|
|
|
|
// Figure out what type of packet this is
|
|
|
|
|
|
QR_OP = ( mDNSu8 ) ( pkt->msg.h.flags.b[0] & kDNSFlag0_QROP_Mask );
|
|
|
|
|
|
if ( IsQuery( pkt ) )
|
|
|
{
|
|
|
DNSQuestion question;
|
|
|
|
|
|
// It's a query
|
|
|
|
|
|
ptr = getQuestion( &pkt->msg, ptr, ( ( mDNSu8* ) &pkt->msg ) + pkt->len, NULL, &question );
|
|
|
|
|
|
AppendDomainName( &zname, &question.qname );
|
|
|
|
|
|
exception = ( ( question.qtype == kDNSType_SOA ) || ( question.qtype == kDNSType_NS ) || ( ( question.qtype == kDNSType_SRV ) && IsPublicSRV( self, &question ) ) );
|
|
|
}
|
|
|
else if ( IsUpdate( pkt ) )
|
|
|
{
|
|
|
DNSQuestion question;
|
|
|
|
|
|
// It's an update. The format of the zone section is the same as the format for the question section
|
|
|
// according to RFC 2136, so we'll just treat this as a question so we can get at the zone.
|
|
|
|
|
|
ptr = getQuestion( &pkt->msg, ptr, ( ( mDNSu8* ) &pkt->msg ) + pkt->len, NULL, &question );
|
|
|
|
|
|
AppendDomainName( &zname, &question.qname );
|
|
|
|
|
|
exception = mDNSfalse;
|
|
|
}
|
|
|
|
|
|
if ( zname.c[0] != '\0' )
|
|
|
{
|
|
|
// Find the right zone
|
|
|
|
|
|
for ( pkt->zone = self->zones; pkt->zone; pkt->zone = pkt->zone->next )
|
|
|
{
|
|
|
if ( ZoneHandlesName( &pkt->zone->name, &zname ) )
|
|
|
{
|
|
|
VLog( "found correct zone %##s for query", pkt->zone->name.c );
|
|
|
|
|
|
pkt->isZonePublic = ( ( pkt->zone->type == kDNSZonePublic ) || exception );
|
|
|
|
|
|
VLog( "zone %##s is %s", pkt->zone->name.c, ( pkt->isZonePublic ) ? "public" : "private" );
|
|
|
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
mDNSlocal int
|
|
|
UDPServerTransaction(const DaemonInfo *d, const PktMsg *request, PktMsg *reply, mDNSBool *trunc)
|
|
|
{
|
|
|
fd_set rset;
|
|
|
struct timeval timeout = { 3, 0 }; // until we remove all calls from main thread, keep timeout short
|
|
|
int sd;
|
|
|
int res;
|
|
|
mStatus err = mStatus_NoError;
|
|
|
|
|
|
// Initialize
|
|
|
|
|
|
*trunc = mDNSfalse;
|
|
|
|
|
|
// Create a socket
|
|
|
|
|
|
sd = socket( AF_INET, SOCK_DGRAM, 0 );
|
|
|
require_action( sd >= 0, exit, err = mStatus_UnknownErr; LogErr( "UDPServerTransaction", "socket" ) );
|
|
|
|
|
|
// Send the packet to the nameserver
|
|
|
|
|
|
VLog("UDPServerTransaction Q:%d A:%d A:%d A:%d ",
|
|
|
ntohs(request->msg.h.numQuestions),
|
|
|
ntohs(request->msg.h.numAnswers),
|
|
|
ntohs(request->msg.h.numAuthorities),
|
|
|
ntohs(request->msg.h.numAdditionals));
|
|
|
res = sendto( sd, (char *)&request->msg, request->len, 0, ( struct sockaddr* ) &d->ns_addr, sizeof( d->ns_addr ) );
|
|
|
require_action( res == (int) request->len, exit, err = mStatus_UnknownErr; LogErr( "UDPServerTransaction", "sendto" ) );
|
|
|
|
|
|
// Wait for reply
|
|
|
|
|
|
FD_ZERO( &rset );
|
|
|
FD_SET( sd, &rset );
|
|
|
res = select( sd + 1, &rset, NULL, NULL, &timeout );
|
|
|
require_action( res >= 0, exit, err = mStatus_UnknownErr; LogErr( "UDPServerTransaction", "select" ) );
|
|
|
require_action( ( res > 0 ) && FD_ISSET( sd, &rset ), exit, err = mStatus_UnknownErr; Log( "UDPServerTransaction - timeout" ) );
|
|
|
|
|
|
// Receive reply
|
|
|
|
|
|
reply->len = recvfrom( sd, &reply->msg, sizeof(reply->msg), 0, NULL, NULL );
|
|
|
require_action( ( ( int ) reply->len ) >= 0, exit, err = mStatus_UnknownErr; LogErr( "UDPServerTransaction", "recvfrom" ) );
|
|
|
require_action( reply->len >= sizeof( DNSMessageHeader ), exit, err = mStatus_UnknownErr; Log( "UDPServerTransaction - Message too short (%d bytes)", reply->len ) );
|
|
|
|
|
|
// Check for truncation bit
|
|
|
|
|
|
if ( reply->msg.h.flags.b[0] & kDNSFlag0_TC )
|
|
|
{
|
|
|
*trunc = mDNStrue;
|
|
|
}
|
|
|
|
|
|
exit:
|
|
|
|
|
|
if ( sd >= 0 )
|
|
|
{
|
|
|
close( sd );
|
|
|
}
|
|
|
|
|
|
return err;
|
|
|
}
|
|
|
|
|
|
//
|
|
|
// Dynamic Update Utility Routines
|
|
|
//
|
|
|
|
|
|
// check if a request and server response complete a successful dynamic update
|
|
|
mDNSlocal mDNSBool SuccessfulUpdateTransaction(PktMsg *request, PktMsg *reply)
|
|
|
{
|
|
|
char buf[32];
|
|
|
char *vlogmsg = NULL;
|
|
|
|
|
|
// check messages
|
|
|
if (!request || !reply) { vlogmsg = "NULL message"; goto failure; }
|
|
|
if (request->len < sizeof(DNSMessageHeader) || reply->len < sizeof(DNSMessageHeader)) { vlogmsg = "Malformatted message"; goto failure; }
|
|
|
|
|
|
// check request operation
|
|
|
if ((request->msg.h.flags.b[0] & kDNSFlag0_QROP_Mask) != (request->msg.h.flags.b[0] & kDNSFlag0_QROP_Mask))
|
|
|
{ vlogmsg = "Request opcode not an update"; goto failure; }
|
|
|
|
|
|
// check result
|
|
|
if ((reply->msg.h.flags.b[1] & kDNSFlag1_RC_Mask)) { vlogmsg = "Reply contains non-zero rcode"; goto failure; }
|
|
|
if ((reply->msg.h.flags.b[0] & kDNSFlag0_QROP_Mask) != (kDNSFlag0_OP_Update | kDNSFlag0_QR_Response))
|
|
|
{ vlogmsg = "Reply opcode not an update response"; goto failure; }
|
|
|
|
|
|
VLog("Successful update from %s", inet_ntop(AF_INET, &request->src.sin_addr, buf, 32));
|
|
|
return mDNStrue;
|
|
|
|
|
|
failure:
|
|
|
VLog("Request %s: %s", inet_ntop(AF_INET, &request->src.sin_addr, buf, 32), vlogmsg);
|
|
|
return mDNSfalse;
|
|
|
}
|
|
|
|
|
|
// Allocate an appropriately sized CacheRecord and copy data from original.
|
|
|
// Name pointer in CacheRecord object is set to point to the name specified
|
|
|
//
|
|
|
mDNSlocal CacheRecord *CopyCacheRecord(const CacheRecord *orig, domainname *name)
|
|
|
{
|
|
|
CacheRecord *cr;
|
|
|
size_t size = sizeof(*cr);
|
|
|
if (orig->resrec.rdlength > InlineCacheRDSize) size += orig->resrec.rdlength - InlineCacheRDSize;
|
|
|
cr = malloc(size);
|
|
|
if (!cr) { LogErr("CopyCacheRecord", "malloc"); return NULL; }
|
|
|
memcpy(cr, orig, size);
|
|
|
cr->resrec.rdata = (RData*)&cr->smallrdatastorage;
|
|
|
cr->resrec.name = name;
|
|
|
|
|
|
return cr;
|
|
|
}
|
|
|
|
|
|
|
|
|
//
|
|
|
// Lease Hashtable Utility Routines
|
|
|
//
|
|
|
|
|
|
// double hash table size
|
|
|
// caller must lock table prior to invocation
|
|
|
mDNSlocal void RehashTable(DaemonInfo *d)
|
|
|
{
|
|
|
RRTableElem *ptr, *tmp, **new;
|
|
|
int i, bucket, newnbuckets = d->nbuckets * 2;
|
|
|
|
|
|
VLog("Rehashing lease table (new size %d buckets)", newnbuckets);
|
|
|
new = malloc(sizeof(RRTableElem *) * newnbuckets);
|
|
|
if (!new) { LogErr("RehashTable", "malloc"); return; }
|
|
|
mDNSPlatformMemZero(new, newnbuckets * sizeof(RRTableElem *));
|
|
|
|
|
|
for (i = 0; i < d->nbuckets; i++)
|
|
|
{
|
|
|
ptr = d->table[i];
|
|
|
while (ptr)
|
|
|
{
|
|
|
bucket = ptr->rr.resrec.namehash % newnbuckets;
|
|
|
tmp = ptr;
|
|
|
ptr = ptr->next;
|
|
|
tmp->next = new[bucket];
|
|
|
new[bucket] = tmp;
|
|
|
}
|
|
|
}
|
|
|
d->nbuckets = newnbuckets;
|
|
|
free(d->table);
|
|
|
d->table = new;
|
|
|
}
|
|
|
|
|
|
// print entire contents of hashtable, invoked via SIGINFO
|
|
|
mDNSlocal void PrintLeaseTable(DaemonInfo *d)
|
|
|
{
|
|
|
int i;
|
|
|
RRTableElem *ptr;
|
|
|
char rrbuf[MaxMsg], addrbuf[16];
|
|
|
struct timeval now;
|
|
|
int hr, min, sec;
|
|
|
|
|
|
if (gettimeofday(&now, NULL)) { LogErr("PrintTable", "gettimeofday"); return; }
|
|
|
if (pthread_mutex_lock(&d->tablelock)) { LogErr("PrintTable", "pthread_mutex_lock"); return; }
|
|
|
|
|
|
Log("Dumping Lease Table Contents (table contains %d resource records)", d->nelems);
|
|
|
for (i = 0; i < d->nbuckets; i++)
|
|
|
{
|
|
|
for (ptr = d->table[i]; ptr; ptr = ptr->next)
|
|
|
{
|
|
|
hr = ((ptr->expire - now.tv_sec) / 60) / 60;
|
|
|
min = ((ptr->expire - now.tv_sec) / 60) % 60;
|
|
|
sec = (ptr->expire - now.tv_sec) % 60;
|
|
|
Log("Update from %s, Expires in %d:%d:%d\n\t%s", inet_ntop(AF_INET, &ptr->cli.sin_addr, addrbuf, 16), hr, min, sec,
|
|
|
GetRRDisplayString_rdb(&ptr->rr.resrec, &ptr->rr.resrec.rdata->u, rrbuf));
|
|
|
}
|
|
|
}
|
|
|
pthread_mutex_unlock(&d->tablelock);
|
|
|
}
|
|
|
|
|
|
//
|
|
|
// Startup SRV Registration Routines
|
|
|
// Register _dns-update._udp/_tcp.<zone> SRV records indicating the port on which
|
|
|
// the daemon accepts requests
|
|
|
//
|
|
|
|
|
|
// delete all RRS of a given name/type
|
|
|
mDNSlocal mDNSu8 *putRRSetDeletion(DNSMessage *msg, mDNSu8 *ptr, mDNSu8 *limit, ResourceRecord *rr)
|
|
|
{
|
|
|
ptr = putDomainNameAsLabels(msg, ptr, limit, rr->name);
|
|
|
if (!ptr || ptr + 10 >= limit) return NULL; // out of space
|
|
|
ptr[0] = (mDNSu8)(rr->rrtype >> 8);
|
|
|
ptr[1] = (mDNSu8)(rr->rrtype & 0xFF);
|
|
|
ptr[2] = (mDNSu8)((mDNSu16)kDNSQClass_ANY >> 8);
|
|
|
ptr[3] = (mDNSu8)((mDNSu16)kDNSQClass_ANY & 0xFF);
|
|
|
mDNSPlatformMemZero(ptr+4, sizeof(rr->rroriginalttl) + sizeof(rr->rdlength)); // zero ttl/rdata
|
|
|
msg->h.mDNS_numUpdates++;
|
|
|
return ptr + 10;
|
|
|
}
|
|
|
|
|
|
mDNSlocal mDNSu8 *PutUpdateSRV(DaemonInfo *d, DNSZone * zone, PktMsg *pkt, mDNSu8 *ptr, char *regtype, mDNSIPPort port, mDNSBool registration)
|
|
|
{
|
|
|
AuthRecord rr;
|
|
|
char hostname[1024], buf[MaxMsg];
|
|
|
mDNSu8 *end = (mDNSu8 *)&pkt->msg + sizeof(DNSMessage);
|
|
|
|
|
|
( void ) d;
|
|
|
|
|
|
mDNS_SetupResourceRecord(&rr, NULL, 0, kDNSType_SRV, SRV_TTL, kDNSRecordTypeUnique, AuthRecordAny, NULL, NULL);
|
|
|
rr.resrec.rrclass = kDNSClass_IN;
|
|
|
rr.resrec.rdata->u.srv.priority = 0;
|
|
|
rr.resrec.rdata->u.srv.weight = 0;
|
|
|
rr.resrec.rdata->u.srv.port = port;
|
|
|
if (gethostname(hostname, 1024) < 0 || !MakeDomainNameFromDNSNameString(&rr.resrec.rdata->u.srv.target, hostname))
|
|
|
rr.resrec.rdata->u.srv.target.c[0] = '\0';
|
|
|
|
|
|
MakeDomainNameFromDNSNameString(&rr.namestorage, regtype);
|
|
|
AppendDomainName(&rr.namestorage, &zone->name);
|
|
|
VLog("%s %s", registration ? "Registering SRV record" : "Deleting existing RRSet",
|
|
|
GetRRDisplayString_rdb(&rr.resrec, &rr.resrec.rdata->u, buf));
|
|
|
if (registration) ptr = PutResourceRecord(&pkt->msg, ptr, &pkt->msg.h.mDNS_numUpdates, &rr.resrec);
|
|
|
else ptr = putRRSetDeletion(&pkt->msg, ptr, end, &rr.resrec);
|
|
|
return ptr;
|
|
|
}
|
|
|
|
|
|
|
|
|
// perform dynamic update.
|
|
|
// specify deletion by passing false for the register parameter, otherwise register the records.
|
|
|
mDNSlocal int UpdateSRV(DaemonInfo *d, mDNSBool registration)
|
|
|
{
|
|
|
TCPSocket *sock = NULL;
|
|
|
DNSZone * zone;
|
|
|
int err = mStatus_NoError;
|
|
|
|
|
|
sock = ConnectToServer( d );
|
|
|
require_action( sock, exit, err = mStatus_UnknownErr; Log( "UpdateSRV: ConnectToServer failed" ) );
|
|
|
|
|
|
for ( zone = d->zones; zone; zone = zone->next )
|
|
|
{
|
|
|
PktMsg pkt;
|
|
|
mDNSu8 *ptr = pkt.msg.data;
|
|
|
mDNSu8 *end = (mDNSu8 *)&pkt.msg + sizeof(DNSMessage);
|
|
|
PktMsg *reply = NULL;
|
|
|
mDNSBool closed;
|
|
|
mDNSBool ok;
|
|
|
|
|
|
// Initialize message
|
|
|
InitializeDNSMessage(&pkt.msg.h, zeroID, UpdateReqFlags);
|
|
|
pkt.src.sin_addr.s_addr = zerov4Addr.NotAnInteger; // address field set solely for verbose logging in subroutines
|
|
|
pkt.src.sin_family = AF_INET;
|
|
|
|
|
|
// format message body
|
|
|
ptr = putZone(&pkt.msg, ptr, end, &zone->name, mDNSOpaque16fromIntVal(kDNSClass_IN));
|
|
|
require_action( ptr, exit, err = mStatus_UnknownErr; Log("UpdateSRV: Error constructing lease expiration update" ) );
|
|
|
|
|
|
if ( zone->type == kDNSZonePrivate )
|
|
|
{
|
|
|
ptr = PutUpdateSRV(d, zone, &pkt, ptr, "_dns-update-tls._tcp.", d->private_port, registration);
|
|
|
require_action( ptr, exit, err = mStatus_UnknownErr; Log("UpdateSRV: Error constructing lease expiration update" ) );
|
|
|
ptr = PutUpdateSRV(d, zone, &pkt, ptr, "_dns-query-tls._tcp.", d->private_port, registration);
|
|
|
require_action( ptr, exit, err = mStatus_UnknownErr; Log("UpdateSRV: Error constructing lease expiration update" ) );
|
|
|
ptr = PutUpdateSRV(d, zone, &pkt, ptr, "_dns-llq-tls._tcp.", d->private_port, registration);
|
|
|
require_action( ptr, exit, err = mStatus_UnknownErr; Log("UpdateSRV: Error constructing lease expiration update" ) );
|
|
|
|
|
|
if ( !registration )
|
|
|
{
|
|
|
ptr = PutUpdateSRV(d, zone, &pkt, ptr, "_dns-update._udp.", d->llq_port, registration);
|
|
|
require_action( ptr, exit, err = mStatus_UnknownErr; Log("UpdateSRV: Error constructing lease expiration update" ) );
|
|
|
ptr = PutUpdateSRV(d, zone, &pkt, ptr, "_dns-llq._udp.", d->llq_port, registration);
|
|
|
require_action( ptr, exit, err = mStatus_UnknownErr; Log("UpdateSRV: Error constructing lease expiration update" ) );
|
|
|
}
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
if ( !registration )
|
|
|
{
|
|
|
ptr = PutUpdateSRV(d, zone, &pkt, ptr, "_dns-update-tls.", d->private_port, registration);
|
|
|
require_action( ptr, exit, err = mStatus_UnknownErr; Log("UpdateSRV: Error constructing lease expiration update" ) );
|
|
|
ptr = PutUpdateSRV(d, zone, &pkt, ptr, "_dns-query-tls.", d->private_port, registration);
|
|
|
require_action( ptr, exit, err = mStatus_UnknownErr; Log("UpdateSRV: Error constructing lease expiration update" ) );
|
|
|
ptr = PutUpdateSRV(d, zone, &pkt, ptr, "_dns-llq-tls.", d->private_port, registration);
|
|
|
require_action( ptr, exit, err = mStatus_UnknownErr; Log("UpdateSRV: Error constructing lease expiration update" ) );
|
|
|
}
|
|
|
|
|
|
ptr = PutUpdateSRV(d, zone, &pkt, ptr, "_dns-update._udp.", d->llq_port, registration);
|
|
|
require_action( ptr, exit, err = mStatus_UnknownErr; Log("UpdateSRV: Error constructing lease expiration update" ) );
|
|
|
ptr = PutUpdateSRV(d, zone, &pkt, ptr, "_dns-llq._udp.", d->llq_port, registration);
|
|
|
require_action( ptr, exit, err = mStatus_UnknownErr; Log("UpdateSRV: Error constructing lease expiration update" ) );
|
|
|
}
|
|
|
|
|
|
HdrHToN(&pkt);
|
|
|
|
|
|
if ( zone->updateKeys )
|
|
|
{
|
|
|
DNSDigest_SignMessage( &pkt.msg, &ptr, zone->updateKeys, 0 );
|
|
|
require_action( ptr, exit, Log("UpdateSRV: Error constructing lease expiration update" ) );
|
|
|
}
|
|
|
|
|
|
pkt.len = ptr - (mDNSu8 *)&pkt.msg;
|
|
|
|
|
|
// send message, receive reply
|
|
|
|
|
|
err = SendPacket( sock, &pkt );
|
|
|
require_action( !err, exit, Log( "UpdateSRV: SendPacket failed" ) );
|
|
|
|
|
|
reply = RecvPacket( sock, NULL, &closed );
|
|
|
require_action( reply, exit, err = mStatus_UnknownErr; Log( "UpdateSRV: RecvPacket returned NULL" ) );
|
|
|
|
|
|
ok = SuccessfulUpdateTransaction( &pkt, reply );
|
|
|
|
|
|
if ( !ok )
|
|
|
{
|
|
|
Log("SRV record registration failed with rcode %d", reply->msg.h.flags.b[1] & kDNSFlag1_RC_Mask);
|
|
|
}
|
|
|
|
|
|
free( reply );
|
|
|
}
|
|
|
|
|
|
exit:
|
|
|
|
|
|
if ( sock )
|
|
|
{
|
|
|
mDNSPlatformTCPCloseConnection( sock );
|
|
|
}
|
|
|
|
|
|
return err;
|
|
|
}
|
|
|
|
|
|
// wrapper routines/macros
|
|
|
#define ClearUpdateSRV(d) UpdateSRV(d, 0)
|
|
|
|
|
|
// clear any existing records prior to registration
|
|
|
mDNSlocal int SetUpdateSRV(DaemonInfo *d)
|
|
|
{
|
|
|
int err;
|
|
|
|
|
|
err = ClearUpdateSRV(d); // clear any existing record
|
|
|
if (!err) err = UpdateSRV(d, 1);
|
|
|
return err;
|
|
|
}
|
|
|
|
|
|
//
|
|
|
// Argument Parsing and Configuration
|
|
|
//
|
|
|
|
|
|
mDNSlocal void PrintUsage(void)
|
|
|
{
|
|
|
fprintf(stderr, "Usage: dnsextd [-f <config file>] [-vhd] ...\n"
|
|
|
"Use \"dnsextd -h\" for help\n");
|
|
|
}
|
|
|
|
|
|
mDNSlocal void PrintHelp(void)
|
|
|
{
|
|
|
fprintf(stderr, "\n\n");
|
|
|
PrintUsage();
|
|
|
|
|
|
fprintf(stderr,
|
|
|
"dnsextd is a daemon that implements DNS extensions supporting Dynamic DNS Update Leases\n"
|
|
|
"and Long Lived Queries, used in Wide-Area DNS Service Discovery, on behalf of name servers\n"
|
|
|
"that do not natively support these extensions. (See dns-sd.org for more info on DNS Service\n"
|
|
|
"Discovery, Update Leases, and Long Lived Queries.)\n\n"
|
|
|
|
|
|
"dnsextd requires one argument,the zone, which is the domain for which Update Leases\n"
|
|
|
"and Long Lived Queries are to be administered. dnsextd communicates directly with the\n"
|
|
|
"primary master server for this zone.\n\n"
|
|
|
|
|
|
"The options are as follows:\n\n"
|
|
|
|
|
|
"-f Specify configuration file. The default is /etc/dnsextd.conf.\n\n"
|
|
|
|
|
|
"-d Run daemon in foreground.\n\n"
|
|
|
|
|
|
"-h Print help.\n\n"
|
|
|
|
|
|
"-v Verbose output.\n\n"
|
|
|
);
|
|
|
}
|
|
|
|
|
|
|
|
|
// Note: ProcessArgs called before process is daemonized, and therefore must open no descriptors
|
|
|
// returns 0 (success) if program is to continue execution
|
|
|
// output control arguments (-f, -v) do not affect this routine
|
|
|
mDNSlocal int ProcessArgs(int argc, char *argv[], DaemonInfo *d)
|
|
|
{
|
|
|
DNSZone * zone;
|
|
|
int opt;
|
|
|
int err = 0;
|
|
|
|
|
|
cfgfile = strdup( CONFIG_FILE );
|
|
|
require_action( cfgfile, arg_error, err = mStatus_NoMemoryErr );
|
|
|
|
|
|
// defaults, may be overriden by command option
|
|
|
|
|
|
// setup our sockaddr
|
|
|
|
|
|
mDNSPlatformMemZero( &d->addr, sizeof( d->addr ) );
|
|
|
d->addr.sin_addr.s_addr = zerov4Addr.NotAnInteger;
|
|
|
d->addr.sin_port = UnicastDNSPort.NotAnInteger;
|
|
|
d->addr.sin_family = AF_INET;
|
|
|
#ifndef NOT_HAVE_SA_LEN
|
|
|
d->addr.sin_len = sizeof( d->addr );
|
|
|
#endif
|
|
|
|
|
|
// setup nameserver's sockaddr
|
|
|
|
|
|
mDNSPlatformMemZero(&d->ns_addr, sizeof(d->ns_addr));
|
|
|
d->ns_addr.sin_family = AF_INET;
|
|
|
inet_pton( AF_INET, LOOPBACK, &d->ns_addr.sin_addr );
|
|
|
d->ns_addr.sin_port = NSIPCPort.NotAnInteger;
|
|
|
#ifndef NOT_HAVE_SA_LEN
|
|
|
d->ns_addr.sin_len = sizeof( d->ns_addr );
|
|
|
#endif
|
|
|
|
|
|
// setup our ports
|
|
|
|
|
|
d->private_port = PrivateDNSPort;
|
|
|
d->llq_port = DNSEXTPort;
|
|
|
|
|
|
while ((opt = getopt(argc, argv, "f:hdv")) != -1)
|
|
|
{
|
|
|
switch(opt)
|
|
|
{
|
|
|
case 'f': free( cfgfile ); cfgfile = strdup( optarg ); require_action( cfgfile, arg_error, err = mStatus_NoMemoryErr ); break;
|
|
|
case 'h': PrintHelp(); return -1;
|
|
|
case 'd': foreground = 1; break; // Also used when launched via OS X's launchd mechanism
|
|
|
case 'v': verbose = 1; break;
|
|
|
default: goto arg_error;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
err = ParseConfig( d, cfgfile );
|
|
|
require_noerr( err, arg_error );
|
|
|
|
|
|
// Make sure we've specified some zones
|
|
|
|
|
|
require_action( d->zones, arg_error, err = mStatus_UnknownErr );
|
|
|
|
|
|
// if we have a shared secret, use it for the entire zone
|
|
|
|
|
|
for ( zone = d->zones; zone; zone = zone->next )
|
|
|
{
|
|
|
if ( zone->updateKeys )
|
|
|
{
|
|
|
AssignDomainName( &zone->updateKeys->domain, &zone->name );
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
arg_error:
|
|
|
|
|
|
PrintUsage();
|
|
|
return -1;
|
|
|
}
|
|
|
|
|
|
|
|
|
//
|
|
|
// Initialization Routines
|
|
|
//
|
|
|
|
|
|
// Allocate memory, initialize locks and bookkeeping variables
|
|
|
mDNSlocal int InitLeaseTable(DaemonInfo *d)
|
|
|
{
|
|
|
if (pthread_mutex_init(&d->tablelock, NULL)) { LogErr("InitLeaseTable", "pthread_mutex_init"); return -1; }
|
|
|
d->nbuckets = LEASETABLE_INIT_NBUCKETS;
|
|
|
d->nelems = 0;
|
|
|
d->table = malloc(sizeof(RRTableElem *) * LEASETABLE_INIT_NBUCKETS);
|
|
|
if (!d->table) { LogErr("InitLeaseTable", "malloc"); return -1; }
|
|
|
mDNSPlatformMemZero(d->table, sizeof(RRTableElem *) * LEASETABLE_INIT_NBUCKETS);
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
|
|
|
mDNSlocal int
|
|
|
SetupSockets
|
|
|
(
|
|
|
DaemonInfo * self
|
|
|
)
|
|
|
{
|
|
|
static const int kOn = 1;
|
|
|
int sockpair[2];
|
|
|
mDNSBool private = mDNSfalse;
|
|
|
struct sockaddr_in daddr;
|
|
|
DNSZone * zone;
|
|
|
mStatus err = 0;
|
|
|
|
|
|
// set up sockets on which we all ns requests
|
|
|
|
|
|
self->tcpsd = socket( AF_INET, SOCK_STREAM, 0 );
|
|
|
require_action( dnssd_SocketValid(self->tcpsd), exit, err = mStatus_UnknownErr; LogErr( "SetupSockets", "socket" ) );
|
|
|
|
|
|
#if defined(SO_REUSEADDR)
|
|
|
err = setsockopt(self->tcpsd, SOL_SOCKET, SO_REUSEADDR, &kOn, sizeof(kOn));
|
|
|
require_action( !err, exit, LogErr( "SetupSockets", "SO_REUSEADDR self->tcpsd" ) );
|
|
|
#endif
|
|
|
|
|
|
err = bind( self->tcpsd, ( struct sockaddr* ) &self->addr, sizeof( self->addr ) );
|
|
|
require_action( !err, exit, LogErr( "SetupSockets", "bind self->tcpsd" ) );
|
|
|
|
|
|
err = listen( self->tcpsd, LISTENQ );
|
|
|
require_action( !err, exit, LogErr( "SetupSockets", "listen" ) );
|
|
|
|
|
|
self->udpsd = socket( AF_INET, SOCK_DGRAM, 0 );
|
|
|
require_action( dnssd_SocketValid(self->udpsd), exit, err = mStatus_UnknownErr; LogErr( "SetupSockets", "socket" ) );
|
|
|
|
|
|
#if defined(SO_REUSEADDR)
|
|
|
err = setsockopt(self->udpsd, SOL_SOCKET, SO_REUSEADDR, &kOn, sizeof(kOn));
|
|
|
require_action( !err, exit, LogErr( "SetupSockets", "SO_REUSEADDR self->udpsd" ) );
|
|
|
#endif
|
|
|
|
|
|
err = bind( self->udpsd, ( struct sockaddr* ) &self->addr, sizeof( self->addr ) );
|
|
|
require_action( !err, exit, LogErr( "SetupSockets", "bind self->udpsd" ) );
|
|
|
|
|
|
// set up sockets on which we receive llq requests
|
|
|
|
|
|
mDNSPlatformMemZero(&self->llq_addr, sizeof(self->llq_addr));
|
|
|
self->llq_addr.sin_family = AF_INET;
|
|
|
self->llq_addr.sin_addr.s_addr = zerov4Addr.NotAnInteger;
|
|
|
self->llq_addr.sin_port = ( self->llq_port.NotAnInteger ) ? self->llq_port.NotAnInteger : DNSEXTPort.NotAnInteger;
|
|
|
|
|
|
if (self->llq_addr.sin_port == self->addr.sin_port)
|
|
|
{
|
|
|
self->llq_tcpsd = self->tcpsd;
|
|
|
self->llq_udpsd = self->udpsd;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
self->llq_tcpsd = socket( AF_INET, SOCK_STREAM, 0 );
|
|
|
require_action( dnssd_SocketValid(self->llq_tcpsd), exit, err = mStatus_UnknownErr; LogErr( "SetupSockets", "socket" ) );
|
|
|
|
|
|
#if defined(SO_REUSEADDR)
|
|
|
err = setsockopt(self->llq_tcpsd, SOL_SOCKET, SO_REUSEADDR, &kOn, sizeof(kOn));
|
|
|
require_action( !err, exit, LogErr( "SetupSockets", "SO_REUSEADDR self->llq_tcpsd" ) );
|
|
|
#endif
|
|
|
|
|
|
err = bind( self->llq_tcpsd, ( struct sockaddr* ) &self->llq_addr, sizeof( self->llq_addr ) );
|
|
|
require_action( !err, exit, LogErr( "SetupSockets", "bind self->llq_tcpsd" ) );
|
|
|
|
|
|
err = listen( self->llq_tcpsd, LISTENQ );
|
|
|
require_action( !err, exit, LogErr( "SetupSockets", "listen" ) );
|
|
|
|
|
|
self->llq_udpsd = socket( AF_INET, SOCK_DGRAM, 0 );
|
|
|
require_action( dnssd_SocketValid(self->llq_udpsd), exit, err = mStatus_UnknownErr; LogErr( "SetupSockets", "socket" ) );
|
|
|
|
|
|
#if defined(SO_REUSEADDR)
|
|
|
err = setsockopt(self->llq_udpsd, SOL_SOCKET, SO_REUSEADDR, &kOn, sizeof(kOn));
|
|
|
require_action( !err, exit, LogErr( "SetupSockets", "SO_REUSEADDR self->llq_udpsd" ) );
|
|
|
#endif
|
|
|
|
|
|
err = bind(self->llq_udpsd, ( struct sockaddr* ) &self->llq_addr, sizeof( self->llq_addr ) );
|
|
|
require_action( !err, exit, LogErr( "SetupSockets", "bind self->llq_udpsd" ) );
|
|
|
}
|
|
|
|
|
|
// set up Unix domain socket pair for LLQ polling thread to signal main thread that a change to the zone occurred
|
|
|
|
|
|
err = socketpair( AF_LOCAL, SOCK_STREAM, 0, sockpair );
|
|
|
require_action( !err, exit, LogErr( "SetupSockets", "socketpair" ) );
|
|
|
|
|
|
self->LLQEventListenSock = sockpair[0];
|
|
|
self->LLQEventNotifySock = sockpair[1];
|
|
|
|
|
|
// set up socket on which we receive private requests
|
|
|
|
|
|
self->llq_tcpsd = socket( AF_INET, SOCK_STREAM, 0 );
|
|
|
require_action( dnssd_SocketValid(self->tlssd), exit, err = mStatus_UnknownErr; LogErr( "SetupSockets", "socket" ) );
|
|
|
mDNSPlatformMemZero(&daddr, sizeof(daddr));
|
|
|
daddr.sin_family = AF_INET;
|
|
|
daddr.sin_addr.s_addr = zerov4Addr.NotAnInteger;
|
|
|
daddr.sin_port = ( self->private_port.NotAnInteger ) ? self->private_port.NotAnInteger : PrivateDNSPort.NotAnInteger;
|
|
|
|
|
|
self->tlssd = socket( AF_INET, SOCK_STREAM, 0 );
|
|
|
require_action( dnssd_SocketValid(self->tlssd), exit, err = mStatus_UnknownErr; LogErr( "SetupSockets", "socket" ) );
|
|
|
|
|
|
#if defined(SO_REUSEADDR)
|
|
|
err = setsockopt(self->tlssd, SOL_SOCKET, SO_REUSEADDR, &kOn, sizeof(kOn));
|
|
|
require_action( !err, exit, LogErr( "SetupSockets", "SO_REUSEADDR self->tlssd" ) );
|
|
|
#endif
|
|
|
|
|
|
err = bind( self->tlssd, ( struct sockaddr* ) &daddr, sizeof( daddr ) );
|
|
|
require_action( !err, exit, LogErr( "SetupSockets", "bind self->tlssd" ) );
|
|
|
|
|
|
err = listen( self->tlssd, LISTENQ );
|
|
|
require_action( !err, exit, LogErr( "SetupSockets", "listen" ) );
|
|
|
|
|
|
// Do we have any private zones?
|
|
|
|
|
|
for ( zone = self->zones; zone; zone = zone->next )
|
|
|
{
|
|
|
if ( zone->type == kDNSZonePrivate )
|
|
|
{
|
|
|
private = mDNStrue;
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if ( private )
|
|
|
{
|
|
|
err = mDNSPlatformTLSSetupCerts();
|
|
|
require_action( !err, exit, LogErr( "SetupSockets", "mDNSPlatformTLSSetupCerts" ) );
|
|
|
}
|
|
|
|
|
|
exit:
|
|
|
|
|
|
return err;
|
|
|
}
|
|
|
|
|
|
//
|
|
|
// periodic table updates
|
|
|
//
|
|
|
|
|
|
// Delete a resource record from the nameserver via a dynamic update
|
|
|
// sd is a socket already connected to the server
|
|
|
mDNSlocal void DeleteOneRecord(DaemonInfo *d, CacheRecord *rr, domainname *zname, TCPSocket *sock)
|
|
|
{
|
|
|
DNSZone * zone;
|
|
|
PktMsg pkt;
|
|
|
mDNSu8 *ptr = pkt.msg.data;
|
|
|
mDNSu8 *end = (mDNSu8 *)&pkt.msg + sizeof(DNSMessage);
|
|
|
char buf[MaxMsg];
|
|
|
mDNSBool closed;
|
|
|
PktMsg *reply = NULL;
|
|
|
|
|
|
VLog("Expiring record %s", GetRRDisplayString_rdb(&rr->resrec, &rr->resrec.rdata->u, buf));
|
|
|
|
|
|
InitializeDNSMessage(&pkt.msg.h, zeroID, UpdateReqFlags);
|
|
|
|
|
|
ptr = putZone(&pkt.msg, ptr, end, zname, mDNSOpaque16fromIntVal(rr->resrec.rrclass));
|
|
|
if (!ptr) goto end;
|
|
|
ptr = putDeletionRecord(&pkt.msg, ptr, &rr->resrec);
|
|
|
if (!ptr) goto end;
|
|
|
|
|
|
HdrHToN(&pkt);
|
|
|
|
|
|
zone = FindZone( d, zname );
|
|
|
|
|
|
if ( zone && zone->updateKeys)
|
|
|
{
|
|
|
DNSDigest_SignMessage(&pkt.msg, &ptr, zone->updateKeys, 0 );
|
|
|
if (!ptr) goto end;
|
|
|
}
|
|
|
|
|
|
pkt.len = ptr - (mDNSu8 *)&pkt.msg;
|
|
|
pkt.src.sin_addr.s_addr = zerov4Addr.NotAnInteger; // address field set solely for verbose logging in subroutines
|
|
|
pkt.src.sin_family = AF_INET;
|
|
|
if (SendPacket( sock, &pkt)) { Log("DeleteOneRecord: SendPacket failed"); }
|
|
|
reply = RecvPacket( sock, NULL, &closed );
|
|
|
if (reply) HdrNToH(reply);
|
|
|
require_action( reply, end, Log( "DeleteOneRecord: RecvPacket returned NULL" ) );
|
|
|
|
|
|
if (!SuccessfulUpdateTransaction(&pkt, reply))
|
|
|
Log("Expiration update failed with rcode %d", reply ? reply->msg.h.flags.b[1] & kDNSFlag1_RC_Mask : -1);
|
|
|
|
|
|
end:
|
|
|
if (!ptr) { Log("DeleteOneRecord: Error constructing lease expiration update"); }
|
|
|
if (reply) free(reply);
|
|
|
}
|
|
|
|
|
|
// iterate over table, deleting expired records (or all records if DeleteAll is true)
|
|
|
mDNSlocal void DeleteRecords(DaemonInfo *d, mDNSBool DeleteAll)
|
|
|
{
|
|
|
struct timeval now;
|
|
|
int i;
|
|
|
TCPSocket *sock = ConnectToServer(d);
|
|
|
if (!sock) { Log("DeleteRecords: ConnectToServer failed"); return; }
|
|
|
if (gettimeofday(&now, NULL)) { LogErr("DeleteRecords ", "gettimeofday"); return; }
|
|
|
if (pthread_mutex_lock(&d->tablelock)) { LogErr("DeleteRecords", "pthread_mutex_lock"); return; }
|
|
|
|
|
|
for (i = 0; i < d->nbuckets; i++)
|
|
|
{
|
|
|
RRTableElem **ptr = &d->table[i];
|
|
|
while (*ptr)
|
|
|
{
|
|
|
if (DeleteAll || (*ptr)->expire - now.tv_sec < 0)
|
|
|
{
|
|
|
RRTableElem *fptr;
|
|
|
// delete record from server
|
|
|
DeleteOneRecord(d, &(*ptr)->rr, &(*ptr)->zone, sock);
|
|
|
fptr = *ptr;
|
|
|
*ptr = (*ptr)->next;
|
|
|
free(fptr);
|
|
|
d->nelems--;
|
|
|
}
|
|
|
else ptr = &(*ptr)->next;
|
|
|
}
|
|
|
}
|
|
|
pthread_mutex_unlock(&d->tablelock);
|
|
|
mDNSPlatformTCPCloseConnection( sock );
|
|
|
}
|
|
|
|
|
|
//
|
|
|
// main update request handling
|
|
|
//
|
|
|
|
|
|
// Add, delete, or refresh records in table based on contents of a successfully completed dynamic update
|
|
|
mDNSlocal void UpdateLeaseTable(PktMsg *pkt, DaemonInfo *d, mDNSs32 lease)
|
|
|
{
|
|
|
RRTableElem **rptr, *tmp;
|
|
|
int i, allocsize, bucket;
|
|
|
LargeCacheRecord lcr;
|
|
|
ResourceRecord *rr = &lcr.r.resrec;
|
|
|
const mDNSu8 *ptr, *end;
|
|
|
struct timeval tv;
|
|
|
DNSQuestion zone;
|
|
|
char buf[MaxMsg];
|
|
|
|
|
|
if (pthread_mutex_lock(&d->tablelock)) { LogErr("UpdateLeaseTable", "pthread_mutex_lock"); return; }
|
|
|
HdrNToH(pkt);
|
|
|
ptr = pkt->msg.data;
|
|
|
end = (mDNSu8 *)&pkt->msg + pkt->len;
|
|
|
ptr = getQuestion(&pkt->msg, ptr, end, 0, &zone);
|
|
|
if (!ptr) { Log("UpdateLeaseTable: cannot read zone"); goto cleanup; }
|
|
|
ptr = LocateAuthorities(&pkt->msg, end);
|
|
|
if (!ptr) { Log("UpdateLeaseTable: Format error"); goto cleanup; }
|
|
|
|
|
|
for (i = 0; i < pkt->msg.h.mDNS_numUpdates; i++)
|
|
|
{
|
|
|
mDNSBool DeleteAllRRSets = mDNSfalse, DeleteOneRRSet = mDNSfalse, DeleteOneRR = mDNSfalse;
|
|
|
|
|
|
ptr = GetLargeResourceRecord(NULL, &pkt->msg, ptr, end, 0, kDNSRecordTypePacketAns, &lcr);
|
|
|
if (!ptr || lcr.r.resrec.RecordType == kDNSRecordTypePacketNegative) { Log("UpdateLeaseTable: GetLargeResourceRecord failed"); goto cleanup; }
|
|
|
bucket = rr->namehash % d->nbuckets;
|
|
|
rptr = &d->table[bucket];
|
|
|
|
|
|
// handle deletions
|
|
|
if (rr->rrtype == kDNSQType_ANY && !rr->rroriginalttl && rr->rrclass == kDNSQClass_ANY && !rr->rdlength)
|
|
|
DeleteAllRRSets = mDNStrue; // delete all rrsets for a name
|
|
|
else if (!rr->rroriginalttl && rr->rrclass == kDNSQClass_ANY && !rr->rdlength)
|
|
|
DeleteOneRRSet = mDNStrue;
|
|
|
else if (!rr->rroriginalttl && rr->rrclass == kDNSClass_NONE)
|
|
|
DeleteOneRR = mDNStrue;
|
|
|
|
|
|
if (DeleteAllRRSets || DeleteOneRRSet || DeleteOneRR)
|
|
|
{
|
|
|
while (*rptr)
|
|
|
{
|
|
|
if (SameDomainName((*rptr)->rr.resrec.name, rr->name) &&
|
|
|
(DeleteAllRRSets ||
|
|
|
(DeleteOneRRSet && (*rptr)->rr.resrec.rrtype == rr->rrtype) ||
|
|
|
(DeleteOneRR && IdenticalResourceRecord(&(*rptr)->rr.resrec, rr))))
|
|
|
{
|
|
|
tmp = *rptr;
|
|
|
VLog("Received deletion update for %s", GetRRDisplayString_rdb(&tmp->rr.resrec, &tmp->rr.resrec.rdata->u, buf));
|
|
|
*rptr = (*rptr)->next;
|
|
|
free(tmp);
|
|
|
d->nelems--;
|
|
|
}
|
|
|
else rptr = &(*rptr)->next;
|
|
|
}
|
|
|
}
|
|
|
else if (lease > 0)
|
|
|
{
|
|
|
// see if add or refresh
|
|
|
while (*rptr && !IdenticalResourceRecord(&(*rptr)->rr.resrec, rr)) rptr = &(*rptr)->next;
|
|
|
if (*rptr)
|
|
|
{
|
|
|
// refresh
|
|
|
if (gettimeofday(&tv, NULL)) { LogErr("UpdateLeaseTable", "gettimeofday"); goto cleanup; }
|
|
|
(*rptr)->expire = tv.tv_sec + (unsigned)lease;
|
|
|
VLog("Refreshing lease for %s", GetRRDisplayString_rdb(&lcr.r.resrec, &lcr.r.resrec.rdata->u, buf));
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
// New record - add to table
|
|
|
if (d->nelems > d->nbuckets)
|
|
|
{
|
|
|
RehashTable(d);
|
|
|
bucket = rr->namehash % d->nbuckets;
|
|
|
rptr = &d->table[bucket];
|
|
|
}
|
|
|
if (gettimeofday(&tv, NULL)) { LogErr("UpdateLeaseTable", "gettimeofday"); goto cleanup; }
|
|
|
allocsize = sizeof(RRTableElem);
|
|
|
if (rr->rdlength > InlineCacheRDSize) allocsize += (rr->rdlength - InlineCacheRDSize);
|
|
|
tmp = malloc(allocsize);
|
|
|
if (!tmp) { LogErr("UpdateLeaseTable", "malloc"); goto cleanup; }
|
|
|
memcpy(&tmp->rr, &lcr.r, sizeof(CacheRecord) + rr->rdlength - InlineCacheRDSize);
|
|
|
tmp->rr.resrec.rdata = (RData *)&tmp->rr.smallrdatastorage;
|
|
|
AssignDomainName(&tmp->name, rr->name);
|
|
|
tmp->rr.resrec.name = &tmp->name;
|
|
|
tmp->expire = tv.tv_sec + (unsigned)lease;
|
|
|
tmp->cli.sin_addr = pkt->src.sin_addr;
|
|
|
AssignDomainName(&tmp->zone, &zone.qname);
|
|
|
tmp->next = d->table[bucket];
|
|
|
d->table[bucket] = tmp;
|
|
|
d->nelems++;
|
|
|
VLog("Adding update for %s to lease table", GetRRDisplayString_rdb(&lcr.r.resrec, &lcr.r.resrec.rdata->u, buf));
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
cleanup:
|
|
|
pthread_mutex_unlock(&d->tablelock);
|
|
|
HdrHToN(pkt);
|
|
|
}
|
|
|
|
|
|
// Given a successful reply from a server, create a new reply that contains lease information
|
|
|
// Replies are currently not signed !!!KRS change this
|
|
|
mDNSlocal PktMsg *FormatLeaseReply(DaemonInfo *d, PktMsg *orig, mDNSu32 lease)
|
|
|
{
|
|
|
PktMsg *reply;
|
|
|
mDNSu8 *ptr, *end;
|
|
|
mDNSOpaque16 flags;
|
|
|
|
|
|
(void)d; //unused
|
|
|
reply = malloc(sizeof(*reply));
|
|
|
if (!reply) { LogErr("FormatLeaseReply", "malloc"); return NULL; }
|
|
|
flags.b[0] = kDNSFlag0_QR_Response | kDNSFlag0_OP_Update;
|
|
|
flags.b[1] = 0;
|
|
|
|
|
|
InitializeDNSMessage(&reply->msg.h, orig->msg.h.id, flags);
|
|
|
reply->src.sin_addr.s_addr = zerov4Addr.NotAnInteger; // unused except for log messages
|
|
|
reply->src.sin_family = AF_INET;
|
|
|
ptr = reply->msg.data;
|
|
|
end = (mDNSu8 *)&reply->msg + sizeof(DNSMessage);
|
|
|
ptr = putUpdateLease(&reply->msg, ptr, lease);
|
|
|
if (!ptr) { Log("FormatLeaseReply: putUpdateLease failed"); free(reply); return NULL; }
|
|
|
reply->len = ptr - (mDNSu8 *)&reply->msg;
|
|
|
HdrHToN(reply);
|
|
|
return reply;
|
|
|
}
|
|
|
|
|
|
|
|
|
// pkt is thread-local, not requiring locking
|
|
|
|
|
|
mDNSlocal PktMsg*
|
|
|
HandleRequest
|
|
|
(
|
|
|
DaemonInfo * self,
|
|
|
PktMsg * request
|
|
|
)
|
|
|
{
|
|
|
PktMsg * reply = NULL;
|
|
|
PktMsg * leaseReply;
|
|
|
PktMsg buf;
|
|
|
char addrbuf[32];
|
|
|
TCPSocket * sock = NULL;
|
|
|
mStatus err;
|
|
|
mDNSs32 lease = 0;
|
|
|
if ((request->msg.h.flags.b[0] & kDNSFlag0_QROP_Mask) == kDNSFlag0_OP_Update)
|
|
|
{
|
|
|
int i, adds = 0, dels = 0;
|
|
|
const mDNSu8 *ptr, *end = (mDNSu8 *)&request->msg + request->len;
|
|
|
HdrNToH(request);
|
|
|
lease = GetPktLease(&mDNSStorage, &request->msg, end);
|
|
|
ptr = LocateAuthorities(&request->msg, end);
|
|
|
for (i = 0; i < request->msg.h.mDNS_numUpdates; i++)
|
|
|
{
|
|
|
LargeCacheRecord lcr;
|
|
|
ptr = GetLargeResourceRecord(NULL, &request->msg, ptr, end, 0, kDNSRecordTypePacketAns, &lcr);
|
|
|
if (lcr.r.resrec.RecordType != kDNSRecordTypePacketNegative && lcr.r.resrec.rroriginalttl) adds++; else dels++;
|
|
|
}
|
|
|
HdrHToN(request);
|
|
|
if (adds && !lease)
|
|
|
{
|
|
|
static const mDNSOpaque16 UpdateRefused = { { kDNSFlag0_QR_Response | kDNSFlag0_OP_Update, kDNSFlag1_RC_Refused } };
|
|
|
Log("Rejecting Update Request with %d additions but no lease", adds);
|
|
|
reply = malloc(sizeof(*reply));
|
|
|
mDNSPlatformMemZero(&reply->src, sizeof(reply->src));
|
|
|
reply->len = sizeof(DNSMessageHeader);
|
|
|
reply->zone = NULL;
|
|
|
reply->isZonePublic = 0;
|
|
|
InitializeDNSMessage(&reply->msg.h, request->msg.h.id, UpdateRefused);
|
|
|
return(reply);
|
|
|
}
|
|
|
if (lease > 7200) // Don't allow lease greater than two hours; typically 90-minute renewal period
|
|
|
lease = 7200;
|
|
|
}
|
|
|
// Send msg to server, read reply
|
|
|
|
|
|
if ( request->len <= 512 )
|
|
|
{
|
|
|
mDNSBool trunc;
|
|
|
|
|
|
if ( UDPServerTransaction( self, request, &buf, &trunc) < 0 )
|
|
|
{
|
|
|
Log("HandleRequest - UDPServerTransaction failed. Trying TCP");
|
|
|
}
|
|
|
else if ( trunc )
|
|
|
{
|
|
|
VLog("HandleRequest - answer truncated. Using TCP");
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
reply = &buf; // success
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if ( !reply )
|
|
|
{
|
|
|
mDNSBool closed;
|
|
|
int res;
|
|
|
|
|
|
sock = ConnectToServer( self );
|
|
|
require_action_quiet( sock, exit, err = mStatus_UnknownErr ; Log( "Discarding request from %s due to connection errors", inet_ntop( AF_INET, &request->src.sin_addr, addrbuf, 32 ) ) );
|
|
|
|
|
|
res = SendPacket( sock, request );
|
|
|
require_action_quiet( res >= 0, exit, err = mStatus_UnknownErr ; Log( "Couldn't relay message from %s to server. Discarding.", inet_ntop(AF_INET, &request->src.sin_addr, addrbuf, 32 ) ) );
|
|
|
|
|
|
reply = RecvPacket( sock, &buf, &closed );
|
|
|
}
|
|
|
|
|
|
// IMPORTANT: reply is in network byte order at this point in the code
|
|
|
// We keep it this way because we send it back to the client in the same form
|
|
|
|
|
|
// Is it an update?
|
|
|
|
|
|
if ( reply && ( ( reply->msg.h.flags.b[0] & kDNSFlag0_QROP_Mask ) == ( kDNSFlag0_OP_Update | kDNSFlag0_QR_Response ) ) )
|
|
|
{
|
|
|
char pingmsg[4];
|
|
|
mDNSBool ok = SuccessfulUpdateTransaction( request, reply );
|
|
|
require_action( ok, exit, err = mStatus_UnknownErr; VLog( "Message from %s not a successful update.", inet_ntop(AF_INET, &request->src.sin_addr, addrbuf, 32 ) ) );
|
|
|
|
|
|
UpdateLeaseTable( request, self, lease );
|
|
|
|
|
|
if ( lease > 0 )
|
|
|
{
|
|
|
leaseReply = FormatLeaseReply( self, reply, lease );
|
|
|
|
|
|
if ( !leaseReply )
|
|
|
{
|
|
|
Log("HandleRequest - unable to format lease reply");
|
|
|
}
|
|
|
|
|
|
// %%% Looks like a potential memory leak -- who frees the original reply?
|
|
|
reply = leaseReply;
|
|
|
}
|
|
|
|
|
|
// tell the main thread there was an update so it can send LLQs
|
|
|
|
|
|
if ( send( self->LLQEventNotifySock, pingmsg, sizeof( pingmsg ), 0 ) != sizeof( pingmsg ) )
|
|
|
{
|
|
|
LogErr("HandleRequest", "send");
|
|
|
}
|
|
|
}
|
|
|
|
|
|
exit:
|
|
|
|
|
|
if ( sock )
|
|
|
{
|
|
|
mDNSPlatformTCPCloseConnection( sock );
|
|
|
}
|
|
|
|
|
|
if ( reply == &buf )
|
|
|
{
|
|
|
reply = malloc( sizeof( *reply ) );
|
|
|
|
|
|
if ( reply )
|
|
|
{
|
|
|
reply->len = buf.len;
|
|
|
memcpy(&reply->msg, &buf.msg, buf.len);
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
LogErr("HandleRequest", "malloc");
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return reply;
|
|
|
}
|
|
|
|
|
|
|
|
|
//
|
|
|
// LLQ Support Routines
|
|
|
//
|
|
|
|
|
|
// Set fields of an LLQ OPT Resource Record
|
|
|
mDNSlocal void FormatLLQOpt(AuthRecord *opt, int opcode, const mDNSOpaque64 *const id, mDNSs32 lease)
|
|
|
{
|
|
|
mDNSPlatformMemZero(opt, sizeof(*opt));
|
|
|
mDNS_SetupResourceRecord(opt, mDNSNULL, mDNSInterface_Any, kDNSType_OPT, kStandardTTL, kDNSRecordTypeKnownUnique, AuthRecordAny, mDNSNULL, mDNSNULL);
|
|
|
opt->resrec.rrclass = NormalMaxDNSMessageData;
|
|
|
opt->resrec.rdlength = sizeof(rdataOPT); // One option in this OPT record
|
|
|
opt->resrec.rdestimate = sizeof(rdataOPT);
|
|
|
opt->resrec.rdata->u.opt[0].opt = kDNSOpt_LLQ;
|
|
|
opt->resrec.rdata->u.opt[0].u.llq.vers = kLLQ_Vers;
|
|
|
opt->resrec.rdata->u.opt[0].u.llq.llqOp = opcode;
|
|
|
opt->resrec.rdata->u.opt[0].u.llq.err = LLQErr_NoError;
|
|
|
opt->resrec.rdata->u.opt[0].u.llq.id = *id;
|
|
|
opt->resrec.rdata->u.opt[0].u.llq.llqlease = lease;
|
|
|
}
|
|
|
|
|
|
// Calculate effective remaining lease of an LLQ
|
|
|
mDNSlocal mDNSu32 LLQLease(LLQEntry *e)
|
|
|
{
|
|
|
struct timeval t;
|
|
|
|
|
|
gettimeofday(&t, NULL);
|
|
|
if (e->expire < t.tv_sec) return 0;
|
|
|
else return e->expire - t.tv_sec;
|
|
|
}
|
|
|
|
|
|
mDNSlocal void DeleteLLQ(DaemonInfo *d, LLQEntry *e)
|
|
|
{
|
|
|
int bucket = DomainNameHashValue(&e->qname) % LLQ_TABLESIZE;
|
|
|
LLQEntry **ptr = &d->LLQTable[bucket];
|
|
|
AnswerListElem *a = e->AnswerList;
|
|
|
char addr[32];
|
|
|
|
|
|
inet_ntop(AF_INET, &e->cli.sin_addr, addr, 32);
|
|
|
VLog("Deleting LLQ table entry for %##s client %s", e->qname.c, addr);
|
|
|
|
|
|
if (a && !(--a->refcount) && d->AnswerTableCount >= LLQ_TABLESIZE)
|
|
|
{
|
|
|
// currently, generating initial answers blocks the main thread, so we keep the answer list
|
|
|
// even if the ref count drops to zero. To prevent unbounded table growth, we free shared answers
|
|
|
// if the ref count drops to zero AND there are more table elements than buckets
|
|
|
// !!!KRS update this when we make the table dynamically growable
|
|
|
|
|
|
CacheRecord *cr = a->KnownAnswers, *tmp;
|
|
|
AnswerListElem **tbl = &d->AnswerTable[bucket];
|
|
|
|
|
|
while (cr)
|
|
|
{
|
|
|
tmp = cr;
|
|
|
cr = cr->next;
|
|
|
free(tmp);
|
|
|
}
|
|
|
|
|
|
while (*tbl && *tbl != a) tbl = &(*tbl)->next;
|
|
|
if (*tbl) { *tbl = (*tbl)->next; free(a); d->AnswerTableCount--; }
|
|
|
else Log("Error: DeleteLLQ - AnswerList not found in table");
|
|
|
}
|
|
|
|
|
|
// remove LLQ from table, free memory
|
|
|
while(*ptr && *ptr != e) ptr = &(*ptr)->next;
|
|
|
if (!*ptr) { Log("Error: DeleteLLQ - LLQ not in table"); return; }
|
|
|
*ptr = (*ptr)->next;
|
|
|
free(e);
|
|
|
}
|
|
|
|
|
|
mDNSlocal int SendLLQ(DaemonInfo *d, PktMsg *pkt, struct sockaddr_in dst, TCPSocket *sock)
|
|
|
{
|
|
|
char addr[32];
|
|
|
int err = -1;
|
|
|
|
|
|
HdrHToN(pkt);
|
|
|
|
|
|
if ( sock )
|
|
|
{
|
|
|
if ( SendPacket( sock, pkt ) != 0 )
|
|
|
{
|
|
|
LogErr("DaemonInfo", "MySend");
|
|
|
Log("Could not send response to client %s", inet_ntop(AF_INET, &dst.sin_addr, addr, 32));
|
|
|
}
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
if (sendto(d->llq_udpsd, &pkt->msg, pkt->len, 0, (struct sockaddr *)&dst, sizeof(dst)) != (int)pkt->len)
|
|
|
{
|
|
|
LogErr("DaemonInfo", "sendto");
|
|
|
Log("Could not send response to client %s", inet_ntop(AF_INET, &dst.sin_addr, addr, 32));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
err = 0;
|
|
|
HdrNToH(pkt);
|
|
|
return err;
|
|
|
}
|
|
|
|
|
|
mDNSlocal CacheRecord *AnswerQuestion(DaemonInfo *d, AnswerListElem *e)
|
|
|
{
|
|
|
PktMsg q;
|
|
|
int i;
|
|
|
TCPSocket *sock = NULL;
|
|
|
const mDNSu8 *ansptr;
|
|
|
mDNSu8 *end = q.msg.data;
|
|
|
PktMsg buf, *reply = NULL;
|
|
|
LargeCacheRecord lcr;
|
|
|
CacheRecord *AnswerList = NULL;
|
|
|
mDNSu8 rcode;
|
|
|
|
|
|
VLog("Querying server for %##s type %d", e->name.c, e->type);
|
|
|
|
|
|
InitializeDNSMessage(&q.msg.h, zeroID, uQueryFlags);
|
|
|
|
|
|
end = putQuestion(&q.msg, end, end + AbsoluteMaxDNSMessageData, &e->name, e->type, kDNSClass_IN);
|
|
|
if (!end) { Log("Error: AnswerQuestion - putQuestion returned NULL"); goto end; }
|
|
|
q.len = (int)(end - (mDNSu8 *)&q.msg);
|
|
|
|
|
|
HdrHToN(&q);
|
|
|
|
|
|
if (!e->UseTCP)
|
|
|
{
|
|
|
mDNSBool trunc;
|
|
|
|
|
|
if (UDPServerTransaction(d, &q, &buf, &trunc) < 0)
|
|
|
Log("AnswerQuestion %##s - UDPServerTransaction failed. Trying TCP", e->name.c);
|
|
|
else if (trunc)
|
|
|
{ VLog("AnswerQuestion %##s - answer truncated. Using TCP", e->name.c); e->UseTCP = mDNStrue; }
|
|
|
else reply = &buf; // success
|
|
|
}
|
|
|
|
|
|
if (!reply)
|
|
|
{
|
|
|
mDNSBool closed;
|
|
|
|
|
|
sock = ConnectToServer(d);
|
|
|
if (!sock) { Log("AnswerQuestion: ConnectToServer failed"); goto end; }
|
|
|
if (SendPacket( sock, &q)) { Log("AnswerQuestion: SendPacket failed"); mDNSPlatformTCPCloseConnection( sock ); goto end; }
|
|
|
reply = RecvPacket( sock, NULL, &closed );
|
|
|
mDNSPlatformTCPCloseConnection( sock );
|
|
|
require_action( reply, end, Log( "AnswerQuestion: RecvPacket returned NULL" ) );
|
|
|
}
|
|
|
|
|
|
HdrNToH(&q);
|
|
|
if (reply) HdrNToH(reply);
|
|
|
|
|
|
if ((reply->msg.h.flags.b[0] & kDNSFlag0_QROP_Mask) != (kDNSFlag0_QR_Response | kDNSFlag0_OP_StdQuery))
|
|
|
{ Log("AnswerQuestion: %##s type %d - Invalid response flags from server"); goto end; }
|
|
|
rcode = (mDNSu8)(reply->msg.h.flags.b[1] & kDNSFlag1_RC_Mask);
|
|
|
if (rcode && rcode != kDNSFlag1_RC_NXDomain) { Log("AnswerQuestion: %##s type %d - non-zero rcode %d from server", e->name.c, e->type, rcode); goto end; }
|
|
|
|
|
|
end = (mDNSu8 *)&reply->msg + reply->len;
|
|
|
ansptr = LocateAnswers(&reply->msg, end);
|
|
|
if (!ansptr) { Log("Error: AnswerQuestion - LocateAnswers returned NULL"); goto end; }
|
|
|
|
|
|
for (i = 0; i < reply->msg.h.numAnswers; i++)
|
|
|
{
|
|
|
ansptr = GetLargeResourceRecord(NULL, &reply->msg, ansptr, end, 0, kDNSRecordTypePacketAns, &lcr);
|
|
|
if (!ansptr) { Log("AnswerQuestions: GetLargeResourceRecord returned NULL"); goto end; }
|
|
|
if (lcr.r.resrec.RecordType != kDNSRecordTypePacketNegative)
|
|
|
{
|
|
|
if (lcr.r.resrec.rrtype != e->type || lcr.r.resrec.rrclass != kDNSClass_IN || !SameDomainName(lcr.r.resrec.name, &e->name))
|
|
|
{
|
|
|
Log("AnswerQuestion: response %##s type #d does not answer question %##s type #d. Discarding",
|
|
|
lcr.r.resrec.name->c, lcr.r.resrec.rrtype, e->name.c, e->type);
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
CacheRecord *cr = CopyCacheRecord(&lcr.r, &e->name);
|
|
|
if (!cr) { Log("Error: AnswerQuestion - CopyCacheRecord returned NULL"); goto end; }
|
|
|
cr->next = AnswerList;
|
|
|
AnswerList = cr;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
end:
|
|
|
if (reply && reply != &buf) free(reply);
|
|
|
return AnswerList;
|
|
|
}
|
|
|
|
|
|
// Routine forks a thread to set EventList to contain Add/Remove events, and deletes any removes from the KnownAnswer list
|
|
|
mDNSlocal void *UpdateAnswerList(void *args)
|
|
|
{
|
|
|
CacheRecord *cr, *NewAnswers, **na, **ka; // "new answer", "known answer"
|
|
|
DaemonInfo *d = ((UpdateAnswerListArgs *)args)->d;
|
|
|
AnswerListElem *a = ((UpdateAnswerListArgs *)args)->a;
|
|
|
|
|
|
free(args);
|
|
|
args = NULL;
|
|
|
|
|
|
// get up to date answers
|
|
|
NewAnswers = AnswerQuestion(d, a);
|
|
|
|
|
|
// first pass - mark all answers for deletion
|
|
|
for (ka = &a->KnownAnswers; *ka; ka = &(*ka)->next)
|
|
|
(*ka)->resrec.rroriginalttl = (unsigned)-1; // -1 means delete
|
|
|
|
|
|
// second pass - mark answers pre-existent
|
|
|
for (ka = &a->KnownAnswers; *ka; ka = &(*ka)->next)
|
|
|
{
|
|
|
for (na = &NewAnswers; *na; na = &(*na)->next)
|
|
|
{
|
|
|
if (IdenticalResourceRecord(&(*ka)->resrec, &(*na)->resrec))
|
|
|
{ (*ka)->resrec.rroriginalttl = 0; break; } // 0 means no change
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// third pass - add new records to Event list
|
|
|
na = &NewAnswers;
|
|
|
while (*na)
|
|
|
{
|
|
|
for (ka = &a->KnownAnswers; *ka; ka = &(*ka)->next)
|
|
|
if (IdenticalResourceRecord(&(*ka)->resrec, &(*na)->resrec)) break;
|
|
|
if (!*ka)
|
|
|
{
|
|
|
// answer is not in list - splice from NewAnswers list, add to Event list
|
|
|
cr = *na;
|
|
|
*na = (*na)->next; // splice from list
|
|
|
cr->next = a->EventList; // add spliced record to event list
|
|
|
a->EventList = cr;
|
|
|
cr->resrec.rroriginalttl = 1; // 1 means add
|
|
|
}
|
|
|
else na = &(*na)->next;
|
|
|
}
|
|
|
|
|
|
// move all the removes from the answer list to the event list
|
|
|
ka = &a->KnownAnswers;
|
|
|
while (*ka)
|
|
|
{
|
|
|
if ((*ka)->resrec.rroriginalttl == (unsigned)-1)
|
|
|
{
|
|
|
cr = *ka;
|
|
|
*ka = (*ka)->next;
|
|
|
cr->next = a->EventList;
|
|
|
a->EventList = cr;
|
|
|
}
|
|
|
else ka = &(*ka)->next;
|
|
|
}
|
|
|
|
|
|
// lastly, free the remaining records (known answers) in NewAnswers list
|
|
|
while (NewAnswers)
|
|
|
{
|
|
|
cr = NewAnswers;
|
|
|
NewAnswers = NewAnswers->next;
|
|
|
free(cr);
|
|
|
}
|
|
|
|
|
|
return NULL;
|
|
|
}
|
|
|
|
|
|
mDNSlocal void SendEvents(DaemonInfo *d, LLQEntry *e)
|
|
|
{
|
|
|
PktMsg response;
|
|
|
CacheRecord *cr;
|
|
|
mDNSu8 *end = (mDNSu8 *)&response.msg.data;
|
|
|
mDNSOpaque16 msgID;
|
|
|
char rrbuf[MaxMsg], addrbuf[32];
|
|
|
AuthRecord opt;
|
|
|
|
|
|
// Should this really be random? Do we use the msgID on the receiving end?
|
|
|
msgID.NotAnInteger = random();
|
|
|
if (verbose) inet_ntop(AF_INET, &e->cli.sin_addr, addrbuf, 32);
|
|
|
InitializeDNSMessage(&response.msg.h, msgID, ResponseFlags);
|
|
|
end = putQuestion(&response.msg, end, end + AbsoluteMaxDNSMessageData, &e->qname, e->qtype, kDNSClass_IN);
|
|
|
if (!end) { Log("Error: SendEvents - putQuestion returned NULL"); return; }
|
|
|
|
|
|
// put adds/removes in packet
|
|
|
for (cr = e->AnswerList->EventList; cr; cr = cr->next)
|
|
|
{
|
|
|
if (verbose) GetRRDisplayString_rdb(&cr->resrec, &cr->resrec.rdata->u, rrbuf);
|
|
|
VLog("%s (%s): %s", addrbuf, (mDNSs32)cr->resrec.rroriginalttl < 0 ? "Remove": "Add", rrbuf);
|
|
|
end = PutResourceRecordTTLJumbo(&response.msg, end, &response.msg.h.numAnswers, &cr->resrec, cr->resrec.rroriginalttl);
|
|
|
if (!end) { Log("Error: SendEvents - PutResourceRecordTTLJumbo returned NULL"); return; }
|
|
|
}
|
|
|
|
|
|
FormatLLQOpt(&opt, kLLQOp_Event, &e->id, LLQLease(e));
|
|
|
end = PutResourceRecordTTLJumbo(&response.msg, end, &response.msg.h.numAdditionals, &opt.resrec, 0);
|
|
|
if (!end) { Log("Error: SendEvents - PutResourceRecordTTLJumbo"); return; }
|
|
|
|
|
|
response.len = (int)(end - (mDNSu8 *)&response.msg);
|
|
|
if (SendLLQ(d, &response, e->cli, NULL ) < 0) LogMsg("Error: SendEvents - SendLLQ");
|
|
|
}
|
|
|
|
|
|
mDNSlocal void PrintLLQAnswers(DaemonInfo *d)
|
|
|
{
|
|
|
int i;
|
|
|
char rrbuf[MaxMsg];
|
|
|
|
|
|
Log("Printing LLQ Answer Table contents");
|
|
|
|
|
|
for (i = 0; i < LLQ_TABLESIZE; i++)
|
|
|
{
|
|
|
AnswerListElem *a = d->AnswerTable[i];
|
|
|
while(a)
|
|
|
{
|
|
|
int ancount = 0;
|
|
|
const CacheRecord *rr = a->KnownAnswers;
|
|
|
while (rr) { ancount++; rr = rr->next; }
|
|
|
Log("%p : Question %##s; type %d; referenced by %d LLQs; %d answers:", a, a->name.c, a->type, a->refcount, ancount);
|
|
|
for (rr = a->KnownAnswers; rr; rr = rr->next) Log("\t%s", GetRRDisplayString_rdb(&rr->resrec, &rr->resrec.rdata->u, rrbuf));
|
|
|
a = a->next;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
mDNSlocal void PrintLLQTable(DaemonInfo *d)
|
|
|
{
|
|
|
LLQEntry *e;
|
|
|
char addr[32];
|
|
|
int i;
|
|
|
|
|
|
Log("Printing LLQ table contents");
|
|
|
|
|
|
for (i = 0; i < LLQ_TABLESIZE; i++)
|
|
|
{
|
|
|
e = d->LLQTable[i];
|
|
|
while(e)
|
|
|
{
|
|
|
char *state;
|
|
|
|
|
|
switch (e->state)
|
|
|
{
|
|
|
case RequestReceived: state = "RequestReceived"; break;
|
|
|
case ChallengeSent: state = "ChallengeSent"; break;
|
|
|
case Established: state = "Established"; break;
|
|
|
default: state = "unknown";
|
|
|
}
|
|
|
inet_ntop(AF_INET, &e->cli.sin_addr, addr, 32);
|
|
|
|
|
|
Log("LLQ from %s in state %s; %##s; type %d; orig lease %d; remaining lease %d; AnswerList %p)",
|
|
|
addr, state, e->qname.c, e->qtype, e->lease, LLQLease(e), e->AnswerList);
|
|
|
e = e->next;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// Send events to clients as a result of a change in the zone
|
|
|
mDNSlocal void GenLLQEvents(DaemonInfo *d)
|
|
|
{
|
|
|
LLQEntry **e;
|
|
|
int i;
|
|
|
struct timeval t;
|
|
|
UpdateAnswerListArgs *args;
|
|
|
|
|
|
VLog("Generating LLQ Events");
|
|
|
|
|
|
gettimeofday(&t, NULL);
|
|
|
|
|
|
// get all answers up to date
|
|
|
for (i = 0; i < LLQ_TABLESIZE; i++)
|
|
|
{
|
|
|
AnswerListElem *a = d->AnswerTable[i];
|
|
|
while(a)
|
|
|
{
|
|
|
args = malloc(sizeof(*args));
|
|
|
if (!args) { LogErr("GenLLQEvents", "malloc"); return; }
|
|
|
args->d = d;
|
|
|
args->a = a;
|
|
|
if (pthread_create(&a->tid, NULL, UpdateAnswerList, args)) { LogErr("GenLLQEvents", "pthread_create"); return; }
|
|
|
usleep(1);
|
|
|
a = a->next;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
for (i = 0; i < LLQ_TABLESIZE; i++)
|
|
|
{
|
|
|
AnswerListElem *a = d->AnswerTable[i];
|
|
|
while(a)
|
|
|
{
|
|
|
if (pthread_join(a->tid, NULL)) LogErr("GenLLQEvents", "pthread_join");
|
|
|
a = a->next;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// for each established LLQ, send events
|
|
|
for (i = 0; i < LLQ_TABLESIZE; i++)
|
|
|
{
|
|
|
e = &d->LLQTable[i];
|
|
|
while(*e)
|
|
|
{
|
|
|
if ((*e)->expire < t.tv_sec) DeleteLLQ(d, *e);
|
|
|
else
|
|
|
{
|
|
|
if ((*e)->state == Established && (*e)->AnswerList->EventList) SendEvents(d, *e);
|
|
|
e = &(*e)->next;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// now that all LLQs are updated, we move Add events from the Event list to the Known Answer list, and free Removes
|
|
|
for (i = 0; i < LLQ_TABLESIZE; i++)
|
|
|
{
|
|
|
AnswerListElem *a = d->AnswerTable[i];
|
|
|
while(a)
|
|
|
{
|
|
|
if (a->EventList)
|
|
|
{
|
|
|
CacheRecord *cr = a->EventList, *tmp;
|
|
|
while (cr)
|
|
|
{
|
|
|
tmp = cr;
|
|
|
cr = cr->next;
|
|
|
if ((signed)tmp->resrec.rroriginalttl < 0) free(tmp);
|
|
|
else
|
|
|
{
|
|
|
tmp->next = a->KnownAnswers;
|
|
|
a->KnownAnswers = tmp;
|
|
|
tmp->resrec.rroriginalttl = 0;
|
|
|
}
|
|
|
}
|
|
|
a->EventList = NULL;
|
|
|
}
|
|
|
a = a->next;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
mDNSlocal void SetAnswerList(DaemonInfo *d, LLQEntry *e)
|
|
|
{
|
|
|
int bucket = DomainNameHashValue(&e->qname) % LLQ_TABLESIZE;
|
|
|
AnswerListElem *a = d->AnswerTable[bucket];
|
|
|
while (a && (a->type != e->qtype ||!SameDomainName(&a->name, &e->qname))) a = a->next;
|
|
|
if (!a)
|
|
|
{
|
|
|
a = malloc(sizeof(*a));
|
|
|
if (!a) { LogErr("SetAnswerList", "malloc"); return; }
|
|
|
AssignDomainName(&a->name, &e->qname);
|
|
|
a->type = e->qtype;
|
|
|
a->refcount = 0;
|
|
|
a->EventList = NULL;
|
|
|
a->UseTCP = mDNSfalse;
|
|
|
a->next = d->AnswerTable[bucket];
|
|
|
d->AnswerTable[bucket] = a;
|
|
|
d->AnswerTableCount++;
|
|
|
a->KnownAnswers = AnswerQuestion(d, a);
|
|
|
}
|
|
|
|
|
|
e->AnswerList = a;
|
|
|
a->refcount ++;
|
|
|
}
|
|
|
|
|
|
// Allocate LLQ entry, insert into table
|
|
|
mDNSlocal LLQEntry *NewLLQ(DaemonInfo *d, struct sockaddr_in cli, domainname *qname, mDNSu16 qtype, mDNSu32 lease )
|
|
|
{
|
|
|
char addr[32];
|
|
|
struct timeval t;
|
|
|
int bucket = DomainNameHashValue(qname) % LLQ_TABLESIZE;
|
|
|
LLQEntry *e;
|
|
|
|
|
|
e = malloc(sizeof(*e));
|
|
|
if (!e) { LogErr("NewLLQ", "malloc"); return NULL; }
|
|
|
|
|
|
inet_ntop(AF_INET, &cli.sin_addr, addr, 32);
|
|
|
VLog("Allocating LLQ entry for client %s question %##s type %d", addr, qname->c, qtype);
|
|
|
|
|
|
// initialize structure
|
|
|
e->cli = cli;
|
|
|
AssignDomainName(&e->qname, qname);
|
|
|
e->qtype = qtype;
|
|
|
e->id = zeroOpaque64;
|
|
|
e->state = RequestReceived;
|
|
|
e->AnswerList = NULL;
|
|
|
|
|
|
if (lease < LLQ_MIN_LEASE) lease = LLQ_MIN_LEASE;
|
|
|
else if (lease > LLQ_MAX_LEASE) lease = LLQ_MAX_LEASE;
|
|
|
|
|
|
gettimeofday(&t, NULL);
|
|
|
e->expire = t.tv_sec + (int)lease;
|
|
|
e->lease = lease;
|
|
|
|
|
|
// add to table
|
|
|
e->next = d->LLQTable[bucket];
|
|
|
d->LLQTable[bucket] = e;
|
|
|
|
|
|
return e;
|
|
|
}
|
|
|
|
|
|
// Handle a refresh request from client
|
|
|
mDNSlocal void LLQRefresh(DaemonInfo *d, LLQEntry *e, LLQOptData *llq, mDNSOpaque16 msgID, TCPSocket *sock )
|
|
|
{
|
|
|
AuthRecord opt;
|
|
|
PktMsg ack;
|
|
|
mDNSu8 *end = (mDNSu8 *)&ack.msg.data;
|
|
|
char addr[32];
|
|
|
|
|
|
inet_ntop(AF_INET, &e->cli.sin_addr, addr, 32);
|
|
|
VLog("%s LLQ for %##s from %s", llq->llqlease ? "Refreshing" : "Deleting", e->qname.c, addr);
|
|
|
|
|
|
if (llq->llqlease)
|
|
|
{
|
|
|
struct timeval t;
|
|
|
if (llq->llqlease < LLQ_MIN_LEASE) llq->llqlease = LLQ_MIN_LEASE;
|
|
|
else if (llq->llqlease > LLQ_MAX_LEASE) llq->llqlease = LLQ_MIN_LEASE;
|
|
|
gettimeofday(&t, NULL);
|
|
|
e->expire = t.tv_sec + llq->llqlease;
|
|
|
}
|
|
|
|
|
|
ack.src.sin_addr.s_addr = 0; // unused
|
|
|
InitializeDNSMessage(&ack.msg.h, msgID, ResponseFlags);
|
|
|
end = putQuestion(&ack.msg, end, end + AbsoluteMaxDNSMessageData, &e->qname, e->qtype, kDNSClass_IN);
|
|
|
if (!end) { Log("Error: putQuestion"); return; }
|
|
|
|
|
|
FormatLLQOpt(&opt, kLLQOp_Refresh, &e->id, llq->llqlease ? LLQLease(e) : 0);
|
|
|
end = PutResourceRecordTTLJumbo(&ack.msg, end, &ack.msg.h.numAdditionals, &opt.resrec, 0);
|
|
|
if (!end) { Log("Error: PutResourceRecordTTLJumbo"); return; }
|
|
|
|
|
|
ack.len = (int)(end - (mDNSu8 *)&ack.msg);
|
|
|
if (SendLLQ(d, &ack, e->cli, sock)) Log("Error: LLQRefresh");
|
|
|
|
|
|
if (llq->llqlease) e->state = Established;
|
|
|
else DeleteLLQ(d, e);
|
|
|
}
|
|
|
|
|
|
// Complete handshake with Ack an initial answers
|
|
|
mDNSlocal void LLQCompleteHandshake(DaemonInfo *d, LLQEntry *e, LLQOptData *llq, mDNSOpaque16 msgID, TCPSocket *sock)
|
|
|
{
|
|
|
char addr[32];
|
|
|
CacheRecord *ptr;
|
|
|
AuthRecord opt;
|
|
|
PktMsg ack;
|
|
|
mDNSu8 *end = (mDNSu8 *)&ack.msg.data;
|
|
|
char rrbuf[MaxMsg], addrbuf[32];
|
|
|
|
|
|
inet_ntop(AF_INET, &e->cli.sin_addr, addr, 32);
|
|
|
|
|
|
if (!mDNSSameOpaque64(&llq->id, &e->id) ||
|
|
|
llq->vers != kLLQ_Vers ||
|
|
|
llq->llqOp != kLLQOp_Setup ||
|
|
|
llq->err != LLQErr_NoError ||
|
|
|
llq->llqlease > e->lease + LLQ_LEASE_FUDGE ||
|
|
|
llq->llqlease < e->lease - LLQ_LEASE_FUDGE)
|
|
|
{
|
|
|
Log("Incorrect challenge response from %s", addr);
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
if (e->state == Established) VLog("Retransmitting LLQ ack + answers for %##s", e->qname.c);
|
|
|
else VLog("Delivering LLQ ack + answers for %##s", e->qname.c);
|
|
|
|
|
|
// format ack + answers
|
|
|
ack.src.sin_addr.s_addr = 0; // unused
|
|
|
InitializeDNSMessage(&ack.msg.h, msgID, ResponseFlags);
|
|
|
end = putQuestion(&ack.msg, end, end + AbsoluteMaxDNSMessageData, &e->qname, e->qtype, kDNSClass_IN);
|
|
|
if (!end) { Log("Error: putQuestion"); return; }
|
|
|
|
|
|
if (e->state != Established) { SetAnswerList(d, e); e->state = Established; }
|
|
|
|
|
|
if (verbose) inet_ntop(AF_INET, &e->cli.sin_addr, addrbuf, 32);
|
|
|
for (ptr = e->AnswerList->KnownAnswers; ptr; ptr = ptr->next)
|
|
|
{
|
|
|
if (verbose) GetRRDisplayString_rdb(&ptr->resrec, &ptr->resrec.rdata->u, rrbuf);
|
|
|
VLog("%s Intitial Answer - %s", addr, rrbuf);
|
|
|
end = PutResourceRecordTTLJumbo(&ack.msg, end, &ack.msg.h.numAnswers, &ptr->resrec, 1);
|
|
|
if (!end) { Log("Error: PutResourceRecordTTLJumbo"); return; }
|
|
|
}
|
|
|
|
|
|
FormatLLQOpt(&opt, kLLQOp_Setup, &e->id, LLQLease(e));
|
|
|
end = PutResourceRecordTTLJumbo(&ack.msg, end, &ack.msg.h.numAdditionals, &opt.resrec, 0);
|
|
|
if (!end) { Log("Error: PutResourceRecordTTLJumbo"); return; }
|
|
|
|
|
|
ack.len = (int)(end - (mDNSu8 *)&ack.msg);
|
|
|
if (SendLLQ(d, &ack, e->cli, sock)) Log("Error: LLQCompleteHandshake");
|
|
|
}
|
|
|
|
|
|
mDNSlocal void LLQSetupChallenge(DaemonInfo *d, LLQEntry *e, LLQOptData *llq, mDNSOpaque16 msgID)
|
|
|
{
|
|
|
struct timeval t;
|
|
|
PktMsg challenge;
|
|
|
mDNSu8 *end = challenge.msg.data;
|
|
|
AuthRecord opt;
|
|
|
|
|
|
if (e->state == ChallengeSent) VLog("Retransmitting LLQ setup challenge for %##s", e->qname.c);
|
|
|
else VLog("Sending LLQ setup challenge for %##s", e->qname.c);
|
|
|
|
|
|
if (!mDNSOpaque64IsZero(&llq->id)) { Log("Error: LLQSetupChallenge - nonzero ID"); return; } // server bug
|
|
|
if (llq->llqOp != kLLQOp_Setup) { Log("LLQSetupChallenge - incorrrect operation from client"); return; } // client error
|
|
|
|
|
|
if (mDNSOpaque64IsZero(&e->id)) // don't regenerate random ID for retransmissions
|
|
|
{
|
|
|
// construct ID <time><random>
|
|
|
gettimeofday(&t, NULL);
|
|
|
e->id.l[0] = t.tv_sec;
|
|
|
e->id.l[1] = random();
|
|
|
}
|
|
|
|
|
|
// format response (query + LLQ opt rr)
|
|
|
challenge.src.sin_addr.s_addr = 0; // unused
|
|
|
InitializeDNSMessage(&challenge.msg.h, msgID, ResponseFlags);
|
|
|
end = putQuestion(&challenge.msg, end, end + AbsoluteMaxDNSMessageData, &e->qname, e->qtype, kDNSClass_IN);
|
|
|
if (!end) { Log("Error: putQuestion"); return; }
|
|
|
FormatLLQOpt(&opt, kLLQOp_Setup, &e->id, LLQLease(e));
|
|
|
end = PutResourceRecordTTLJumbo(&challenge.msg, end, &challenge.msg.h.numAdditionals, &opt.resrec, 0);
|
|
|
if (!end) { Log("Error: PutResourceRecordTTLJumbo"); return; }
|
|
|
challenge.len = (int)(end - (mDNSu8 *)&challenge.msg);
|
|
|
if (SendLLQ(d, &challenge, e->cli, NULL)) { Log("Error: LLQSetupChallenge"); return; }
|
|
|
e->state = ChallengeSent;
|
|
|
}
|
|
|
|
|
|
// Take action on an LLQ message from client. Entry must be initialized and in table
|
|
|
mDNSlocal void UpdateLLQ(DaemonInfo *d, LLQEntry *e, LLQOptData *llq, mDNSOpaque16 msgID, TCPSocket *sock )
|
|
|
{
|
|
|
switch(e->state)
|
|
|
{
|
|
|
case RequestReceived:
|
|
|
if ( sock )
|
|
|
{
|
|
|
struct timeval t;
|
|
|
gettimeofday(&t, NULL);
|
|
|
e->id.l[0] = t.tv_sec; // construct ID <time><random>
|
|
|
e->id.l[1] = random();
|
|
|
llq->id = e->id;
|
|
|
LLQCompleteHandshake( d, e, llq, msgID, sock );
|
|
|
|
|
|
// Set the state to established because we've just set the LLQ up using TCP
|
|
|
e->state = Established;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
LLQSetupChallenge(d, e, llq, msgID);
|
|
|
}
|
|
|
return;
|
|
|
case ChallengeSent:
|
|
|
if (mDNSOpaque64IsZero(&llq->id)) LLQSetupChallenge(d, e, llq, msgID); // challenge sent and lost
|
|
|
else LLQCompleteHandshake(d, e, llq, msgID, sock );
|
|
|
return;
|
|
|
case Established:
|
|
|
if (mDNSOpaque64IsZero(&llq->id))
|
|
|
{
|
|
|
// client started over. reset state.
|
|
|
LLQEntry *newe = NewLLQ(d, e->cli, &e->qname, e->qtype, llq->llqlease );
|
|
|
if (!newe) return;
|
|
|
DeleteLLQ(d, e);
|
|
|
LLQSetupChallenge(d, newe, llq, msgID);
|
|
|
return;
|
|
|
}
|
|
|
else if (llq->llqOp == kLLQOp_Setup)
|
|
|
{ LLQCompleteHandshake(d, e, llq, msgID, sock); return; } // Ack lost
|
|
|
else if (llq->llqOp == kLLQOp_Refresh)
|
|
|
{ LLQRefresh(d, e, llq, msgID, sock); return; }
|
|
|
else { Log("Unhandled message for established LLQ"); return; }
|
|
|
}
|
|
|
}
|
|
|
|
|
|
mDNSlocal LLQEntry *LookupLLQ(DaemonInfo *d, struct sockaddr_in cli, domainname *qname, mDNSu16 qtype, const mDNSOpaque64 *const id)
|
|
|
{
|
|
|
int bucket = bucket = DomainNameHashValue(qname) % LLQ_TABLESIZE;
|
|
|
LLQEntry *ptr = d->LLQTable[bucket];
|
|
|
|
|
|
while(ptr)
|
|
|
{
|
|
|
if (((ptr->state == ChallengeSent && mDNSOpaque64IsZero(id) && (cli.sin_port == ptr->cli.sin_port)) || // zero-id due to packet loss OK in state ChallengeSent
|
|
|
mDNSSameOpaque64(id, &ptr->id)) && // id match
|
|
|
(cli.sin_addr.s_addr == ptr->cli.sin_addr.s_addr) && (qtype == ptr->qtype) && SameDomainName(&ptr->qname, qname)) // same source, type, qname
|
|
|
return ptr;
|
|
|
ptr = ptr->next;
|
|
|
}
|
|
|
return NULL;
|
|
|
}
|
|
|
|
|
|
mDNSlocal int
|
|
|
RecvNotify
|
|
|
(
|
|
|
DaemonInfo * d,
|
|
|
PktMsg * pkt
|
|
|
)
|
|
|
{
|
|
|
int res;
|
|
|
int err = 0;
|
|
|
|
|
|
pkt->msg.h.flags.b[0] |= kDNSFlag0_QR_Response;
|
|
|
|
|
|
res = sendto( d->udpsd, &pkt->msg, pkt->len, 0, ( struct sockaddr* ) &pkt->src, sizeof( pkt->src ) );
|
|
|
require_action( res == ( int ) pkt->len, exit, err = mStatus_UnknownErr; LogErr( "RecvNotify", "sendto" ) );
|
|
|
|
|
|
exit:
|
|
|
|
|
|
return err;
|
|
|
}
|
|
|
|
|
|
|
|
|
mDNSlocal int RecvLLQ( DaemonInfo *d, PktMsg *pkt, TCPSocket *sock )
|
|
|
{
|
|
|
DNSQuestion q;
|
|
|
LargeCacheRecord opt;
|
|
|
int i, err = -1;
|
|
|
char addr[32];
|
|
|
const mDNSu8 *qptr = pkt->msg.data;
|
|
|
const mDNSu8 *end = (mDNSu8 *)&pkt->msg + pkt->len;
|
|
|
const mDNSu8 *aptr;
|
|
|
LLQOptData *llq = NULL;
|
|
|
LLQEntry *e = NULL;
|
|
|
|
|
|
HdrNToH(pkt);
|
|
|
aptr = LocateAdditionals(&pkt->msg, end); // Can't do this until after HdrNToH(pkt);
|
|
|
inet_ntop(AF_INET, &pkt->src.sin_addr, addr, 32);
|
|
|
|
|
|
VLog("Received LLQ msg from %s", addr);
|
|
|
// sanity-check packet
|
|
|
if (!pkt->msg.h.numQuestions || !pkt->msg.h.numAdditionals)
|
|
|
{
|
|
|
Log("Malformatted LLQ from %s with %d questions, %d additionals", addr, pkt->msg.h.numQuestions, pkt->msg.h.numAdditionals);
|
|
|
goto end;
|
|
|
}
|
|
|
|
|
|
// Locate the OPT record.
|
|
|
// According to RFC 2671, "One OPT pseudo-RR can be added to the additional data section of either a request or a response."
|
|
|
// This implies that there may be *at most* one OPT record per DNS message, in the Additional Section,
|
|
|
// but not necessarily the *last* entry in the Additional Section.
|
|
|
for (i = 0; i < pkt->msg.h.numAdditionals; i++)
|
|
|
{
|
|
|
aptr = GetLargeResourceRecord(NULL, &pkt->msg, aptr, end, 0, kDNSRecordTypePacketAdd, &opt);
|
|
|
if (!aptr) { Log("Malformatted LLQ from %s: could not get Additional record %d", addr, i); goto end; }
|
|
|
if (opt.r.resrec.RecordType != kDNSRecordTypePacketNegative && opt.r.resrec.rrtype == kDNSType_OPT) break;
|
|
|
}
|
|
|
|
|
|
// validate OPT
|
|
|
if (opt.r.resrec.rrtype != kDNSType_OPT) { Log("Malformatted LLQ from %s: last Additional not an OPT RR", addr); goto end; }
|
|
|
if (opt.r.resrec.rdlength < pkt->msg.h.numQuestions * DNSOpt_LLQData_Space) { Log("Malformatted LLQ from %s: OPT RR to small (%d bytes for %d questions)", addr, opt.r.resrec.rdlength, pkt->msg.h.numQuestions); }
|
|
|
|
|
|
// dispatch each question
|
|
|
for (i = 0; i < pkt->msg.h.numQuestions; i++)
|
|
|
{
|
|
|
qptr = getQuestion(&pkt->msg, qptr, end, 0, &q);
|
|
|
if (!qptr) { Log("Malformatted LLQ from %s: cannot read question %d", addr, i); goto end; }
|
|
|
llq = (LLQOptData *)&opt.r.resrec.rdata->u.opt[0].u.llq + i; // point into OptData at index i
|
|
|
if (llq->vers != kLLQ_Vers) { Log("LLQ from %s contains bad version %d (expected %d)", addr, llq->vers, kLLQ_Vers); goto end; }
|
|
|
|
|
|
e = LookupLLQ(d, pkt->src, &q.qname, q.qtype, &llq->id);
|
|
|
if (!e)
|
|
|
{
|
|
|
// no entry - if zero ID, create new
|
|
|
e = NewLLQ(d, pkt->src, &q.qname, q.qtype, llq->llqlease );
|
|
|
if (!e) goto end;
|
|
|
}
|
|
|
UpdateLLQ(d, e, llq, pkt->msg.h.id, sock);
|
|
|
}
|
|
|
err = 0;
|
|
|
|
|
|
end:
|
|
|
HdrHToN(pkt);
|
|
|
return err;
|
|
|
}
|
|
|
|
|
|
|
|
|
mDNSlocal mDNSBool IsAuthorized( DaemonInfo * d, PktMsg * pkt, DomainAuthInfo ** key, mDNSu16 * rcode, mDNSu16 * tcode )
|
|
|
{
|
|
|
const mDNSu8 * lastPtr = NULL;
|
|
|
const mDNSu8 * ptr = NULL;
|
|
|
DomainAuthInfo * keys;
|
|
|
mDNSu8 * end = ( mDNSu8* ) &pkt->msg + pkt->len;
|
|
|
LargeCacheRecord lcr;
|
|
|
mDNSBool hasTSIG = mDNSfalse;
|
|
|
mDNSBool strip = mDNSfalse;
|
|
|
mDNSBool ok = mDNSfalse;
|
|
|
int i;
|
|
|
|
|
|
// Unused parameters
|
|
|
|
|
|
( void ) d;
|
|
|
|
|
|
HdrNToH(pkt);
|
|
|
|
|
|
*key = NULL;
|
|
|
|
|
|
if ( pkt->msg.h.numAdditionals )
|
|
|
{
|
|
|
ptr = LocateAdditionals(&pkt->msg, end);
|
|
|
if (ptr)
|
|
|
{
|
|
|
for (i = 0; i < pkt->msg.h.numAdditionals; i++)
|
|
|
{
|
|
|
lastPtr = ptr;
|
|
|
ptr = GetLargeResourceRecord(NULL, &pkt->msg, ptr, end, 0, kDNSRecordTypePacketAdd, &lcr);
|
|
|
if (!ptr)
|
|
|
{
|
|
|
Log("Unable to read additional record");
|
|
|
lastPtr = NULL;
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
hasTSIG = ( ptr && lcr.r.resrec.RecordType != kDNSRecordTypePacketNegative && lcr.r.resrec.rrtype == kDNSType_TSIG );
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
LogMsg( "IsAuthorized: unable to find Additional section" );
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// If we don't know what zone this is, then it's authorized.
|
|
|
|
|
|
if ( !pkt->zone )
|
|
|
{
|
|
|
ok = mDNStrue;
|
|
|
strip = mDNSfalse;
|
|
|
goto exit;
|
|
|
}
|
|
|
|
|
|
if ( IsQuery( pkt ) )
|
|
|
{
|
|
|
keys = pkt->zone->queryKeys;
|
|
|
strip = mDNStrue;
|
|
|
}
|
|
|
else if ( IsUpdate( pkt ) )
|
|
|
{
|
|
|
keys = pkt->zone->updateKeys;
|
|
|
strip = mDNSfalse;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
ok = mDNStrue;
|
|
|
strip = mDNSfalse;
|
|
|
goto exit;
|
|
|
}
|
|
|
|
|
|
if ( pkt->isZonePublic )
|
|
|
{
|
|
|
ok = mDNStrue;
|
|
|
goto exit;
|
|
|
}
|
|
|
|
|
|
// If there are no keys, then we're authorized
|
|
|
|
|
|
if ( ( hasTSIG && !keys ) || ( !hasTSIG && keys ) )
|
|
|
{
|
|
|
Log( "Invalid TSIG spec %##s for zone %##s", lcr.r.resrec.name->c, pkt->zone->name.c );
|
|
|
*rcode = kDNSFlag1_RC_NotAuth;
|
|
|
*tcode = TSIG_ErrBadKey;
|
|
|
strip = mDNStrue;
|
|
|
ok = mDNSfalse;
|
|
|
goto exit;
|
|
|
}
|
|
|
|
|
|
// Find the right key
|
|
|
|
|
|
for ( *key = keys; *key; *key = (*key)->next )
|
|
|
{
|
|
|
if ( SameDomainName( lcr.r.resrec.name, &(*key)->keyname ) )
|
|
|
{
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if ( !(*key) )
|
|
|
{
|
|
|
Log( "Invalid TSIG name %##s for zone %##s", lcr.r.resrec.name->c, pkt->zone->name.c );
|
|
|
*rcode = kDNSFlag1_RC_NotAuth;
|
|
|
*tcode = TSIG_ErrBadKey;
|
|
|
strip = mDNStrue;
|
|
|
ok = mDNSfalse;
|
|
|
goto exit;
|
|
|
}
|
|
|
|
|
|
// Okay, we have the correct key and a TSIG record. DNSDigest_VerifyMessage does the heavy
|
|
|
// lifting of message verification
|
|
|
|
|
|
pkt->msg.h.numAdditionals--;
|
|
|
|
|
|
HdrHToN( pkt );
|
|
|
|
|
|
ok = DNSDigest_VerifyMessage( &pkt->msg, ( mDNSu8* ) lastPtr, &lcr, (*key), rcode, tcode );
|
|
|
|
|
|
HdrNToH( pkt );
|
|
|
|
|
|
pkt->msg.h.numAdditionals++;
|
|
|
|
|
|
exit:
|
|
|
|
|
|
if ( hasTSIG && strip )
|
|
|
{
|
|
|
// Strip the TSIG from the message
|
|
|
|
|
|
pkt->msg.h.numAdditionals--;
|
|
|
pkt->len = lastPtr - ( mDNSu8* ) ( &pkt->msg );
|
|
|
}
|
|
|
|
|
|
HdrHToN(pkt);
|
|
|
|
|
|
return ok;
|
|
|
}
|
|
|
|
|
|
// request handler wrappers for TCP and UDP requests
|
|
|
// (read message off socket, fork thread that invokes main processing routine and handles cleanup)
|
|
|
|
|
|
mDNSlocal void*
|
|
|
UDPMessageHandler
|
|
|
(
|
|
|
void * vptr
|
|
|
)
|
|
|
{
|
|
|
UDPContext * context = ( UDPContext* ) vptr;
|
|
|
PktMsg * reply = NULL;
|
|
|
int res;
|
|
|
mStatus err;
|
|
|
|
|
|
// !!!KRS strictly speaking, we shouldn't use TCP for a UDP request because the server
|
|
|
// may give us a long answer that would require truncation for UDP delivery to client
|
|
|
|
|
|
reply = HandleRequest( context->d, &context->pkt );
|
|
|
require_action( reply, exit, err = mStatus_UnknownErr );
|
|
|
|
|
|
res = sendto( context->sd, &reply->msg, reply->len, 0, ( struct sockaddr* ) &context->pkt.src, sizeof( context->pkt.src ) );
|
|
|
require_action_quiet( res == ( int ) reply->len, exit, LogErr( "UDPMessageHandler", "sendto" ) );
|
|
|
|
|
|
exit:
|
|
|
|
|
|
if ( reply )
|
|
|
{
|
|
|
free( reply );
|
|
|
}
|
|
|
|
|
|
free( context );
|
|
|
|
|
|
pthread_exit( NULL );
|
|
|
|
|
|
return NULL;
|
|
|
}
|
|
|
|
|
|
|
|
|
mDNSlocal int
|
|
|
RecvUDPMessage
|
|
|
(
|
|
|
DaemonInfo * self,
|
|
|
int sd
|
|
|
)
|
|
|
{
|
|
|
UDPContext * context = NULL;
|
|
|
pthread_t tid;
|
|
|
mDNSu16 rcode;
|
|
|
mDNSu16 tcode;
|
|
|
DomainAuthInfo * key;
|
|
|
unsigned int clisize = sizeof( context->cliaddr );
|
|
|
int res;
|
|
|
mStatus err = mStatus_NoError;
|
|
|
|
|
|
context = malloc( sizeof( UDPContext ) );
|
|
|
require_action( context, exit, err = mStatus_NoMemoryErr ; LogErr( "RecvUDPMessage", "malloc" ) );
|
|
|
|
|
|
mDNSPlatformMemZero( context, sizeof( *context ) );
|
|
|
context->d = self;
|
|
|
context->sd = sd;
|
|
|
|
|
|
res = recvfrom(sd, &context->pkt.msg, sizeof(context->pkt.msg), 0, (struct sockaddr *)&context->cliaddr, &clisize);
|
|
|
|
|
|
require_action( res >= 0, exit, err = mStatus_UnknownErr ; LogErr( "RecvUDPMessage", "recvfrom" ) );
|
|
|
context->pkt.len = res;
|
|
|
require_action( clisize == sizeof( context->cliaddr ), exit, err = mStatus_UnknownErr ; Log( "Client address of unknown size %d", clisize ) );
|
|
|
context->pkt.src = context->cliaddr;
|
|
|
|
|
|
// Set the zone in the packet
|
|
|
|
|
|
SetZone( context->d, &context->pkt );
|
|
|
|
|
|
// Notify messages handled by main thread
|
|
|
|
|
|
if ( IsNotify( &context->pkt ) )
|
|
|
{
|
|
|
int e = RecvNotify( self, &context->pkt );
|
|
|
free(context);
|
|
|
return e;
|
|
|
}
|
|
|
else if ( IsAuthorized( context->d, &context->pkt, &key, &rcode, &tcode ) )
|
|
|
{
|
|
|
if ( IsLLQRequest( &context->pkt ) )
|
|
|
{
|
|
|
// LLQ messages handled by main thread
|
|
|
int e = RecvLLQ( self, &context->pkt, NULL );
|
|
|
free(context);
|
|
|
return e;
|
|
|
}
|
|
|
|
|
|
if ( IsLLQAck(&context->pkt ) )
|
|
|
{
|
|
|
// !!!KRS need to do acks + retrans
|
|
|
|
|
|
free(context);
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
err = pthread_create( &tid, NULL, UDPMessageHandler, context );
|
|
|
require_action( !err, exit, LogErr( "RecvUDPMessage", "pthread_create" ) );
|
|
|
|
|
|
pthread_detach(tid);
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
PktMsg reply;
|
|
|
int e;
|
|
|
|
|
|
memcpy( &reply, &context->pkt, sizeof( PktMsg ) );
|
|
|
|
|
|
reply.msg.h.flags.b[0] = kDNSFlag0_QR_Response | kDNSFlag0_AA | kDNSFlag0_RD;
|
|
|
reply.msg.h.flags.b[1] = kDNSFlag1_RA | kDNSFlag1_RC_NXDomain;
|
|
|
|
|
|
e = sendto( sd, &reply.msg, reply.len, 0, ( struct sockaddr* ) &context->pkt.src, sizeof( context->pkt.src ) );
|
|
|
require_action_quiet( e == ( int ) reply.len, exit, LogErr( "RecvUDPMessage", "sendto" ) );
|
|
|
|
|
|
err = mStatus_NoAuth;
|
|
|
}
|
|
|
|
|
|
exit:
|
|
|
|
|
|
if ( err && context )
|
|
|
{
|
|
|
free( context );
|
|
|
}
|
|
|
|
|
|
return err;
|
|
|
}
|
|
|
|
|
|
|
|
|
mDNSlocal void
|
|
|
FreeTCPContext
|
|
|
(
|
|
|
TCPContext * context
|
|
|
)
|
|
|
{
|
|
|
if ( context )
|
|
|
{
|
|
|
if ( context->sock )
|
|
|
{
|
|
|
mDNSPlatformTCPCloseConnection( context->sock );
|
|
|
}
|
|
|
|
|
|
free( context );
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
mDNSlocal void*
|
|
|
TCPMessageHandler
|
|
|
(
|
|
|
void * vptr
|
|
|
)
|
|
|
{
|
|
|
TCPContext * context = ( TCPContext* ) vptr;
|
|
|
PktMsg * reply = NULL;
|
|
|
int res;
|
|
|
char buf[32];
|
|
|
|
|
|
//!!!KRS if this read blocks indefinitely, we can run out of threads
|
|
|
// read the request
|
|
|
|
|
|
reply = HandleRequest( context->d, &context->pkt );
|
|
|
require_action_quiet( reply, exit, LogMsg( "TCPMessageHandler: No reply for client %s", inet_ntop( AF_INET, &context->cliaddr.sin_addr, buf, 32 ) ) );
|
|
|
|
|
|
// deliver reply to client
|
|
|
|
|
|
res = SendPacket( context->sock, reply );
|
|
|
require_action( res >= 0, exit, LogMsg("TCPMessageHandler: Unable to send reply to client %s", inet_ntop(AF_INET, &context->cliaddr.sin_addr, buf, 32 ) ) );
|
|
|
|
|
|
exit:
|
|
|
|
|
|
FreeTCPContext( context );
|
|
|
|
|
|
if ( reply )
|
|
|
{
|
|
|
free( reply );
|
|
|
}
|
|
|
|
|
|
pthread_exit(NULL);
|
|
|
}
|
|
|
|
|
|
|
|
|
mDNSlocal void
|
|
|
RecvTCPMessage
|
|
|
(
|
|
|
void * param
|
|
|
)
|
|
|
{
|
|
|
TCPContext * context = ( TCPContext* ) param;
|
|
|
mDNSu16 rcode;
|
|
|
mDNSu16 tcode;
|
|
|
pthread_t tid;
|
|
|
DomainAuthInfo * key;
|
|
|
PktMsg * pkt;
|
|
|
mDNSBool closed;
|
|
|
mDNSBool freeContext = mDNStrue;
|
|
|
mStatus err = mStatus_NoError;
|
|
|
|
|
|
// Receive a packet. It's okay if we don't actually read a packet, as long as the closed flag is
|
|
|
// set to false. This is because SSL/TLS layer might gobble up the first packet that we read off the
|
|
|
// wire. We'll let it do that, and wait for the next packet which will be ours.
|
|
|
|
|
|
pkt = RecvPacket( context->sock, &context->pkt, &closed );
|
|
|
if (pkt) HdrNToH(pkt);
|
|
|
require_action( pkt || !closed, exit, err = mStatus_UnknownErr; LogMsg( "client disconnected" ) );
|
|
|
|
|
|
if ( pkt )
|
|
|
{
|
|
|
// Always do this, regardless of what kind of packet it is. If we wanted LLQ events to be sent over TCP,
|
|
|
// we would change this line of code. As it is now, we will reply to an LLQ via TCP, but then events
|
|
|
// are sent over UDP
|
|
|
|
|
|
RemoveSourceFromEventLoop( context->d, context->sock );
|
|
|
|
|
|
// Set's the DNS Zone that is associated with this message
|
|
|
|
|
|
SetZone( context->d, &context->pkt );
|
|
|
|
|
|
// IsAuthorized will make sure the message is authorized for the designated zone.
|
|
|
// After verifying the signature, it will strip the TSIG from the message
|
|
|
|
|
|
if ( IsAuthorized( context->d, &context->pkt, &key, &rcode, &tcode ) )
|
|
|
{
|
|
|
if ( IsLLQRequest( &context->pkt ) )
|
|
|
{
|
|
|
// LLQ messages handled by main thread
|
|
|
RecvLLQ( context->d, &context->pkt, context->sock);
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
err = pthread_create( &tid, NULL, TCPMessageHandler, context );
|
|
|
|
|
|
if ( err )
|
|
|
{
|
|
|
LogErr( "RecvTCPMessage", "pthread_create" );
|
|
|
err = mStatus_NoError;
|
|
|
goto exit;
|
|
|
}
|
|
|
|
|
|
// Let the thread free the context
|
|
|
|
|
|
freeContext = mDNSfalse;
|
|
|
|
|
|
pthread_detach(tid);
|
|
|
}
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
PktMsg reply;
|
|
|
|
|
|
LogMsg( "Client %s Not authorized for zone %##s", inet_ntoa( context->pkt.src.sin_addr ), pkt->zone->name.c );
|
|
|
|
|
|
memcpy( &reply, &context->pkt, sizeof( PktMsg ) );
|
|
|
|
|
|
reply.msg.h.flags.b[0] = kDNSFlag0_QR_Response | kDNSFlag0_AA | kDNSFlag0_RD;
|
|
|
reply.msg.h.flags.b[1] = kDNSFlag1_RA | kDNSFlag1_RC_Refused;
|
|
|
|
|
|
SendPacket( context->sock, &reply );
|
|
|
}
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
freeContext = mDNSfalse;
|
|
|
}
|
|
|
|
|
|
exit:
|
|
|
|
|
|
if ( err )
|
|
|
{
|
|
|
RemoveSourceFromEventLoop( context->d, context->sock );
|
|
|
}
|
|
|
|
|
|
if ( freeContext )
|
|
|
{
|
|
|
FreeTCPContext( context );
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
mDNSlocal int
|
|
|
AcceptTCPConnection
|
|
|
(
|
|
|
DaemonInfo * self,
|
|
|
int sd,
|
|
|
TCPSocketFlags flags
|
|
|
)
|
|
|
{
|
|
|
TCPContext * context = NULL;
|
|
|
unsigned int clilen = sizeof( context->cliaddr);
|
|
|
int newSock;
|
|
|
mStatus err = mStatus_NoError;
|
|
|
|
|
|
context = ( TCPContext* ) malloc( sizeof( TCPContext ) );
|
|
|
require_action( context, exit, err = mStatus_NoMemoryErr; LogErr( "AcceptTCPConnection", "malloc" ) );
|
|
|
mDNSPlatformMemZero( context, sizeof( sizeof( TCPContext ) ) );
|
|
|
context->d = self;
|
|
|
newSock = accept( sd, ( struct sockaddr* ) &context->cliaddr, &clilen );
|
|
|
require_action( newSock != -1, exit, err = mStatus_UnknownErr; LogErr( "AcceptTCPConnection", "accept" ) );
|
|
|
|
|
|
context->sock = mDNSPlatformTCPAccept( flags, newSock );
|
|
|
require_action( context->sock, exit, err = mStatus_UnknownErr; LogErr( "AcceptTCPConnection", "mDNSPlatformTCPAccept" ) );
|
|
|
|
|
|
err = AddSourceToEventLoop( self, context->sock, RecvTCPMessage, context );
|
|
|
require_action( !err, exit, LogErr( "AcceptTCPConnection", "AddSourceToEventLoop" ) );
|
|
|
|
|
|
exit:
|
|
|
|
|
|
if ( err && context )
|
|
|
{
|
|
|
free( context );
|
|
|
context = NULL;
|
|
|
}
|
|
|
|
|
|
return err;
|
|
|
}
|
|
|
|
|
|
|
|
|
// main event loop
|
|
|
// listen for incoming requests, periodically check table for expired records, respond to signals
|
|
|
mDNSlocal int Run(DaemonInfo *d)
|
|
|
{
|
|
|
int staticMaxFD, nfds;
|
|
|
fd_set rset;
|
|
|
struct timeval timenow, timeout, EventTS, tablecheck = { 0, 0 };
|
|
|
mDNSBool EventsPending = mDNSfalse;
|
|
|
|
|
|
VLog("Listening for requests...");
|
|
|
|
|
|
staticMaxFD = 0;
|
|
|
|
|
|
if ( d->tcpsd + 1 > staticMaxFD ) staticMaxFD = d->tcpsd + 1;
|
|
|
if ( d->udpsd + 1 > staticMaxFD ) staticMaxFD = d->udpsd + 1;
|
|
|
if ( d->tlssd + 1 > staticMaxFD ) staticMaxFD = d->tlssd + 1;
|
|
|
if ( d->llq_tcpsd + 1 > staticMaxFD ) staticMaxFD = d->llq_tcpsd + 1;
|
|
|
if ( d->llq_udpsd + 1 > staticMaxFD ) staticMaxFD = d->llq_udpsd + 1;
|
|
|
if ( d->LLQEventListenSock + 1 > staticMaxFD ) staticMaxFD = d->LLQEventListenSock + 1;
|
|
|
|
|
|
while(1)
|
|
|
{
|
|
|
EventSource * source;
|
|
|
int maxFD;
|
|
|
|
|
|
// set timeout
|
|
|
timeout.tv_sec = timeout.tv_usec = 0;
|
|
|
if (gettimeofday(&timenow, NULL)) { LogErr("Run", "gettimeofday"); return -1; }
|
|
|
|
|
|
if (EventsPending)
|
|
|
{
|
|
|
if (timenow.tv_sec - EventTS.tv_sec >= 5) // if we've been waiting 5 seconds for a "quiet" period to send
|
|
|
{ GenLLQEvents(d); EventsPending = mDNSfalse; } // events, we go ahead and do it now
|
|
|
else timeout.tv_usec = 500000; // else do events after 1/2 second with no new events or LLQs
|
|
|
}
|
|
|
if (!EventsPending)
|
|
|
{
|
|
|
// if no pending events, timeout when we need to check for expired records
|
|
|
if (tablecheck.tv_sec && timenow.tv_sec - tablecheck.tv_sec >= 0)
|
|
|
{ DeleteRecords(d, mDNSfalse); tablecheck.tv_sec = 0; } // table check overdue
|
|
|
if (!tablecheck.tv_sec) tablecheck.tv_sec = timenow.tv_sec + EXPIRATION_INTERVAL;
|
|
|
timeout.tv_sec = tablecheck.tv_sec - timenow.tv_sec;
|
|
|
}
|
|
|
|
|
|
FD_ZERO(&rset);
|
|
|
FD_SET( d->tcpsd, &rset );
|
|
|
FD_SET( d->udpsd, &rset );
|
|
|
FD_SET( d->tlssd, &rset );
|
|
|
FD_SET( d->llq_tcpsd, &rset );
|
|
|
FD_SET( d->llq_udpsd, &rset );
|
|
|
FD_SET( d->LLQEventListenSock, &rset );
|
|
|
|
|
|
maxFD = staticMaxFD;
|
|
|
|
|
|
for ( source = ( EventSource* ) d->eventSources.Head; source; source = source->next )
|
|
|
{
|
|
|
FD_SET( source->fd, &rset );
|
|
|
|
|
|
if ( source->fd > maxFD )
|
|
|
{
|
|
|
maxFD = source->fd;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
nfds = select( maxFD + 1, &rset, NULL, NULL, &timeout);
|
|
|
if (nfds < 0)
|
|
|
{
|
|
|
if (errno == EINTR)
|
|
|
{
|
|
|
if (terminate)
|
|
|
{
|
|
|
// close sockets to prevent clients from making new requests during shutdown
|
|
|
close( d->tcpsd );
|
|
|
close( d->udpsd );
|
|
|
close( d->tlssd );
|
|
|
close( d->llq_tcpsd );
|
|
|
close( d->llq_udpsd );
|
|
|
d->tcpsd = d->udpsd = d->tlssd = d->llq_tcpsd = d->llq_udpsd = -1;
|
|
|
DeleteRecords(d, mDNStrue);
|
|
|
return 0;
|
|
|
}
|
|
|
else if (dumptable)
|
|
|
{
|
|
|
Log( "Received SIGINFO" );
|
|
|
|
|
|
PrintLeaseTable(d);
|
|
|
PrintLLQTable(d);
|
|
|
PrintLLQAnswers(d);
|
|
|
dumptable = 0;
|
|
|
}
|
|
|
else if (hangup)
|
|
|
{
|
|
|
int err;
|
|
|
|
|
|
Log( "Received SIGHUP" );
|
|
|
|
|
|
err = ParseConfig( d, cfgfile );
|
|
|
|
|
|
if ( err )
|
|
|
{
|
|
|
LogErr( "Run", "ParseConfig" );
|
|
|
return -1;
|
|
|
}
|
|
|
|
|
|
hangup = 0;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
Log("Received unhandled signal - continuing");
|
|
|
}
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
LogErr("Run", "select"); return -1;
|
|
|
}
|
|
|
}
|
|
|
else if (nfds)
|
|
|
{
|
|
|
if (FD_ISSET(d->udpsd, &rset)) RecvUDPMessage( d, d->udpsd );
|
|
|
if (FD_ISSET(d->llq_udpsd, &rset)) RecvUDPMessage( d, d->llq_udpsd );
|
|
|
if (FD_ISSET(d->tcpsd, &rset)) AcceptTCPConnection( d, d->tcpsd, 0 );
|
|
|
if (FD_ISSET(d->llq_tcpsd, &rset)) AcceptTCPConnection( d, d->llq_tcpsd, 0 );
|
|
|
if (FD_ISSET(d->tlssd, &rset)) AcceptTCPConnection( d, d->tlssd, TCP_SOCKET_FLAGS );
|
|
|
if (FD_ISSET(d->LLQEventListenSock, &rset))
|
|
|
{
|
|
|
// clear signalling data off socket
|
|
|
char buf[256];
|
|
|
recv(d->LLQEventListenSock, buf, 256, 0);
|
|
|
if (!EventsPending)
|
|
|
{
|
|
|
EventsPending = mDNStrue;
|
|
|
if (gettimeofday(&EventTS, NULL)) { LogErr("Run", "gettimeofday"); return -1; }
|
|
|
}
|
|
|
}
|
|
|
|
|
|
for ( source = ( EventSource* ) d->eventSources.Head; source; source = source->next )
|
|
|
{
|
|
|
if ( FD_ISSET( source->fd, &rset ) )
|
|
|
{
|
|
|
source->callback( source->context );
|
|
|
break; // in case we removed this guy from the event loop
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
// timeout
|
|
|
if (EventsPending) { GenLLQEvents(d); EventsPending = mDNSfalse; }
|
|
|
else { DeleteRecords(d, mDNSfalse); tablecheck.tv_sec = 0; }
|
|
|
}
|
|
|
}
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
// signal handler sets global variables, which are inspected by main event loop
|
|
|
// (select automatically returns due to the handled signal)
|
|
|
mDNSlocal void HndlSignal(int sig)
|
|
|
{
|
|
|
if (sig == SIGTERM || sig == SIGINT ) { terminate = 1; return; }
|
|
|
if (sig == INFO_SIGNAL) { dumptable = 1; return; }
|
|
|
if (sig == SIGHUP) { hangup = 1; return; }
|
|
|
}
|
|
|
|
|
|
mDNSlocal mStatus
|
|
|
SetPublicSRV
|
|
|
(
|
|
|
DaemonInfo * d,
|
|
|
const char * name
|
|
|
)
|
|
|
{
|
|
|
DNameListElem * elem;
|
|
|
mStatus err = mStatus_NoError;
|
|
|
|
|
|
elem = ( DNameListElem* ) malloc( sizeof( DNameListElem ) );
|
|
|
require_action( elem, exit, err = mStatus_NoMemoryErr );
|
|
|
MakeDomainNameFromDNSNameString( &elem->name, name );
|
|
|
elem->next = d->public_names;
|
|
|
d->public_names = elem;
|
|
|
|
|
|
exit:
|
|
|
|
|
|
return err;
|
|
|
}
|
|
|
|
|
|
|
|
|
int main(int argc, char *argv[])
|
|
|
{
|
|
|
int started_via_launchd = 0;
|
|
|
DaemonInfo *d;
|
|
|
struct rlimit rlim;
|
|
|
|
|
|
Log("dnsextd starting");
|
|
|
|
|
|
d = malloc(sizeof(*d));
|
|
|
if (!d) { LogErr("main", "malloc"); exit(1); }
|
|
|
mDNSPlatformMemZero(d, sizeof(DaemonInfo));
|
|
|
|
|
|
// Setup the public SRV record names
|
|
|
|
|
|
SetPublicSRV(d, "_dns-update._udp.");
|
|
|
SetPublicSRV(d, "_dns-llq._udp.");
|
|
|
SetPublicSRV(d, "_dns-update-tls._tcp.");
|
|
|
SetPublicSRV(d, "_dns-query-tls._tcp.");
|
|
|
SetPublicSRV(d, "_dns-llq-tls._tcp.");
|
|
|
|
|
|
// Setup signal handling
|
|
|
|
|
|
if (signal(SIGHUP, HndlSignal) == SIG_ERR) perror("Can't catch SIGHUP");
|
|
|
if (signal(SIGTERM, HndlSignal) == SIG_ERR) perror("Can't catch SIGTERM");
|
|
|
if (signal(INFO_SIGNAL, HndlSignal) == SIG_ERR) perror("Can't catch SIGINFO");
|
|
|
if (signal(SIGINT, HndlSignal) == SIG_ERR) perror("Can't catch SIGINT");
|
|
|
if (signal(SIGPIPE, SIG_IGN ) == SIG_ERR) perror("Can't ignore SIGPIPE");
|
|
|
|
|
|
// remove open file limit
|
|
|
rlim.rlim_max = RLIM_INFINITY;
|
|
|
rlim.rlim_cur = RLIM_INFINITY;
|
|
|
if (setrlimit(RLIMIT_NOFILE, &rlim) < 0)
|
|
|
{
|
|
|
LogErr("main", "setrlimit");
|
|
|
Log("Using default file descriptor resource limit");
|
|
|
}
|
|
|
|
|
|
if (argc > 1 && !strcasecmp(argv[1], "-launchd"))
|
|
|
{
|
|
|
Log("started_via_launchd");
|
|
|
started_via_launchd = 1;
|
|
|
argv++;
|
|
|
argc--;
|
|
|
}
|
|
|
if (ProcessArgs(argc, argv, d) < 0) { LogErr("main", "ProcessArgs"); exit(1); }
|
|
|
|
|
|
if (!foreground && !started_via_launchd)
|
|
|
{
|
|
|
if (daemon(0,0))
|
|
|
{
|
|
|
LogErr("main", "daemon");
|
|
|
foreground = 1;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if (InitLeaseTable(d) < 0) { LogErr("main", "InitLeaseTable"); exit(1); }
|
|
|
if (SetupSockets(d) < 0) { LogErr("main", "SetupSockets"); exit(1); }
|
|
|
if (SetUpdateSRV(d) < 0) { LogErr("main", "SetUpdateSRV"); exit(1); }
|
|
|
|
|
|
Run(d);
|
|
|
|
|
|
Log("dnsextd stopping");
|
|
|
|
|
|
if (ClearUpdateSRV(d) < 0) { LogErr("main", "ClearUpdateSRV"); exit(1); } // clear update srv's even if Run or pthread_create returns an error
|
|
|
free(d);
|
|
|
exit(0);
|
|
|
}
|
|
|
|
|
|
|
|
|
// These are stubbed out implementations of up-call routines that the various platform support layers
|
|
|
// call. These routines are fully implemented in both mDNS.c and uDNS.c, but dnsextd doesn't
|
|
|
// link this code in.
|
|
|
//
|
|
|
// It's an error for these routines to actually be called, so perhaps we should log any call
|
|
|
// to them.
|
|
|
void mDNSCoreInitComplete( mDNS * const m, mStatus result) { ( void ) m; ( void ) result; }
|
|
|
void mDNS_ConfigChanged(mDNS *const m) { ( void ) m; }
|
|
|
void mDNSCoreMachineSleep(mDNS * const m, mDNSBool wake) { ( void ) m; ( void ) wake; }
|
|
|
void mDNSCoreReceive(mDNS *const m, void *const msg, const mDNSu8 *const end,
|
|
|
const mDNSAddr *const srcaddr, const mDNSIPPort srcport,
|
|
|
const mDNSAddr *const dstaddr, const mDNSIPPort dstport, const mDNSInterfaceID iid)
|
|
|
{ ( void ) m; ( void ) msg; ( void ) end; ( void ) srcaddr; ( void ) srcport; ( void ) dstaddr; ( void ) dstport; ( void ) iid; }
|
|
|
DNSServer *mDNS_AddDNSServer(mDNS *const m, const domainname *d, const mDNSInterfaceID interface, const mDNSAddr *addr, const mDNSIPPort port, mDNSBool scoped, mDNSu32 timeout)
|
|
|
{ ( void ) m; ( void ) d; ( void ) interface; ( void ) addr; ( void ) port; ( void ) scoped; ( void ) timeout; return(NULL); }
|
|
|
void mDNS_AddSearchDomain(const domainname *const domain, mDNSInterfaceID InterfaceID) { (void)domain; (void) InterfaceID;}
|
|
|
void mDNS_AddDynDNSHostName(mDNS *m, const domainname *fqdn, mDNSRecordCallback *StatusCallback, const void *StatusContext)
|
|
|
{ ( void ) m; ( void ) fqdn; ( void ) StatusCallback; ( void ) StatusContext; }
|
|
|
mDNSs32 mDNS_Execute (mDNS *const m) { ( void ) m; return 0; }
|
|
|
mDNSs32 mDNS_TimeNow(const mDNS *const m) { ( void ) m; return 0; }
|
|
|
mStatus mDNS_Deregister(mDNS *const m, AuthRecord *const rr) { ( void ) m; ( void ) rr; return 0; }
|
|
|
void mDNS_DeregisterInterface(mDNS *const m, NetworkInterfaceInfo *set, mDNSBool flapping)
|
|
|
{ ( void ) m; ( void ) set; ( void ) flapping; }
|
|
|
const char * const mDNS_DomainTypeNames[1] = {};
|
|
|
mStatus mDNS_GetDomains(mDNS *const m, DNSQuestion *const question, mDNS_DomainType DomainType, const domainname *dom,
|
|
|
const mDNSInterfaceID InterfaceID, mDNSQuestionCallback *Callback, void *Context)
|
|
|
{ ( void ) m; ( void ) question; ( void ) DomainType; ( void ) dom; ( void ) InterfaceID; ( void ) Callback; ( void ) Context; return 0; }
|
|
|
mStatus mDNS_Register(mDNS *const m, AuthRecord *const rr) { ( void ) m; ( void ) rr; return 0; }
|
|
|
mStatus mDNS_RegisterInterface(mDNS *const m, NetworkInterfaceInfo *set, mDNSBool flapping)
|
|
|
{ ( void ) m; ( void ) set; ( void ) flapping; return 0; }
|
|
|
void mDNS_RemoveDynDNSHostName(mDNS *m, const domainname *fqdn) { ( void ) m; ( void ) fqdn; }
|
|
|
void mDNS_SetFQDN(mDNS * const m) { ( void ) m; }
|
|
|
void mDNS_SetPrimaryInterfaceInfo(mDNS *m, const mDNSAddr *v4addr, const mDNSAddr *v6addr, const mDNSAddr *router)
|
|
|
{ ( void ) m; ( void ) v4addr; ( void ) v6addr; ( void ) router; }
|
|
|
mStatus uDNS_SetupDNSConfig( mDNS *const m ) { ( void ) m; return 0; }
|
|
|
mStatus mDNS_SetSecretForDomain(mDNS *m, DomainAuthInfo *info,
|
|
|
const domainname *domain, const domainname *keyname, const char *b64keydata, const domainname *hostname, mDNSIPPort *port, const char *autoTunnelPrefix)
|
|
|
{ ( void ) m; ( void ) info; ( void ) domain; ( void ) keyname; ( void ) b64keydata; ( void ) hostname; (void) port; ( void ) autoTunnelPrefix; return 0; }
|
|
|
mStatus mDNS_StopQuery(mDNS *const m, DNSQuestion *const question) { ( void ) m; ( void ) question; return 0; }
|
|
|
void TriggerEventCompletion(void);
|
|
|
void TriggerEventCompletion() {}
|
|
|
mDNS mDNSStorage;
|
|
|
|
|
|
|
|
|
// For convenience when using the "strings" command, this is the last thing in the file
|
|
|
// The "@(#) " pattern is a special prefix the "what" command looks for
|
|
|
const char mDNSResponderVersionString_SCCS[] = "@(#) dnsextd " STRINGIFY(mDNSResponderVersion) " (" __DATE__ " " __TIME__ ")";
|
|
|
|
|
|
#if _BUILDING_XCODE_PROJECT_
|
|
|
// If the process crashes, then this string will be magically included in the automatically-generated crash log
|
|
|
const char *__crashreporter_info__ = mDNSResponderVersionString_SCCS + 5;
|
|
|
asm(".desc ___crashreporter_info__, 0x10");
|
|
|
#endif
|