/* -*- Mode: C; tab-width: 4 -*-
*
* Copyright (c) 2003-2015 Apple 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
*
*
* 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 defined(_WIN32)
#include <process.h>
#else
#include <fcntl.h>
#include <errno.h>
#include <sys/resource.h>
#endif
#include <stdlib.h>
#include <stdio.h>
#include "mDNSEmbeddedAPI.h"
#include "DNSCommon.h"
#include "uDNS.h"
#include "uds_daemon.h"
// Normally we append search domains only for queries with a single label that are not
// fully qualified. This can be overridden to apply search domains for queries (that are
// Apple-specific functionality, not required for other platforms
#ifndef PID_FILE
#endif
#endif
#ifdef LOCAL_PEERPID
#include <libproc.h> // for proc_pidinfo()
#endif //LOCAL_PEERPID
//upto 16 characters of process name (defined in <sys/proc.h> but we do not want to include that file)
#include <WebFilterDNS/WebFilterDNS.h>
#if !NO_WCF
int WCFNameResolvesToAddr(WCFConnection *conn, char* domainName, struct sockaddr* address, uid_t userid) __attribute__((weak_import));
int WCFNameResolvesToName(WCFConnection *conn, char* fromName, char* toName, uid_t userid) __attribute__((weak_import));
// Do we really need to define a macro for "if"?
#define CHECK_WCF_FUNCTION(X) if (X)
#endif // ! NO_WCF
#else
#endif // APPLE_OSX_mDNSResponder
// User IDs 0-500 are system-wide processes, not actual users in the usual sense
// User IDs for real user accounts start at 501 and count up from there
// ***************************************************************************
#pragma mark -
#endif
typedef enum
{
typedef struct registered_record_entry
{
AuthRecord *rr; // Pointer to variable-sized AuthRecord (Why a pointer? Why not just embed it here?)
// A single registered service: ServiceRecordSet + bookkeeping
// Note that we duplicate some fields from parent service_info object
// to facilitate cleanup, when instances and parent may be deallocated at different times.
typedef struct service_instance
{
// for multi-domain default browsing
typedef struct browser_t
{
DNSQuestion q;
} browser_t;
#ifdef _WIN32
typedef unsigned int pid_t;
typedef unsigned int socklen_t;
#endif
struct request_state
{
// request_state for the original DNSServiceCreateConnection() operation
void * platform_data;
// Note: On a shared connection these fields in the primary structure, including hdr, are re-used
// for each new request. This is because, until we've read the ipc_msg_hdr to find out what the
// operation is, we don't know if we're going to need to allocate a new request_state or not.
// reply, termination, error, and client context info
union
{
struct
{
} browser;
struct
{
void *txtdata;
int num_subtypes;
} servicereg;
struct
{
} addrinfo;
struct
{
} pm;
struct
{
} enumeration;
struct
{
DNSQuestion q;
} queryrecord;
struct
{
} resolve;
} u;
};
// struct physically sits between ipc message header and call-specific fields in the message buffer
typedef struct
{
} reply_hdr;
typedef struct reply_state
{
} reply_state;
// ***************************************************************************
#pragma mark -
#endif
// globals
#ifdef LOCAL_PEERPID
#endif //LOCAL_PEERPID
// Note asymmetry here between registration and browsing.
// For service registrations we only automatically register in domains that explicitly appear in local configuration data
// (so AutoRegistrationDomains could equally well be called SCPrefRegDomains)
// For service browsing we also learn automatic browsing domains from the network, so for that case we have:
// 1. SCPrefBrowseDomains (local configuration data)
// 2. LocalDomainEnumRecords (locally-generated local-only PTR records -- equivalent to slElem->AuthRecs in uDNS.c)
// 3. AutoBrowseDomains, which is populated by tracking add/rmv events in AutomaticBrowseDomainChange, the callback function for our mDNS_GetDomains call.
// By creating and removing our own LocalDomainEnumRecords, we trigger AutomaticBrowseDomainChange callbacks just like domains learned from the network would.
mDNSexport DNameListElem *AutoRegistrationDomains; // Domains where we automatically register for empty-string registrations
static DNameListElem *SCPrefBrowseDomains; // List of automatic browsing domains read from SCPreferences for "empty string" browsing
static ARListElem *LocalDomainEnumRecords; // List of locally-generated PTR records to augment those we learn from the network
mDNSexport DNameListElem *AutoBrowseDomains; // List created from those local-only PTR records plus records we get from the network
// n get_string() calls w/o buffer overrun
// If a platform specifies its own PID file name, we use that
#ifndef PID_FILE
#endif
// ***************************************************************************
#pragma mark -
#endif
{
abort();
}
{
put_uint32(l, &data);
return ret;
}
// hack to search-replace perror's to LogMsg's
{
}
// Throttled version of my_perror: Logs once every 250 msgs
{
static int uds_throttle_count = 0;
if ((uds_throttle_count++ % 250) == 0)
}
// LogMcastQuestion/LogMcastQ should be called after the DNSQuestion struct is initialized(especially for q->TargetQID)
// Hence all calls are made after mDNS_StartQuery()/mDNS_StopQuery()/mDNS_StopBrowse() is called.
mDNSlocal void LogMcastQuestion(mDNS *const m, const DNSQuestion *const q, request_state *req, q_state status)
{
{
{
if (++mcount == 1)
}
else
{
mcount--;
}
LogMcast("%s: %##s (%s) (%s) Client(%d)[%s]", status ? "+Question" : "-Question", q->qname.c, DNSTypeName(q->qtype),
}
return;
}
// LogMcastService/LogMcastS should be called after the AuthRecord struct is initialized
// Hence all calls are made after mDNS_Register()/ just before mDNS_Deregister()
mDNSlocal void LogMcastService(mDNS *const m, const AuthRecord *const ar, request_state *req, reg_state status)
{
{
{
if (++mcount == 1)
}
else
{
mcount--;
}
LogMcast("%s: %##s (%s) (%s) Client(%d)[%s]", status ? "+Service" : "-Service", ar->resrec.name->c, DNSTypeName(ar->resrec.rrtype),
ar->resrec.InterfaceID == mDNSInterface_LocalOnly ? "lo" : ar->resrec.InterfaceID == mDNSInterface_P2P ? "p2p" :
ar->resrec.InterfaceID == mDNSInterface_Any ? "all" : InterfaceNameForID(m, ar->resrec.InterfaceID),
}
return;
}
// For complete Mcast State Log, pass mDNStrue to mstatelog in LogMcastStateInfo()
mDNSexport void LogMcastStateInfo(mDNS *const m, mDNSBool mflag, mDNSBool start, mDNSBool mstatelog)
{
if (!mstatelog)
{
if (!all_requests)
{
LogMcastNoIdent("<None>");
}
else
{
{
{
goto foundpar;
}
// For non-subbordinate operations, and subbordinate operations that have lost their parent, write out their info
foundpar:;
}
LogMcastNoIdent("--- MCAST RECORDS COUNT[%d] MCAST QUESTIONS COUNT[%d] ---", n_mrecords, n_mquests);
}
}
else
{
i_mcount = 0;
if (start)
mcount = 0;
// mcount is initialized to 0 when the PROF signal is sent since mcount could have
// wrong value if MulticastLogging is disabled and then re-enabled
LogMcastNoIdent("--- START MCAST STATE LOG ---");
if (!all_requests)
{
mcount = 0;
LogMcastNoIdent("<None>");
}
else
{
{
{
goto foundparent;
}
// For non-subbordinate operations, and subbordinate operations that have lost their parent, write out their info
}
if(!mcount) // To initially set mcount
}
if (mcount == 0)
{
}
if (mflag)
LogMcastNoIdent("--- MCOUNT[%d]: CMPKTNUM[%d] - IMPKTNUM[%d] = [%d]PKTS ---", mcount, m->MPktNum, i_mpktnum, (m->MPktNum - i_mpktnum));
LogMcastNoIdent("--- END MCAST STATE LOG ---");
}
}
{
{ LogMsg("abort_request: ERROR: Attempt to abort operation %p with req->terminate %p", req, req->terminate); return; }
// First stop whatever mDNSCore operation we were doing
// If this is actually a shared connection operation, then its req->terminate function will scan
// the all_requests list and terminate any subbordinate operations sharing this file descriptor
{ LogMsg("abort_request: ERROR: Attempt to abort operation %p with invalid fd %d", req, req->sd); return; }
// Now, if this request_state is not subordinate to some other primary, close file descriptor and discard replies
{
if (req->errsd != req->sd) LogOperation("%3d: Removing FD and closing errsd %d", req->sd, req->errsd);
udsSupportRemoveFDFromEventLoop(req->sd, req->platform_data); // Note: This also closes file descriptor req->sd for us
{
}
}
// Set req->sd to something invalid, so that udsserver_idle knows to unlink and free this structure
// Don't use dnssd_InvalidSocket (-1) because that's the sentinel value MACOSX_MDNS_MALLOC_DEBUGGING uses
// for detecting when the memory for an object is inadvertently freed while the object is still on some list
#else
#endif
// We also set req->terminate to a bogus value so we know if abort_request() gets called again for this request
}
{
request_state **p = &all_requests;
}
mDNSlocal reply_state *create_reply(const reply_op_t op, const size_t datalen, request_state *const request)
{
{
LogMsg("ERROR: create_reply - data length less than length of required fields");
return NULL;
}
return reply;
}
// Append a reply to the list in a request object
// If our request is sharing a connection, then we append our reply_state onto the primary's list
// If the request does not want asynchronous replies, then the reply is freed instead of being appended to any list.
{
request_state *r;
{
return;
}
}
// Generates a response message giving name, type, domain, plus interface index,
// suitable for a browse result or service registration result.
// On successful completion rep is set to point to a malloc'd reply_state struct
mDNSlocal mStatus GenerateNTDResponse(const domainname *const servicename, const mDNSInterfaceID id,
request_state *const request, reply_state **const rep, reply_op_t op, DNSServiceFlags flags, mStatus err)
{
return kDNSServiceErr_Invalid;
else
{
int len;
char *data;
// Calculate reply data length
len = sizeof(DNSServiceFlags);
len += sizeof(DNSServiceErrorType);
// Build reply header
(*rep)->rhdr->ifi = dnssd_htonl(mDNSPlatformInterfaceIndexfromInterfaceID(&mDNSStorage, id, mDNSfalse));
// Build reply body
return mStatus_NoError;
}
}
// Special support to enable the DNSServiceBrowse call made by Bonjour Browser
// Remove after Bonjour Browser is updated to use DNSServiceQueryRecord instead of DNSServiceBrowse
mDNSlocal void GenerateBonjourBrowserResponse(const domainname *const servicename, const mDNSInterfaceID id,
request_state *const request, reply_state **const rep, reply_op_t op, DNSServiceFlags flags, mStatus err)
{
int len;
char *data;
// 1. Put first label in namestr
// 2. Put second label and "local" into typestr
// Calculate reply data length
len = sizeof(DNSServiceFlags);
len += sizeof(DNSServiceErrorType);
// Build reply header
(*rep)->rhdr->ifi = dnssd_htonl(mDNSPlatformInterfaceIndexfromInterfaceID(&mDNSStorage, id, mDNSfalse));
// Build reply body
}
// Returns a resource record (allocated w/ malloc) containing the data found in an IPC message
// Data must be in the following format: flags, interfaceIndex, name, rrtype, rrclass, rdlen, rdata, (optional) ttl
{
if (validate_flags &&
{
LogMsg("ERROR: Bad resource record flags (must be kDNSServiceFlagsShared or kDNSServiceFlagsUnique)");
return NULL;
}
rr = mallocL("AuthRecord/read_rr_from_ipc_msg", sizeof(AuthRecord) - sizeof(RDataBody) + storage_size);
if (InterfaceID == mDNSInterface_LocalOnly)
else if (InterfaceID == mDNSInterface_P2P)
&& (flags & kDNSServiceFlagsIncludeAWDL))
else
(mDNSu8) ((flags & kDNSServiceFlagsShared) ? kDNSRecordTypeShared : kDNSRecordTypeUnique), artype, mDNSNULL, mDNSNULL);
{
return NULL;
}
return rr;
}
mDNSlocal int build_domainname_from_strings(domainname *srv, char *name, char *regtype, char *domain)
{
domainlabel n;
domainname d, t;
return 0;
}
{
// On a freshly-created Unix Domain Socket, the kernel should *never* fail to buffer a small write for us
// (four bytes for a typical error code return, 12 bytes for DNSServiceGetProperty(DaemonVersion)).
// If it does fail, we don't attempt to handle this failure, but we do log it so we know something is wrong.
if (n < len)
LogMsg("ERROR: send_all(%d) wrote %d of %d errno %d (%s)",
}
#if 0
mDNSlocal mDNSBool AuthorizedDomain(const request_state * const request, const domainname * const d, const DNameListElem * const doms)
{
int dLabels = 0;
dLabels = CountLabels(d);
{
{
if ((bestDelta == -1 || delta <= bestDelta) && SameDomainName(&delem->name, SkipLeadingLabels(d, delta)))
{
}
}
}
}
#endif
// ***************************************************************************
#pragma mark -
#endif
mDNSlocal mDNSBool callExternalHelpers(mDNSInterfaceID InterfaceID, const domainname *const domain, DNSServiceFlags flags)
{
if ( ((InterfaceID == mDNSInterface_Any) && (flags & (kDNSServiceFlagsIncludeP2P | kDNSServiceFlagsIncludeAWDL)) && IsLocalDomain(domain))
{
return mDNStrue;
}
else
return mDNSfalse;
#else
(void) InterfaceID;
(void) domain;
(void) flags;
return mDNSfalse;
#endif // APPLE_OSX_mDNSResponder
}
{
int i;
{
LogInfo("external_start_advertising_helper: Not registering service with port number zero");
return;
}
if (instance->external_advertise) LogMsg("external_start_advertising_helper: external_advertise already set!");
}
{
int i;
if (!instance->external_advertise) return;
LogInfo("external_stop_advertising_helper: calling external_stop_advertising_service");
}
// ***************************************************************************
#pragma mark -
#pragma mark - DNSServiceRegister
#endif
{
(void)m; // Unused
if (result != mStatus_MemFree) { LogMsg("Error: FreeExtraRR invoked with unexpected error %d", result); return; }
}
{
// clear pointers from parent struct
{
while (*p)
{
p = &(*p)->next;
}
}
while (e)
{
e->r.RecordContext = e;
tmp = e;
e = e->next;
}
{
}
{
}
}
// Count how many other service records we have locally with the same name, but different rdata.
// For auto-named services, we can have at most one per machine -- if we allowed two auto-named services of
// the same type on the same machine, we'd get into an infinite autoimmune-response loop of continuous renaming.
{
int count = 0;
if (rr->resrec.rrtype == kDNSType_SRV && SameDomainName(rr->resrec.name, r->name) && !IdenticalSameNameRecord(&rr->resrec, r))
count++;
return(count);
}
{
int count = 0;
count++;
return(count);
}
{
if (GenerateNTDResponse(srs->RR_SRV.resrec.name, srs->RR_SRV.resrec.InterfaceID, instance->request, &rep, reg_service_reply_op, 0, mStatus_NoError) != mStatus_NoError)
LogMsg("%3d: SendServiceRemovalNotification: %##s is not valid DNS-SD SRV name", instance->request->sd, srs->RR_SRV.resrec.name->c);
}
// service registration callback performs three duties - frees memory for deregistered services,
// handles name conflicts, and delivers completed registration information to the client
{
(void)m; // Unused
// don't send errors up to client for wide-area, empty-string registrations
if (mDNS_LoggingEnabled)
{
const char *const fmt =
"%s DNSServiceRegister(%##s, %u) %s %d";
LogOperation(fmt, prefix, srs->RR_SRV.resrec.name->c, mDNSVal16(srs->RR_SRV.resrec.rdata->u.srv.port),
}
if (!instance->request && result != mStatus_MemFree) { LogMsg("regservice_callback: instance->request is NULL %d", result); return; }
if (result == mStatus_NoError)
{
{
}
if (GenerateNTDResponse(srs->RR_SRV.resrec.name, srs->RR_SRV.resrec.InterfaceID, instance->request, &rep, reg_service_reply_op, kDNSServiceFlagsAdd, result) != mStatus_NoError)
LogMsg("%3d: regservice_callback: %##s is not valid DNS-SD SRV name", instance->request->sd, srs->RR_SRV.resrec.name->c);
if (callExternalHelpers(instance->request->u.servicereg.InterfaceID, &instance->domain, instance->request->flags))
{
LogInfo("regservice_callback: calling external_start_advertising_helper()");
}
RecordUpdatedNiceLabel(m, 0); // Successfully got new name, tell user immediately
}
else if (result == mStatus_MemFree)
{
{
instance->renameonmemfree = 0;
// error should never happen - safest to log and continue
}
else
}
else if (result == mStatus_NameConflict)
{
{
{
// On conflict for an autoname service, rename and reregister *all* autoname services
mDNS_ConfigChanged(m); // Will call back into udsserver_handle_configchange()
}
else // On conflict for a non-autoname service, rename and reregister just that one service
{
}
}
else
{
if (!SuppressError)
{
if (GenerateNTDResponse(srs->RR_SRV.resrec.name, srs->RR_SRV.resrec.InterfaceID, instance->request, &rep, reg_service_reply_op, kDNSServiceFlagsAdd, result) != mStatus_NoError)
LogMsg("%3d: regservice_callback: %##s is not valid DNS-SD SRV name", instance->request->sd, srs->RR_SRV.resrec.name->c);
}
}
}
else // Not mStatus_NoError, mStatus_MemFree, or mStatus_NameConflict
{
if (!SuppressError)
{
if (GenerateNTDResponse(srs->RR_SRV.resrec.name, srs->RR_SRV.resrec.InterfaceID, instance->request, &rep, reg_service_reply_op, kDNSServiceFlagsAdd, result) != mStatus_NoError)
LogMsg("%3d: regservice_callback: %##s is not valid DNS-SD SRV name", instance->request->sd, srs->RR_SRV.resrec.name->c);
}
}
}
{
(void)m; // Unused
{
if (result == mStatus_NoError)
LogMsg("Error: regrecord_callback: successful registration of orphaned record %s", ARDisplayString(m, rr));
else
{
if (result != mStatus_MemFree) LogMsg("regrecord_callback: error %d received after parent termination", result);
// We come here when the record is being deregistered either from DNSServiceRemoveRecord or connection_termination.
// If the record has been updated, we need to free the rdata. Every time we call mDNS_Update, it calls update_callback
// with the old rdata (so that we can free it) and stores the new rdata in "rr->resrec.rdata". This means, we need
// to free the latest rdata for which the update_callback was never called with.
}
}
else
{
if (mDNS_LoggingEnabled)
{
"%3d: DNSServiceRegisterRecord(%u %s) %d";
}
if (result != mStatus_MemFree)
{
reply->rhdr->ifi = dnssd_htonl(mDNSPlatformInterfaceIndexfromInterfaceID(m, rr->resrec.InterfaceID, mDNSfalse));
}
if (result)
{
// If this is a callback to a keepalive record, do not free it.
if (result == mStatus_BadStateErr)
{
LogInfo("regrecord_callback: Callback with error code mStatus_BadStateErr - not freeing the record.");
}
else
{
// unlink from list, free memory
}
}
else
{
{
LogInfo("regrecord_callback: calling external_start_advertising_service");
}
}
}
}
// set_peer_pid() is called after mem is allocated for each new request in NewRequest()
// This accounts for 2 places (connect_callback, request_callback)
{
#ifdef LOCAL_PEERPID
return;
// to extract the pid value
return;
// to extract the process name from the pid value
return;
request->process_id = p;
#else // !LOCAL_PEERPID
LogInfo("set_peer_pid: Not Supported on this version of OS");
return;
#endif // LOCAL_PEERPID
}
{
// When terminating a shared connection, we need to scan the all_requests list
// and terminate any subbordinate operations sharing this file descriptor
LogOperation("%3d: DNSServiceCreateConnection STOP PID[%d](%s)", request->sd, request->process_id, request->pid_name);
while (*req)
{
{
// Since we're already doing a list traversal, we unlink the request directly instead of using AbortUnlinkAndFree()
if (tmp->primary == tmp) LogMsg("connection_termination ERROR (*req)->primary == *req for %p %d", tmp, tmp->sd);
if (tmp->replies) LogMsg("connection_termination ERROR How can subordinate req %p %d have replies queued?", tmp, tmp->sd);
}
else
}
{
LogOperation("%3d: DNSServiceRegisterRecord(%u %s) STOP PID[%d](%s)", request->sd, ptr->key, RRDisplayString(&mDNSStorage, &ptr->rr->resrec), request->process_id, request->pid_name);
if (ptr->external_advertise)
{
}
}
}
{
LogOperation("%3d: Cancel %08X %08X", request->sd, request->hdr.client_context.u32[1], request->hdr.client_context.u32[0]);
while (*req)
{
{
// Since we're already doing a list traversal, we unlink the request directly instead of using AbortUnlinkAndFree()
}
else
}
}
{
if (rr)
{
// Don't allow non-local domains to be regsitered as LocalOnly. Allowing this would permit
// clients to register records such as www.bigbank.com A w.x.y.z to redirect Safari.
rr->resrec.rrclass == kDNSClass_IN && (rr->resrec.rrtype == kDNSType_A || rr->resrec.rrtype == kDNSType_AAAA ||
{
return (mStatus_BadParamErr);
}
// allocate registration entry, link into list
if (!re)
FatalError("ERROR: malloc");
#if 0
#endif
LogOperation("%3d: DNSServiceRegisterRecord(%u %s) START PID[%d](%s)", request->sd, re->key, RRDisplayString(&mDNSStorage, &rr->resrec),
if (err)
{
LogOperation("%3d: DNSServiceRegisterRecord(%u %s) ERROR (%d)", request->sd, re->key, RRDisplayString(&mDNSStorage, &rr->resrec), err);
}
else
{
}
}
return(err);
}
{
if (!request)
{
LogMsg("regservice_termination_callback context is NULL");
return;
}
{
// only safe to free memory if registration is not valid, i.e. deregister fails (which invalidates p)
LogOperation("%3d: DNSServiceRegister(%##s, %u) STOP PID[%d](%s)", request->sd, p->srs.RR_SRV.resrec.name->c,
// Clear backpointer *before* calling mDNS_DeregisterService/unlink_and_free_service_instance
// We don't need unlink_and_free_service_instance to cut its element from the list, because we're already advancing
// request->u.servicereg.instances as we work our way through the list, implicitly cutting one element at a time
// We can't clear p->request *after* the calling mDNS_DeregisterService/unlink_and_free_service_instance
// because by then we might have already freed p
{
// Don't touch service_instance *p after this -- it's likely to have been freed already
}
}
{
}
{
// Clear autoname before calling UpdateDeviceInfoRecord() so it doesn't mistakenly include this in its count of active autoname registrations
}
}
{
return(request);
}
mDNSlocal mStatus add_record_to_service(request_state *request, service_instance *instance, mDNSu16 rrtype, mDNSu16 rdlen, const char *rdata, mDNSu32 ttl)
{
ExtraResourceRecord *extra = mallocL("ExtraResourceRecord", sizeof(*extra) - sizeof(RDataBody) + size);
// use InterfaceID value from DNSServiceRegister() call that created the original service
if (result)
{
return result;
}
if ( instance->external_advertise
{
LogInfo("add_record_to_service: calling external_start_advertising_service");
}
return result;
}
{
service_instance *i;
(void)flags; // Unused
if (!request->msgptr) { LogMsg("%3d: DNSServiceAddRecord(unreadable parameters)", request->sd); return(mStatus_BadParamErr); }
// If this is a shared connection, check if the operation actually applies to a subordinate request_state object
{ LogMsg("%3d: DNSServiceAddRecord(not a registered service ref)", request->sd); return(mStatus_BadParamErr); }
// For a service registered with zero port, don't allow adding records. This mostly happens due to a bug
// in the application. See radar://9165807.
{ LogMsg("%3d: DNSServiceAddRecord: adding record to a service registered with zero port", request->sd); return(mStatus_BadParamErr); }
(request->u.servicereg.instances) ? request->u.servicereg.instances->srs.RR_SRV.resrec.name->c : NULL, DNSTypeName(rrtype), rdlen);
{
if (result && i->default_local) break;
}
return(result);
}
{
(void)m; // Unused
// There are three cases.
//
// 1. We have updated the primary TXT record of the service
// 2. We have updated the TXT record that was added to the service using DNSServiceAddRecord
// 3. We have updated the TXT record that was registered using DNSServiceRegisterRecord
//
// external_advertise is set if we have advertised at least once during the initial addition
// of the record in all of the three cases above. We should have checked for InterfaceID/LocalDomain
// checks during the first time and hence we don't do any checks here
if (external_advertise)
{
// Since we don't have a copy of the flags value used when the record was registered,
// we'll have to derive it from the ARType field.
LogInfo("update_callback: calling external_start_advertising_service");
}
exit:
}
mDNSlocal mStatus update_record(AuthRecord *rr, mDNSu16 rdlen, const char *rdata, mDNSu32 ttl, const mDNSBool *const external_advertise)
{
// BIND named (name daemon) doesn't allow TXT records with zero-length rdata. This is strictly speaking correct,
// since RFC 1035 specifies a TXT record as "One or more <character-string>s", not "Zero or more <character-string>s".
// Since some legacy apps try to create zero-length TXT records, we'll silently correct it here.
if (result) { LogMsg("update_record: Error %d for %s", (int)result, ARDisplayString(&mDNSStorage, rr)); freeL("RData/update_record", newrd); }
return result;
}
{
service_instance *i;
// get the message data
(void)flags; // Unused
if (!request->msgptr) { LogMsg("%3d: DNSServiceUpdateRecord(unreadable parameters)", request->sd); return(mStatus_BadParamErr); }
// If this is a shared connection, check if the operation actually applies to a subordinate request_state object
{
// update an individually registered record
{
{
LogOperation("%3d: DNSServiceUpdateRecord(%##s, %s)",
request->sd, reptr->rr->resrec.name->c, reptr->rr ? DNSTypeName(reptr->rr->resrec.rrtype) : "<NONE>");
goto end;
}
}
goto end;
}
{ LogMsg("%3d: DNSServiceUpdateRecord(not a registered service ref)", request->sd); return(mStatus_BadParamErr); }
// For a service registered with zero port, only SRV record is initialized. Don't allow any updates.
{ LogMsg("%3d: DNSServiceUpdateRecord: updating the record of a service registered with zero port", request->sd); return(mStatus_BadParamErr); }
// update the saved off TXT data for the service
{
{ freeL("service_info txtdata", request->u.servicereg.txtdata); request->u.servicereg.txtdata = NULL; }
if (rdlen > 0)
{
}
}
// update a record from a service record set
{
else
{
}
}
end:
(request->u.servicereg.instances) ? request->u.servicereg.instances->srs.RR_SRV.resrec.name->c : NULL,
return(result);
}
// remove a resource record registered via DNSServiceRegisterRecord()
{
if (!*ptr) { LogMsg("%3d: DNSServiceRemoveRecord(%u) not found", request->sd, request->hdr.reg_index); return mStatus_BadReferenceErr; }
e = *ptr;
LogOperation("%3d: DNSServiceRemoveRecord(%u %s)", request->sd, e->key, RRDisplayString(&mDNSStorage, &e->rr->resrec));
if (e->external_advertise)
{
e->external_advertise = mDNSfalse;
}
err = mDNS_Deregister(&mDNSStorage, e->rr); // Will free e->rr for us; we're responsible for freeing e
if (err)
{
}
freeL("registered_record_entry remove_record", e);
return err;
}
mDNSlocal mStatus remove_extra(const request_state *const request, service_instance *const serv, mDNSu16 *const rrtype)
{
{
{
break;
}
}
return err;
}
{
if (!request->msgptr) { LogMsg("%3d: DNSServiceRemoveRecord(unreadable parameters)", request->sd); return(mStatus_BadParamErr); }
// If this is a shared connection, check if the operation actually applies to a subordinate request_state object
{ LogMsg("%3d: DNSServiceRemoveRecord(not a registered service ref)", request->sd); return(mStatus_BadParamErr); }
else
{
service_instance *i;
(request->u.servicereg.instances) ? request->u.servicereg.instances->srs.RR_SRV.resrec.name->c : NULL,
{
if (err && i->default_local) break;
}
}
return(err);
}
// If there's a comma followed by another character,
// FindFirstSubType overwrites the comma with a nul and returns the pointer to the next character.
// Otherwise, it returns a pointer to the final nul at the end of the string
{
while (*p)
{
if (p[0] == '\\' && p[1])
{
p += 2;
}
else if (p[0] == ',' && p[1])
{
*p++ = 0;
return(p);
}
else if (p[0] == ':' && p[1])
{
*p++ = 0;
*AnonData = p;
}
else
{
p++;
}
}
return(p);
}
// If there's a comma followed by another character,
// FindNextSubType overwrites the comma with a nul and returns the pointer to the next character.
// If it finds an illegal unescaped dot in the subtype name, it returns mDNSNULL
// Otherwise, it returns a pointer to the final nul at the end of the string
{
while (*p)
{
if (p[0] == '\\' && p[1]) // If escape character
p += 2; // ignore following character
else if (p[0] == ',') // If we found a comma
{
if (p[1]) *p++ = 0;
return(p);
}
else if (p[0] == '.')
return(mDNSNULL);
else p++;
}
return(p);
}
// Returns -1 if illegal subtype found
{
{
NumSubTypes++;
}
if (!stp) return(-1);
return(NumSubTypes);
}
{
//
// "p" is pointing at the regtype e.g., _http._tcp followed by ":<AnonData>" indicated
// by AnonData being non-NULL which is in turn follwed by ",<SubTypes>" indicated by
// NumSubTypes being non-zero. We need to skip the initial regtype to get to the actual
// data that we want. When we come here, ChopSubTypes has null terminated like this e.g.,
//
// _http._tcp<NULL><AnonData><NULL><SubType1><NULL><SubType2><NULL> etc.
//
// 1. If we have Anonymous data and subtypes, skip the regtype (e.g., "_http._tcp")
// to get the AnonData and then skip the AnonData to get to the SubType.
//
// 2. If we have only SubTypes, skip the regtype to get to the SubType data.
//
// 3. If we have only AnonData, skip the regtype to get to the AnonData.
//
// 4. If we don't have AnonData or NumStypes, it is a noop.
//
if (AnonData)
{
int len;
// Skip the regtype
while (*p) p++;
p++;
if (!(*AnonData))
{
return (mDNSNULL);
}
}
if (NumSubTypes)
{
mDNSs32 i;
for (i = 0; i < NumSubTypes; i++)
{
mDNS_SetupResourceRecord(&st[i], mDNSNULL, mDNSInterface_Any, kDNSQType_ANY, kStandardTTL, 0, AuthRecordAny, mDNSNULL, mDNSNULL);
// First time through we skip the regtype or AnonData. Subsequently, the
// previous subtype.
while (*p) p++;
p++;
{
if (*AnonData)
return(mDNSNULL);
}
}
}
// If NumSubTypes is zero and AnonData is non-NULL, we still return NULL but AnonData has been
// initialized. The caller knows how to handle this.
return(st);
}
{
const int extra_size = (request->u.servicereg.txtlen > sizeof(RDataBody)) ? (request->u.servicereg.txtlen - sizeof(RDataBody)) : 0;
// Client guarantees that record names are unique, so we can skip sending out initial
// probe messages. Standard name conflict resolution is still done if a conflict is discovered.
// If the client specified an interface, but no domain, then we honor the specified interface for the "local" (mDNS)
// registration but for the wide-area registrations we don't (currently) have any concept of a wide-area unicast
// registrations scoped to a specific interface, so for the automatic domains we add we must *not* specify an interface.
// (Specifying an interface with an apparently wide-area domain (i.e. something other than "local")
// currently forces the registration to use mDNS multicast despite the apparently wide-area domain.)
{
{
LogMsg("register_service_instance: domain %##s already registered for %#s.%##s",
return mStatus_AlreadyRegistered;
}
}
instance->renameonmemfree = 0;
{
instance->subtypes = AllocateSubTypes(request->u.servicereg.num_subtypes, request->u.servicereg.type_as_string, mDNSNULL);
}
else
{
instance->subtypes = AllocateSubTypes(request->u.servicereg.num_subtypes, request->u.servicereg.type_as_string, &AnonData);
if (AnonData)
}
{
FatalError("ERROR: malloc");
}
if (!result)
{
}
else
{
LogMsg("register_service_instance %#s.%##s%##s error %d",
}
return result;
}
mDNSlocal void udsserver_default_reg_domain_changed(const DNameListElem *const d, const mDNSBool add)
{
#endif // APPLE_OSX_mDNSResponder
{
{
if (add)
{
// If we don't already have this domain in our list for this registration, add it now
}
else
{
// Normally we should not fail to find the specified instance
// One case where this can happen is if a uDNS update fails for some reason,
// and regservice_callback then calls unlink_and_free_service_instance and disposes of that instance.
if (!*ptr)
LogMsg("udsserver_default_reg_domain_changed domain %##s not found for service %#s type %s",
else
{
DNameListElem *p;
for (p = AutoRegistrationDomains; p; p=p->next)
else
{
if (si->clientnotified) SendServiceRemovalNotification(&si->srs); // Do this *before* clearing si->request backpointer
// Now that we've cut this service_instance from the list, we MUST clear the si->request backpointer.
// Otherwise what can happen is this: While our mDNS_DeregisterService is in the
// process of completing asynchronously, the client cancels the entire operation, so
// regservice_termination_callback then runs through the whole list deregistering each
// instance, clearing the backpointers, and then disposing the parent request_state object.
// However, because this service_instance isn't in the list any more, regservice_termination_callback
// has no way to find it and clear its backpointer, and then when our mDNS_DeregisterService finally
// completes later with a mStatus_MemFree message, it calls unlink_and_free_service_instance() with
// a service_instance with a stale si->request backpointer pointing to memory that's already been freed.
if (err) { LogMsg("udsserver_default_reg_domain_changed err %d", err); unlink_and_free_service_instance(si); }
}
}
}
}
}
}
// Don't allow normal and anonymous registration to coexist.
mDNSlocal mDNSBool CheckForMixedRegistrations(domainname *regtype, domainname *domain, mDNSBool AnonData)
{
// We only care about local domains where the anonymous extension is
// implemented.
{
return mDNStrue;
}
{
{
{
continue;
}
// If we are about to register a anonymous registraion, we dont't want to
// allow the regular ones and vice versa.
if (AnonData)
{
{
return mDNSfalse;
}
}
else
{
// Allow multiple regular registrations
{
return mDNSfalse;
}
}
}
}
return mDNStrue;
}
// Returns true if the interfaceIndex value matches one of the pre-defined
// special values listed in the switch statement below.
{
switch(interfaceIndex)
{
return mDNStrue;
default:
return mDNSfalse;
}
}
{
char name[256]; // Lots of spare space for extra-long names that we'll auto-truncate down to 63 bytes
const char *msgTXTData;
// Map kDNSServiceInterfaceIndexP2P to kDNSServiceInterfaceIndexAny with the
// kDNSServiceFlagsIncludeP2P flag set.
{
LogOperation("handle_regservice_request: mapping kDNSServiceInterfaceIndexP2P to kDNSServiceInterfaceIndexAny + kDNSServiceFlagsIncludeP2P");
}
// The registration is scoped to a specific interface index, but the
// interface is not currently in our list.
if (interfaceIndex && !InterfaceID)
{
// If it's one of the specially defined inteface index values, just return an error.
{
return(mStatus_BadParamErr);
}
// Otherwise, use the specified interface index value and the registration will
// be applied to that interface when it comes up.
}
{ LogMsg("ERROR: handle_regservice_request - Couldn't read name/regtype/domain"); return(mStatus_BadParamErr); }
else
{
}
if (!request->msgptr) { LogMsg("%3d: DNSServiceRegister(unreadable parameters)", request->sd); return(mStatus_BadParamErr); }
{
}
// Check for sub-types after the service type
request->u.servicereg.num_subtypes = ChopSubTypes(request->u.servicereg.type_as_string, &AnonData); // Note: Modifies regtype string to remove trailing subtypes
{
LogMsg("ERROR: handle_regservice_request - ChopSubTypes failed %s", request->u.servicereg.type_as_string);
goto bad_param;
}
if (AnonData)
{
if (AnonDataLen > MAX_ANONYMOUS_DATA)
{
goto bad_param;
}
}
else
{
}
// Don't try to construct "domainname t" until *after* ChopSubTypes has worked its magic
if (!*request->u.servicereg.type_as_string || !MakeDomainNameFromDNSNameString(&request->u.servicereg.type, request->u.servicereg.type_as_string))
{ LogMsg("ERROR: handle_regservice_request - type_as_string bad %s", request->u.servicereg.type_as_string); goto bad_param; }
if (!name[0])
{
}
else
{
// If the client is allowing AutoRename, then truncate name to legal length before converting it to a DomainLabel
if ((flags & kDNSServiceFlagsNoAutoRename) == 0)
{
}
}
if (*domain)
{
if (!MakeDomainNameFromDNSNameString(&d, domain))
}
else
{
MakeDomainNameFromDNSNameString(&d, "local.");
}
// We don't allow the anonymous and the regular ones to coexist
if (!CheckForMixedRegistrations(&request->u.servicereg.type, &d, request->u.servicereg.AnonData)) { goto bad_param; }
{
LogMsg("ERROR: handle_regservice_request - Couldn't ConstructServiceName from, “%#s” “%##s” “%##s”",
}
// Some clients use mDNS for lightweight copy protection, registering a pseudo-service with
// a port number of zero. When two instances of the protected client are allowed to run on one
// machine, we don't want to see misleading "Bogus client" messages in syslog and the console.
{
if (count)
LogMsg("Client application[%d](%s) registered %d identical instances of service %##s port %u.", request->process_id,
}
LogOperation("%3d: DNSServiceRegister(%X, %d, \"%s\", \"%s\", \"%s\", \"%s\", %u) START PID[%d](%s)",
// We need to unconditionally set request->terminate, because even if we didn't successfully
// start any registrations right now, subsequent configuration changes may cause successful
// registrations to be added, and we'll need to cancel them before freeing this memory.
// We also need to set request->terminate first, before adding additional service instances,
// because the uds_validatelists uses the request->terminate function pointer to determine
// what kind of request this is, and therefore what kind of list validation is required.
#if 0
err = AuthorizedDomain(request, &d, AutoRegistrationDomains) ? register_service_instance(request, &d) : mStatus_NoError;
#endif
if (!err)
{
if (!*domain)
{
// Note that we don't report errors for non-local, non-explicit domains
}
}
return(err);
return mStatus_BadParamErr;
}
// ***************************************************************************
#pragma mark -
#pragma mark - DNSServiceBrowse
#endif
mDNSlocal void FoundInstance(mDNS *const m, DNSQuestion *question, const ResourceRecord *const answer, QC_result AddRecord)
{
(void)m; // Unused
{ LogMsg("%3d: FoundInstance: Should not be called with rrtype %d (not a PTR record)", req->sd, answer->rrtype); return; }
if (mDNSOpaque16IsZero(question->TargetQID) && (question->BrowseThreshold > 0) && (question->CurrentAnswers >= question->BrowseThreshold))
{
}
if (GenerateNTDResponse(&answer->rdata->u.name, answer->InterfaceID, req, &rep, browse_reply_op, flags, mStatus_NoError) != mStatus_NoError)
{
{
// Special support to enable the DNSServiceBrowse call made by Bonjour Browser
// Remove after Bonjour Browser is updated to use DNSServiceQueryRecord instead of DNSServiceBrowse
GenerateBonjourBrowserResponse(&answer->rdata->u.name, answer->InterfaceID, req, &rep, browse_reply_op, flags, mStatus_NoError);
goto bonjourbrowserhack;
}
LogMsg("%3d: FoundInstance: %##s PTR %##s received from network is not valid DNS-SD service pointer",
return;
}
LogOperation("%3d: DNSServiceBrowse(%##s, %s) RESULT %s %d: %s",
mDNSPlatformInterfaceIndexfromInterfaceID(m, answer->InterfaceID, mDNSfalse), RRDisplayString(m, answer));
}
{
browser_t *b, *p;
{
if (SameDomainName(&p->domain, d))
}
b = mallocL("browser_t", sizeof(*b));
if (!b) return mStatus_NoMemoryErr;
AssignDomainName(&b->domain, d);
err = mDNS_StartBrowse(&mDNSStorage, &b->q, &info->u.browser.regtype, d, info->u.browser.AnonData, info->u.browser.interface_id, info->flags,
info->u.browser.ForceMCast, (info->flags & kDNSServiceFlagsBackgroundTrafficClass) != 0, FoundInstance, info);
if (err)
{
LogMsg("mDNS_StartBrowse returned %d for type %##s domain %##s", err, info->u.browser.regtype.c, d->c);
freeL("browser_t/add_domain_to_browser", b);
}
else
{
LogOperation("%3d: DNSServiceBrowse(%##s) START PID[%d](%s)", info->sd, b->q.qname.c, info->process_id,
{
LogInfo("add_domain_to_browser: calling external_start_browsing_for_service()");
}
}
return err;
}
{
{
// Stop the domain enumeration queries to discover the WAB legacy browse domains
LogInfo("%3d: DNSServiceBrowse Cancel WAB PID[%d](%s)", info->sd, info->process_id, info->pid_name);
}
{
{
LogInfo("browse_termination_callback: calling external_stop_browsing_for_service()");
}
LogOperation("%3d: DNSServiceBrowse(%##s) STOP PID[%d](%s)", info->sd, ptr->q.qname.c, info->process_id, info->pid_name);
}
}
mDNSlocal void udsserver_automatic_browse_domain_changed(const DNameListElem *const d, const mDNSBool add)
{
debugf("udsserver_automatic_browse_domain_changed: %s default browse domain %##s", add ? "Adding" : "Removing", d->name.c);
#endif // APPLE_OSX_mDNSResponder
{
{
if (add)
{
// If we don't already have this domain in our list for this browse operation, add it now
else debugf("udsserver_automatic_browse_domain_changed %##s already in list, not re-adding", &d->name);
}
else
{
else
{
DNameListElem *p;
for (p = AutoBrowseDomains; p; p=p->next)
if (p) debugf("udsserver_automatic_browse_domain_changed %##s still in list, not removing", &d->name);
else
{
}
}
}
}
}
}
{
(void)m; // unused
if (result == mStatus_MemFree)
{
// On shutdown, mDNS_Close automatically deregisters all records
// Since in this case no one has called DeregisterLocalOnlyDomainEnumPTR to cut the record
// from the LocalDomainEnumRecords list, we do this here before we free the memory.
// (This should actually no longer be necessary, now that we do the proper cleanup in
// udsserver_exit. To confirm this, we'll log an error message if we do find a record that
// hasn't been cut from the list yet. If these messages don't appear, we can delete this code.)
if (*ptr) { *ptr = (*ptr)->next; LogMsg("FreeARElemCallback: Have to cut %s", ARDisplayString(m, rr)); }
}
}
// RegisterLocalOnlyDomainEnumPTR and DeregisterLocalOnlyDomainEnumPTR largely duplicate code in
// "FoundDomain" in uDNS.c for creating and destroying these special mDNSInterface_LocalOnly records.
// We may want to turn the common code into a subroutine.
{
debugf("Incrementing %s refcount for %##s",
mDNS_SetupResourceRecord(&ptr->ar, mDNSNULL, mDNSInterface_LocalOnly, kDNSType_PTR, 7200, kDNSRecordTypeShared, AuthRecordLocalOnly, FreeARElemCallback, ptr);
if (err)
{
}
else
{
}
}
{
debugf("Decrementing %s refcount for %##s",
while (*ptr)
{
if (SameDomainName(&(*ptr)->ar.resrec.rdata->u.name, d) && SameDomainName((*ptr)->ar.resrec.name, &lhs))
{
return;
}
}
}
{
}
{
DNameListElem **p = &AutoBrowseDomains;
else
{
}
}
{
DNameListElem *d;
for (d = browseDomains; d; d = d->next)
{
if (add)
{
}
else
{
}
}
}
{
int num_autoname = 0;
// Don't need to register the device info record for kDNSServiceInterfaceIndexLocalOnly registrations.
{
if (req->terminate == regservice_termination_callback && req->u.servicereg.autoname && req->interfaceIndex != kDNSServiceInterfaceIndexLocalOnly)
num_autoname++;
}
// If DeviceInfo record is currently registered, see if we need to deregister it
{
mDNS_Deregister(m, &m->DeviceInfo);
}
// If DeviceInfo record is not currently registered, see if we need to register it
if (num_autoname > 0)
{
mDNS_SetupResourceRecord(&m->DeviceInfo, mDNSNULL, mDNSNULL, kDNSType_TXT, kStandardTTL, kDNSRecordTypeAdvisory, AuthRecordAny, mDNSNULL, mDNSNULL);
mDNS_Register(m, &m->DeviceInfo);
}
}
#else // APPLE_OSX_mDNSResponder
{
(void)m; // unused
}
#endif // APPLE_OSX_mDNSResponder
{
DNameListElem *p;
// For autoname services, see if the default service name has changed, necessitating an automatic update
{
{
LogInfo("udsserver_handle_configchange: Calling deregister for Service %##s", ptr->srs.RR_PTR.resrec.name->c);
regservice_callback(m, &ptr->srs, mStatus_MemFree); // If service deregistered already, we can re-register immediately
}
}
// Let the platform layer get the current DNS information
mDNS_Lock(m);
mDNSPlatformSetDNSConfig(m, mDNSfalse, mDNSfalse, mDNSNULL, &RegDomains, &BrowseDomains, mDNSfalse);
mDNS_Unlock(m);
// Any automatic registration domains are also implicitly automatic browsing domains
if (AutoRegistrationDomains) SetPrefsBrowseDomains(m, AutoRegistrationDomains, mDNSfalse); // Then clear the old list
// Add any new domains not already in our AutoRegistrationDomains list
for (p=RegDomains; p; p=p->next)
{
if (!*pp) // If not found in our existing list, this is a new default registration domain
{
}
else // else found same domainname in both old and new lists, so no change, just delete old copy
{
}
}
// Delete any domains in our old AutoRegistrationDomains list that are now gone
while (AutoRegistrationDomains)
{
udsserver_default_reg_domain_changed(del, mDNSfalse); // before calling udsserver_default_reg_domain_changed()
}
// Now we have our new updated automatic registration domain list
// Add new browse domains to internal list
// Remove old browse domains from internal list
if (SCPrefBrowseDomains)
{
while (SCPrefBrowseDomains)
{
}
}
// Replace the old browse domains array with the new array
}
mDNSlocal void AutomaticBrowseDomainChange(mDNS *const m, DNSQuestion *q, const ResourceRecord *const answer, QC_result AddRecord)
{
(void)m; // unused;
(void)q; // unused
LogOperation("AutomaticBrowseDomainChange: %s automatic browse domain %##s",
}
{
int AnonDataLen;
mDNSInterfaceID InterfaceID = mDNSPlatformInterfaceIDfromInterfaceIndex(&mDNSStorage, interfaceIndex);
// The browse is scoped to a specific interface index, but the
// interface is not currently in our list.
if (interfaceIndex && !InterfaceID)
{
// If it's one of the specially defined inteface index values, just return an error.
{
return(mStatus_BadParamErr);
}
// Otherwise, use the specified interface index value and the browse will
// be applied to that interface when it comes up.
}
get_string(&request->msgptr, request->msgend, domain, MAX_ESCAPED_DOMAIN_NAME) < 0) return(mStatus_BadParamErr);
if (!request->msgptr) { LogMsg("%3d: DNSServiceBrowse(unreadable parameters)", request->sd); return(mStatus_BadParamErr); }
typedn.c[0] = 0;
NumSubTypes = ChopSubTypes(regtype, &AnonData); // Note: Modifies regtype string to remove trailing subtypes
return(mStatus_BadParamErr);
AnonDataLen = 0;
if (AnonData)
{
if (AnonDataLen > MAX_ANONYMOUS_DATA)
{
return(mStatus_BadParamErr);
}
// Account for the null byte
AnonDataLen += 1;
}
if (NumSubTypes == 1)
{
return(mStatus_BadParamErr);
}
// For over-long service types, we only allow domain "local"
// Set up browser info
LogOperation("%3d: DNSServiceBrowse(%X, %d, \"%##s\", \"%s\") START PID[%d](%s)",
request->sd, request->flags, interfaceIndex, request->u.browser.regtype.c, domain, request->process_id, request->pid_name);
{
// Start the domain enumeration queries to discover the WAB browse domains
LogInfo("%3d: DNSServiceBrowse Start WAB PID[%d](%s)", request->sd, request->process_id, request->pid_name);
}
if (AnonData)
{
return mStatus_NoMemoryErr;
else
}
// We need to unconditionally set request->terminate, because even if we didn't successfully
// start any browses right now, subsequent configuration changes may cause successful
// browses to be added, and we'll need to cancel them before freeing this memory.
if (domain[0])
{
}
else
{
{
if (err)
{
}
}
}
return(err);
}
// ***************************************************************************
#pragma mark -
#pragma mark - DNSServiceResolve
#endif
mDNSlocal void resolve_result_callback(mDNS *const m, DNSQuestion *question, const ResourceRecord *const answer, QC_result AddRecord)
{
char *data;
(void)m; // Unused
LogOperation("%3d: DNSServiceResolve(%##s) %s %s", req->sd, question->qname.c, AddRecord ? "ADD" : "RMV", RRDisplayString(m, answer));
if (!AddRecord)
{
return;
}
if (!req->u.resolve.txt || !req->u.resolve.srv) return; // only deliver result to client if we have both answers
// calculate reply length
len += sizeof(DNSServiceFlags);
len += sizeof(DNSServiceErrorType);
rep->rhdr->ifi = dnssd_htonl(mDNSPlatformInterfaceIndexfromInterfaceID(m, answer->InterfaceID, mDNSfalse));
// write reply data to message
LogOperation("%3d: DNSServiceResolve(%s) RESULT %s:%d", req->sd, fullname, target, mDNSVal16(req->u.resolve.srv->rdata->u.srv.port));
}
{
LogOperation("%3d: DNSServiceResolve(%##s) STOP PID[%d](%s)", request->sd, request->u.resolve.qtxt.qname.c, request->process_id, request->pid_name);
external_stop_resolving_service(request->u.resolve.qsrv.InterfaceID, &request->u.resolve.qsrv.qname, request->flags);
}
{
// extract the data from the message
// Map kDNSServiceInterfaceIndexP2P to kDNSServiceInterfaceIndexAny with the kDNSServiceFlagsIncludeP2P
// flag set so that the resolve will run over P2P interfaces that are not yet created.
{
LogOperation("handle_resolve_request: mapping kDNSServiceInterfaceIndexP2P to kDNSServiceInterfaceIndexAny + kDNSServiceFlagsIncludeP2P");
}
// The operation is scoped to a specific interface index, but the
// interface is not currently in our list.
if (interfaceIndex && !InterfaceID)
{
// If it's one of the specially defined inteface index values, just return an error.
{
return(mStatus_BadParamErr);
}
// Otherwise, use the specified interface index value and the operation will
// be applied to that interface when it comes up.
}
{ LogMsg("ERROR: handle_resolve_request - Couldn't read name/regtype/domain"); return(mStatus_BadParamErr); }
if (!request->msgptr) { LogMsg("%3d: DNSServiceResolve(unreadable parameters)", request->sd); return(mStatus_BadParamErr); }
{ LogMsg("ERROR: handle_resolve_request bad “%s” “%s” “%s”", name, regtype, domain); return(mStatus_BadParamErr); }
// format questions
request->u.resolve.qsrv.UseBackgroundTrafficClass = (flags & kDNSServiceFlagsBackgroundTrafficClass) != 0;
request->u.resolve.qtxt.UseBackgroundTrafficClass = (flags & kDNSServiceFlagsBackgroundTrafficClass) != 0;
request->u.resolve.ReportTime = NonZeroTime(mDNS_TimeNow(&mDNSStorage) + 130 * mDNSPlatformOneSecond);
#if 0
#endif
// ask the questions
LogOperation("%3d: DNSServiceResolve(%X %d %##s) START PID[%d](%s)", request->sd, flags, interfaceIndex,
if (!err)
{
if (err)
{
}
else
{
{
LogInfo("handle_resolve_request: calling external_start_resolving_service()");
}
}
}
return(err);
}
// ***************************************************************************
#pragma mark -
#pragma mark - DNSServiceQueryRecord
#endif
// mDNS operation functions. Each operation has 3 associated functions - a request handler that parses
// the client's request and makes the appropriate mDNSCore call, a result handler (passed as a callback
// to the mDNSCore routine) that sends results back to the client, and a termination routine that aborts
// the mDNSCore operation if the client dies or closes its socket.
// Returns -1 to tell the caller that it should not try to reissue the query anymore
// Returns 1 on successfully appending a search domain and the caller should reissue the new query
// Returns 0 when there are no more search domains and the caller should reissue the query
{
// Sanity check: The caller already checks this. We use -1 to indicate that we have searched all
// the domains and should try the single label query directly on the wire.
{
LogMsg("AppendNewSearchDomain: question %##s (%s) SearchListIndex is -1", question->qname.c, DNSTypeName(question->qtype));
return -1;
}
if (!question->AppendSearchDomains)
{
LogMsg("AppendNewSearchDomain: question %##s (%s) AppendSearchDoamins is 0", question->qname.c, DNSTypeName(question->qtype));
return -1;
}
// Save the original name, before we modify them below.
{
}
sd = uDNS_GetNextSearchDomain(m, question->InterfaceID, &question->SearchListIndex, !question->AppendLocalSearchDomains);
// We use -1 to indicate that we have searched all the domains and should try the single label
// query directly on the wire. uDNS_GetNextSearchDomain should never return a negative value
{
LogMsg("AppendNewSearchDomain: ERROR!! uDNS_GetNextSearchDomain returned -1");
return -1;
}
// Not a common case. Perhaps, we should try the next search domain if it exceeds ?
{
LogMsg("AppendNewSearchDomain: ERROR!! exceeding max domain length for %##s (%s) SearchDomain %##s length %d, Question name length %d", question->qnameOrig->c, DNSTypeName(question->qtype), sd->c, DomainNameLength(question->qnameOrig), DomainNameLength(sd));
return -1;
}
// if there are no more search domains and we have already tried this question
// without appending search domains, then we are done.
{
LogInfo("AppnedNewSearchDomain: No more search domains for question with name %##s (%s), not trying anymore", question->qname.c, DNSTypeName(question->qtype));
return -1;
}
// Stop the question before changing the name as negative cache entries could be pointing at this question.
// Even if we don't change the question in the case of returning 0, the caller is going to restart the
// question.
if (err) { LogMsg("AppendNewSearchDomain: ERROR!! %##s %s mDNS_StopQuery: %d, while retrying with search domains", question->qname.c, DNSTypeName(question->qtype), (int)err); }
if (sd)
{
LogInfo("AppnedNewSearchDomain: Returning question with name %##s, SearchListIndex %d", question->qname.c, question->SearchListIndex);
return 1;
}
// Try the question as single label
LogInfo("AppnedNewSearchDomain: No more search domains for question with name %##s (%s), trying one last time", question->qname.c, DNSTypeName(question->qtype));
return 0;
}
{
const SearchListElem *s;
for (s=SearchList; s; s=s->next)
{
continue;
{
// Note: When qcount == scount, we do a complete match of the domain
// which is expected by the callers.
if (SameDomainName(&s->domain, d))
{
return mDNStrue;
}
}
}
return mDNSfalse;
}
// The caller already checks that this is a dotlocal question.
{
// If the question matches the search domain exactly or the search domain is a
// subdomain of the question, it is most likely a valid unicast domain and hence
// don't suppress negative responses.
//
// If the user has configured ".local" as a search domain, we don't want
// to deliver a negative response for names ending in ".local" as that would
// prevent bonjour discovery. Passing mDNStrue for the last argument excludes
// ".local" search domains.
{
LogOperation("ShouldDeliverNegativeResponse: Question %##s (%s) in SearchList", question->qname.c, DNSTypeName(question->qtype));
return mDNStrue;
}
{
LogOperation("ShouldDeliverNegativeResponse: Question %##s (%s) not answering local question with negative unicast response",
return mDNSfalse;
}
{
LogOperation("ShouldDeliverNegativeResponse:Question %##s (%s) not answering local question with negative unicast response"
return mDNSfalse;
}
LogOperation("ShouldDeliverNegativeResponse:Question %##s (%s) answering local with negative unicast response (found positive record)",
return mDNStrue;
}
// Workaround for networks using Microsoft Active Directory using "local" as a private internal
// top-level domain
{
#ifndef UNICAST_DISABLED
#define VALID_MSAD_SRV_TRANSPORT(T) (SameDomainLabel((T)->c, (const mDNSu8 *)"\x4_tcp") || SameDomainLabel((T)->c, (const mDNSu8 *)"\x4_udp"))
#define VALID_MSAD_SRV(Q) ((Q)->qtype == kDNSType_SRV && VALID_MSAD_SRV_TRANSPORT(SecondLabel(&(Q)->qname)))
{
if (q->qtype == kDNSType_A)
else if (q->qtype == kDNSType_AAAA)
}
if (!question2)
{
return mStatus_BadParamErr;
}
// Sanity check: If we already sent an additonal query, we don't need to send one more.
//
// 1. When the application calls DNSServiceQueryRecord or DNSServiceGetAddrInfo with a .local name, this function
// is called to see whether a unicast query should be sent or not.
//
// 2. As a result of appending search domains, the question may be end up with a .local suffix even though it
// was not a .local name to start with. In that case, queryrecord_result_callback calls this function to
// send the additional query.
//
// Thus, it should not be called more than once.
if (*question2)
{
LogInfo("SendAdditionalQuery: question2 already sent for %##s (%s), no more q2", q->qname.c, DNSTypeName(q->qtype));
return err;
}
{
*q2 = *q;
// Always set the QuestionContext to indicate that this question should be stopped
// before freeing. Don't rely on "q".
// If the query starts as a single label e.g., somehost, and we have search domains with .local,
// queryrecord_result_callback calls this function when .local is appended to "somehost".
// At that time, the name in "q" is pointing at somehost.local and its qnameOrig pointing at
// "somehost". We need to copy that information so that when we retry with a different search
// domain e.g., mycompany.local, we get "somehost.mycompany.local".
if (q->qnameOrig)
{
if (!(*question2)->qnameOrig) { LogMsg("SendAdditionalQuery: ERROR!! malloc failure"); return mStatus_NoMemoryErr; }
}
// For names of the form "<one-or-more-labels>.bar.local." we always do a second unicast query in parallel.
// For names of the form "<one-label>.local." it's less clear whether we should do a unicast query.
// If the name being queried is exactly the same as the name in the DHCP "domain" option (e.g. the DHCP
// "domain" is my-small-company.local, and the user types "my-small-company.local" into their web browser)
// then that's a hint that it's worth doing a unicast query. Otherwise, we first check to see if the
// site's DNS server claims there's an SOA record for "local", and if so, that's also a hint that queries
// for names in the "local" domain will be safely answered privately before they hit the root name servers.
// Note that in the "my-small-company.local" example above there will typically be an SOA record for
// "my-small-company.local" but *not* for "local", which is why the "local SOA" check would fail in that case.
// We need to check against both ActiveDirectoryPrimaryDomain and SearchList. If it matches against either
// of those, we don't want do the SOA check for the local
if (labels == 2 && !SameDomainName(&q->qname, &ActiveDirectoryPrimaryDomain) && !DomainInSearchList(&q->qname, mDNSfalse))
{
// Don't append search domains for the .local SOA query
q2->AppendSearchDomains = 0;
q2->AppendLocalSearchDomains = 0;
q2->SearchListIndex = 0;
q2->TimeoutQuestion = 0;
}
LogOperation("%3d: DNSServiceQueryRecord(%##s, %s) unicast", request->sd, q2->qname.c, DNSTypeName(q2->qtype));
if (err) LogMsg("%3d: ERROR: DNSServiceQueryRecord %##s %s mDNS_StartQuery: %d", request->sd, q2->qname.c, DNSTypeName(q2->qtype), (int)err);
}
return(err);
#else // !UNICAST_DISABLED
(void) q;
(void) request;
(void) err;
return mStatus_NoError;
#endif // !UNICAST_DISABLED
}
#endif // APPLE_OSX_mDNSResponder
// This function tries to append a search domain if valid and possible. If so, returns true.
mDNSlocal mDNSBool RetryQuestionWithSearchDomains(mDNS *const m, DNSQuestion *question, request_state *req, QC_result AddRecord)
{
int result;
// RetryWithSearchDomains tells the core to call us back so that we can retry with search domains if there is no
// answer in the cache or /etc/hosts. In the first call back from the core, we clear RetryWithSearchDomains so
// that we don't get called back repeatedly. If we got an answer from the cache or /etc/hosts, we don't touch
// RetryWithSearchDomains which may or may not be set.
//
// If we get e.g., NXDOMAIN and the query is neither suppressed nor exhausted the domain search list and
// is a valid question for appending search domains, retry by appending domains
if ((AddRecord != QC_suppressed) && question->SearchListIndex != -1 && question->AppendSearchDomains)
{
// As long as the result is either zero or 1, we retry the question. If we exahaust the search
// domains (result is zero) we try the original query (as it was before appending the search
// domains) as such on the wire as a last resort if we have not tried them before. For queries
// with more than one label, we have already tried them before appending search domains and
// hence don't retry again
if (result != -1)
{
if (!err)
{
LogOperation("%3d: RetryQuestionWithSearchDomains(%##s, %s), retrying after appending search domain", req->sd, question->qname.c, DNSTypeName(question->qtype));
// If the result was zero, it meant that there are no search domains and we just retried the question
// as a single label and we should not retry with search domains anymore.
return mDNStrue;
}
else
{
LogMsg("%3d: ERROR: RetryQuestionWithSearchDomains %##s %s mDNS_StartQuery: %d, while retrying with search domains", req->sd, question->qname.c, DNSTypeName(question->qtype), (int)err);
// We have already stopped the query and could not restart. Reset the appropriate pointers
// so that we don't call stop again when the question terminates
}
}
}
else
{
LogInfo("%3d: RetryQuestionWithSearchDomains: Not appending search domains - SuppressQuery %d, SearchListIndex %d, AppendSearchDomains %d", req->sd, AddRecord, question->SearchListIndex, question->AppendSearchDomains);
}
return mDNSfalse;
}
mDNSlocal void queryrecord_result_reply(mDNS *const m, request_state *req, DNSQuestion *question, const ResourceRecord *const answer, QC_result AddRecord,
{
char *data;
question->qname.c, DNSTypeName(question->qtype), AddRecord ? "ADD" : "RMV", RRDisplayString(m, answer));
len += sizeof(DNSServiceErrorType);
if (AddRecord)
if (question->ValidationStatus != 0)
{
{
{
case DNSSEC_Secure:
break;
case DNSSEC_Insecure:
break;
case DNSSEC_Indeterminate:
break;
case DNSSEC_Bogus:
break;
default:
LogMsg("queryrecord_result_reply unknown status %d for %##s", question->ValidationStatus, question->qname.c);
}
}
}
// Call mDNSPlatformInterfaceIndexfromInterfaceID, but suppressNetworkChange (last argument). Otherwise, if the
// InterfaceID is not valid, then it simulates a "NetworkChanged" which in turn makes questions
// to be stopped and started including *this* one. Normally the InterfaceID is valid. But when we
// mDNS core . Eventually, we should remove the calls to "NetworkChanged" in
// mDNSPlatformInterfaceIndexfromInterfaceID when it can't find InterfaceID as ResourceRecords
// should not have existed to answer this question if the corresponding interface is not valid.
rep->rhdr->ifi = dnssd_htonl(mDNSPlatformInterfaceIndexfromInterfaceID(m, answer->InterfaceID, mDNStrue));
// We need to use putRData here instead of the crude put_rdata function, because the crude put_rdata
// function just does a blind memory copy without regard to structures that may have holes in them.
// Stop the question, if we just timed out
if (error == kDNSServiceErr_Timeout)
{
mDNS_StopQuery(m, question);
// Reset the pointers so that we don't call stop on termination
}
{
// we did not answer questions and setup the status to deliver triggers.
}
{
}
#if !NO_WCF
{
struct xucred x;
{
(x.cr_version == XUCRED_VERSION))
{
{
{
// Instead of this stupid call to putRData it would be much simpler to just assign the value in the sensible way, like this:
// sin->sin_addr.s_addr = answer->rdata->u.ipv4.NotAnInteger;
if (!putRData(mDNSNULL, (mDNSu8 *)&sin->sin_addr, (mDNSu8 *)(&sin->sin_addr + sizeof(mDNSv4Addr)), answer))
LogMsg("queryrecord_result_reply: WCF AF_INET putRData failed");
else
{
}
}
{
// Instead of this stupid call to putRData it would be much simpler to just assign the value in the sensible way, like this:
// sin6->sin6_addr.__u6_addr.__u6_addr32[0] = answer->rdata->u.ipv6.l[0];
// sin6->sin6_addr.__u6_addr.__u6_addr32[1] = answer->rdata->u.ipv6.l[1];
// sin6->sin6_addr.__u6_addr.__u6_addr32[2] = answer->rdata->u.ipv6.l[2];
// sin6->sin6_addr.__u6_addr.__u6_addr32[3] = answer->rdata->u.ipv6.l[3];
if (!putRData(mDNSNULL, (mDNSu8 *)&sin6->sin6_addr, (mDNSu8 *)(&sin6->sin6_addr + sizeof(mDNSv6Addr)), answer))
LogMsg("queryrecord_result_reply: WCF AF_INET6 putRData failed");
else
{
}
}
{
{
}
}
}
{
LogMsg("queryrecord_result_reply: WCF CNAME putRData failed");
else
{
{
}
}
}
}
else my_perror("queryrecord_result_reply: ERROR: getsockopt LOCAL_PEERCRED");
}
}
#endif
#endif
}
mDNSlocal void queryrecord_result_callback(mDNS *const m, DNSQuestion *question, const ResourceRecord *const answer, QC_result AddRecord)
{
DNSQuestion *q = mDNSNULL;
{
// Sanity check: QuestionContext is set to NULL after we stop the question and hence we should not
// get any callbacks from the core after this.
if (!req)
{
LogMsg("queryrecord_result_callback: ERROR!! QuestionContext NULL for %##s (%s)", question->qname.c, DNSTypeName(question->qtype));
return;
}
q = &req->u.queryrecord.q;
{
mDNS_StopQuery(m, question);
// We got a negative response for the SOA record indicating that .local does not exist.
// But we might have other search domains (that does not end in .local) that can be
// appended to this question. In that case, we want to retry the question. Otherwise,
// we don't want to try this question as unicast.
{
return;
}
// If we got a non-negative answer for our "local SOA" test query, start an additional parallel unicast query
//
// Note: When we copy the original question, we copy everything including the AppendSearchDomains,
// RetryWithSearchDomains except for qnameOrig which can be non-NULL if the original question is
// e.g., somehost and then we appended e.g., ".local" and retried that question. See comment in
// SendAdditionalQuery as to how qnameOrig gets initialized.
*question = *q;
LogOperation("%3d: DNSServiceQueryRecord(%##s, %s) unicast, context %p", req->sd, question->qname.c, DNSTypeName(question->qtype), question->QuestionContext);
// If the original question timed out, its QuestionContext would already be set to NULL and that's what we copied above.
// Hence, we need to set it explicitly here.
if (err) LogMsg("%3d: ERROR: queryrecord_result_callback %##s %s mDNS_StartQuery: %d", req->sd, question->qname.c, DNSTypeName(question->qtype), (int)err);
// If we got a positive response to local SOA, then try the .local question as unicast
// Fall through and get the next search domain. The question is pointing at .local
// and we don't want to try that. Try the next search domain. Don't try with local
// search domains for the unicast question anymore.
//
// Note: we started the question above which will be stopped immediately (never sent on the wire)
// before we pick the next search domain below. RetryQuestionWithSearchDomains assumes that the
// question has already started.
}
if (q && AddRecord && AddRecord != QC_dnssec && (question->InterfaceID == mDNSInterface_Unicast) && !answer->rdlength)
{
// If we get a negative response to the unicast query that we sent above, retry after appending search domains
// Note: We could have appended search domains below (where do it for regular unicast questions) instead of doing it here.
// As we ignore negative unicast answers below, we would never reach the code where the search domains are appended.
// To keep things simple, we handle unicast ".local" separately here.
LogInfo("queryrecord_result_callback: Retrying .local question %##s (%s) as unicast after appending search domains", question->qname.c, DNSTypeName(question->qtype));
return;
if (question->AppendSearchDomains && !question->AppendLocalSearchDomains && IsLocalDomain(&question->qname))
{
// If "local" is the last search domain, we need to stop the question so that we don't send the "local"
// question on the wire as we got a negative response for the local SOA. But, we can't stop the question
// yet as we may have to timeout the question (done by the "core") for which we need to leave the question
// in the list. We leave it disabled so that it does not hit the wire.
LogInfo("queryrecord_result_callback: Disabling .local question %##s (%s)", question->qname.c, DNSTypeName(question->qtype));
question->ThisQInterval = 0;
}
}
// If we are here it means that either "question" is not "q2" OR we got a positive response for "q2" OR we have no more search
// domains to append for "q2". In all cases, fall through and deliver the response
}
#endif // APPLE_OSX_mDNSResponder
// If a query is being suppressed for some reason, we don't have to do any other
// processing.
//
// Note: We don't check for "SuppressQuery" and instead use QC_suppressed because
// the "core" needs to temporarily turn off SuppressQuery to answer this query.
if (AddRecord == QC_suppressed)
{
LogInfo("queryrecord_result_callback: Suppressed question %##s (%s)", question->qname.c, DNSTypeName(question->qtype));
return;
}
{
// If this question needs to be timed out and we have reached the stop time, mark
// the error as timeout. It is possible that we might get a negative response from an
// external DNS server at the same time when this question reaches its stop time. We
// can't tell the difference as there is no indication in the callback. This should
// be okay as we will be timing out this query anyway.
mDNS_Lock(m);
if (question->TimeoutQuestion)
{
{
LogInfo("queryrecord_result_callback:Question %##s (%s) timing out, InterfaceID %p", question->qname.c, DNSTypeName(question->qtype), question->InterfaceID);
}
}
mDNS_Unlock(m);
// When we're doing parallel unicast and multicast queries for dot-local names (for supporting Microsoft
// Active Directory sites) we need to ignore negative unicast answers. Otherwise we'll generate negative
// answers for just about every single multicast name we ever look up, since the Microsoft Active Directory
// server is going to assert that pretty much every single multicast name doesn't exist.
//
// If we are timing out this query, we need to deliver the negative answer to the application
if (error != kDNSServiceErr_Timeout)
{
{
// Sanity check: "q" will be set only if "question" is the .local unicast query.
if (!q)
{
LogMsg("queryrecord_result_callback: ERROR!! answering multicast question %s with unicast cache record",
RRDisplayString(m, answer));
return;
}
if (!ShouldDeliverNegativeResponse(m, question))
{
return;
}
#endif // APPLE_OSX_mDNSResponder
LogInfo("queryrecord_result_callback:Question %##s (%s) answering local with negative unicast response", question->qname.c,
}
}
}
// If we get a negative answer, try appending search domains. Don't append search domains
// - if we are timing out this question
// - if the negative response was received as a result of a multicast query
// - if this is an additional query (q2), we already appended search domains above (indicated by "!q" below)
// - if this response is forced e.g., dnssec validation result
if (error != kDNSServiceErr_Timeout)
{
{
// If the original question did not end in .local, we did not send an SOA query
// to figure out whether we should send an additional unicast query or not. If we just
// appended .local, we need to see if we need to send an additional query. This should
// normally happen just once because after we append .local, we ignore all negative
// responses for .local above.
LogInfo("queryrecord_result_callback: Retrying question %##s (%s) after appending search domains", question->qname.c, DNSTypeName(question->qtype));
{
// Note: We need to call SendAdditionalQuery every time after appending a search domain as .local could
// be anywhere in the search domain list.
if (err) LogMsg("queryrecord_result_callback: Sending .local SOA query failed, after appending domains");
#endif // APPLE_OSX_mDNSResponder
return;
}
}
}
}
{
LogOperation("%3d: DNSServiceQueryRecord(%##s, %s) STOP PID[%d](%s)",
request->sd, request->u.queryrecord.q.qname.c, DNSTypeName(request->u.queryrecord.q.qtype), request->process_id, request->pid_name);
{
}
else
{
LogInfo("queryrecord_termination_callback: question %##s (%s) already stopped, InterfaceID %p", question->qname.c, DNSTypeName(question->qtype), question->InterfaceID);
}
{
}
if (callExternalHelpers(request->u.queryrecord.q.InterfaceID, &request->u.queryrecord.q.qname, request->flags))
{
LogInfo("queryrecord_termination_callback: calling external_stop_browsing_for_service()");
external_stop_browsing_for_service(request->u.queryrecord.q.InterfaceID, &request->u.queryrecord.q.qname, request->u.queryrecord.q.qtype, request->flags);
}
{
{
}
else
{
LogInfo("queryrecord_termination_callback: q2 %##s (%s) already stopped, InterfaceID %p", question->qname.c, DNSTypeName(question->qtype), question->InterfaceID);
}
{
LogInfo("queryrecord_termination_callback: freeing q2 qnameOrig %##s", request->u.queryrecord.q2->qnameOrig->c);
}
}
{
{
// If we are receiving poisitive answers, provide the hint to the
// upper layer.
}
}
#endif // APPLE_OSX_mDNSResponder
}
{
int i;
// The policy is either based on pid or UUID. Pass a zero pid
// to the "core" if the UUID is valid. If we always pass the pid,
// then the "core" needs to determine whether the uuid is valid
// by examining all the 16 bytes at the time of the policy
// check and also when setting the delegate socket option. Also, it
// requires that we zero out the uuid wherever the question is
// initialized to make sure that it is not interpreted as valid.
// To prevent these intrusive changes, just pass a zero pid to indicate
// that pid is not valid when uuid is valid. In future if we need the
// pid in the question, we will reevaluate this strategy.
{
for (i = 0; i < UUID_SIZE; i++)
{
}
q->pid = 0;
}
else
{
}
//debugf("SetQuestionPolicy: q->euid[%d] q->pid[%d] uuid is valid : %s", q->euid, q->pid, req->validUUID ? "true" : "false");
}
{
mDNSInterfaceID InterfaceID = mDNSPlatformInterfaceIDfromInterfaceIndex(&mDNSStorage, interfaceIndex);
// The request is scoped to a specific interface index, but the
// interface is not currently in our list.
if (interfaceIndex && !InterfaceID)
{
// If it's one of the specially defined inteface index values, just return an error.
// Also, caller should return an error immediately if lo0 (index 1) is not configured
// into the current active interfaces. See background in Radar 21967160.
{
return(mStatus_BadParamErr);
}
// Otherwise, use the specified interface index value and the request will
// be applied to that interface when it comes up.
}
{ LogMsg("%3d: DNSServiceQueryRecord(unreadable parameters)", request->sd); return(mStatus_BadParamErr); }
q->InterfaceID = InterfaceID;
#if 0
#endif
q->ExpectUnique = mDNSfalse;
q->WakeOnResolve = 0;
if ((flags & kDNSServiceFlagsValidate) != 0)
else if ((flags & kDNSServiceFlagsValidateOptional) != 0)
q->ValidatingResponse = 0;
q->ProxyQuestion = 0;
q->QuestionContext = request;
q->SearchListIndex = 0;
q->DNSSECAuthInfo = mDNSNULL;
q->DAIFreeCallback = mDNSNULL;
//Turn off dnssec validation for local domains and Question Types: RRSIG/ANY(ANY Type is not supported yet)
if ((IsLocalDomain(&q->qname)) || (q->qtype == kDNSServiceType_RRSIG) || (q->qtype == kDNSServiceType_ANY))
q->ValidationRequired = 0;
// Don't append search domains for fully qualified domain names including queries
// such as e.g., "abc." that has only one label. We convert all names to FQDNs as internally
// we only deal with FQDNs. Hence, we cannot look at qname to figure out whether we should
// append search domains or not. So, we record that information in AppendSearchDomains.
//
// We append search domains only for queries that are a single label. If overriden using command line
// argument "AlwaysAppendSearchDomains", then we do it for any query which is not fully qualified.
// For DNSSEC questions, append search domains only if kDNSServiceFlagsValidateOptional is set.
if ((!(q->ValidationRequired == DNSSEC_VALIDATION_SECURE)) && (!(q->ValidationRequired == DNSSEC_VALIDATION_INSECURE))
{
q->AppendSearchDomains = 1;
q->AppendLocalSearchDomains = 1;
}
else
{
q->AppendSearchDomains = 0;
q->AppendLocalSearchDomains = 0;
}
// search domains before trying them on the wire as a single label query. RetryWithSearchDomains
// the cache
SetQuestionPolicy(q, request);
LogOperation("%3d: DNSServiceQueryRecord(%X, %d, %##s, %s) START PID[%d](%s)",
request->sd, flags, interfaceIndex, q->qname.c, DNSTypeName(q->qtype), request->process_id, request->pid_name);
if (err)
{
LogMsg("%3d: ERROR: DNSServiceQueryRecord %##s %s mDNS_StartQuery: %d", request->sd, q->qname.c, DNSTypeName(q->qtype), (int)err);
}
else
{
{
LogInfo("handle_queryrecord_request: calling external_start_browsing_for_service()");
}
}
#endif // APPLE_OSX_mDNSResponder
return(err);
}
// ***************************************************************************
#pragma mark -
#pragma mark - DNSServiceEnumerateDomains
#endif
{
char *data;
len = sizeof(DNSServiceFlags);
len += sizeof(DNSServiceErrorType);
return reply;
}
{
// Stop the domain enumeration queries to discover the WAB Browse/Registration domains
{
LogInfo("%3d: DNSServiceEnumeration Cancel WAB Registration PID[%d](%s)", request->sd, request->process_id, request->pid_name);
}
else
{
LogInfo("%3d: DNSServiceEnumeration Cancel WAB Browse PID[%d](%s)", request->sd, request->process_id, request->pid_name);
}
}
{
(void)m; // Unused
#if 0
if (!AuthorizedDomain(request, &answer->rdata->u.name, request->u.enumeration.flags ? AutoRegistrationDomains : AutoBrowseDomains)) return;
#endif
// For the default browse and registration answers, we only give an "ADD" event
if (AddRecord)
{
}
// Note that we do NOT propagate specific interface indexes to the client - for example, a domain we learn from
// a machine's system preferences may be discovered on the LocalOnly interface, but should be browsed on the
// network, so we just pass kDNSServiceInterfaceIndexAny
reply = format_enumeration_reply(request, domain, flags, kDNSServiceInterfaceIndexAny, kDNSServiceErr_NoError);
LogOperation("%3d: DNSServiceEnumerateDomains(%#2s) RESULT %s: %s", request->sd, question->qname.c, AddRecord ? "Add" : "Rmv", domain);
}
{
mDNS_DomainType t_default = reg ? mDNS_DomainTypeRegistrationDefault : mDNS_DomainTypeBrowseDefault;
mDNSInterfaceID InterfaceID = mDNSPlatformInterfaceIDfromInterfaceIndex(&mDNSStorage, interfaceIndex);
{ LogMsg("%3d: DNSServiceEnumerateDomains(unreadable parameters)", request->sd); return(mStatus_BadParamErr); }
// mark which kind of enumeration we're doing so that we know what domain enumeration queries to stop
// enumeration requires multiple questions, so we must link all the context pointers so that
// necessary context can be reached from the callbacks
// if the caller hasn't specified an explicit interface, we use local-only to get the system-wide list.
// make the calls
(flags & kDNSServiceFlagsRegistrationDomains) ? "kDNSServiceFlagsRegistrationDomains" : "<<Unknown>>");
err = mDNS_GetDomains(&mDNSStorage, &request->u.enumeration.q_all, t_all, NULL, InterfaceID, enum_result_callback, request);
if (!err)
{
err = mDNS_GetDomains(&mDNSStorage, &request->u.enumeration.q_default, t_default, NULL, InterfaceID, enum_result_callback, request);
}
if (!err)
{
// Start the domain enumeration queries to discover the WAB Browse/Registration domains
if (reg)
{
LogInfo("%3d: DNSServiceEnumerateDomains Start WAB Registration PID[%d](%s)", request->sd, request->process_id, request->pid_name);
}
else
{
LogInfo("%3d: DNSServiceEnumerateDomains Start WAB Browse PID[%d](%s)", request->sd, request->process_id, request->pid_name);
}
}
return(err);
}
// ***************************************************************************
#pragma mark -
#endif
{
if (rr)
{
(status == mStatus_NoError) ?
"%3d: DNSServiceReconfirmRecord(%s) interface %d initiated" :
"%3d: DNSServiceReconfirmRecord(%s) interface %d failed: %d",
mDNSPlatformInterfaceIndexfromInterfaceID(&mDNSStorage, rr->resrec.InterfaceID, mDNSfalse), status);
}
return(status);
}
{
// extract the data from the message
{
return(mStatus_BadParamErr);
}
{
return(mStatus_BadParamErr);
}
{
return(mStatus_BadParamErr);
}
LogOperation("%3d: PeerConnectionRelease(%X %##s) START PID[%d](%s)",
return(err);
}
#else // APPLE_OSX_mDNSResponder
{
(void) request;
return mStatus_UnsupportedErr;
}
#endif // APPLE_OSX_mDNSResponder
{
(void)flags; // Unused
{ LogMsg("%3d: DNSServiceSetDefaultDomainForUser(unreadable parameters)", request->sd); return(mStatus_BadParamErr); }
return(mStatus_NoError);
}
typedef packedstruct
{
{
{
{
return;
}
}
// If we didn't recogize the requested property name, return BadParamErr
}
#ifdef APPLE_OSX_mDNSResponder
// The caller can specify either the pid or the uuid. If the pid is not specified,
// update the effective uuid. Don't overwrite the pid which is used for debugging
// purposes and initialized when the socket is opened.
{
len = 0;
#ifdef LOCAL_PEEREPID
if (pid)
{
return;
// to extract the process name from the pid value
if (proc_pidinfo(request->process_id, PROC_PIDT_SHORTBSDINFO, 1, &proc, PROC_PIDT_SHORTBSDINFO_SIZE) == 0)
return;
debugf("handle_connection_delegate_request: process id %d, name %s", request->process_id, request->pid_name);
}
#endif
#ifdef LOCAL_PEEREUUID
if (!pid)
{
return;
}
#endif
}
#else
{
(void) request;
}
#endif
typedef packedstruct
{
} PIDInfo;
{
const DNSQuestion *q = NULL;
{
q = &req->u.queryrecord.q;
if (q && q->LocalSocket != NULL)
{
{
LogMsg("DNSServiceGetPID: srcport %d, pid %d [%s] question %##s", htons(srcport), pid, req->pid_name, q->qname.c);
break;
}
}
}
// If we cannot find in the client requests, look to see if this was
// started by mDNSResponder.
if (pid == -1)
{
{
if (q && q->LocalSocket != NULL)
{
{
#endif // APPLE_OSX_mDNSResponder
LogMsg("DNSServiceGetPID: srcport %d, pid %d [%s], question %##s", htons(srcport), pid, "_mDNSResponder", q->qname.c);
break;
}
}
}
}
}
// ***************************************************************************
#pragma mark -
#pragma mark - DNSServiceNATPortMappingCreate
#endif
#define DNSServiceProtocol(X) ((X) == NATOp_AddrRequest ? 0 : (X) == NATOp_MapUDP ? kDNSServiceProtocol_UDP : kDNSServiceProtocol_TCP)
{
mDNSVal16(request->u.pm.NATinfo.IntPort), mDNSVal16(request->u.pm.ReqExt), request->u.pm.NATinfo.NATLease,
}
// Called via function pointer when we get a NAT Traversal (address request or port mapping) response
{
int replyLen;
char *data;
if (!request) { LogMsg("port_mapping_create_request_callback called with unknown request_state object"); return; }
// calculate reply data length
replyLen = sizeof(DNSServiceFlags);
replyLen += sizeof(DNSServiceErrorType);
rep->rhdr->ifi = dnssd_htonl(mDNSPlatformInterfaceIndexfromInterfaceID(m, n->InterfaceID, mDNSfalse));
LogOperation("%3d: DNSServiceNATPortMappingCreate(%X, %u, %u, %d) RESULT %.4a:%u TTL %u", request->sd,
mDNSVal16(request->u.pm.NATinfo.IntPort), mDNSVal16(request->u.pm.ReqExt), request->u.pm.NATinfo.NATLease,
&request->u.pm.NATinfo.ExternalAddress, mDNSVal16(request->u.pm.NATinfo.ExternalPort), request->u.pm.NATinfo.Lifetime);
}
{
mDNSInterfaceID InterfaceID = mDNSPlatformInterfaceIDfromInterfaceIndex(&mDNSStorage, interfaceIndex);
(void)flags; // Unused
else
{
}
{ LogMsg("%3d: DNSServiceNATPortMappingCreate(unreadable parameters)", request->sd); return(mStatus_BadParamErr); }
if (protocol == 0) // If protocol == 0 (i.e. just request public address) then IntPort, ExtPort, ttl must be zero too
{
if (!mDNSIPPortIsZero(request->u.pm.NATinfo.IntPort) || !mDNSIPPortIsZero(request->u.pm.ReqExt) || ttl) return(mStatus_BadParamErr);
}
else
{
}
request->u.pm.NATinfo.Protocol = !protocol ? NATOp_AddrRequest : (protocol == kDNSServiceProtocol_UDP) ? NATOp_MapUDP : NATOp_MapTCP;
// u.pm.NATinfo.IntPort = already set above
protocol, mDNSVal16(request->u.pm.NATinfo.IntPort), mDNSVal16(request->u.pm.ReqExt), request->u.pm.NATinfo.NATLease,
return(err);
}
// ***************************************************************************
#pragma mark -
#pragma mark - DNSServiceGetAddrInfo
#endif
{
LogOperation("%3d: DNSServiceGetAddrInfo(%##s) STOP PID[%d](%s)", request->sd, request->u.addrinfo.q4.qname.c,
{
}
{
}
{
{
}
{
LogInfo("addrinfo_termination_callback: freeing q42 qnameOrig %##s", request->u.addrinfo.q42->qnameOrig->c);
}
}
{
}
{
}
{
{
}
{
LogInfo("addrinfo_termination_callback: freeing q62 qnameOrig %##s", request->u.addrinfo.q62->qnameOrig->c);
}
}
{
{
// If we are not delivering answers, we may be timing out prematurely.
// Note down the current state so that we know to retry when we see a
// valid response again.
{
}
// If we have a v4 answer and if we timed out prematurely before, provide
// a trigger to the upper layer so that it can retry questions if needed.
}
{
{
}
}
}
#endif // APPLE_OSX_mDNSResponder
}
{
domainname d;
{
// NOTE: kDNSServiceFlagsServiceIndex flag can only be set for DNSServiceGetAddrInfo()
LogInfo("DNSServiceGetAddrInfo: kDNSServiceFlagsServiceIndex is SET by the client");
// if kDNSServiceFlagsServiceIndex is SET,
// interpret the interfaceID as the serviceId and set the interfaceID to 0.
interfaceIndex = 0;
}
// The request is scoped to a specific interface index, but the
// interface is not currently in our list.
if (interfaceIndex && !InterfaceID)
{
// If it's one of the specially defined inteface index values, just return an error.
{
return(mStatus_BadParamErr);
}
// Otherwise, use the specified interface index value and the registration will
// be applied to that interface when it comes up.
}
if (request->u.addrinfo.protocol > (kDNSServiceProtocol_IPv4|kDNSServiceProtocol_IPv6)) return(mStatus_BadParamErr);
if (!request->msgptr) { LogMsg("%3d: DNSServiceGetAddrInfo(unreadable parameters)", request->sd); return(mStatus_BadParamErr); }
if (!MakeDomainNameFromDNSNameString(&d, hostname))
{ LogMsg("ERROR: handle_addrinfo_request: bad hostname: %s", hostname); return(mStatus_BadParamErr); }
#if 0
#endif
{
}
request->u.addrinfo.q4.InterfaceID = request->u.addrinfo.q6.InterfaceID = request->u.addrinfo.interface_id;
request->u.addrinfo.q4.LongLived = request->u.addrinfo.q6.LongLived = (flags & kDNSServiceFlagsLongLivedQuery ) != 0;
request->u.addrinfo.q4.ForceMCast = request->u.addrinfo.q6.ForceMCast = (flags & kDNSServiceFlagsForceMulticast ) != 0;
request->u.addrinfo.q4.ReturnIntermed = request->u.addrinfo.q6.ReturnIntermed = (flags & kDNSServiceFlagsReturnIntermediates) != 0;
request->u.addrinfo.q4.SuppressUnusable = request->u.addrinfo.q6.SuppressUnusable = (flags & kDNSServiceFlagsSuppressUnusable ) != 0;
request->u.addrinfo.q4.TimeoutQuestion = request->u.addrinfo.q6.TimeoutQuestion = (flags & kDNSServiceFlagsTimeout ) != 0;
request->u.addrinfo.q4.UseBackgroundTrafficClass = request->u.addrinfo.q6.UseBackgroundTrafficClass = (flags & kDNSServiceFlagsBackgroundTrafficClass) != 0;
request->u.addrinfo.q4.DenyOnCellInterface = request->u.addrinfo.q6.DenyOnCellInterface = (flags & kDNSServiceFlagsDenyCellular) != 0;
request->u.addrinfo.q4.DenyOnExpInterface = request->u.addrinfo.q6.DenyOnExpInterface = (flags & kDNSServiceFlagsDenyExpensive) != 0;
if ((flags & kDNSServiceFlagsValidate) != 0)
request->u.addrinfo.q4.ValidationRequired = request->u.addrinfo.q6.ValidationRequired = DNSSEC_VALIDATION_SECURE;
else if ((flags & kDNSServiceFlagsValidateOptional) != 0)
request->u.addrinfo.q4.ValidationRequired = request->u.addrinfo.q6.ValidationRequired = DNSSEC_VALIDATION_SECURE_OPTIONAL;
//Turn off dnssec validation for local domains
if (IsLocalDomain(&d))
{
// For DNSSEC questions, append search domains only if kDNSServiceFlagsValidateOptional is set
if ((!(request->u.addrinfo.q6.ValidationRequired == DNSSEC_VALIDATION_SECURE)) && (!(request->u.addrinfo.q6.ValidationRequired == DNSSEC_VALIDATION_INSECURE))
{
}
else
{
}
request->u.addrinfo.q6.RetryWithSearchDomains = (ApplySearchDomainsFirst(&request->u.addrinfo.q6) ? 1 : 0);
if (err != mStatus_NoError)
{
}
#endif // APPLE_OSX_mDNSResponder
if (!err)
{
}
}
{
// We append search domains only for queries that are a single label. If overriden using cmd line arg
// "AlwaysAppendSearchDomains", then we do it for any query which is not fully qualified.
// For DNSSEC questions, append search domains only if kDNSServiceFlagsValidateOptional is set.
if ((!(request->u.addrinfo.q4.ValidationRequired == DNSSEC_VALIDATION_SECURE)) && (!(request->u.addrinfo.q4.ValidationRequired == DNSSEC_VALIDATION_INSECURE))
{
}
else
{
}
request->u.addrinfo.q4.RetryWithSearchDomains = (ApplySearchDomainsFirst(&request->u.addrinfo.q4) ? 1 : 0);
if (err != mStatus_NoError)
{
{
// If we started a query for IPv6, we need to cancel it
}
}
#endif // APPLE_OSX_mDNSResponder
if (!err)
{
}
}
LogOperation("%3d: DNSServiceGetAddrInfo(%X, %d, %d, %##s) START PID[%d](%s)", request->sd, flags, interfaceIndex, request->u.addrinfo.protocol, d.c, request->process_id, request->pid_name);
return(err);
}
// ***************************************************************************
#pragma mark -
#endif
{
request_state **p = &all_requests;
while (*p)
p=&(*p)->next;
if (!*p)
FatalError("ERROR: malloc");
mDNSPlatformMemZero(*p, sizeof(request_state));
return(*p);
}
// read_msg may be called any time when the transfer state (req->ts) is t_morecoming.
// if there is no data on the socket, the socket will be closed and t_terminated will be returned
{
{ LogMsg("%3d: ERROR: read_msg called with transfer state terminated or error", req->sd); req->ts = t_error; return; }
{
return;
}
{ LogMsg("%3d: ERROR: read_msg called with invalid transfer state (%d)", req->sd, req->ts); req->ts = t_error; return; }
{
int nread = udsSupportReadFD(req->sd, (char *)&req->hdr + req->hdr_bytes, nleft, 0, req->platform_data);
{ LogMsg("%3d: ERROR: read_msg - read too many header bytes", req->sd); req->ts = t_error; return; }
// only read data if header is complete
{
{ LogMsg("%3d: ERROR: client version 0x%08X daemon version 0x%08X", req->sd, req->hdr.version, VERSION); req->ts = t_error; return; }
// Largest conceivable single request is a DNSServiceRegisterRecord() or DNSServiceAddRecord()
// with 64kB of rdata. Adding 1009 byte for a maximal domain name, plus a safety margin
// for other overhead, this means any message above 70kB is definitely bogus.
{ LogMsg("%3d: ERROR: read_msg: hdr.datalen %u (0x%X) > 70000", req->sd, req->hdr.datalen, req->hdr.datalen); req->ts = t_error; return; }
}
}
// If our header is complete, but we're still needing more body data, then try to read it now
// Note: For cancel_request req->hdr.datalen == 0, but there's no error return socket for cancel_request
// Any time we need to get the error return socket we know we'll have at least one data byte
// (even if only the one-byte empty C string placeholder for the old ctrl_path parameter)
{
int nread;
#if !defined(_WIN32)
struct iovec vec = { req->msgbuf + req->data_bytes, nleft }; // Tell recvmsg where we want the bytes put
msg.msg_namelen = 0;
#else
nread = udsSupportReadFD(req->sd, (char *)req->msgbuf + req->data_bytes, nleft, 0, req->platform_data);
#endif
#if !defined(_WIN32)
LogMsg("%3d: Got %d %d %d %d", req->sd, msg.msg_controllen, cmsg ? cmsg->cmsg_len : -1, cmsg ? cmsg->cmsg_level : -1, cmsg ? cmsg->cmsg_type : -1);
#endif // DEBUG_64BIT_SCM_RIGHTS
{
// Strictly speaking BPF_fd belongs solely in the platform support layer, but because
// of privilege separation on Mac OS X we need to get BPF_fd from mDNSResponderHelper,
// and it's convenient to repurpose the existing fd-passing code here for that task
{
}
else
#endif // APPLE_OSX_mDNSResponder
#endif // DEBUG_64BIT_SCM_RIGHTS
{
LogMsg("%3d: Client(PID [%d](%s)) sent result code socket %d via SCM_RIGHTS with req->data_bytes %d < req->hdr.datalen %d",
return;
}
}
#endif
}
// If our header and data are both complete, see if we need to make our separate error return socket
{
{
#if defined(USE_TCP_LOOPBACK)
#else
get_string(&req->msgptr, req->msgend, ctrl_path, MAX_CTLPATH); // path is first element in message buffer
// If the error return path UDS name is empty string, that tells us
// that this is a new version of the library that's going to pass us
if (ctrl_path[0] == 0)
{
{ LogMsg("%3d: read_msg: ERROR failed to get errsd via SCM_RIGHTS", req->sd); req->ts = t_error; return; }
goto got_errfd;
}
#endif
{
my_throttled_perror("ERROR: socket");
return;
}
{
#if !defined(USE_TCP_LOOPBACK)
LogMsg("%3d: read_msg: Couldn't connect to error return path socket “%s” errno %d (%s)",
LogMsg("%3d: read_msg: stat failed “%s” errno %d (%s)", req->sd, cliaddr.sun_path, dnssd_errno, dnssd_strerror(dnssd_errno));
else
LogMsg("%3d: read_msg: file “%s” mode %o (octal) uid %d gid %d", req->sd, cliaddr.sun_path, sb.st_mode, sb.st_uid, sb.st_gid);
#endif
return;
}
#if !defined(USE_TCP_LOOPBACK)
#endif
LogOperation("%3d: Result code socket %d created %08X %08X", req->sd, req->errsd, req->hdr.client_context.u32[1], req->hdr.client_context.u32[0]);
#if defined(_WIN32)
#else
#endif
{
LogMsg("%3d: ERROR: could not set control socket to non-blocking mode errno %d (%s)",
return;
}
}
}
return;
}
#define RecordOrientedOp(X) \
((X) == reg_record_request || (X) == add_record_request || (X) == update_record_request || (X) == remove_record_request)
// The lightweight operations are the ones that don't need a dedicated request_state structure allocated for them
{
(void)fd; // Unused
(void)filter; // Unused
for (;;)
{
return;
{
return;
}
{
LogMsg("request_callback: req->ts %d != t_complete PID[%d][%s]", req->ts, req->process_id, req->pid_name);
return;
}
{
LogMsg("request_callback: ERROR: client IPC version %d incompatible with daemon IPC version %d PID[%d][%s]",
return;
}
{
case connection_request: min_size = 0; break;
case reg_service_request: min_size += sizeof(mDNSu32) + 4 /* name, type, domain, host */ + 4 /* port, textlen */; break;
case remove_record_request: break;
case reg_record_request: min_size += sizeof(mDNSu32) + 1 /* name */ + 6 /* type, class, rdlen */ + 4 /* ttl */; break;
case reconfirm_record_request: min_size += sizeof(mDNSu32) + 1 /* name */ + 6 /* type, class, rdlen */; break;
case port_mapping_request: min_size += sizeof(mDNSu32) + 4 /* udp/tcp */ + 4 /* int/ext port */ + 4 /* ttl */; break;
case send_bpf: // Same as cancel_request below
case cancel_request: min_size = 0; break;
default: LogMsg("request_callback: ERROR: validate_message - unsupported req type: %d PID[%d][%s]",
min_size = -1; break;
}
{
LogMsg("request_callback: Invalid message %d bytes; min for %d is %d PID[%d][%s]",
return;
}
{
return;
}
// If req->terminate is already set, this means this operation is sharing an existing connection
{
// if the parent request is a delegate connection, copy the
// relevant bits
{
int i;
for (i = 0; i < UUID_SIZE; i++)
{
}
}
else
{
if (req->process_id)
{
}
else
{
}
}
}
// Check if the request wants no asynchronous replies.
// If we're shutting down, don't allow new client requests
// We do allow "cancel" and "getproperty" during shutdown
if (mDNSStorage.ShutdownTime && req->hdr.op != cancel_request && req->hdr.op != getproperty_request)
{
}
else
{
{
// These are all operations that have their own first-class request_state object
case connection_request:
LogOperation("%3d: DNSServiceCreateConnection START PID[%d](%s)",
break;
LogOperation("%3d: DNSServiceCreateDelegateConnection START PID[%d](%s)",
break;
case send_bpf: /* Do nothing for send_bpf */ break;
// These are all operations that work with an existing request_state object
default: LogMsg("request_callback: %3d:ERROR: Unsupported UDS req:%d PID[%d][%s]",
}
}
// req->msgbuf may be NULL, e.g. for connection_request or remove_record_request
// There's no return data for a cancel request (DNSServiceRefDeallocate returns no result)
// For a DNSServiceGetProperty call, the handler already generated the response, so no need to do it again here
if (req->hdr.op != cancel_request && req->hdr.op != getproperty_request && req->hdr.op != send_bpf && req->hdr.op != getpid_request)
{
{
LogOperation("%3d: Result code socket %d closed %08X %08X (%d)",
// Also need to reset the parent's errsd, if this is a subordinate operation
}
}
// Reset ready to accept the next req on this pipe
req->data_bytes = 0;
}
}
{
#if defined(SO_NOSIGPIPE) || defined(_WIN32)
#endif
(void)filter; // Unused
(void)info; // Unused
if (!dnssd_SocketValid(sd))
{
if (dnssd_errno != dnssd_EWOULDBLOCK)
my_throttled_perror("ERROR: accept");
return;
}
#ifdef SO_NOSIGPIPE
// Some environments (e.g. OS X) support turning off SIGPIPE for a socket
LogMsg("%3d: WARNING: setsockopt - SO_NOSIGPIPE %d (%s)", sd, dnssd_errno, dnssd_strerror(dnssd_errno));
#endif
#if defined(_WIN32)
#else
#endif
{
my_perror("ERROR: fcntl(sd, F_SETFL, O_NONBLOCK) - aborting client");
return;
}
else
{
struct xucred x;
else
my_perror("ERROR: getsockopt, LOCAL_PEERCRED");
#endif // APPLE_OSX_mDNSResponder
}
}
{
#if defined(SO_NP_EXTENSIONS)
my_perror("WARNING: could not set sockopt - SO_NP_EXTENSIONS");
#endif
#if defined(_WIN32)
// SEH: do we even need to do this on windows?
// This socket will be given to WSAEventSelect which will automatically set it to non-blocking
#else
#endif
{
my_perror("ERROR: could not set listen socket to non-blocking mode");
return mDNSfalse;
}
{
my_perror("ERROR: could not listen on listen socket");
return mDNSfalse;
}
if (mStatus_NoError != udsSupportAddFDToEventLoop(skt, connect_callback, (void *) NULL, (void **) NULL))
{
my_perror("ERROR: could not add listen socket to event loop");
return mDNSfalse;
}
else
{
}
return mDNStrue;
}
{
int ret;
mDNSu32 i = 0;
// If a particular platform wants to opt out of having a PID file, define PID_FILE to be ""
if (PID_FILE[0])
{
{
}
}
if (skts)
{
for (i = 0; i < count; i++)
goto error;
}
else
{
if (!dnssd_SocketValid(listenfd))
{
my_perror("ERROR: socket(AF_DNSSD, SOCK_STREAM, 0); failed");
goto error;
}
#if defined(USE_TCP_LOOPBACK)
{
if (ret < 0)
{
my_perror("ERROR: bind(listenfd, (struct sockaddr *) &laddr, sizeof(laddr)); failed");
goto error;
}
}
#else
{
#ifndef NOT_HAVE_SA_LEN
// According to Stevens (section 3.2), there is no portable way to
// determine whether sa_len is defined on a particular platform.
#endif
{
goto error;
}
if (ret < 0)
{
my_perror("ERROR: bind(listenfd, (struct sockaddr *) &laddr, sizeof(laddr)); failed");
goto error;
}
}
#endif
}
#if !defined(PLATFORM_NO_RLIMIT)
{
// Set maximum number of open file descriptors
// Due to bugs in OS X (<rdar://problem/2941095>, <rdar://problem/3342704>, <rdar://problem/3839173>)
// you have to get and set rlimits once before getrlimit will return sensible values
if (getrlimit(RLIMIT_NOFILE, &maxfds) < 0) { my_perror("ERROR: Unable to get file descriptor limit"); return 0; }
if (setrlimit(RLIMIT_NOFILE, &maxfds) < 0) my_perror("ERROR: Unable to set maximum file descriptor limit");
if (getrlimit(RLIMIT_NOFILE, &maxfds) < 0) { my_perror("ERROR: Unable to get file descriptor limit"); return 0; }
if (setrlimit(RLIMIT_NOFILE, &newfds) < 0) my_perror("ERROR: Unable to set maximum file descriptor limit");
if (getrlimit(RLIMIT_NOFILE, &maxfds) < 0) { my_perror("ERROR: Unable to get file descriptor limit"); return 0; }
}
#endif
// We start a "LocalOnly" query looking for Automatic Browse Domain records.
// When Domain Enumeration in uDNS.c finds an "lb" record from the network, its "FoundDomain" routine
// creates a "LocalOnly" record, which results in our AutomaticBrowseDomainChange callback being invoked
// Add "local" as recommended registration domain ("dns-sd -E"), recommended browsing domain ("dns-sd -F"), and automatic browsing domain
return 0;
my_perror("ERROR: udsserver_init");
return -1;
}
{
// Cancel all outstanding client requests
// Clean up any special mDNSInterface_LocalOnly records we created, both the entries for "local" we
// created in udsserver_init, and others we created as a result of reading local configuration data
while (LocalDomainEnumRecords)
{
}
// If the launching environment created no listening socket,
// that means we created it ourselves, so we should clean it up on exit
if (dnssd_SocketValid(listenfd))
{
#if !defined(USE_TCP_LOOPBACK)
// to give up unnecessary privilege, but we need to be root to remove this Unix Domain Socket.
// It would be nice if we could find a solution to this problem
if (unlink(MDNS_UDS_SERVERPATH))
#endif
}
return 0;
}
{
else
{
const registered_record_entry *p;
request_state *r;
LogMsgNoIdent("%s DNSServiceCreateConnection: %d registered record%s, %d kDNSServiceFlagsShareConnection operation%s PID[%d](%s)",
LogMsgNoIdent(" -> DNSServiceRegisterRecord 0x%08X %2d %3d %s PID[%d](%s)",
req->flags, req->interfaceIndex, p->key, ARDisplayString(m, p->rr), req->process_id, req->pid_name);
}
{
LogMsgNoIdent("%s DNSServiceRegister 0x%08X %2d %##s%s %u/%u PID[%d](%s)",
(ptr == req->u.servicereg.instances) ? prefix : " ", req->flags, req->interfaceIndex, ptr->srs.RR_SRV.resrec.name->c,
AnonDataToString(ptr->srs.AnonData, 0, anonstr, sizeof(anonstr)), mDNSVal16(req->u.servicereg.port),
}
{
LogMsgNoIdent("%s DNSServiceBrowse 0x%08X %2d %##s%s PID[%d](%s)",
(blist == req->u.browser.browsers) ? prefix : " ", req->flags, req->interfaceIndex, blist->q.qname.c,
AnonDataToString(req->u.browser.AnonData, 0, anonstr, sizeof(anonstr)), req->process_id, req->pid_name);
}
LogMsgNoIdent("%s DNSServiceResolve 0x%08X %2d %##s PID[%d](%s)",
prefix, req->flags, req->interfaceIndex, req->u.resolve.qsrv.qname.c, req->process_id, req->pid_name);
LogMsgNoIdent("%s DNSServiceQueryRecord 0x%08X %2d %##s (%s) PID[%d](%s)",
prefix, req->flags, req->interfaceIndex, req->u.queryrecord.q.qname.c, DNSTypeName(req->u.queryrecord.q.qtype), req->process_id, req->pid_name);
LogMsgNoIdent("%s DNSServiceEnumerateDomains 0x%08X %2d %##s PID[%d](%s)",
prefix, req->flags, req->interfaceIndex, req->u.enumeration.q_all.qname.c, req->process_id, req->pid_name);
LogMsgNoIdent("%s DNSServiceNATPortMapping 0x%08X %2d %s%s Int %5d Req %5d Ext %.4a:%5d Req TTL %5d Granted TTL %5d PID[%d](%s)",
LogMsgNoIdent("%s DNSServiceGetAddrInfo 0x%08X %2d %s%s %##s PID[%d](%s)",
else
}
{
{
const registered_record_entry *p;
request_state *r;
num_records++;
num_ops++;
{
if (!AuthRecord_uDNS(p->rr))
n_mrecords++;
}
GetMcastClients(r);
}
{
{
n_mrecords++;
}
}
{
{
n_mquests++;
}
}
{
n_mquests++;
}
{
if ((mDNSOpaque16IsZero(req->u.queryrecord.q.TargetQID)) && (req->u.queryrecord.q.ThisQInterval > 0))
n_mquests++;
}
{
n_mquests++;
}
else
{
return;
}
}
{
LogMcastNoIdent("No operation yet on this socket");
{
const registered_record_entry *p;
request_state *r;
num_records++;
num_ops++;
{
if (!AuthRecord_uDNS(p->rr))
}
}
{
{
LogMcastNoIdent("R: DNSServiceRegister: %##s %u/%u PID[%d](%s)", ptr->srs.RR_SRV.resrec.name->c, mDNSVal16(req->u.servicereg.port),
}
}
{
{
LogMcastNoIdent("Q: DNSServiceBrowse %##s %s PID[%d](%s)", blist->q.qname.c, DNSTypeName(blist->q.qtype),
}
}
{
LogMcastNoIdent("Q: DNSServiceResolve %##s %s PID[%d](%s)", req->u.resolve.qsrv.qname.c, DNSTypeName(req->u.resolve.qsrv.qtype),
}
{
if ((mDNSOpaque16IsZero(req->u.queryrecord.q.TargetQID)) && (req->u.queryrecord.q.ThisQInterval > 0))
LogMcastNoIdent("Q: DNSServiceQueryRecord %##s %s PID[%d](%s)", req->u.queryrecord.q.qname.c, DNSTypeName(req->u.queryrecord.q.qtype),
}
{
LogMcastNoIdent("Q: DNSServiceGetAddrInfo %s%s %##s PID[%d](%s)",
}
else
{
return;
}
}
{
switch (rtype)
{
case kDNSRecordTypeUnregistered: return ("Unregistered ");
case kDNSRecordTypeDeregistering: return ("Deregistering");
case kDNSRecordTypeUnique: return ("Unique ");
case kDNSRecordTypeAdvisory: return ("Advisory ");
case kDNSRecordTypeShared: return ("Shared ");
case kDNSRecordTypeVerified: return ("Verified ");
case kDNSRecordTypeKnownUnique: return ("KnownUnique ");
default: return("Unknown");
}
}
{
int count = 0;
int authslot = 0;
{
{
// Print a maximum of 50 records
{
else
{
}
}
}
}
else if (truncated) LogMsgNoIdent("<Truncated: to 50 records, Total records %d, Total Auth Groups %d, Auth Slots %d>", count, m->rrauth.rrauth_totalused, authslot);
}
{
{
{
// Print a maximum of 400 records
}
}
}
{
anonstr[0] = 0;
{
}
return anonstr;
}
mDNSlocal void LogOneAuthRecord(mDNS *const m, const AuthRecord *ar, mDNSs32 now, const char *const ifname)
{
if (AuthRecord_uDNS(ar))
{
LogMsgNoIdent("%7d %7d %7d %7d %s %s",
ARDisplayString(m, ar));
}
else
{
LogMsgNoIdent("%7d %7d %7d %7s %s %s%s",
}
}
mDNSlocal void LogAuthRecords(mDNS *const m, const mDNSs32 now, AuthRecord *ResourceRecords, int *proxy)
{
{
{
{
LogMsgNoIdent("Proxying for H-MAC %.6a I-MAC %.6a Password %.6a seq %d", &owner.HMAC, &owner.IMAC, &owner.password, owner.seq);
else
}
if (AuthRecord_uDNS(ar))
{
}
{
}
{
}
else
{
{
// We just print the values from the AuthRecord to keep it nicely aligned though
// all we want here is the nsec3 information.
LogMsgNoIdent("%7d %7d %7d %7s %s",
RRDisplayString(m, nsec3));
}
}
}
}
}
mDNSlocal void PrintOneCacheRecord(mDNS *const m, const CacheRecord *cr, mDNSu32 slot, const mDNSu32 remain, const char *ifname, mDNSu32 *CacheUsed)
{
LogMsgNoIdent("%3d %s%8d %-7s%s %-6s%s",
slot,
CRDisplayString(m, cr));
(*CacheUsed)++;
}
mDNSlocal void PrintCachedRecords(mDNS *const m, const CacheRecord *cr, mDNSu32 slot, const mDNSu32 remain, const char *ifname, mDNSu32 *CacheUsed)
{
// The records that are cached under the main cache record like nsec, soa don't have
// their own lifetime. If the main cache record expires, they also expire.
while (nsec)
{
}
if (soa)
{
}
{
// Even though it is a resource record, we print the sameway
// as a cache record so that it aligns properly.
if (nsec3)
{
LogMsgNoIdent("%3d %s%8d %-7s%s %-6s%s",
slot,
" ",
RRDisplayString(m, nsec3));
}
}
}
{
adstr[0] = 0;
if (ad)
{
int len;
// If the caller is lazy to compute the length, we do it for them.
if (!adlen)
else
// Print the anondata within brackets. Hence, we need space for two
// brackets and a NULL byte.
*adstr++ = '(';
return orig;
}
return adstr;
}
{
LogMsgNoIdent("--- MDNS Statistics ---");
LogMsgNoIdent("--------------------------------");
LogMsgNoIdent("--------------------------------");
}
{
const DNSQuestion *q;
const DNameListElem *d;
const SearchListElem *s;
LogMsgNoIdent("------------ Cache -------------");
LogMsgNoIdent("Slt Q TTL if U Type rdlen");
{
{
CacheUsed++; // Count one cache entity for the CacheGroup object
{
const char *ifname;
}
}
}
if (m->rrcache_totalused != CacheUsed)
LogMsgNoIdent("Cache use mismatch: rrcache_totalused is %lu, true count %lu", m->rrcache_totalused, CacheUsed);
if (m->rrcache_active != CacheActive)
LogMsgNoIdent("Cache use mismatch: rrcache_active is %lu, true count %lu", m->rrcache_active, CacheActive);
LogMsgNoIdent("Cache currently contains %lu entities; %lu referenced by active questions", CacheUsed, CacheActive);
LogMsgNoIdent("--------- Auth Records ---------");
LogMsgNoIdent("--------- LocalOnly, P2P Auth Records ---------");
LogMsgNoIdent("--------- /etc/hosts ---------");
LogEtcHosts(m);
LogMsgNoIdent("------ Duplicate Records -------");
LogMsgNoIdent("----- Auth Records Proxied -----");
LogMsgNoIdent("-- Duplicate Records Proxied ---");
LogMsgNoIdent("---------- Questions -----------");
else
{
CacheUsed = 0;
CacheActive = 0;
LogMsgNoIdent(" Int Next if T NumAns VDNS Qptr DupOf SU SQ Type Name");
{
CacheUsed++;
if (q->ThisQInterval) CacheActive++;
LogMsgNoIdent("%6d%6d %-7s%s%s %5d 0x%x%x 0x%p 0x%p %1d %2d %-5s%##s%s%s",
i, n,
}
}
LogMsgNoIdent("----- Local-Only Questions -----");
else for (q = m->LocalOnlyQuestions; q; q=q->next)
LogMsgNoIdent(" %5d %-6s%##s%s",
LogMsgNoIdent("---- Active UDS Client Requests ----");
else
{
{
{
}
// For non-subbordinate operations, and subbordinate operations that have lost their parent, write out their info
LogClientInfo(m, req);
}
}
LogMsgNoIdent("-------- NAT Traversals --------");
LogMsgNoIdent("ExtAddress %.4a Retry %d Interval %d",
&m->ExtAddress,
if (m->NATTraversals)
{
{
LogMsgNoIdent("%p %s Int %5d %s Err %d Retry %5d Interval %5d Expire %5d Req %.4a:%d Ext %.4a:%d",
nat,
/* else */ "Unknown " ),
}
}
LogMsgNoIdent("--------- AuthInfoList ---------");
else
{
const DomainAuthInfo *a;
for (a = m->AuthInfoList; a; a = a->next)
{
LogMsgNoIdent("%##s %##s %##s %d %d %.16a%s",
}
}
LogMsgNoIdent("--------- TunnelClients --------");
else
{
const ClientTunnel *c;
for (c = m->TunnelClients; c; c = c->next)
LogMsgNoIdent("%##s local %.16a %.4a %.16a remote %.16a %.4a %5d %.16a interval %d",
c->dstname.c, &c->loc_inner, &c->loc_outer, &c->loc_outer6, &c->rmt_inner, &c->rmt_outer, mDNSVal16(c->rmt_outer_port), &c->rmt_outer6, c->q.ThisQInterval);
}
#endif // APPLE_OSX_mDNSResponder
LogMsgNoIdent("---------- Misc State ----------");
LogMsgNoIdent("m->SleepState %d (%s) seq %d",
m->SleepState,
m->SleepSeqNum);
#ifndef SPC_DISABLED
#endif
if (m->ProxyRecords == ProxyA + ProxyD) LogMsgNoIdent("ProxyRecords: %d + %d = %d", ProxyA, ProxyD, ProxyA + ProxyD);
else LogMsgNoIdent("ProxyRecords: MISMATCH %d + %d = %d ≠ %d", ProxyA, ProxyD, ProxyA + ProxyD, m->ProxyRecords);
LogMsgNoIdent("------ Auto Browse Domains -----");
LogMsgNoIdent("--- Auto Registration Domains --");
LogMsgNoIdent("--- Search Domains --");
else
{
for (s=SearchList; s; s=s->next)
{
}
}
LogInfo("--- Trust Anchors ---");
if (!m->TrustAnchors)
{
LogInfo("<None>");
}
else
{
{
}
}
LogInfo("--- DNSSEC Statistics ---");
if (m->rrcache_totalused_unicast)
LogInfo("DNSSEC usage percentage %u", ((unsigned long)(m->DNSSECStats.TotalMemUsed * 100))/m->rrcache_totalused_unicast);
LogMsgNoIdent("---- Task Scheduling Timers ----");
if (!m->NewQuestions)
LogMsgNoIdent("NewQuestion <NONE>");
else
LogMsgNoIdent("NewQuestion DelayAnswering %d %d %##s (%s)",
if (!m->NewLocalOnlyQuestions)
LogMsgNoIdent("NewLocalOnlyQuestions <NONE>");
else
LogMsgNoIdent("NewLocalOnlyQuestions %##s (%s)",
if (!m->NewLocalRecords)
LogMsgNoIdent("NewLocalRecords <NONE>");
else
LogMsgNoIdent("NewLocalRecords %02X %s", m->NewLocalRecords->resrec.RecordType, ARDisplayString(m, m->NewLocalRecords));
LogMsgNoIdent(" ABS (hex) ABS (dec) REL (hex) REL (dec)");
#ifndef UNICAST_DISABLED
#endif
}
{
{
LogMemCorruption("UDS request list: Subordinate request %p/%d/%p should not have replies (%p)",
if ((long)p & 3)
{
if (r->next == (registered_record_entry *)~0)
LogMemCorruption("UDS req->u.reg_recs: %p is garbage", r);
}
{
service_instance *s;
if (s->next == (service_instance *)~0)
LogMemCorruption("UDS req->u.servicereg.instances: %p is garbage", s);
}
{
browser_t *b;
LogMemCorruption("UDS req->u.browser.browsers: %p is garbage", b);
}
}
DNameListElem *d;
for (d = SCPrefBrowseDomains; d; d=d->next)
ARListElem *b;
for (b = LocalDomainEnumRecords; b; b=b->next)
for (d = AutoBrowseDomains; d; d=d->next)
for (d = AutoRegistrationDomains; d; d=d->next)
}
#endif // APPLE_OSX_mDNSResponder && MACOSX_MDNS_MALLOC_DEBUGGING
{
if (nwriten < 0)
{
else
{
#if !defined(PLATFORM_NO_EPIPE)
if (dnssd_errno == EPIPE)
else
#endif
{
LogMsg("send_msg ERROR: failed to write %d of %d bytes to fd %d errno %d (%s)",
return(t_error);
}
}
}
}
{
while (*req)
{
request_state *const r = *req;
if (r->terminate == resolve_termination_callback)
{
r->u.resolve.ReportTime = 0;
LogMsgNoIdent("Client application bug PID[%d](%s) : DNSServiceResolve(%##s) active for over two minutes. "
"This places considerable burden on the network.", r->process_id, r->pid_name, r->u.resolve.qsrv.qname.c);
}
// Note: Only primary req's have reply lists, not subordinate req's.
while (r->replies) // Send queued replies
{
if (result == t_complete)
{
r->time_blocked = 0; // reset failure counter after successful send
r->unresponsiveness_reports = 0;
continue;
}
{
LogMsg("%3d: Could not write data to client PID[%d](%s) because of error - aborting connection", r->sd, r->process_id, r->pid_name);
LogClientInfo(&mDNSStorage, r);
abort_request(r);
}
break;
}
if (r->replies) // If we failed to send everything, check our time_blocked timer
{
r->time_blocked = 0;
else if (!r->time_blocked)
{
int num = 0;
struct reply_state *x = r->replies;
while (x)
{
num++;
x=x->next;
}
LogMsg("%3d: Could not write data to client PID[%d](%s) after %ld seconds, %d repl%s waiting",
r->sd, r->process_id, r->pid_name, (now - r->time_blocked) / mDNSPlatformOneSecond, num, num == 1 ? "y" : "ies");
if (++r->unresponsiveness_reports >= 60)
{
LogMsg("%3d: Client PID[%d](%s) unresponsive; aborting connection", r->sd, r->process_id, r->pid_name);
LogClientInfo(&mDNSStorage, r);
abort_request(r);
}
}
}
if (!dnssd_SocketValid(r->sd)) // If this request is finished, unlink it from the list and free the memory
{
// Since we're already doing a list traversal, we unlink the request directly instead of using AbortUnlinkAndFree()
freeL("request_state/udsserver_idle", r);
}
else
}
return nextevent;
}
{
// Check our structures are reasonable sizes. Including overly-large buffers, or embedding
// other overly-large structures instead of having a pointer to them, can inadvertently
// cause structure sizes (and therefore memory usage) to balloon unreasonably.
};