summaryrefslogtreecommitdiffstats
path: root/mDNSResponder/Clients
diff options
context:
space:
mode:
authorSebastian Huber <sebastian.huber@embedded-brains.de>2018-09-19 08:56:09 +0200
committerSebastian Huber <sebastian.huber@embedded-brains.de>2018-09-20 11:23:33 +0200
commit4c086a244624bf36865edcfa4309c333d7d7200d (patch)
treee566ffc50a6b6fdb46702ac57c8e7c4088b60b42 /mDNSResponder/Clients
parentmDNSResponder: Update to v765.50.9 (diff)
downloadrtems-libbsd-4c086a244624bf36865edcfa4309c333d7d7200d.tar.bz2
mDNSResponder: Update to v878.1.1
The sources can be obtained via: https://opensource.apple.com/tarballs/mDNSResponder/mDNSResponder-878.1.1.tar.gz Update #3522.
Diffstat (limited to 'mDNSResponder/Clients')
-rw-r--r--mDNSResponder/Clients/dns-sd.c345
-rw-r--r--mDNSResponder/Clients/dnssdutil.c7899
2 files changed, 8168 insertions, 76 deletions
diff --git a/mDNSResponder/Clients/dns-sd.c b/mDNSResponder/Clients/dns-sd.c
index f36211e5..78c8f29f 100644
--- a/mDNSResponder/Clients/dns-sd.c
+++ b/mDNSResponder/Clients/dns-sd.c
@@ -165,6 +165,7 @@ static const char kFilePathSep = '/';
#undef _DNS_SD_LIBDISPATCH
#endif
#include "dns_sd.h"
+#include "dns_sd_internal.h"
#include "ClientCommon.h"
#if TEST_NEW_CLIENTSTUB
@@ -1520,138 +1521,321 @@ DNSServiceErrorType DNSSD_API DNSServiceCreateDelegateConnection(DNSServiceRef *
static int API_NULL_input_test()
{
- printf("Running basic API input range tests with all parameters set to 0:\n");
+ printf("Running basic API input range tests with various pointer parameters set to NULL:\n");
// Test that API's handle NULL pointers by returning an error when appropriate.
- printf("DNSServiceRefSockFD()\n");
+
+ // DNSServiceRefSockFD()
if (DNSServiceRefSockFD(0) != -1)
{
printf("DNSServiceRefSockFD(): expected dnssd_InvalidSocket return\n");
return 1;
- };
+ }
- printf("DNSServiceProcessResult()\n");
+ // DNSServiceProcessResult()
if (DNSServiceProcessResult(0) == 0)
{
printf("DNSServiceProcessResult(): expected error return\n");
return 1;
- };
+ }
- // no return value, just verify it doesn't crash
- printf("DNSServiceRefDeallocate()\n");
+ // DNSServiceRefDeallocate(): no return value, just verify it doesn't crash
DNSServiceRefDeallocate(0);
- printf("DNSServiceGetProperty()\n");
- if (DNSServiceGetProperty(0, 0, 0) == 0)
+ // DNSServiceGetProperty()
{
- printf("DNSServiceGetProperty(): expected error return\n");
- return 1;
- };
+ uint32_t result;
+ uint32_t size;
- printf("DNSServiceResolve()\n");
- if (DNSServiceResolve(0, 0, 0, 0, 0, 0, 0, 0) == 0)
+ if ( (DNSServiceGetProperty( 0, &result, &size) == 0)
+ || (DNSServiceGetProperty(kDNSServiceProperty_DaemonVersion, 0, &size) == 0)
+ || (DNSServiceGetProperty(kDNSServiceProperty_DaemonVersion, &result, 0) == 0)
+ )
+ {
+ printf("DNSServiceGetProperty(): expected error return\n");
+ return 1;
+ }
+ }
+
+ // DNSServiceResolve()
{
- printf("DNSServiceResolve(): expected error return\n");
- return 1;
- };
+ DNSServiceRef sdRef;
+ DNSServiceFlags flags = 0;
+ uint32_t interfaceIndex = 0;
+ const char *name = "name";
+ const char *regtype = "_test._tcp";
+ const char *domain = "local";
+ DNSServiceResolveReply callBack = 0;
+ void *context = 0; // can be a NULL pointer
+
+ if ( (DNSServiceResolve( 0, flags, interfaceIndex, name, regtype, domain, callBack, context) == 0)
+ || (DNSServiceResolve(&sdRef, flags, interfaceIndex, 0, regtype, domain, callBack, context) == 0)
+ || (DNSServiceResolve(&sdRef, flags, interfaceIndex, name, 0, domain, callBack, context) == 0)
+ || (DNSServiceResolve(&sdRef, flags, interfaceIndex, name, regtype, 0, callBack, context) == 0)
+ || (DNSServiceResolve(&sdRef, flags, interfaceIndex, name, regtype, domain, callBack, context) == 0)
+ )
+ {
+ printf("DNSServiceResolve(): expected error return\n");
+ return 1;
+ }
+ }
- printf("DNSServiceQueryRecord()\n");
- if (DNSServiceQueryRecord(0, 0, 0, 0, 0, 0, 0, 0) == 0)
+ // DNSServiceQueryRecord()
{
- printf("DNSServiceQueryRecord(): expected error return\n");
- return 1;
- };
+ DNSServiceRef sdRef;
+ DNSServiceFlags flags = 0;
+ uint32_t interfaceIndex = 0;
+ const char *fullname = "fullname";
+ uint16_t rrtype = 0;
+ uint16_t rrclass = 0;
+ DNSServiceQueryRecordReply callBack = 0;
+ void *context = 0; /* may be NULL */
+
+ if ( (DNSServiceQueryRecord( 0, flags, interfaceIndex, fullname, rrtype, rrclass, callBack, context) == 0)
+ || (DNSServiceQueryRecord(&sdRef, flags, interfaceIndex, 0, rrtype, rrclass, callBack, context) == 0)
+ || (DNSServiceQueryRecord(&sdRef, flags, interfaceIndex, fullname, rrtype, rrclass, 0, context) == 0)
+ )
+ {
+ printf("DNSServiceQueryRecord(): expected error return\n");
+ return 1;
+ }
+ }
- printf("DNSServiceGetAddrInfo()\n");
- if (DNSServiceGetAddrInfo(0, 0, 0, 0, 0, 0, 0) == 0)
+ // DNSServiceGetAddrInfo()
{
- printf("DNSServiceGetAddrInfo(): expected error return\n");
- return 1;
- };
+ DNSServiceRef sdRef;
+ DNSServiceFlags flags = 0;
+ uint32_t interfaceIndex = 0;
+ DNSServiceProtocol protocol = kDNSServiceProtocol_IPv4|kDNSServiceProtocol_IPv6;
+ const char *hostname = "host.local";
+ DNSServiceGetAddrInfoReply callBack = 0;
+ void *context = 0; // may be NULL
+
+ if ( (DNSServiceGetAddrInfo( 0, flags, interfaceIndex, protocol, hostname, callBack, context) == 0)
+ || (DNSServiceGetAddrInfo(&sdRef, flags, interfaceIndex, protocol, 0, callBack, context) == 0)
+ || (DNSServiceGetAddrInfo(&sdRef, flags, interfaceIndex, protocol, hostname, 0, context) == 0)
+ )
+ {
+ printf("DNSServiceGetAddrInfo(): expected error return\n");
+ return 1;
+ }
+ }
- printf("DNSServiceBrowse()\n");
- if (DNSServiceBrowse(0, 0, 0, 0, 0, 0, 0) == 0)
+ // DNSServiceBrowse()
{
- printf("DNSServiceBrowse(): expected error return\n");
- return 1;
- };
+ DNSServiceRef sdRef;
+ DNSServiceFlags flags = 0;
+ uint32_t interfaceIndex = 0;
+ const char *regtype = "_test._tcp";
+ const char *domain = 0; /* may be NULL */
+ DNSServiceBrowseReply callBack = 0;
+ void *context = 0; /* may be NULL */
+
+ if ( (DNSServiceBrowse( 0, flags, interfaceIndex, regtype, domain, callBack, context) == 0)
+ || (DNSServiceBrowse(&sdRef, flags, interfaceIndex, 0, domain, callBack, context) == 0)
+ || (DNSServiceBrowse(&sdRef, flags, interfaceIndex, regtype, domain, 0, context) == 0)
+ )
+ {
+ printf("DNSServiceBrowse(): expected error return\n");
+ return 1;
+ }
+ }
#if APPLE_OSX_mDNSResponder
- printf("DNSServiceSetDefaultDomainForUser()\n");
+ // DNSServiceSetDefaultDomainForUser()
if (DNSServiceSetDefaultDomainForUser(0, 0) == 0)
{
printf("DNSServiceSetDefaultDomainForUser(): expected error return\n");
return 1;
- };
+ }
#endif
- printf("DNSServiceRegister()\n");
- if (DNSServiceRegister(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) == 0)
+ // DNSServiceRegister()
{
- printf("DNSServiceRegister(): expected error return\n");
- return 1;
- };
+ DNSServiceRef sdRef;
+ DNSServiceFlags flags = 0;
+ uint32_t interfaceIndex = 0;
+ const char *name = 0; /* may be NULL */
+ const char *regtype = "_test._tcp";
+ const char *domain = 0; /* may be NULL */
+ const char *host = 0; /* may be NULL */
+ uint16_t port = 0x2211; /* In network byte order */
+ uint16_t txtLen = 1;
+ const void *txtRecord = "\0"; /* may be NULL */
+ DNSServiceRegisterReply callBack = 0; /* may be NULL */
+ void *context = 0; /* may be NULL */
+
+ if ( (DNSServiceRegister( 0, flags, interfaceIndex, name, regtype, domain, host, port, txtLen, txtRecord, callBack, context) == 0)
+ || (DNSServiceRegister(&sdRef, flags, interfaceIndex, name, 0, domain, host, port, txtLen, txtRecord, callBack, context) == 0)
+ )
+ {
+ printf("DNSServiceRegister(): expected error return\n");
+ return 1;
+ }
+ }
- printf("DNSServiceEnumerateDomains()\n");
- if (DNSServiceEnumerateDomains(0, 0, 0, 0, 0) == 0)
+ // DNSServiceEnumerateDomains()
{
- printf("DNSServiceEnumerateDomains(): expected error return\n");
- return 1;
- };
+ DNSServiceRef sdRef;
+ DNSServiceFlags flags = 0;
+ uint32_t interfaceIndex = 0;
+ DNSServiceDomainEnumReply callBack = 0;
+ void *context = 0; /* may be NULL */
+
+ if ( (DNSServiceEnumerateDomains( 0, flags, interfaceIndex, callBack, context) == 0)
+ || (DNSServiceEnumerateDomains(&sdRef, flags, interfaceIndex, 0, context) == 0)
+ )
+ {
+ printf("DNSServiceEnumerateDomains(): expected error return\n");
+ return 1;
+ }
+ }
- printf("DNSServiceCreateConnection()\n");
+ // DNSServiceCreateConnection()
if (DNSServiceCreateConnection(0) == 0)
{
printf("DNSServiceCreateConnection(): expected error return\n");
return 1;
- };
+ }
#if APPLE_OSX_mDNSResponder
- printf("DNSServiceCreateDelegateConnection()\n");
+ // DNSServiceCreateDelegateConnection()
if (DNSServiceCreateDelegateConnection(0, 0, 0) == 0)
{
printf("DNSServiceCreateDelegateConnection(): expected error return\n");
return 1;
- };
+ }
#endif
- printf("DNSServiceRegisterRecord()\n");
- if (DNSServiceRegisterRecord(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) == 0)
+ // DNSServiceRegisterRecord()
{
- printf("DNSServiceRegisterRecord(): expected error return\n");
- return 1;
- };
+ DNSServiceRef sdRef;
+ DNSRecordRef RecordRef;
+ DNSServiceFlags flags = 0;
+ uint32_t interfaceIndex = 0;
+ const char *fullname = "test1._test._tcp.local";
+ uint16_t rrtype = kDNSServiceType_TXT;
+ uint16_t rrclass = kDNSServiceClass_IN;
+ uint16_t rdlen = 1;
+ const void *rdata = "\0";
+ uint32_t ttl = 0;
+ DNSServiceRegisterRecordReply callBack = 0;
+ void *context = 0; /* may be NULL */
+
+ // Need an initialize sdRef
+ if (DNSServiceCreateConnection(&sdRef))
+ {
+ printf("DNSServiceCreateConnection(): failed\n");
+ return 1;
+ }
- printf("DNSServiceAddRecord()\n");
- if (DNSServiceAddRecord(0, 0, 0, 0, 0, 0, 0) == 0)
- {
- printf("DNSServiceAddRecord(): expected error return\n");
- return 1;
- };
+ if ( (DNSServiceRegisterRecord( 0, &RecordRef, flags, interfaceIndex, fullname, rrtype, rrclass, rdlen, rdata, ttl, callBack, context) == 0)
+ || (DNSServiceRegisterRecord(sdRef, &RecordRef, flags, interfaceIndex, 0, rrtype, rrclass, rdlen, rdata, ttl, callBack, context) == 0)
+ || (DNSServiceRegisterRecord(sdRef, &RecordRef, flags, interfaceIndex, fullname, rrtype, rrclass, rdlen, 0, ttl, callBack, context) == 0)
+ || (DNSServiceRegisterRecord(sdRef, &RecordRef, flags, interfaceIndex, fullname, rrtype, rrclass, rdlen, rdata, ttl, 0, context) == 0)
+ )
+ {
+ printf("DNSServiceRegisterRecord(): expected error return\n");
+ return 1;
+ }
+ }
- printf("DNSServiceUpdateRecord()\n");
- if (DNSServiceUpdateRecord(0, 0, 0, 0, 0, 0) == 0)
+ // DNSServiceAddRecord(), DNSServiceUpdateRecord(), and DNSServiceRemoveRecord() verify that they
+ // get a valid DNSServiceRef returned from DNSServiceRegister()
{
- printf("DNSServiceUpdateRecord(): expected error return\n");
- return 1;
- };
+ DNSServiceErrorType err;
+ Opaque16 registerPort = { { 0x12, 0x34 } };
+ static const char TXT[] = "\xC" "First String";
+ DNSServiceRef sdRef;
+
+ DNSRecordRef RecordRef;
+ DNSServiceFlags flags = 0;
+ uint16_t rrtype = kDNSServiceType_TXT;
+ uint16_t rdlen = 1;
+ const void *rdata = "\0";
+ uint32_t ttl = 100;
+
+ err = DNSServiceRegister(&sdRef, 0, 0, "Test", "_test._tcp.", "", NULL, registerPort.NotAnInteger, sizeof(TXT)-1, TXT, reg_reply, NULL);
+ if (err)
+ {
+ printf("DNSServiceRegister() failed with: %d\n", err);
+ return 1;
+ }
+
+ // DNSServiceAddRecord()
+ if ( (DNSServiceAddRecord( 0, &RecordRef, flags, rrtype, rdlen, rdata, ttl) == 0)
+ || (DNSServiceAddRecord(sdRef, 0, flags, rrtype, rdlen, rdata, ttl) == 0)
+ || (DNSServiceAddRecord(sdRef, &RecordRef, flags, rrtype, rdlen, 0, ttl) == 0)
+ )
- printf("DNSServiceRemoveRecord()\n");
- if (DNSServiceRemoveRecord(0, 0, 0) == 0)
- {
- printf("DNSServiceRemoveRecord(): expected error return\n");
- return 1;
- };
+ {
+ printf("DNSServiceAddRecord(): expected error return\n");
+ return 1;
+ }
+
+ // (rdlen == 0 && rdata == 0) should indicate a TXT with rdata containing only a 0 length byte.
+ if (DNSServiceAddRecord(sdRef, &RecordRef, flags, rrtype, 0, 0, ttl) == kDNSServiceErr_BadParam)
+ {
+ printf("DNSServiceAddRecord(): with (rdlen == 0 && rdata == 0) returned kDNSServiceErr_BadParam\n");
+ return 1;
+ }
+
+ // DNSServiceUpdateRecord()
+ // Note, RecordRef can be NULL per explanation with declaration in dns_sd.h
+ if ( (DNSServiceUpdateRecord( 0, RecordRef, flags, rdlen, rdata, ttl) == 0)
+ || (DNSServiceUpdateRecord(sdRef, RecordRef, flags, rdlen, 0, ttl) == 0)
+ )
+ {
+ printf("DNSServiceUpdateRecord(): expected error return\n");
+ return 1;
+ }
+
+ // (rdlen == 0 && rdata == 0) should indicate a TXT with rdata containing only a 0 length byte.
+ if (DNSServiceUpdateRecord(sdRef, RecordRef, flags, 0, 0, ttl) == kDNSServiceErr_BadParam)
+ {
+ printf("DNSServiceUpdateRecord(): with (rdlen == 0 && rdata == 0) returned kDNSServiceErr_BadParam\n");
+ return 1;
+ }
+
+ // DNSServiceRemoveRecord()
+ if ( (DNSServiceRemoveRecord( 0, RecordRef, flags) == 0)
+ || (DNSServiceRemoveRecord(sdRef, 0, flags) == 0)
+ )
+ {
+ printf("DNSServiceRemoveRecord(): expected error return\n");
+ return 1;
+ }
+
+ DNSServiceRefDeallocate(sdRef);
+ }
- printf("DNSServiceReconfirmRecord()\n");
- if (DNSServiceReconfirmRecord(0, 0, 0, 0, 0, 0, 0) == 0)
+ // DNSServiceReconfirmRecord()
{
- printf("DNSServiceReconfirmRecord(): expected error return\n");
- return 1;
- };
+ DNSServiceFlags flags = 0;
+ uint32_t interfaceIndex = 0;
+ const char *fullname = "aaa._test._tcp.local";
+ uint16_t rrtype = kDNSServiceType_TXT;
+ uint16_t rrclass = kDNSServiceClass_IN;
+ uint16_t rdlen = 1;
+ const void *rdata = "\0";
+
+ if ( (DNSServiceReconfirmRecord(flags, interfaceIndex, 0, rrtype, rrclass, rdlen, rdata) == 0)
+ || (DNSServiceReconfirmRecord(flags, interfaceIndex, fullname, rrtype, rrclass, rdlen, 0) == 0)
+ )
+ {
+ printf("DNSServiceReconfirmRecord(): expected error return\n");
+ return 1;
+ }
+ // (rdlen == 0 && rdata == 0) should indicate a TXT with rdata containing only a 0 length byte.
+ if (DNSServiceReconfirmRecord(flags, interfaceIndex, fullname, rrtype, rrclass, 0, 0) == kDNSServiceErr_BadParam)
+ {
+ printf("DNSServiceReconfirmRecord(): with (rdlen == 0 && rdata == 0) returned kDNSServiceErr_BadParam\n");
+ return 1;
+ }
+ }
- printf("Basic API input range tests with all parameters set to 0: PASSED\n");
+ printf("Basic API input range tests: PASSED\n");
return 0;
}
@@ -1739,6 +1923,14 @@ int main(int argc, char **argv)
flags |= kDNSServiceFlagsIncludeP2P;
printf("Setting kDNSServiceFlagsIncludeP2P\n");
}
+
+ if (argc > 1 && !strcasecmp(argv[1], "-fmc"))
+ {
+ argc--;
+ argv++;
+ flags |= kDNSServiceFlagsForceMulticast;
+ printf("Setting kDNSServiceFlagsForceMulticast flag for this request\n");
+ }
if (argc > 1 && !strcasecmp(argv[1], "-includeAWDL"))
{
@@ -2002,6 +2194,7 @@ int main(int argc, char **argv)
case 'g':
case 'G': {
flags |= kDNSServiceFlagsReturnIntermediates;
+
if (operation == 'g')
{
flags |= kDNSServiceFlagsSuppressUnusable;
diff --git a/mDNSResponder/Clients/dnssdutil.c b/mDNSResponder/Clients/dnssdutil.c
new file mode 100644
index 00000000..068f05a6
--- /dev/null
+++ b/mDNSResponder/Clients/dnssdutil.c
@@ -0,0 +1,7899 @@
+/*
+ Copyright (c) 2016-2017 Apple Inc. All rights reserved.
+
+ dnssdutil is a command-line utility for testing the DNS-SD API.
+*/
+
+#include <CoreUtils/CommonServices.h> // Include early.
+#include <CoreUtils/AsyncConnection.h>
+#include <CoreUtils/CommandLineUtils.h>
+#include <CoreUtils/DataBufferUtils.h>
+#include <CoreUtils/DebugServices.h>
+#include <CoreUtils/MiscUtils.h>
+#include <CoreUtils/NetUtils.h>
+#include <CoreUtils/PrintFUtils.h>
+#include <CoreUtils/RandomNumberUtils.h>
+#include <CoreUtils/StringUtils.h>
+#include <CoreUtils/TickUtils.h>
+#include <dns_sd.h>
+#include <dns_sd_private.h>
+
+#if( TARGET_OS_DARWIN )
+ #include <libproc.h>
+ #include <sys/proc_info.h>
+#endif
+
+#if( TARGET_OS_POSIX )
+ #include <sys/resource.h>
+#endif
+
+#if( DNSSDUTIL_INCLUDE_DNSCRYPT )
+ #include "tweetnacl.h" // TweetNaCl from <https://tweetnacl.cr.yp.to/software.html>.
+#endif
+
+//===========================================================================================================================
+// Global Constants
+//===========================================================================================================================
+
+// Versioning
+
+#define kDNSSDUtilNumVersion NumVersionBuild( 2, 0, 0, kVersionStageBeta, 0 )
+
+#if( !MDNSRESPONDER_PROJECT && !defined( DNSSDUTIL_SOURCE_VERSION ) )
+ #define DNSSDUTIL_SOURCE_VERSION "0.0.0"
+#endif
+
+// DNS-SD API flag descriptors
+
+#define kDNSServiceFlagsDescriptors \
+ "\x00" "AutoTrigger\0" \
+ "\x01" "Add\0" \
+ "\x02" "Default\0" \
+ "\x03" "NoAutoRename\0" \
+ "\x04" "Shared\0" \
+ "\x05" "Unique\0" \
+ "\x06" "BrowseDomains\0" \
+ "\x07" "RegistrationDomains\0" \
+ "\x08" "LongLivedQuery\0" \
+ "\x09" "AllowRemoteQuery\0" \
+ "\x0A" "ForceMulticast\0" \
+ "\x0B" "KnownUnique\0" \
+ "\x0C" "ReturnIntermediates\0" \
+ "\x0D" "NonBrowsable\0" \
+ "\x0E" "ShareConnection\0" \
+ "\x0F" "SuppressUnusable\0" \
+ "\x10" "Timeout\0" \
+ "\x11" "IncludeP2P\0" \
+ "\x12" "WakeOnResolve\0" \
+ "\x13" "BackgroundTrafficClass\0" \
+ "\x14" "IncludeAWDL\0" \
+ "\x15" "Validate\0" \
+ "\x16" "UnicastResponse\0" \
+ "\x17" "ValidateOptional\0" \
+ "\x18" "WakeOnlyService\0" \
+ "\x19" "ThresholdOne\0" \
+ "\x1A" "ThresholdFinder\0" \
+ "\x1B" "DenyCellular\0" \
+ "\x1C" "ServiceIndex\0" \
+ "\x1D" "DenyExpensive\0" \
+ "\x1E" "PathEvaluationDone\0" \
+ "\x00"
+
+#define kDNSServiceProtocolDescriptors \
+ "\x00" "IPv4\0" \
+ "\x01" "IPv6\0" \
+ "\x00"
+
+// (m)DNS
+
+#define kDNSHeaderFlag_Response ( 1 << 15 )
+#define kDNSHeaderFlag_AuthAnswer ( 1 << 10 )
+#define kDNSHeaderFlag_Truncation ( 1 << 9 )
+#define kDNSHeaderFlag_RecursionDesired ( 1 << 8 )
+#define kDNSHeaderFlag_RecursionAvailable ( 1 << 7 )
+
+#define kDNSOpCode_Query 0
+#define kDNSOpCode_InverseQuery 1
+#define kDNSOpCode_Status 2
+#define kDNSOpCode_Notify 4
+#define kDNSOpCode_Update 5
+
+#define kDNSRCode_NoError 0
+#define kDNSRCode_FormatError 1
+#define kDNSRCode_ServerFailure 2
+#define kDNSRCode_NXDomain 3
+#define kDNSRCode_NotImplemented 4
+#define kDNSRCode_Refused 5
+
+#define kQClassUnicastResponseBit ( 1U << 15 )
+#define kRRClassCacheFlushBit ( 1U << 15 )
+
+#define kDomainLabelLengthMax 63
+#define kDomainNameLengthMax 256
+
+//===========================================================================================================================
+// Gerneral Command Options
+//===========================================================================================================================
+
+// Command option macros
+
+#define Command( NAME, CALLBACK, SUB_OPTIONS, SHORT_HELP, IS_NOTCOMMON ) \
+ CLI_COMMAND_EX( NAME, CALLBACK, SUB_OPTIONS, (IS_NOTCOMMON) ? kCLIOptionFlags_NotCommon : kCLIOptionFlags_None, \
+ (SHORT_HELP), NULL )
+
+#define kRequiredOptionSuffix " [REQUIRED]"
+
+#define MultiStringOptionEx( SHORT_CHAR, LONG_NAME, VAL_PTR, VAL_COUNT_PTR, ARG_HELP, SHORT_HELP, IS_REQUIRED, LONG_HELP ) \
+ CLI_OPTION_MULTI_STRING_EX( SHORT_CHAR, LONG_NAME, VAL_PTR, VAL_COUNT_PTR, ARG_HELP, \
+ (IS_REQUIRED) ? SHORT_HELP kRequiredOptionSuffix : SHORT_HELP, \
+ (IS_REQUIRED) ? kCLIOptionFlags_Required : kCLIOptionFlags_None, LONG_HELP )
+
+#define MultiStringOption( SHORT_CHAR, LONG_NAME, VAL_PTR, VAL_COUNT_PTR, ARG_HELP, SHORT_HELP, IS_REQUIRED ) \
+ MultiStringOptionEx( SHORT_CHAR, LONG_NAME, VAL_PTR, VAL_COUNT_PTR, ARG_HELP, SHORT_HELP, IS_REQUIRED, NULL )
+
+#define IntegerOption( SHORT_CHAR, LONG_NAME, VAL_PTR, ARG_HELP, SHORT_HELP, IS_REQUIRED ) \
+ CLI_OPTION_INTEGER_EX( SHORT_CHAR, LONG_NAME, VAL_PTR, ARG_HELP, \
+ (IS_REQUIRED) ? SHORT_HELP kRequiredOptionSuffix : SHORT_HELP, \
+ (IS_REQUIRED) ? kCLIOptionFlags_Required : kCLIOptionFlags_None, NULL )
+
+#define BooleanOption( SHORT_CHAR, LONG_NAME, VAL_PTR, SHORT_HELP ) \
+ CLI_OPTION_BOOLEAN( (SHORT_CHAR), (LONG_NAME), (VAL_PTR), (SHORT_HELP), NULL )
+
+#define StringOptionEx( SHORT_CHAR, LONG_NAME, VAL_PTR, ARG_HELP, SHORT_HELP, IS_REQUIRED, LONG_HELP ) \
+ CLI_OPTION_STRING_EX( SHORT_CHAR, LONG_NAME, VAL_PTR, ARG_HELP, \
+ (IS_REQUIRED) ? SHORT_HELP kRequiredOptionSuffix : SHORT_HELP, \
+ (IS_REQUIRED) ? kCLIOptionFlags_Required : kCLIOptionFlags_None, LONG_HELP )
+
+#define StringOption( SHORT_CHAR, LONG_NAME, VAL_PTR, ARG_HELP, SHORT_HELP, IS_REQUIRED ) \
+ StringOptionEx( SHORT_CHAR, LONG_NAME, VAL_PTR, ARG_HELP, SHORT_HELP, IS_REQUIRED, NULL )
+
+// DNS-SD API flag options
+
+static int gDNSSDFlags = 0;
+static int gDNSSDFlag_BrowseDomains = false;
+static int gDNSSDFlag_DenyCellular = false;
+static int gDNSSDFlag_DenyExpensive = false;
+static int gDNSSDFlag_ForceMulticast = false;
+static int gDNSSDFlag_IncludeAWDL = false;
+static int gDNSSDFlag_NoAutoRename = false;
+static int gDNSSDFlag_PathEvaluationDone = false;
+static int gDNSSDFlag_RegistrationDomains = false;
+static int gDNSSDFlag_ReturnIntermediates = false;
+static int gDNSSDFlag_Shared = false;
+static int gDNSSDFlag_SuppressUnusable = false;
+static int gDNSSDFlag_Timeout = false;
+static int gDNSSDFlag_UnicastResponse = false;
+static int gDNSSDFlag_Unique = false;
+
+#define DNSSDFlagsOption() \
+ IntegerOption( 'f', "flags", &gDNSSDFlags, "flags", \
+ "DNSServiceFlags to use. This value is bitwise ORed with other single flag options.", false )
+
+#define DNSSDFlagOption( SHORT_CHAR, FLAG_NAME ) \
+ BooleanOption( SHORT_CHAR, Stringify( FLAG_NAME ), &gDNSSDFlag_ ## FLAG_NAME, \
+ "Use kDNSServiceFlags" Stringify( FLAG_NAME ) "." )
+
+#define DNSSDFlagsOption_DenyCellular() DNSSDFlagOption( 'C', DenyCellular )
+#define DNSSDFlagsOption_DenyExpensive() DNSSDFlagOption( 'E', DenyExpensive )
+#define DNSSDFlagsOption_ForceMulticast() DNSSDFlagOption( 'M', ForceMulticast )
+#define DNSSDFlagsOption_IncludeAWDL() DNSSDFlagOption( 'A', IncludeAWDL )
+#define DNSSDFlagsOption_NoAutoRename() DNSSDFlagOption( 'N', NoAutoRename )
+#define DNSSDFlagsOption_PathEvalDone() DNSSDFlagOption( 'P', PathEvaluationDone )
+#define DNSSDFlagsOption_ReturnIntermediates() DNSSDFlagOption( 'I', ReturnIntermediates )
+#define DNSSDFlagsOption_Shared() DNSSDFlagOption( 'S', Shared )
+#define DNSSDFlagsOption_SuppressUnusable() DNSSDFlagOption( 'S', SuppressUnusable )
+#define DNSSDFlagsOption_Timeout() DNSSDFlagOption( 'T', Timeout )
+#define DNSSDFlagsOption_UnicastResponse() DNSSDFlagOption( 'U', UnicastResponse )
+#define DNSSDFlagsOption_Unique() DNSSDFlagOption( 'U', Unique )
+
+// Interface option
+
+static const char * gInterface = NULL;
+
+#define InterfaceOption() \
+ StringOption( 'i', "interface", &gInterface, "interface", \
+ "Network interface by name or index. Use index -1 for local-only.", false )
+
+// Connection options
+
+#define kConnectionArg_Normal ""
+#define kConnectionArgPrefix_PID "pid:"
+#define kConnectionArgPrefix_UUID "uuid:"
+
+static const char * gConnectionOpt = kConnectionArg_Normal;
+
+#define ConnectionOptions() \
+ { kCLIOptionType_String, 0, "connection", &gConnectionOpt, NULL, (intptr_t) kConnectionArg_Normal, "type", \
+ kCLIOptionFlags_OptionalArgument, NULL, NULL, NULL, NULL, \
+ "Specifies the type of main connection to use. See " kConnectionSection_Name " below.", NULL }
+
+#define kConnectionSection_Name "Connection Option"
+#define kConnectionSection_Text \
+ "The default behavior is to create a main connection with DNSServiceCreateConnection() and perform operations on\n" \
+ "the main connection using the kDNSServiceFlagsShareConnection flag. This behavior can be explicitly invoked by\n" \
+ "specifying the connection option without an argument, i.e.,\n" \
+ "\n" \
+ " --connection\n" \
+ "\n" \
+ "To instead use a delegate connection created with DNSServiceCreateDelegateConnection(), use\n" \
+ "\n" \
+ " --connection=pid:<PID>\n" \
+ "\n" \
+ "to specify the delegator by PID, or use\n" \
+ "\n" \
+ " --connection=uuid:<UUID>\n" \
+ "\n" \
+ "to specify the delegator by UUID.\n" \
+ "\n" \
+ "To not use a main connection at all, but instead perform operations on their own connections, use\n" \
+ "\n" \
+ " --no-connection\n"
+
+#define ConnectionSection() CLI_SECTION( kConnectionSection_Name, kConnectionSection_Text )
+
+// Help text for record data options
+
+#define kRDataArgPrefix_File "file:"
+#define kRDataArgPrefix_HexString "hex:"
+#define kRDataArgPrefix_String "string:"
+#define kRDataArgPrefix_TXT "txt:"
+
+#define kRecordDataSection_Name "Record Data Arguments"
+#define kRecordDataSection_Text \
+ "A record data argument is specified in one of the following formats:\n" \
+ "\n" \
+ "Format Syntax Example\n" \
+ "String string:<string> string:'\\x09color=red'\n" \
+ "Hexadecimal string hex:<hex string> hex:c0a80101 or hex:'C0 A8 01 01'\n" \
+ "TXT record keys and values txt:<comma-delimited keys and values> txt:'key1=x,key2=y\\,z,key3'\n" \
+ "File containing raw record data file:<file path> file:dir/record_data.bin\n"
+
+#define RecordDataSection() CLI_SECTION( kRecordDataSection_Name, kRecordDataSection_Text )
+
+//===========================================================================================================================
+// Browse Command Options
+//===========================================================================================================================
+
+static char ** gBrowse_ServiceTypes = NULL;
+static size_t gBrowse_ServiceTypesCount = 0;
+static const char * gBrowse_Domain = NULL;
+static int gBrowse_DoResolve = false;
+static int gBrowse_QueryTXT = false;
+static int gBrowse_TimeLimitSecs = 0;
+
+static CLIOption kBrowseOpts[] =
+{
+ InterfaceOption(),
+ MultiStringOption( 't', "type", &gBrowse_ServiceTypes, &gBrowse_ServiceTypesCount, "service type", "Service type(s), e.g., \"_ssh._tcp\".", true ),
+ StringOption( 'd', "domain", &gBrowse_Domain, "domain", "Domain in which to browse for the service type(s).", false ),
+
+ CLI_OPTION_GROUP( "Flags" ),
+ DNSSDFlagsOption(),
+ DNSSDFlagsOption_IncludeAWDL(),
+
+ CLI_OPTION_GROUP( "Operation" ),
+ ConnectionOptions(),
+ BooleanOption( 0 , "resolve", &gBrowse_DoResolve, "Resolve service instances." ),
+ BooleanOption( 0 , "queryTXT", &gBrowse_QueryTXT, "Query TXT records of service instances." ),
+ IntegerOption( 'l', "timeLimit", &gBrowse_TimeLimitSecs, "seconds", "Specifies the max duration of the browse operation. Use '0' for no time limit.", false ),
+
+ ConnectionSection(),
+ CLI_OPTION_END()
+};
+
+//===========================================================================================================================
+// GetAddrInfo Command Options
+//===========================================================================================================================
+
+static const char * gGetAddrInfo_Name = NULL;
+static int gGetAddrInfo_ProtocolIPv4 = false;
+static int gGetAddrInfo_ProtocolIPv6 = false;
+static int gGetAddrInfo_OneShot = false;
+static int gGetAddrInfo_TimeLimitSecs = 0;
+
+static CLIOption kGetAddrInfoOpts[] =
+{
+ InterfaceOption(),
+ StringOption( 'n', "name", &gGetAddrInfo_Name, "domain name", "Domain name to resolve.", true ),
+ BooleanOption( 0 , "ipv4", &gGetAddrInfo_ProtocolIPv4, "Use kDNSServiceProtocol_IPv4." ),
+ BooleanOption( 0 , "ipv6", &gGetAddrInfo_ProtocolIPv6, "Use kDNSServiceProtocol_IPv6." ),
+
+ CLI_OPTION_GROUP( "Flags" ),
+ DNSSDFlagsOption(),
+ DNSSDFlagsOption_DenyCellular(),
+ DNSSDFlagsOption_DenyExpensive(),
+ DNSSDFlagsOption_PathEvalDone(),
+ DNSSDFlagsOption_ReturnIntermediates(),
+ DNSSDFlagsOption_SuppressUnusable(),
+ DNSSDFlagsOption_Timeout(),
+
+ CLI_OPTION_GROUP( "Operation" ),
+ ConnectionOptions(),
+ BooleanOption( 'o', "oneshot", &gGetAddrInfo_OneShot, "Finish after first set of results." ),
+ IntegerOption( 'l', "timeLimit", &gGetAddrInfo_TimeLimitSecs, "seconds", "Maximum duration of the GetAddrInfo operation. Use '0' for no time limit.", false ),
+
+ ConnectionSection(),
+ CLI_OPTION_END()
+};
+
+//===========================================================================================================================
+// QueryRecord Command Options
+//===========================================================================================================================
+
+static const char * gQueryRecord_Name = NULL;
+static const char * gQueryRecord_Type = NULL;
+static int gQueryRecord_OneShot = false;
+static int gQueryRecord_TimeLimitSecs = 0;
+static int gQueryRecord_RawRData = false;
+
+static CLIOption kQueryRecordOpts[] =
+{
+ InterfaceOption(),
+ StringOption( 'n', "name", &gQueryRecord_Name, "domain name", "Full domain name of record to query.", true ),
+ StringOption( 't', "type", &gQueryRecord_Type, "record type", "Record type by name (e.g., TXT, SRV, etc.) or number.", true ),
+
+ CLI_OPTION_GROUP( "Flags" ),
+ DNSSDFlagsOption(),
+ DNSSDFlagsOption_IncludeAWDL(),
+ DNSSDFlagsOption_ForceMulticast(),
+ DNSSDFlagsOption_Timeout(),
+ DNSSDFlagsOption_ReturnIntermediates(),
+ DNSSDFlagsOption_SuppressUnusable(),
+ DNSSDFlagsOption_UnicastResponse(),
+ DNSSDFlagsOption_DenyCellular(),
+ DNSSDFlagsOption_DenyExpensive(),
+ DNSSDFlagsOption_PathEvalDone(),
+
+ CLI_OPTION_GROUP( "Operation" ),
+ ConnectionOptions(),
+ BooleanOption( 'o', "oneshot", &gQueryRecord_OneShot, "Finish after first set of results." ),
+ IntegerOption( 'l', "timeLimit", &gQueryRecord_TimeLimitSecs, "seconds", "Maximum duration of the query record operation. Use '0' for no time limit.", false ),
+ BooleanOption( 'r', "raw", &gQueryRecord_RawRData, "Show record data as a hexdump." ),
+
+ ConnectionSection(),
+ CLI_OPTION_END()
+};
+
+//===========================================================================================================================
+// Register Command Options
+//===========================================================================================================================
+
+static const char * gRegister_Name = NULL;
+static const char * gRegister_Type = NULL;
+static const char * gRegister_Domain = NULL;
+static int gRegister_Port = 0;
+static const char * gRegister_TXT = NULL;
+static int gRegister_LifetimeMs = -1;
+static const char ** gAddRecord_Types = NULL;
+static size_t gAddRecord_TypesCount = 0;
+static const char ** gAddRecord_Data = NULL;
+static size_t gAddRecord_DataCount = 0;
+static const char ** gAddRecord_TTLs = NULL;
+static size_t gAddRecord_TTLsCount = 0;
+static const char * gUpdateRecord_Data = NULL;
+static int gUpdateRecord_DelayMs = 0;
+static int gUpdateRecord_TTL = 0;
+
+static CLIOption kRegisterOpts[] =
+{
+ InterfaceOption(),
+ StringOption( 'n', "name", &gRegister_Name, "service name", "Name of service.", false ),
+ StringOption( 't', "type", &gRegister_Type, "service type", "Service type, e.g., \"_ssh._tcp\".", true ),
+ StringOption( 'd', "domain", &gRegister_Domain, "domain", "Domain in which to advertise the service.", false ),
+ IntegerOption( 'p', "port", &gRegister_Port, "port number", "Service's port number.", true ),
+ StringOption( 0 , "txt", &gRegister_TXT, "record data", "The TXT record data. See " kRecordDataSection_Name " below.", false ),
+
+ CLI_OPTION_GROUP( "Flags" ),
+ DNSSDFlagsOption(),
+ DNSSDFlagsOption_IncludeAWDL(),
+ DNSSDFlagsOption_NoAutoRename(),
+
+ CLI_OPTION_GROUP( "Operation" ),
+ IntegerOption( 'l', "lifetime", &gRegister_LifetimeMs, "ms", "Lifetime of the service registration in milliseconds.", false ),
+
+ CLI_OPTION_GROUP( "Options for updating the registered service's primary TXT record with DNSServiceUpdateRecord()\n" ),
+ StringOption( 0 , "updateData", &gUpdateRecord_Data, "record data", "Record data for the record update. See " kRecordDataSection_Name " below.", false ),
+ IntegerOption( 0 , "updateDelay", &gUpdateRecord_DelayMs, "ms", "Number of milliseconds after registration to wait before record update.", false ),
+ IntegerOption( 0 , "updateTTL", &gUpdateRecord_TTL, "seconds", "Time-to-live of the updated record.", false ),
+
+ CLI_OPTION_GROUP( "Options for adding extra record(s) to the registered service with DNSServiceAddRecord()\n" ),
+ MultiStringOption( 0 , "addType", &gAddRecord_Types, &gAddRecord_TypesCount, "record type", "Type of additional record by name (e.g., TXT, SRV, etc.) or number.", false ),
+ MultiStringOptionEx( 0 , "addData", &gAddRecord_Data, &gAddRecord_DataCount, "record data", "Additional record's data. See " kRecordDataSection_Name " below.", false, NULL ),
+ MultiStringOption( 0 , "addTTL", &gAddRecord_TTLs, &gAddRecord_TTLsCount, "seconds", "Time-to-live of additional record in seconds. Use '0' for default.", false ),
+
+ RecordDataSection(),
+ CLI_OPTION_END()
+};
+
+//===========================================================================================================================
+// RegisterRecord Command Options
+//===========================================================================================================================
+
+static const char * gRegisterRecord_Name = NULL;
+static const char * gRegisterRecord_Type = NULL;
+static const char * gRegisterRecord_Data = NULL;
+static int gRegisterRecord_TTL = 0;
+static int gRegisterRecord_LifetimeMs = -1;
+static const char * gRegisterRecord_UpdateData = NULL;
+static int gRegisterRecord_UpdateDelayMs = 0;
+static int gRegisterRecord_UpdateTTL = 0;
+
+static CLIOption kRegisterRecordOpts[] =
+{
+ InterfaceOption(),
+ StringOption( 'n', "name", &gRegisterRecord_Name, "record name", "Fully qualified domain name of record.", true ),
+ StringOption( 't', "type", &gRegisterRecord_Type, "record type", "Record type by name (e.g., TXT, PTR, A) or number.", true ),
+ StringOption( 'd', "data", &gRegisterRecord_Data, "record data", "The record data. See " kRecordDataSection_Name " below.", false ),
+ IntegerOption( 0 , "ttl", &gRegisterRecord_TTL, "seconds", "Time-to-live in seconds. Use '0' for default.", false ),
+
+ CLI_OPTION_GROUP( "Flags" ),
+ DNSSDFlagsOption(),
+ DNSSDFlagsOption_IncludeAWDL(),
+ DNSSDFlagsOption_Shared(),
+ DNSSDFlagsOption_Unique(),
+
+ CLI_OPTION_GROUP( "Operation" ),
+ IntegerOption( 'l', "lifetime", &gRegisterRecord_LifetimeMs, "ms", "Lifetime of the service registration in milliseconds.", false ),
+
+ CLI_OPTION_GROUP( "Options for updating the registered record with DNSServiceUpdateRecord()\n" ),
+ StringOption( 0 , "updateData", &gRegisterRecord_UpdateData, "record data", "Record data for the record update.", false ),
+ IntegerOption( 0 , "updateDelay", &gRegisterRecord_UpdateDelayMs, "ms", "Number of milliseconds after registration to wait before record update.", false ),
+ IntegerOption( 0 , "updateTTL", &gRegisterRecord_UpdateTTL, "seconds", "Time-to-live of the updated record.", false ),
+
+ RecordDataSection(),
+ CLI_OPTION_END()
+};
+
+//===========================================================================================================================
+// Resolve Command Options
+//===========================================================================================================================
+
+static char * gResolve_Name = NULL;
+static char * gResolve_Type = NULL;
+static char * gResolve_Domain = NULL;
+static int gResolve_TimeLimitSecs = 0;
+
+static CLIOption kResolveOpts[] =
+{
+ InterfaceOption(),
+ StringOption( 'n', "name", &gResolve_Name, "service name", "Name of the service instance to resolve.", true ),
+ StringOption( 't', "type", &gResolve_Type, "service type", "Type of the service instance to resolve.", true ),
+ StringOption( 'd', "domain", &gResolve_Domain, "domain", "Domain of the service instance to resolve.", true ),
+
+ CLI_OPTION_GROUP( "Flags" ),
+ DNSSDFlagsOption(),
+ DNSSDFlagsOption_ForceMulticast(),
+ DNSSDFlagsOption_IncludeAWDL(),
+ DNSSDFlagsOption_ReturnIntermediates(),
+
+ CLI_OPTION_GROUP( "Operation" ),
+ ConnectionOptions(),
+ IntegerOption( 'l', "timeLimit", &gResolve_TimeLimitSecs, "seconds", "Maximum duration of the resolve operation. Use '0' for no time limit.", false ),
+
+ ConnectionSection(),
+ CLI_OPTION_END()
+};
+
+//===========================================================================================================================
+// Reconfirm Command Options
+//===========================================================================================================================
+
+static const char * gReconfirmRecord_Name = NULL;
+static const char * gReconfirmRecord_Type = NULL;
+static const char * gReconfirmRecord_Class = NULL;
+static const char * gReconfirmRecord_Data = NULL;
+
+static CLIOption kReconfirmOpts[] =
+{
+ InterfaceOption(),
+ StringOption( 'n', "name", &gReconfirmRecord_Name, "record name", "Full name of the record to reconfirm.", true ),
+ StringOption( 't', "type", &gReconfirmRecord_Type, "record type", "Type of the record to reconfirm.", true ),
+ StringOption( 'c', "class", &gReconfirmRecord_Class, "record class", "Class of the record to reconfirm. Default class is IN.", false ),
+ StringOption( 'd', "data", &gReconfirmRecord_Data, "record data", "The record data. See " kRecordDataSection_Name " below.", false ),
+
+ CLI_OPTION_GROUP( "Flags" ),
+ DNSSDFlagsOption(),
+
+ RecordDataSection(),
+ CLI_OPTION_END()
+};
+
+//===========================================================================================================================
+// getaddrinfo-POSIX Command Options
+//===========================================================================================================================
+
+static const char * gGAIPOSIX_Name = NULL;
+static const char * gGAIPOSIX_Family = NULL;
+
+static CLIOption kGetAddrInfoPOSIXOpts[] =
+{
+ StringOption( 'n', "name", &gGAIPOSIX_Name, "domain name", "Domain name to resolve.", true ),
+ StringOptionEx( 'f', "family", &gGAIPOSIX_Family, "address family", "Address family to pass to getaddrinfo().", false,
+ "The address family value determines which value to use for the ai_family element of the hints struct addrinfo\n"
+ "argument used when calling getaddrinfo().\n"
+ "\n"
+ "Possible address family values are 'inet' for AF_INET, 'inet6' for AF_INET6, or 'unspec' for AF_UNSPEC. If no\n"
+ "address family is specified, then AF_UNSPEC is used.\n" ),
+ CLI_OPTION_END()
+};
+
+//===========================================================================================================================
+// ReverseLookup Command Options
+//===========================================================================================================================
+
+static const char * gReverseLookup_IPAddr = NULL;
+static int gReverseLookup_OneShot = false;
+static int gReverseLookup_TimeLimitSecs = 0;
+
+static CLIOption kReverseLookupOpts[] =
+{
+ InterfaceOption(),
+ StringOption( 'a', "address", &gReverseLookup_IPAddr, "IP address", "IPv4 or IPv6 address for which to perform a reverse IP lookup.", true ),
+
+ CLI_OPTION_GROUP( "Flags" ),
+ DNSSDFlagsOption(),
+ DNSSDFlagsOption_ForceMulticast(),
+ DNSSDFlagsOption_ReturnIntermediates(),
+ DNSSDFlagsOption_SuppressUnusable(),
+
+ CLI_OPTION_GROUP( "Operation" ),
+ ConnectionOptions(),
+ BooleanOption( 'o', "oneshot", &gReverseLookup_OneShot, "Finish after first set of results." ),
+ IntegerOption( 'l', "timeLimit", &gReverseLookup_TimeLimitSecs, "seconds", "Specifies the max duration of the query record operation. Use '0' for no time limit.", false ),
+
+ ConnectionSection(),
+ CLI_OPTION_END()
+};
+
+//===========================================================================================================================
+// BrowseAll Command Options
+//===========================================================================================================================
+
+static const char * gBrowseAll_Domain = NULL;
+static char ** gBrowseAll_ServiceTypes = NULL;
+static size_t gBrowseAll_ServiceTypesCount = 0;
+static int gBrowseAll_IncludeAWDL = false;
+static int gBrowseAll_BrowseTimeSecs = 5;
+static int gBrowseAll_ConnectTimeLimitSecs = 5;
+
+static CLIOption kBrowseAllOpts[] =
+{
+ InterfaceOption(),
+ StringOption( 'd', "domain", &gBrowseAll_Domain, "domain", "Domain in which to browse for the service.", false ),
+ MultiStringOption( 't', "type", &gBrowseAll_ServiceTypes, &gBrowseAll_ServiceTypesCount, "service type", "Service type(s), e.g., \"_ssh._tcp\".", false ),
+
+ CLI_OPTION_GROUP( "Flags" ),
+ DNSSDFlagsOption_IncludeAWDL(),
+
+ CLI_OPTION_GROUP( "Operation" ),
+ IntegerOption( 'b', "browseTime", &gBrowseAll_BrowseTimeSecs, "seconds", "Specifies the duration of the browse.", false ),
+ IntegerOption( 'c', "connectTimeLimit", &gBrowseAll_ConnectTimeLimitSecs, "seconds", "Specifies the max duration of the connect operations.", false ),
+ CLI_OPTION_END()
+};
+
+//===========================================================================================================================
+// GetAddrInfoStress Command Options
+//===========================================================================================================================
+
+static int gGAIStress_TestDurationSecs = 0;
+static int gGAIStress_ConnectionCount = 0;
+static int gGAIStress_DurationMinMs = 0;
+static int gGAIStress_DurationMaxMs = 0;
+static int gGAIStress_RequestCountMax = 0;
+
+static CLIOption kGetAddrInfoStressOpts[] =
+{
+ InterfaceOption(),
+
+ CLI_OPTION_GROUP( "Flags" ),
+ DNSSDFlagsOption_ReturnIntermediates(),
+ DNSSDFlagsOption_SuppressUnusable(),
+
+ CLI_OPTION_GROUP( "Operation" ),
+ IntegerOption( 0, "testDuration", &gGAIStress_TestDurationSecs, "seconds", "Stress test duration in seconds. Use '0' for forever.", false ),
+ IntegerOption( 0, "connectionCount", &gGAIStress_ConnectionCount, "integer", "Number of simultaneous DNS-SD connections.", true ),
+ IntegerOption( 0, "requestDurationMin", &gGAIStress_DurationMinMs, "ms", "Minimum duration of DNSServiceGetAddrInfo() request in milliseconds.", true ),
+ IntegerOption( 0, "requestDurationMax", &gGAIStress_DurationMaxMs, "ms", "Maximum duration of DNSServiceGetAddrInfo() request in milliseconds.", true ),
+ IntegerOption( 0, "consecutiveRequestMax", &gGAIStress_RequestCountMax, "integer", "Maximum number of requests on a connection before restarting it.", true ),
+ CLI_OPTION_END()
+};
+
+//===========================================================================================================================
+// DNSQuery Command Options
+//===========================================================================================================================
+
+static char * gDNSQuery_Name = NULL;
+static char * gDNSQuery_Type = "A";
+static char * gDNSQuery_Server = NULL;
+static int gDNSQuery_TimeLimitSecs = 5;
+static int gDNSQuery_UseTCP = false;
+static int gDNSQuery_Flags = kDNSHeaderFlag_RecursionDesired;
+static int gDNSQuery_RawRData = false;
+static int gDNSQuery_Verbose = false;
+
+static CLIOption kDNSQueryOpts[] =
+{
+ StringOption( 'n', "name", &gDNSQuery_Name, "name", "Question name (QNAME) to put in DNS query message.", true ),
+ StringOption( 't', "type", &gDNSQuery_Type, "type", "Question type (QTYPE) to put in DNS query message. Default value is 'A'.", false ),
+ StringOption( 's', "server", &gDNSQuery_Server, "IP address", "DNS server's IPv4 or IPv6 address.", true ),
+ IntegerOption( 'l', "timeLimit", &gDNSQuery_TimeLimitSecs, "seconds", "Specifies query time limit. Use '-1' for no limit and '0' to exit immediately after sending.", false ),
+ BooleanOption( 0 , "tcp", &gDNSQuery_UseTCP, "Send the DNS query via TCP instead of UDP." ),
+ IntegerOption( 'f', "flags", &gDNSQuery_Flags, "flags", "16-bit value for DNS header flags/codes field. Default value is 0x0100 (Recursion Desired).", false ),
+ BooleanOption( 'r', "raw", &gDNSQuery_RawRData, "Present record data as a hexdump." ),
+ BooleanOption( 'v', "verbose", &gDNSQuery_Verbose, "Prints the DNS message to be sent to the server." ),
+ CLI_OPTION_END()
+};
+
+#if( DNSSDUTIL_INCLUDE_DNSCRYPT )
+//===========================================================================================================================
+// DNSCrypt Command Options
+//===========================================================================================================================
+
+static char * gDNSCrypt_ProviderName = NULL;
+static char * gDNSCrypt_ProviderKey = NULL;
+static char * gDNSCrypt_Name = NULL;
+static char * gDNSCrypt_Type = NULL;
+static char * gDNSCrypt_Server = NULL;
+static int gDNSCrypt_TimeLimitSecs = 5;
+static int gDNSCrypt_RawRData = false;
+static int gDNSCrypt_Verbose = false;
+
+static CLIOption kDNSCryptOpts[] =
+{
+ StringOption( 'p', "providerName", &gDNSCrypt_ProviderName, "name", "The DNSCrypt provider name.", true ),
+ StringOption( 'k', "providerKey", &gDNSCrypt_ProviderKey, "hex string", "The DNSCrypt provider's public signing key.", true ),
+ StringOption( 'n', "name", &gDNSCrypt_Name, "name", "Question name (QNAME) to put in DNS query message.", true ),
+ StringOption( 't', "type", &gDNSCrypt_Type, "type", "Question type (QTYPE) to put in DNS query message.", true ),
+ StringOption( 's', "server", &gDNSCrypt_Server, "IP address", "DNS server's IPv4 or IPv6 address.", true ),
+ IntegerOption( 'l', "timeLimit", &gDNSCrypt_TimeLimitSecs, "seconds", "Specifies query time limit. Use '-1' for no time limit and '0' to exit immediately after sending.", false ),
+ BooleanOption( 'r', "raw", &gDNSCrypt_RawRData, "Present record data as a hexdump." ),
+ BooleanOption( 'v', "verbose", &gDNSCrypt_Verbose, "Prints the DNS message to be sent to the server." ),
+ CLI_OPTION_END()
+};
+#endif
+
+//===========================================================================================================================
+// MDNSQuery Command Options
+//===========================================================================================================================
+
+static char * gMDNSQuery_Name = NULL;
+static char * gMDNSQuery_Type = NULL;
+static int gMDNSQuery_SourcePort = 0;
+static int gMDNSQuery_IsQU = false;
+static int gMDNSQuery_RawRData = false;
+static int gMDNSQuery_UseIPv4 = false;
+static int gMDNSQuery_UseIPv6 = false;
+static int gMDNSQuery_AllResponses = false;
+
+static CLIOption kMDNSQueryOpts[] =
+{
+ StringOption( 'i', "interface", &gInterface, "name or index", "Network interface by name or index.", true ),
+ StringOption( 'n', "name", &gMDNSQuery_Name, "name", "Question name (QNAME) to put in mDNS message.", true ),
+ StringOption( 't', "type", &gMDNSQuery_Type, "type", "Question type (QTYPE) to put in mDNS message.", true ),
+ IntegerOption( 'p', "sourcePort", &gMDNSQuery_SourcePort, "port number", "UDP source port to use when sending mDNS messages. Default is 5353 for QM questions.", false ),
+ BooleanOption( 'u', "QU", &gMDNSQuery_IsQU, "Set the unicast-response bit, i.e., send a QU question." ),
+ BooleanOption( 'r', "raw", &gMDNSQuery_RawRData, "Present record data as a hexdump." ),
+ BooleanOption( 0 , "ipv4", &gMDNSQuery_UseIPv4, "Use IPv4." ),
+ BooleanOption( 0 , "ipv6", &gMDNSQuery_UseIPv6, "Use IPv6." ),
+ BooleanOption( 'a', "allResponses", &gMDNSQuery_AllResponses, "Print all responses." ),
+ CLI_OPTION_END()
+};
+
+//===========================================================================================================================
+// PIDToUUID Command Options
+//===========================================================================================================================
+
+static int gPIDToUUID_PID = 0;
+
+static CLIOption kPIDToUUIDOpts[] =
+{
+ IntegerOption( 'p', "pid", &gPIDToUUID_PID, "PID", "Process ID.", true ),
+ CLI_OPTION_END()
+};
+
+//===========================================================================================================================
+// Command Table
+//===========================================================================================================================
+
+static OSStatus VersionOptionCallback( CLIOption *inOption, const char *inArg, int inUnset );
+
+static void BrowseCmd( void );
+static void GetAddrInfoCmd( void );
+static void QueryRecordCmd( void );
+static void RegisterCmd( void );
+static void RegisterRecordCmd( void );
+static void ResolveCmd( void );
+static void ReconfirmCmd( void );
+static void GetAddrInfoPOSIXCmd( void );
+static void ReverseLookupCmd( void );
+static void BrowseAllCmd( void );
+static void GetAddrInfoStressCmd( void );
+static void DNSQueryCmd( void );
+#if( DNSSDUTIL_INCLUDE_DNSCRYPT )
+static void DNSCryptCmd( void );
+#endif
+static void MDNSQueryCmd( void );
+static void PIDToUUIDCmd( void );
+static void DaemonVersionCmd( void );
+
+static CLIOption kGlobalOpts[] =
+{
+ CLI_OPTION_CALLBACK_EX( 'V', "version", VersionOptionCallback, NULL, NULL,
+ kCLIOptionFlags_NoArgument | kCLIOptionFlags_GlobalOnly, "Displays the version of this tool.", NULL ),
+ CLI_OPTION_HELP(),
+
+ // Common commands.
+
+ Command( "browse", BrowseCmd, kBrowseOpts, "Uses DNSServiceBrowse() to browse for one or more service types.", false ),
+ Command( "getAddrInfo", GetAddrInfoCmd, kGetAddrInfoOpts, "Uses DNSServiceGetAddrInfo() to resolve a hostname to IP addresses.", false ),
+ Command( "queryRecord", QueryRecordCmd, kQueryRecordOpts, "Uses DNSServiceQueryRecord() to query for an arbitrary DNS record.", false ),
+ Command( "register", RegisterCmd, kRegisterOpts, "Uses DNSServiceRegister() to register a service.", false ),
+ Command( "registerRecord", RegisterRecordCmd, kRegisterRecordOpts, "Uses DNSServiceRegisterRecord() to register a record.", false ),
+ Command( "resolve", ResolveCmd, kResolveOpts, "Uses DNSServiceResolve() to resolve a service.", false ),
+ Command( "reconfirm", ReconfirmCmd, kReconfirmOpts, "Uses DNSServiceReconfirmRecord() to reconfirm a record.", false ),
+ Command( "getaddrinfo-posix", GetAddrInfoPOSIXCmd, kGetAddrInfoPOSIXOpts, "Uses getaddrinfo() to resolve a hostname to IP addresses.", false ),
+ Command( "reverseLookup", ReverseLookupCmd, kReverseLookupOpts, "Uses DNSServiceQueryRecord() to perform a reverse IP address lookup.", false ),
+ Command( "browseAll", BrowseAllCmd, kBrowseAllOpts, "Browse and resolve all, or just some, services.", false ),
+
+ // Uncommon commands.
+
+ Command( "getAddrInfoStress", GetAddrInfoStressCmd, kGetAddrInfoStressOpts, "Runs DNSServiceGetAddrInfo() stress testing.", true ),
+ Command( "DNSQuery", DNSQueryCmd, kDNSQueryOpts, "Crafts and sends a DNS query.", true ),
+#if( DNSSDUTIL_INCLUDE_DNSCRYPT )
+ Command( "DNSCrypt", DNSCryptCmd, kDNSCryptOpts, "Crafts and sends a DNSCrypt query.", true ),
+#endif
+ Command( "mDNSQuery", MDNSQueryCmd, kMDNSQueryOpts, "Crafts and sends an mDNS query over the specified interface.", true ),
+ Command( "pid2uuid", PIDToUUIDCmd, kPIDToUUIDOpts, "Prints the UUID of a process.", true ),
+ Command( "daemonVersion", DaemonVersionCmd, NULL, "Prints the version of the DNS-SD daemon.", true ),
+
+ CLI_COMMAND_HELP(),
+ CLI_OPTION_END()
+};
+
+//===========================================================================================================================
+// Helper Prototypes
+//===========================================================================================================================
+
+#define kExitReason_OneShotDone "one-shot done"
+#define kExitReason_ReceivedResponse "received response"
+#define kExitReason_SIGINT "interrupt signal"
+#define kExitReason_Timeout "timeout"
+#define kExitReason_TimeLimit "time limit"
+
+static void Exit( void *inContext ) ATTRIBUTE_NORETURN;
+
+#define kTimestampBufLen 27
+
+static char * GetTimestampStr( char inBuffer[ kTimestampBufLen ] );
+static DNSServiceFlags GetDNSSDFlagsFromOpts( void );
+
+typedef enum
+{
+ kConnectionType_None = 0,
+ kConnectionType_Normal = 1,
+ kConnectionType_DelegatePID = 2,
+ kConnectionType_DelegateUUID = 3
+
+} ConnectionType;
+
+typedef struct
+{
+ ConnectionType type;
+ union
+ {
+ int32_t pid;
+ uint8_t uuid[ 16 ];
+
+ } delegate;
+
+} ConnectionDesc;
+
+static OSStatus
+ CreateConnectionFromArgString(
+ const char * inString,
+ dispatch_queue_t inQueue,
+ DNSServiceRef * outSDRef,
+ ConnectionDesc * outDesc );
+static OSStatus InterfaceIndexFromArgString( const char *inString, uint32_t *outIndex );
+static OSStatus RecordDataFromArgString( const char *inString, uint8_t **outDataPtr, size_t *outDataLen );
+static OSStatus RecordTypeFromArgString( const char *inString, uint16_t *outValue );
+static OSStatus RecordClassFromArgString( const char *inString, uint16_t *outValue );
+
+#define kInterfaceNameBufLen ( Max( IF_NAMESIZE, 16 ) + 1 )
+
+static char * InterfaceIndexToName( uint32_t inIfIndex, char inNameBuf[ kInterfaceNameBufLen ] );
+static const char * RecordTypeToString( unsigned int inValue );
+
+// DNS message helpers
+
+typedef struct
+{
+ uint8_t id[ 2 ];
+ uint8_t flags[ 2 ];
+ uint8_t questionCount[ 2 ];
+ uint8_t answerCount[ 2 ];
+ uint8_t authorityCount[ 2 ];
+ uint8_t additionalCount[ 2 ];
+
+} DNSHeader;
+
+#define kDNSHeaderLength 12
+check_compile_time( sizeof( DNSHeader ) == kDNSHeaderLength );
+
+#define DNSHeaderGetID( HDR ) ReadBig16( ( HDR )->id )
+#define DNSHeaderGetFlags( HDR ) ReadBig16( ( HDR )->flags )
+#define DNSHeaderGetQuestionCount( HDR ) ReadBig16( ( HDR )->questionCount )
+#define DNSHeaderGetAnswerCount( HDR ) ReadBig16( ( HDR )->answerCount )
+#define DNSHeaderGetAuthorityCount( HDR ) ReadBig16( ( HDR )->authorityCount )
+#define DNSHeaderGetAdditionalCount( HDR ) ReadBig16( ( HDR )->additionalCount )
+
+static OSStatus
+ DNSMessageExtractDomainName(
+ const uint8_t * inMsgPtr,
+ size_t inMsgLen,
+ const uint8_t * inNamePtr,
+ uint8_t inBuf[ kDomainNameLengthMax ],
+ const uint8_t ** outNextPtr );
+static OSStatus
+ DNSMessageExtractDomainNameString(
+ const void * inMsgPtr,
+ size_t inMsgLen,
+ const void * inNamePtr,
+ char inBuf[ kDNSServiceMaxDomainName ],
+ const uint8_t ** outNextPtr );
+static OSStatus
+ DNSMessageExtractRecord(
+ const uint8_t * inMsgPtr,
+ size_t inMsgLen,
+ const uint8_t * inPtr,
+ uint8_t inNameBuf[ kDomainNameLengthMax ],
+ uint16_t * outType,
+ uint16_t * outClass,
+ uint32_t * outTTL,
+ const uint8_t ** outRDataPtr,
+ size_t * outRDataLen,
+ const uint8_t ** outPtr );
+static OSStatus DNSMessageGetAnswerSection( const uint8_t *inMsgPtr, size_t inMsgLen, const uint8_t **outPtr );
+static OSStatus
+ DNSRecordDataToString(
+ const void * inRDataPtr,
+ size_t inRDataLen,
+ unsigned int inRDataType,
+ const void * inMsgPtr,
+ size_t inMsgLen,
+ char ** outString );
+static OSStatus
+ DomainNameAppendString(
+ uint8_t inDomainName[ kDomainNameLengthMax ],
+ const char * inString,
+ uint8_t ** outEndPtr );
+static Boolean DomainNameEqual( const uint8_t *inName1, const uint8_t *inName2 );
+static OSStatus
+ DomainNameToString(
+ const uint8_t * inDomainName,
+ const uint8_t * inEnd,
+ char inBuf[ kDNSServiceMaxDomainName ],
+ const uint8_t ** outNextPtr );
+
+static OSStatus PrintDNSMessage( const uint8_t *inMsgPtr, size_t inMsgLen, Boolean inIsMDNS, Boolean inPrintRaw );
+
+#define PrintMDNSMessage( MSGPTR, MSGLEN, RAW ) PrintDNSMessage( MSGPTR, MSGLEN, true, RAW )
+#define PrintUDNSMessage( MSGPTR, MSGLEN, RAW ) PrintDNSMessage( MSGPTR, MSGLEN, false, RAW )
+
+#define kDNSQueryMessageMaxLen ( kDNSHeaderLength + kDomainNameLengthMax + 4 )
+
+static OSStatus
+ WriteDNSQueryMessage(
+ uint8_t inMsg[ kDNSQueryMessageMaxLen ],
+ uint16_t inMsgID,
+ uint16_t inFlags,
+ const char * inQName,
+ uint16_t inQType,
+ uint16_t inQClass,
+ size_t * outMsgLen );
+
+// Dispatch helpers
+
+typedef void ( *DispatchHandler )( void *inContext );
+
+static OSStatus
+ DispatchSignalSourceCreate(
+ int inSignal,
+ DispatchHandler inEventHandler,
+ void * inContext,
+ dispatch_source_t * outSource );
+static OSStatus
+ DispatchReadSourceCreate(
+ SocketRef inSock,
+ DispatchHandler inEventHandler,
+ DispatchHandler inCancelHandler,
+ void * inContext,
+ dispatch_source_t * outSource );
+static OSStatus
+ DispatchTimerCreate(
+ dispatch_time_t inStart,
+ uint64_t inIntervalNs,
+ uint64_t inLeewayNs,
+ DispatchHandler inEventHandler,
+ DispatchHandler inCancelHandler,
+ void * inContext,
+ dispatch_source_t * outTimer );
+
+static const char * ServiceTypeDescription( const char *inName );
+
+typedef struct
+{
+ SocketRef sock;
+ void * context;
+
+} SocketContext;
+
+static void SocketContextCancelHandler( void *inContext );
+static OSStatus StringToInt32( const char *inString, int32_t *outValue );
+static OSStatus StringToUInt32( const char *inString, uint32_t *outValue );
+
+#define AddRmvString( X ) ( ( (X) & kDNSServiceFlagsAdd ) ? "Add" : "Rmv" )
+#define Unused( X ) (void)(X)
+
+//===========================================================================================================================
+// main
+//===========================================================================================================================
+
+int main( int argc, const char **argv )
+{
+ // Route DebugServices logging output to stderr.
+
+ dlog_control( "DebugServices:output=file;stderr" );
+
+ CLIInit( argc, argv );
+ CLIParse( kGlobalOpts, kCLIFlags_None );
+
+ return( gExitCode );
+}
+
+//===========================================================================================================================
+// VersionOptionCallback
+//===========================================================================================================================
+
+static OSStatus VersionOptionCallback( CLIOption *inOption, const char *inArg, int inUnset )
+{
+ const char * srcVers;
+#if( MDNSRESPONDER_PROJECT )
+ char srcStr[ 16 ];
+#endif
+
+ Unused( inOption );
+ Unused( inArg );
+ Unused( inUnset );
+
+#if( MDNSRESPONDER_PROJECT )
+ srcVers = SourceVersionToCString( _DNS_SD_H, srcStr );
+#else
+ srcVers = DNSSDUTIL_SOURCE_VERSION;
+#endif
+ FPrintF( stdout, "%s version %v (%s)\n", gProgramName, kDNSSDUtilNumVersion, srcVers );
+
+ return( kEndingErr );
+}
+
+//===========================================================================================================================
+// BrowseCmd
+//===========================================================================================================================
+
+typedef struct BrowseResolveOp BrowseResolveOp;
+
+struct BrowseResolveOp
+{
+ BrowseResolveOp * next; // Next resolve operation in list.
+ DNSServiceRef sdRef; // sdRef of the DNSServiceResolve or DNSServiceQueryRecord operation.
+ char * fullName; // Full name of the service to resolve.
+ uint32_t interfaceIndex; // Interface index of the DNSServiceResolve or DNSServiceQueryRecord operation.
+};
+
+typedef struct
+{
+ DNSServiceRef mainRef; // Main sdRef for shared connection.
+ DNSServiceRef * opRefs; // Array of sdRefs for individual Browse operarions.
+ size_t opRefsCount; // Count of array of sdRefs for non-shared connections.
+ const char * domain; // Domain for DNSServiceBrowse operation(s).
+ DNSServiceFlags flags; // Flags for DNSServiceBrowse operation(s).
+ char ** serviceTypes; // Array of service types to browse for.
+ size_t serviceTypesCount; // Count of array of service types to browse for.
+ int timeLimitSecs; // Time limit of DNSServiceBrowse operation in seconds.
+ BrowseResolveOp * resolveList; // List of resolve and/or TXT record query operations.
+ uint32_t ifIndex; // Interface index of DNSServiceBrowse operation(s).
+ Boolean printedHeader; // True if results header has been printed.
+ Boolean doResolve; // True if service instances are to be resolved.
+ Boolean doResolveTXTOnly; // True if TXT records of service instances are to be queried.
+
+} BrowseContext;
+
+static void BrowsePrintPrologue( const BrowseContext *inContext );
+static void BrowseContextFree( BrowseContext *inContext );
+static OSStatus BrowseResolveOpCreate( const char *inFullName, uint32_t inInterfaceIndex, BrowseResolveOp **outOp );
+static void BrowseResolveOpFree( BrowseResolveOp *inOp );
+static void DNSSD_API
+ BrowseCallback(
+ DNSServiceRef inSDRef,
+ DNSServiceFlags inFlags,
+ uint32_t inInterfaceIndex,
+ DNSServiceErrorType inError,
+ const char * inName,
+ const char * inRegType,
+ const char * inDomain,
+ void * inContext );
+static void DNSSD_API
+ BrowseResolveCallback(
+ DNSServiceRef inSDRef,
+ DNSServiceFlags inFlags,
+ uint32_t inInterfaceIndex,
+ DNSServiceErrorType inError,
+ const char * inFullName,
+ const char * inHostname,
+ uint16_t inPort,
+ uint16_t inTXTLen,
+ const unsigned char * inTXTPtr,
+ void * inContext );
+static void DNSSD_API
+ BrowseQueryRecordCallback(
+ DNSServiceRef inSDRef,
+ DNSServiceFlags inFlags,
+ uint32_t inInterfaceIndex,
+ DNSServiceErrorType inError,
+ const char * inFullName,
+ uint16_t inType,
+ uint16_t inClass,
+ uint16_t inRDataLen,
+ const void * inRDataPtr,
+ uint32_t inTTL,
+ void * inContext );
+
+static void BrowseCmd( void )
+{
+ OSStatus err;
+ size_t i;
+ BrowseContext * context = NULL;
+ dispatch_source_t signalSource = NULL;
+ int useMainConnection;
+
+ // Set up SIGINT handler.
+
+ signal( SIGINT, SIG_IGN );
+ err = DispatchSignalSourceCreate( SIGINT, Exit, kExitReason_SIGINT, &signalSource );
+ require_noerr( err, exit );
+ dispatch_resume( signalSource );
+
+ // Create context.
+
+ context = (BrowseContext *) calloc( 1, sizeof( *context ) );
+ require_action( context, exit, err = kNoMemoryErr );
+
+ context->opRefs = (DNSServiceRef *) calloc( gBrowse_ServiceTypesCount, sizeof( DNSServiceRef ) );
+ require_action( context->opRefs, exit, err = kNoMemoryErr );
+ context->opRefsCount = gBrowse_ServiceTypesCount;
+
+ // Check command parameters.
+
+ if( gBrowse_TimeLimitSecs < 0 )
+ {
+ FPrintF( stderr, "Invalid time limit: %d seconds.\n", gBrowse_TimeLimitSecs );
+ err = kParamErr;
+ goto exit;
+ }
+
+ // Create main connection.
+
+ if( gConnectionOpt )
+ {
+ err = CreateConnectionFromArgString( gConnectionOpt, dispatch_get_main_queue(), &context->mainRef, NULL );
+ require_noerr_quiet( err, exit );
+ useMainConnection = true;
+ }
+ else
+ {
+ useMainConnection = false;
+ }
+
+ // Get flags.
+
+ context->flags = GetDNSSDFlagsFromOpts();
+ if( useMainConnection ) context->flags |= kDNSServiceFlagsShareConnection;
+
+ // Get interface.
+
+ err = InterfaceIndexFromArgString( gInterface, &context->ifIndex );
+ require_noerr_quiet( err, exit );
+
+ // Set remaining parameters.
+
+ context->serviceTypes = gBrowse_ServiceTypes;
+ context->serviceTypesCount = gBrowse_ServiceTypesCount;
+ context->domain = gBrowse_Domain;
+ context->doResolve = gBrowse_DoResolve ? true : false;
+ context->timeLimitSecs = gBrowse_TimeLimitSecs;
+ context->doResolveTXTOnly = gBrowse_QueryTXT ? true : false;
+
+ // Print prologue.
+
+ BrowsePrintPrologue( context );
+
+ // Start operation(s).
+
+ for( i = 0; i < context->serviceTypesCount; ++i )
+ {
+ DNSServiceRef sdRef;
+
+ if( useMainConnection ) sdRef = context->mainRef;
+ err = DNSServiceBrowse( &sdRef, context->flags, context->ifIndex, context->serviceTypes[ i ], context->domain,
+ BrowseCallback, context );
+ require_noerr( err, exit );
+
+ context->opRefs[ i ] = sdRef;
+ if( !useMainConnection )
+ {
+ err = DNSServiceSetDispatchQueue( context->opRefs[ i ], dispatch_get_main_queue() );
+ require_noerr( err, exit );
+ }
+ }
+
+ // Set time limit.
+
+ if( context->timeLimitSecs > 0 )
+ {
+ dispatch_after_f( dispatch_time_seconds( context->timeLimitSecs ), dispatch_get_main_queue(),
+ kExitReason_TimeLimit, Exit );
+ }
+ dispatch_main();
+
+exit:
+ dispatch_source_forget( &signalSource );
+ if( context ) BrowseContextFree( context );
+ if( err ) exit( 1 );
+}
+
+//===========================================================================================================================
+// BrowsePrintPrologue
+//===========================================================================================================================
+
+static void BrowsePrintPrologue( const BrowseContext *inContext )
+{
+ const int timeLimitSecs = inContext->timeLimitSecs;
+ const char * const * serviceType = (const char **) inContext->serviceTypes;
+ const char * const * const end = (const char **) inContext->serviceTypes + inContext->serviceTypesCount;
+ char time[ kTimestampBufLen ];
+ char ifName[ kInterfaceNameBufLen ];
+
+ InterfaceIndexToName( inContext->ifIndex, ifName );
+
+ FPrintF( stdout, "Flags: %#{flags}\n", inContext->flags, kDNSServiceFlagsDescriptors );
+ FPrintF( stdout, "Interface: %d (%s)\n", (int32_t) inContext->ifIndex, ifName );
+ FPrintF( stdout, "Service types: %s", *serviceType++ );
+ while( serviceType < end ) FPrintF( stdout, ", %s", *serviceType++ );
+ FPrintF( stdout, "\n" );
+ FPrintF( stdout, "Domain: %s\n", inContext->domain ? inContext->domain : "<NULL> (default domains)" );
+ FPrintF( stdout, "Time limit: " );
+ if( timeLimitSecs > 0 ) FPrintF( stdout, "%d second%?c\n", timeLimitSecs, timeLimitSecs != 1, 's' );
+ else FPrintF( stdout, "∞\n" );
+ FPrintF( stdout, "Start time: %s\n", GetTimestampStr( time ) );
+ FPrintF( stdout, "---\n" );
+}
+
+//===========================================================================================================================
+// BrowseContextFree
+//===========================================================================================================================
+
+static void BrowseContextFree( BrowseContext *inContext )
+{
+ size_t i;
+
+ for( i = 0; i < inContext->opRefsCount; ++i )
+ {
+ DNSServiceForget( &inContext->opRefs[ i ] );
+ }
+ if( inContext->serviceTypes )
+ {
+ StringArray_Free( inContext->serviceTypes, inContext->serviceTypesCount );
+ inContext->serviceTypes = NULL;
+ inContext->serviceTypesCount = 0;
+ }
+ DNSServiceForget( &inContext->mainRef );
+ free( inContext );
+}
+
+//===========================================================================================================================
+// BrowseResolveOpCreate
+//===========================================================================================================================
+
+static OSStatus BrowseResolveOpCreate( const char *inFullName, uint32_t inInterfaceIndex, BrowseResolveOp **outOp )
+{
+ OSStatus err;
+ BrowseResolveOp * resolveOp;
+
+ resolveOp = (BrowseResolveOp *) calloc( 1, sizeof( *resolveOp ) );
+ require_action( resolveOp, exit, err = kNoMemoryErr );
+
+ resolveOp->fullName = strdup( inFullName );
+ require_action( resolveOp->fullName, exit, err = kNoMemoryErr );
+
+ resolveOp->interfaceIndex = inInterfaceIndex;
+
+ *outOp = resolveOp;
+ resolveOp = NULL;
+ err = kNoErr;
+
+exit:
+ if( resolveOp ) BrowseResolveOpFree( resolveOp );
+ return( err );
+}
+
+//===========================================================================================================================
+// BrowseResolveOpFree
+//===========================================================================================================================
+
+static void BrowseResolveOpFree( BrowseResolveOp *inOp )
+{
+ DNSServiceForget( &inOp->sdRef );
+ ForgetMem( &inOp->fullName );
+ free( inOp );
+}
+
+//===========================================================================================================================
+// BrowseCallback
+//===========================================================================================================================
+
+static void DNSSD_API
+ BrowseCallback(
+ DNSServiceRef inSDRef,
+ DNSServiceFlags inFlags,
+ uint32_t inInterfaceIndex,
+ DNSServiceErrorType inError,
+ const char * inName,
+ const char * inRegType,
+ const char * inDomain,
+ void * inContext )
+{
+ BrowseContext * const context = (BrowseContext *) inContext;
+ OSStatus err;
+ BrowseResolveOp * newOp = NULL;
+ BrowseResolveOp ** p;
+ char fullName[ kDNSServiceMaxDomainName ];
+ char time[ kTimestampBufLen ];
+
+ Unused( inSDRef );
+
+ GetTimestampStr( time );
+
+ err = inError;
+ require_noerr( err, exit );
+
+ if( !context->printedHeader )
+ {
+ FPrintF( stdout, "%-26s A/R Flags IF %-20s %-20s Instance Name\n", "Timestamp", "Domain", "Service Type" );
+ context->printedHeader = true;
+ }
+ FPrintF( stdout, "%-26s %-3s %5X %2d %-20s %-20s %s\n",
+ time, AddRmvString( inFlags ), inFlags, (int32_t) inInterfaceIndex, inDomain, inRegType, inName );
+
+ if( !context->doResolve && !context->doResolveTXTOnly ) goto exit;
+
+ err = DNSServiceConstructFullName( fullName, inName, inRegType, inDomain );
+ require_noerr( err, exit );
+
+ if( inFlags & kDNSServiceFlagsAdd )
+ {
+ DNSServiceRef sdRef;
+ DNSServiceFlags flags;
+
+ err = BrowseResolveOpCreate( fullName, inInterfaceIndex, &newOp );
+ require_noerr( err, exit );
+
+ if( context->mainRef )
+ {
+ sdRef = context->mainRef;
+ flags = kDNSServiceFlagsShareConnection;
+ }
+ else
+ {
+ flags = 0;
+ }
+ if( context->doResolve )
+ {
+ err = DNSServiceResolve( &sdRef, flags, inInterfaceIndex, inName, inRegType, inDomain, BrowseResolveCallback,
+ NULL );
+ require_noerr( err, exit );
+ }
+ else
+ {
+ err = DNSServiceQueryRecord( &sdRef, flags, inInterfaceIndex, fullName, kDNSServiceType_TXT, kDNSServiceClass_IN,
+ BrowseQueryRecordCallback, NULL );
+ require_noerr( err, exit );
+ }
+
+ newOp->sdRef = sdRef;
+ if( !context->mainRef )
+ {
+ err = DNSServiceSetDispatchQueue( newOp->sdRef, dispatch_get_main_queue() );
+ require_noerr( err, exit );
+ }
+ for( p = &context->resolveList; *p; p = &( *p )->next ) {}
+ *p = newOp;
+ newOp = NULL;
+ }
+ else
+ {
+ BrowseResolveOp * resolveOp;
+
+ for( p = &context->resolveList; ( resolveOp = *p ) != NULL; p = &resolveOp->next )
+ {
+ if( ( resolveOp->interfaceIndex == inInterfaceIndex ) && ( strcasecmp( resolveOp->fullName, fullName ) == 0 ) )
+ {
+ break;
+ }
+ }
+ if( resolveOp )
+ {
+ *p = resolveOp->next;
+ BrowseResolveOpFree( resolveOp );
+ }
+ }
+
+exit:
+ if( newOp ) BrowseResolveOpFree( newOp );
+ if( err ) exit( 1 );
+}
+
+//===========================================================================================================================
+// BrowseQueryRecordCallback
+//===========================================================================================================================
+
+static void DNSSD_API
+ BrowseQueryRecordCallback(
+ DNSServiceRef inSDRef,
+ DNSServiceFlags inFlags,
+ uint32_t inInterfaceIndex,
+ DNSServiceErrorType inError,
+ const char * inFullName,
+ uint16_t inType,
+ uint16_t inClass,
+ uint16_t inRDataLen,
+ const void * inRDataPtr,
+ uint32_t inTTL,
+ void * inContext )
+{
+ OSStatus err;
+ char time[ kTimestampBufLen ];
+
+ Unused( inSDRef );
+ Unused( inClass );
+ Unused( inTTL );
+ Unused( inContext );
+
+ GetTimestampStr( time );
+
+ err = inError;
+ require_noerr( err, exit );
+ require_action( inType == kDNSServiceType_TXT, exit, err = kTypeErr );
+
+ FPrintF( stdout, "%s %s %s TXT on interface %d\n TXT: %#{txt}\n",
+ time, AddRmvString( inFlags ), inFullName, (int32_t) inInterfaceIndex, inRDataPtr, (size_t) inRDataLen );
+
+exit:
+ if( err ) exit( 1 );
+}
+
+//===========================================================================================================================
+// BrowseResolveCallback
+//===========================================================================================================================
+
+static void DNSSD_API
+ BrowseResolveCallback(
+ DNSServiceRef inSDRef,
+ DNSServiceFlags inFlags,
+ uint32_t inInterfaceIndex,
+ DNSServiceErrorType inError,
+ const char * inFullName,
+ const char * inHostname,
+ uint16_t inPort,
+ uint16_t inTXTLen,
+ const unsigned char * inTXTPtr,
+ void * inContext )
+{
+ char time[ kTimestampBufLen ];
+ char errorStr[ 64 ];
+
+ Unused( inSDRef );
+ Unused( inFlags );
+ Unused( inContext );
+
+ GetTimestampStr( time );
+
+ if( inError ) SNPrintF( errorStr, sizeof( errorStr ), " error %#m", inError );
+
+ FPrintF( stdout, "%s %s can be reached at %s:%u (interface %d)%?s\n",
+ time, inFullName, inHostname, ntohs( inPort ), (int32_t) inInterfaceIndex, inError, errorStr );
+ if( inTXTLen == 1 )
+ {
+ FPrintF( stdout, " TXT record: %#H\n", inTXTPtr, (int) inTXTLen, INT_MAX );
+ }
+ else
+ {
+ FPrintF( stdout, " TXT record: %#{txt}\n", inTXTPtr, (size_t) inTXTLen );
+ }
+}
+
+//===========================================================================================================================
+// GetAddrInfoCmd
+//===========================================================================================================================
+
+typedef struct
+{
+ DNSServiceRef mainRef; // Main sdRef for shared connection.
+ DNSServiceRef opRef; // sdRef for the DNSServiceGetAddrInfo operation.
+ const char * name; // Hostname to resolve.
+ DNSServiceFlags flags; // Flags argument for DNSServiceGetAddrInfo().
+ DNSServiceProtocol protocols; // Protocols argument for DNSServiceGetAddrInfo().
+ uint32_t ifIndex; // Interface index argument for DNSServiceGetAddrInfo().
+ int timeLimitSecs; // Time limit for the DNSServiceGetAddrInfo() operation in seconds.
+ Boolean printedHeader; // True if the results header has been printed.
+ Boolean oneShotMode; // True if command is done after the first set of results (one-shot mode).
+ Boolean needIPv4; // True if in one-shot mode and an IPv4 result is needed.
+ Boolean needIPv6; // True if in one-shot mode and an IPv6 result is needed.
+
+} GetAddrInfoContext;
+
+static void GetAddrInfoPrintPrologue( const GetAddrInfoContext *inContext );
+static void GetAddrInfoContextFree( GetAddrInfoContext *inContext );
+static void DNSSD_API
+ GetAddrInfoCallback(
+ DNSServiceRef inSDRef,
+ DNSServiceFlags inFlags,
+ uint32_t inInterfaceIndex,
+ DNSServiceErrorType inError,
+ const char * inHostname,
+ const struct sockaddr * inSockAddr,
+ uint32_t inTTL,
+ void * inContext );
+
+static void GetAddrInfoCmd( void )
+{
+ OSStatus err;
+ DNSServiceRef sdRef;
+ GetAddrInfoContext * context = NULL;
+ dispatch_source_t signalSource = NULL;
+ int useMainConnection;
+
+ // Set up SIGINT handler.
+
+ signal( SIGINT, SIG_IGN );
+ err = DispatchSignalSourceCreate( SIGINT, Exit, kExitReason_SIGINT, &signalSource );
+ require_noerr( err, exit );
+ dispatch_resume( signalSource );
+
+ // Check command parameters.
+
+ if( gGetAddrInfo_TimeLimitSecs < 0 )
+ {
+ FPrintF( stderr, "Invalid time limit: %d s.\n", gGetAddrInfo_TimeLimitSecs );
+ err = kParamErr;
+ goto exit;
+ }
+
+ // Create context.
+
+ context = (GetAddrInfoContext *) calloc( 1, sizeof( *context ) );
+ require_action( context, exit, err = kNoMemoryErr );
+
+ // Create main connection.
+
+ if( gConnectionOpt )
+ {
+ err = CreateConnectionFromArgString( gConnectionOpt, dispatch_get_main_queue(), &context->mainRef, NULL );
+ require_noerr_quiet( err, exit );
+ useMainConnection = true;
+ }
+ else
+ {
+ useMainConnection = false;
+ }
+
+ // Get flags.
+
+ context->flags = GetDNSSDFlagsFromOpts();
+ if( useMainConnection ) context->flags |= kDNSServiceFlagsShareConnection;
+
+ // Get interface.
+
+ err = InterfaceIndexFromArgString( gInterface, &context->ifIndex );
+ require_noerr_quiet( err, exit );
+
+ // Set remaining parameters.
+
+ context->name = gGetAddrInfo_Name;
+ context->timeLimitSecs = gGetAddrInfo_TimeLimitSecs;
+ if( gGetAddrInfo_ProtocolIPv4 ) context->protocols |= kDNSServiceProtocol_IPv4;
+ if( gGetAddrInfo_ProtocolIPv6 ) context->protocols |= kDNSServiceProtocol_IPv6;
+ if( gGetAddrInfo_OneShot )
+ {
+ context->oneShotMode = true;
+ context->needIPv4 = ( gGetAddrInfo_ProtocolIPv4 || !gGetAddrInfo_ProtocolIPv6 ) ? true : false;
+ context->needIPv6 = ( gGetAddrInfo_ProtocolIPv6 || !gGetAddrInfo_ProtocolIPv4 ) ? true : false;
+ }
+
+ // Print prologue.
+
+ GetAddrInfoPrintPrologue( context );
+
+ // Start operation.
+
+ if( useMainConnection ) sdRef = context->mainRef;
+ err = DNSServiceGetAddrInfo( &sdRef, context->flags, context->ifIndex, context->protocols, context->name,
+ GetAddrInfoCallback, context );
+ require_noerr( err, exit );
+
+ context->opRef = sdRef;
+ if( !useMainConnection )
+ {
+ err = DNSServiceSetDispatchQueue( context->opRef, dispatch_get_main_queue() );
+ require_noerr( err, exit );
+ }
+
+ // Set time limit.
+
+ if( context->timeLimitSecs > 0 )
+ {
+ dispatch_after_f( dispatch_time_seconds( context->timeLimitSecs ), dispatch_get_main_queue(),
+ kExitReason_TimeLimit, Exit );
+ }
+ dispatch_main();
+
+exit:
+ dispatch_source_forget( &signalSource );
+ if( context ) GetAddrInfoContextFree( context );
+ if( err ) exit( 1 );
+}
+
+//===========================================================================================================================
+// GetAddrInfoPrintPrologue
+//===========================================================================================================================
+
+static void GetAddrInfoPrintPrologue( const GetAddrInfoContext *inContext )
+{
+ const int timeLimitSecs = inContext->timeLimitSecs;
+ char ifName[ kInterfaceNameBufLen ];
+ char time[ kTimestampBufLen ];
+
+ InterfaceIndexToName( inContext->ifIndex, ifName );
+
+ FPrintF( stdout, "Flags: %#{flags}\n", inContext->flags, kDNSServiceFlagsDescriptors );
+ FPrintF( stdout, "Interface: %d (%s)\n", (int32_t) inContext->ifIndex, ifName );
+ FPrintF( stdout, "Protocols: %#{flags}\n", inContext->protocols, kDNSServiceProtocolDescriptors );
+ FPrintF( stdout, "Name: %s\n", inContext->name );
+ FPrintF( stdout, "Mode: %s\n", inContext->oneShotMode ? "one-shot" : "continuous" );
+ FPrintF( stdout, "Time limit: " );
+ if( timeLimitSecs > 0 ) FPrintF( stdout, "%d second%?c\n", timeLimitSecs, timeLimitSecs != 1, 's' );
+ else FPrintF( stdout, "∞\n" );
+ FPrintF( stdout, "Start time: %s\n", GetTimestampStr( time ) );
+ FPrintF( stdout, "---\n" );
+}
+
+//===========================================================================================================================
+// GetAddrInfoContextFree
+//===========================================================================================================================
+
+static void GetAddrInfoContextFree( GetAddrInfoContext *inContext )
+{
+ DNSServiceForget( &inContext->opRef );
+ DNSServiceForget( &inContext->mainRef );
+ free( inContext );
+}
+
+//===========================================================================================================================
+// GetAddrInfoCallback
+//===========================================================================================================================
+
+static void DNSSD_API
+ GetAddrInfoCallback(
+ DNSServiceRef inSDRef,
+ DNSServiceFlags inFlags,
+ uint32_t inInterfaceIndex,
+ DNSServiceErrorType inError,
+ const char * inHostname,
+ const struct sockaddr * inSockAddr,
+ uint32_t inTTL,
+ void * inContext )
+{
+ GetAddrInfoContext * const context = (GetAddrInfoContext *) inContext;
+ OSStatus err;
+ const char * addrStr;
+ char addrStrBuf[ kSockAddrStringMaxSize ];
+ char time[ kTimestampBufLen ];
+
+ Unused( inSDRef );
+
+ GetTimestampStr( time );
+
+ switch( inError )
+ {
+ case kDNSServiceErr_NoError:
+ case kDNSServiceErr_NoSuchRecord:
+ err = kNoErr;
+ break;
+
+ case kDNSServiceErr_Timeout:
+ Exit( kExitReason_Timeout );
+
+ default:
+ err = inError;
+ goto exit;
+ }
+
+ if( ( inSockAddr->sa_family != AF_INET ) && ( inSockAddr->sa_family != AF_INET6 ) )
+ {
+ dlogassert( "Unexpected address family: %d", inSockAddr->sa_family );
+ err = kTypeErr;
+ goto exit;
+ }
+
+ if( !inError )
+ {
+ err = SockAddrToString( inSockAddr, kSockAddrStringFlagsNone, addrStrBuf );
+ require_noerr( err, exit );
+ addrStr = addrStrBuf;
+ }
+ else
+ {
+ addrStr = ( inSockAddr->sa_family == AF_INET ) ? "No Such Record (A)" : "No Such Record (AAAA)";
+ }
+
+ if( !context->printedHeader )
+ {
+ FPrintF( stdout, "%-26s A/R Flags IF %-32s %-38s %6s\n", "Timestamp", "Hostname", "Address", "TTL" );
+ context->printedHeader = true;
+ }
+ FPrintF( stdout, "%-26s %s %5X %2d %-32s %-38s %6u\n",
+ time, AddRmvString( inFlags ), inFlags, (int32_t) inInterfaceIndex, inHostname, addrStr, inTTL );
+
+ if( context->oneShotMode )
+ {
+ if( inFlags & kDNSServiceFlagsAdd )
+ {
+ if( inSockAddr->sa_family == AF_INET ) context->needIPv4 = false;
+ else context->needIPv6 = false;
+ }
+ if( !( inFlags & kDNSServiceFlagsMoreComing ) && !context->needIPv4 && !context->needIPv6 )
+ {
+ Exit( kExitReason_OneShotDone );
+ }
+ }
+
+exit:
+ if( err ) exit( 1 );
+}
+
+//===========================================================================================================================
+// QueryRecordCmd
+//===========================================================================================================================
+
+typedef struct
+{
+ DNSServiceRef mainRef; // Main sdRef for shared connection.
+ DNSServiceRef opRef; // sdRef for the DNSServiceQueryRecord operation.
+ const char * recordName; // Resource record name argument for DNSServiceQueryRecord().
+ DNSServiceFlags flags; // Flags argument for DNSServiceQueryRecord().
+ uint32_t ifIndex; // Interface index argument for DNSServiceQueryRecord().
+ int timeLimitSecs; // Time limit for the DNSServiceQueryRecord() operation in seconds.
+ uint16_t recordType; // Resource record type argument for DNSServiceQueryRecord().
+ Boolean printedHeader; // True if the results header was printed.
+ Boolean oneShotMode; // True if command is done after the first set of results (one-shot mode).
+ Boolean gotRecord; // True if in one-shot mode and received at least one record of the desired type.
+ Boolean printRawRData; // True if RDATA results are not to be formatted.
+
+} QueryRecordContext;
+
+static void QueryRecordPrintPrologue( const QueryRecordContext *inContext );
+static void QueryRecordContextFree( QueryRecordContext *inContext );
+static void DNSSD_API
+ QueryRecordCallback(
+ DNSServiceRef inSDRef,
+ DNSServiceFlags inFlags,
+ uint32_t inInterfaceIndex,
+ DNSServiceErrorType inError,
+ const char * inFullName,
+ uint16_t inType,
+ uint16_t inClass,
+ uint16_t inRDataLen,
+ const void * inRDataPtr,
+ uint32_t inTTL,
+ void * inContext );
+
+static void QueryRecordCmd( void )
+{
+ OSStatus err;
+ DNSServiceRef sdRef;
+ QueryRecordContext * context = NULL;
+ dispatch_source_t signalSource = NULL;
+ int useMainConnection;
+
+ // Set up SIGINT handler.
+
+ signal( SIGINT, SIG_IGN );
+ err = DispatchSignalSourceCreate( SIGINT, Exit, kExitReason_SIGINT, &signalSource );
+ require_noerr( err, exit );
+ dispatch_resume( signalSource );
+
+ // Create context.
+
+ context = (QueryRecordContext *) calloc( 1, sizeof( *context ) );
+ require_action( context, exit, err = kNoMemoryErr );
+
+ // Check command parameters.
+
+ if( gQueryRecord_TimeLimitSecs < 0 )
+ {
+ FPrintF( stderr, "Invalid time limit: %d seconds.\n", gQueryRecord_TimeLimitSecs );
+ err = kParamErr;
+ goto exit;
+ }
+
+ // Create main connection.
+
+ if( gConnectionOpt )
+ {
+ err = CreateConnectionFromArgString( gConnectionOpt, dispatch_get_main_queue(), &context->mainRef, NULL );
+ require_noerr_quiet( err, exit );
+ useMainConnection = true;
+ }
+ else
+ {
+ useMainConnection = false;
+ }
+
+ // Get flags.
+
+ context->flags = GetDNSSDFlagsFromOpts();
+ if( useMainConnection ) context->flags |= kDNSServiceFlagsShareConnection;
+
+ // Get interface.
+
+ err = InterfaceIndexFromArgString( gInterface, &context->ifIndex );
+ require_noerr_quiet( err, exit );
+
+ // Get record type.
+
+ err = RecordTypeFromArgString( gQueryRecord_Type, &context->recordType );
+ require_noerr( err, exit );
+
+ // Set remaining parameters.
+
+ context->recordName = gQueryRecord_Name;
+ context->timeLimitSecs = gQueryRecord_TimeLimitSecs;
+ context->oneShotMode = gQueryRecord_OneShot ? true : false;
+ context->printRawRData = gQueryRecord_RawRData ? true : false;
+
+ // Print prologue.
+
+ QueryRecordPrintPrologue( context );
+
+ // Start operation.
+
+ if( useMainConnection ) sdRef = context->mainRef;
+ err = DNSServiceQueryRecord( &sdRef, context->flags, context->ifIndex, context->recordName, context->recordType,
+ kDNSServiceClass_IN, QueryRecordCallback, context );
+ require_noerr( err, exit );
+
+ context->opRef = sdRef;
+ if( !useMainConnection )
+ {
+ err = DNSServiceSetDispatchQueue( context->opRef, dispatch_get_main_queue() );
+ require_noerr( err, exit );
+ }
+
+ // Set time limit.
+
+ if( context->timeLimitSecs > 0 )
+ {
+ dispatch_after_f( dispatch_time_seconds( context->timeLimitSecs ), dispatch_get_main_queue(), kExitReason_TimeLimit,
+ Exit );
+ }
+ dispatch_main();
+
+exit:
+ dispatch_source_forget( &signalSource );
+ if( context ) QueryRecordContextFree( context );
+ if( err ) exit( 1 );
+}
+
+//===========================================================================================================================
+// QueryRecordContextFree
+//===========================================================================================================================
+
+static void QueryRecordContextFree( QueryRecordContext *inContext )
+{
+ DNSServiceForget( &inContext->opRef );
+ DNSServiceForget( &inContext->mainRef );
+ free( inContext );
+}
+
+//===========================================================================================================================
+// QueryRecordPrintPrologue
+//===========================================================================================================================
+
+static void QueryRecordPrintPrologue( const QueryRecordContext *inContext )
+{
+ const int timeLimitSecs = inContext->timeLimitSecs;
+ char ifName[ kInterfaceNameBufLen ];
+ char time[ kTimestampBufLen ];
+
+ InterfaceIndexToName( inContext->ifIndex, ifName );
+
+ FPrintF( stdout, "Flags: %#{flags}\n", inContext->flags, kDNSServiceFlagsDescriptors );
+ FPrintF( stdout, "Interface: %d (%s)\n", (int32_t) inContext->ifIndex, ifName );
+ FPrintF( stdout, "Name: %s\n", inContext->recordName );
+ FPrintF( stdout, "Type: %s (%u)\n", RecordTypeToString( inContext->recordType ), inContext->recordType );
+ FPrintF( stdout, "Mode: %s\n", inContext->oneShotMode ? "one-shot" : "continuous" );
+ FPrintF( stdout, "Time limit: " );
+ if( timeLimitSecs > 0 ) FPrintF( stdout, "%d second%?c\n", timeLimitSecs, timeLimitSecs != 1, 's' );
+ else FPrintF( stdout, "∞\n" );
+ FPrintF( stdout, "Start time: %s\n", GetTimestampStr( time ) );
+ FPrintF( stdout, "---\n" );
+
+}
+
+//===========================================================================================================================
+// QueryRecordCallback
+//===========================================================================================================================
+
+static void DNSSD_API
+ QueryRecordCallback(
+ DNSServiceRef inSDRef,
+ DNSServiceFlags inFlags,
+ uint32_t inInterfaceIndex,
+ DNSServiceErrorType inError,
+ const char * inFullName,
+ uint16_t inType,
+ uint16_t inClass,
+ uint16_t inRDataLen,
+ const void * inRDataPtr,
+ uint32_t inTTL,
+ void * inContext )
+{
+ QueryRecordContext * const context = (QueryRecordContext *) inContext;
+ OSStatus err;
+ char * rdataStr = NULL;
+ char time[ kTimestampBufLen ];
+
+ Unused( inSDRef );
+
+ GetTimestampStr( time );
+
+ switch( inError )
+ {
+ case kDNSServiceErr_NoError:
+ case kDNSServiceErr_NoSuchRecord:
+ err = kNoErr;
+ break;
+
+ case kDNSServiceErr_Timeout:
+ Exit( kExitReason_Timeout );
+
+ default:
+ err = inError;
+ goto exit;
+ }
+
+ if( inError == kDNSServiceErr_NoSuchRecord )
+ {
+ ASPrintF( &rdataStr, "No Such Record" );
+ }
+ else
+ {
+ if( !context->printRawRData ) DNSRecordDataToString( inRDataPtr, inRDataLen, inType, NULL, 0, &rdataStr );
+ if( !rdataStr )
+ {
+ ASPrintF( &rdataStr, "%#H", inRDataPtr, inRDataLen, INT_MAX );
+ require_action( rdataStr, exit, err = kNoMemoryErr );
+ }
+ }
+
+ if( !context->printedHeader )
+ {
+ FPrintF( stdout, "%-26s A/R Flags IF %-32s %-5s %-5s %6s RData\n", "Timestamp", "Name", "Type", "Class", "TTL" );
+ context->printedHeader = true;
+ }
+ FPrintF( stdout, "%-26s %-3s %5X %2d %-32s %-5s %?-5s%?5u %6u %s\n",
+ time, AddRmvString( inFlags ), inFlags, (int32_t) inInterfaceIndex, inFullName, RecordTypeToString( inType ),
+ ( inClass == kDNSServiceClass_IN ), "IN", ( inClass != kDNSServiceClass_IN ), inClass, inTTL, rdataStr );
+
+ if( context->oneShotMode )
+ {
+ if( ( inFlags & kDNSServiceFlagsAdd ) &&
+ ( ( context->recordType == kDNSServiceType_ANY ) || ( context->recordType == inType ) ) )
+ {
+ context->gotRecord = true;
+ }
+ if( !( inFlags & kDNSServiceFlagsMoreComing ) && context->gotRecord ) Exit( kExitReason_OneShotDone );
+ }
+
+exit:
+ FreeNullSafe( rdataStr );
+ if( err ) exit( 1 );
+}
+
+//===========================================================================================================================
+// RegisterCmd
+//===========================================================================================================================
+
+typedef struct
+{
+ DNSRecordRef recordRef; // Reference returned by DNSServiceAddRecord().
+ uint8_t * dataPtr; // Record data.
+ size_t dataLen; // Record data length.
+ uint32_t ttl; // Record TTL value.
+ uint16_t type; // Record type.
+
+} ExtraRecord;
+
+typedef struct
+{
+ DNSServiceRef opRef; // sdRef for DNSServiceRegister operation.
+ const char * name; // Service name argument for DNSServiceRegister().
+ const char * type; // Service type argument for DNSServiceRegister().
+ const char * domain; // Domain in which advertise the service.
+ uint8_t * txtPtr; // Service TXT record data. (malloc'd)
+ size_t txtLen; // Service TXT record data len.
+ ExtraRecord * extraRecords; // Array of extra records to add to registered service.
+ size_t extraRecordsCount; // Number of extra records.
+ uint8_t * updateTXTPtr; // Pointer to record data for TXT record update. (malloc'd)
+ size_t updateTXTLen; // Length of record data for TXT record update.
+ uint32_t updateTTL; // TTL of updated TXT record.
+ int updateDelayMs; // Post-registration TXT record update delay in milliseconds.
+ DNSServiceFlags flags; // Flags argument for DNSServiceRegister().
+ uint32_t ifIndex; // Interface index argument for DNSServiceRegister().
+ int lifetimeMs; // Lifetime of the record registration in milliseconds.
+ uint16_t port; // Service instance's port number.
+ Boolean printedHeader; // True if results header was printed.
+ Boolean didRegister; // True if service was registered.
+
+} RegisterContext;
+
+static void RegisterPrintPrologue( const RegisterContext *inContext );
+static void RegisterContextFree( RegisterContext *inContext );
+static void DNSSD_API
+ RegisterCallback(
+ DNSServiceRef inSDRef,
+ DNSServiceFlags inFlags,
+ DNSServiceErrorType inError,
+ const char * inName,
+ const char * inType,
+ const char * inDomain,
+ void * inContext );
+static void RegisterUpdate( void *inContext );
+
+static void RegisterCmd( void )
+{
+ OSStatus err;
+ RegisterContext * context = NULL;
+ dispatch_source_t signalSource = NULL;
+
+ // Set up SIGINT handler.
+
+ signal( SIGINT, SIG_IGN );
+ err = DispatchSignalSourceCreate( SIGINT, Exit, kExitReason_SIGINT, &signalSource );
+ require_noerr( err, exit );
+ dispatch_resume( signalSource );
+
+ // Create context.
+
+ context = (RegisterContext *) calloc( 1, sizeof( *context ) );
+ require_action( context, exit, err = kNoMemoryErr );
+
+ // Check command parameters.
+
+ if( ( gRegister_Port < 0 ) || ( gRegister_Port > UINT16_MAX ) )
+ {
+ FPrintF( stderr, "Port number %d is out-of-range.\n", gRegister_Port );
+ err = kParamErr;
+ goto exit;
+ }
+
+ if( ( gAddRecord_DataCount != gAddRecord_TypesCount ) || ( gAddRecord_TTLsCount != gAddRecord_TypesCount ) )
+ {
+ FPrintF( stderr, "There are missing additional record parameters.\n" );
+ err = kParamErr;
+ goto exit;
+ }
+
+ // Get flags.
+
+ context->flags = GetDNSSDFlagsFromOpts();
+
+ // Get interface index.
+
+ err = InterfaceIndexFromArgString( gInterface, &context->ifIndex );
+ require_noerr_quiet( err, exit );
+
+ // Get TXT record data.
+
+ if( gRegister_TXT )
+ {
+ err = RecordDataFromArgString( gRegister_TXT, &context->txtPtr, &context->txtLen );
+ require_noerr_quiet( err, exit );
+ }
+
+ // Set remaining parameters.
+
+ context->name = gRegister_Name;
+ context->type = gRegister_Type;
+ context->domain = gRegister_Domain;
+ context->port = (uint16_t) gRegister_Port;
+ context->lifetimeMs = gRegister_LifetimeMs;
+
+ if( gAddRecord_TypesCount > 0 )
+ {
+ size_t i;
+
+ context->extraRecords = (ExtraRecord *) calloc( gAddRecord_TypesCount, sizeof( ExtraRecord ) );
+ require_action( context, exit, err = kNoMemoryErr );
+ context->extraRecordsCount = gAddRecord_TypesCount;
+
+ for( i = 0; i < gAddRecord_TypesCount; ++i )
+ {
+ ExtraRecord * const extraRecord = &context->extraRecords[ i ];
+
+ err = RecordTypeFromArgString( gAddRecord_Types[ i ], &extraRecord->type );
+ require_noerr( err, exit );
+
+ err = StringToUInt32( gAddRecord_TTLs[ i ], &extraRecord->ttl );
+ if( err )
+ {
+ FPrintF( stderr, "Invalid TTL value: %s\n", gAddRecord_TTLs[ i ] );
+ err = kParamErr;
+ goto exit;
+ }
+
+ err = RecordDataFromArgString( gAddRecord_Data[ i ], &extraRecord->dataPtr, &extraRecord->dataLen );
+ require_noerr_quiet( err, exit );
+ }
+ }
+
+ if( gUpdateRecord_Data )
+ {
+ err = RecordDataFromArgString( gUpdateRecord_Data, &context->updateTXTPtr, &context->updateTXTLen );
+ require_noerr_quiet( err, exit );
+
+ context->updateTTL = (uint32_t) gUpdateRecord_TTL;
+ context->updateDelayMs = gUpdateRecord_DelayMs;
+ }
+
+ // Print prologue.
+
+ RegisterPrintPrologue( context );
+
+ // Start operation.
+
+ err = DNSServiceRegister( &context->opRef, context->flags, context->ifIndex, context->name, context->type,
+ context->domain, NULL, ntohs( context->port ), (uint16_t) context->txtLen, context->txtPtr,
+ RegisterCallback, context );
+ ForgetMem( &context->txtPtr );
+ require_noerr( err, exit );
+
+ err = DNSServiceSetDispatchQueue( context->opRef, dispatch_get_main_queue() );
+ require_noerr( err, exit );
+
+ dispatch_main();
+
+exit:
+ dispatch_source_forget( &signalSource );
+ if( context ) RegisterContextFree( context );
+ if( err ) exit( 1 );
+}
+
+//===========================================================================================================================
+// RegisterPrintPrologue
+//===========================================================================================================================
+
+static void RegisterPrintPrologue( const RegisterContext *inContext )
+{
+ size_t i;
+ int infinite;
+ char ifName[ kInterfaceNameBufLen ];
+ char time[ kTimestampBufLen ];
+
+ InterfaceIndexToName( inContext->ifIndex, ifName );
+
+ FPrintF( stdout, "Flags: %#{flags}\n", inContext->flags, kDNSServiceFlagsDescriptors );
+ FPrintF( stdout, "Interface: %d (%s)\n", (int32_t) inContext->ifIndex, ifName );
+ FPrintF( stdout, "Name: %s\n", inContext->name ? inContext->name : "<NULL>" );
+ FPrintF( stdout, "Type: %s\n", inContext->type );
+ FPrintF( stdout, "Domain: %s\n", inContext->domain ? inContext->domain : "<NULL> (default domains)" );
+ FPrintF( stdout, "Port: %u\n", inContext->port );
+ FPrintF( stdout, "TXT data: %#{txt}\n", inContext->txtPtr, inContext->txtLen );
+ infinite = ( inContext->lifetimeMs < 0 ) ? true : false;
+ FPrintF( stdout, "Lifetime: %?s%?d ms\n", infinite, "∞", !infinite, inContext->lifetimeMs );
+ if( inContext->updateTXTPtr )
+ {
+ FPrintF( stdout, "\nUpdate record:\n" );
+ FPrintF( stdout, " Delay: %d ms\n", ( inContext->updateDelayMs > 0 ) ? inContext->updateDelayMs : 0 );
+ FPrintF( stdout, " TTL: %u%?s\n",
+ inContext->updateTTL, inContext->updateTTL == 0, " (system will use a default value.)" );
+ FPrintF( stdout, " TXT data: %#{txt}\n", inContext->updateTXTPtr, inContext->updateTXTLen );
+ }
+ if( inContext->extraRecordsCount > 0 ) FPrintF( stdout, "\n" );
+ for( i = 0; i < inContext->extraRecordsCount; ++i )
+ {
+ const ExtraRecord * record = &inContext->extraRecords[ i ];
+
+ FPrintF( stdout, "Extra record %zu:\n", i + 1 );
+ FPrintF( stdout, " Type: %s (%u)\n", RecordTypeToString( record->type ), record->type );
+ FPrintF( stdout, " TTL: %u%?s\n", record->ttl, record->ttl == 0, " (system will use a default value.)" );
+ FPrintF( stdout, " RData: %#H\n\n", record->dataPtr, (int) record->dataLen, INT_MAX );
+ }
+ FPrintF( stdout, "Start time: %s\n", GetTimestampStr( time ) );
+ FPrintF( stdout, "---\n" );
+}
+
+//===========================================================================================================================
+// RegisterContextFree
+//===========================================================================================================================
+
+static void RegisterContextFree( RegisterContext *inContext )
+{
+ ExtraRecord * record;
+ const ExtraRecord * const end = inContext->extraRecords + inContext->extraRecordsCount;
+
+ DNSServiceForget( &inContext->opRef );
+ ForgetMem( &inContext->txtPtr );
+ for( record = inContext->extraRecords; record < end; ++record )
+ {
+ check( !record->recordRef );
+ ForgetMem( &record->dataPtr );
+ }
+ ForgetMem( &inContext->extraRecords );
+ ForgetMem( &inContext->updateTXTPtr );
+ free( inContext );
+}
+
+//===========================================================================================================================
+// RegisterCallback
+//===========================================================================================================================
+
+static void DNSSD_API
+ RegisterCallback(
+ DNSServiceRef inSDRef,
+ DNSServiceFlags inFlags,
+ DNSServiceErrorType inError,
+ const char * inName,
+ const char * inType,
+ const char * inDomain,
+ void * inContext )
+{
+ RegisterContext * const context = (RegisterContext *) inContext;
+ OSStatus err;
+ char time[ kTimestampBufLen ];
+
+ Unused( inSDRef );
+
+ GetTimestampStr( time );
+
+ if( !context->printedHeader )
+ {
+ FPrintF( stdout, "%-26s A/R Flags Service\n", "Timestamp" );
+ context->printedHeader = true;
+ }
+ FPrintF( stdout, "%-26s %-3s %5X %s.%s%s %?#m\n",
+ time, AddRmvString( inFlags ), inFlags, inName, inType, inDomain, inError, inError );
+
+ require_noerr_action_quiet( inError, exit, err = inError );
+
+ if( !context->didRegister && ( inFlags & kDNSServiceFlagsAdd ) )
+ {
+ context->didRegister = true;
+ if( context->updateTXTPtr )
+ {
+ if( context->updateDelayMs > 0 )
+ {
+ dispatch_after_f( dispatch_time_milliseconds( context->updateDelayMs ), dispatch_get_main_queue(),
+ context, RegisterUpdate );
+ }
+ else
+ {
+ RegisterUpdate( context );
+ }
+ }
+ if( context->extraRecordsCount > 0 )
+ {
+ ExtraRecord * record;
+ const ExtraRecord * const end = context->extraRecords + context->extraRecordsCount;
+
+ for( record = context->extraRecords; record < end; ++record )
+ {
+ err = DNSServiceAddRecord( context->opRef, &record->recordRef, 0, record->type,
+ (uint16_t) record->dataLen, record->dataPtr, record->ttl );
+ require_noerr( err, exit );
+ }
+ }
+ if( context->lifetimeMs == 0 )
+ {
+ Exit( kExitReason_TimeLimit );
+ }
+ else if( context->lifetimeMs > 0 )
+ {
+ dispatch_after_f( dispatch_time_milliseconds( context->lifetimeMs ), dispatch_get_main_queue(),
+ kExitReason_TimeLimit, Exit );
+ }
+ }
+ err = kNoErr;
+
+exit:
+ if( err ) exit( 1 );
+}
+
+//===========================================================================================================================
+// RegisterUpdate
+//===========================================================================================================================
+
+static void RegisterUpdate( void *inContext )
+{
+ OSStatus err;
+ RegisterContext * const context = (RegisterContext *) inContext;
+
+ err = DNSServiceUpdateRecord( context->opRef, NULL, 0, (uint16_t) context->updateTXTLen, context->updateTXTPtr,
+ context->updateTTL );
+ require_noerr( err, exit );
+
+exit:
+ if( err ) exit( 1 );
+}
+
+//===========================================================================================================================
+// RegisterRecordCmd
+//===========================================================================================================================
+
+typedef struct
+{
+ DNSServiceRef conRef; // sdRef to be initialized by DNSServiceCreateConnection().
+ DNSRecordRef recordRef; // Registered record reference.
+ const char * recordName; // Name of resource record.
+ uint8_t * dataPtr; // Pointer to resource record data.
+ size_t dataLen; // Length of resource record data.
+ uint32_t ttl; // TTL value of resource record in seconds.
+ uint32_t ifIndex; // Interface index argument for DNSServiceRegisterRecord().
+ DNSServiceFlags flags; // Flags argument for DNSServiceRegisterRecord().
+ int lifetimeMs; // Lifetime of the record registration in milliseconds.
+ uint16_t recordType; // Resource record type.
+ uint8_t * updateDataPtr; // Pointer to data for record update. (malloc'd)
+ size_t updateDataLen; // Length of data for record update.
+ uint32_t updateTTL; // TTL for updated record.
+ int updateDelayMs; // Post-registration record update delay in milliseconds.
+ Boolean didRegister; // True if the record was registered.
+
+} RegisterRecordContext;
+
+static void RegisterRecordPrintPrologue( const RegisterRecordContext *inContext );
+static void RegisterRecordContextFree( RegisterRecordContext *inContext );
+static void DNSSD_API
+ RegisterRecordCallback(
+ DNSServiceRef inSDRef,
+ DNSRecordRef inRecordRef,
+ DNSServiceFlags inFlags,
+ DNSServiceErrorType inError,
+ void * inContext );
+static void RegisterRecordUpdate( void *inContext );
+
+static void RegisterRecordCmd( void )
+{
+ OSStatus err;
+ RegisterRecordContext * context = NULL;
+ dispatch_source_t signalSource = NULL;
+
+ // Set up SIGINT handler.
+
+ signal( SIGINT, SIG_IGN );
+ err = DispatchSignalSourceCreate( SIGINT, Exit, kExitReason_SIGINT, &signalSource );
+ require_noerr( err, exit );
+ dispatch_resume( signalSource );
+
+ // Create context.
+
+ context = (RegisterRecordContext *) calloc( 1, sizeof( *context ) );
+ require_action( context, exit, err = kNoMemoryErr );
+
+ // Create connection.
+
+ err = CreateConnectionFromArgString( gConnectionOpt, dispatch_get_main_queue(), &context->conRef, NULL );
+ require_noerr_quiet( err, exit );
+
+ // Get flags.
+
+ context->flags = GetDNSSDFlagsFromOpts();
+
+ // Get interface.
+
+ err = InterfaceIndexFromArgString( gInterface, &context->ifIndex );
+ require_noerr_quiet( err, exit );
+
+ // Get record type.
+
+ err = RecordTypeFromArgString( gRegisterRecord_Type, &context->recordType );
+ require_noerr( err, exit );
+
+ // Get record data.
+
+ if( gRegisterRecord_Data )
+ {
+ err = RecordDataFromArgString( gRegisterRecord_Data, &context->dataPtr, &context->dataLen );
+ require_noerr_quiet( err, exit );
+ }
+
+ // Set remaining parameters.
+
+ context->recordName = gRegisterRecord_Name;
+ context->ttl = (uint32_t) gRegisterRecord_TTL;
+ context->lifetimeMs = gRegisterRecord_LifetimeMs;
+
+ // Get update data.
+
+ if( gRegisterRecord_UpdateData )
+ {
+ err = RecordDataFromArgString( gRegisterRecord_UpdateData, &context->updateDataPtr, &context->updateDataLen );
+ require_noerr_quiet( err, exit );
+
+ context->updateTTL = (uint32_t) gRegisterRecord_UpdateTTL;
+ context->updateDelayMs = gRegisterRecord_UpdateDelayMs;
+ }
+
+ // Print prologue.
+
+ RegisterRecordPrintPrologue( context );
+
+ // Start operation.
+
+ err = DNSServiceRegisterRecord( context->conRef, &context->recordRef, context->flags, context->ifIndex,
+ context->recordName, context->recordType, kDNSServiceClass_IN, (uint16_t) context->dataLen, context->dataPtr,
+ context->ttl, RegisterRecordCallback, context );
+ if( err )
+ {
+ FPrintF( stderr, "DNSServiceRegisterRecord() returned %#m\n", err );
+ goto exit;
+ }
+
+ dispatch_main();
+
+exit:
+ dispatch_source_forget( &signalSource );
+ if( context ) RegisterRecordContextFree( context );
+ if( err ) exit( 1 );
+}
+
+//===========================================================================================================================
+// RegisterRecordPrintPrologue
+//===========================================================================================================================
+
+static void RegisterRecordPrintPrologue( const RegisterRecordContext *inContext )
+{
+ int infinite;
+ char time[ kTimestampBufLen ];
+ char ifName[ kInterfaceNameBufLen ];
+
+ InterfaceIndexToName( inContext->ifIndex, ifName );
+
+ FPrintF( stdout, "Flags: %#{flags}\n", inContext->flags, kDNSServiceFlagsDescriptors );
+ FPrintF( stdout, "Interface: %d (%s)\n", (int32_t) inContext->ifIndex, ifName );
+ FPrintF( stdout, "Name: %s\n", inContext->recordName );
+ FPrintF( stdout, "Type: %s (%u)\n", RecordTypeToString( inContext->recordType ), inContext->recordType );
+ FPrintF( stdout, "TTL: %u\n", inContext->ttl );
+ FPrintF( stdout, "Data: %#H\n", inContext->dataPtr, (int) inContext->dataLen, INT_MAX );
+ infinite = ( inContext->lifetimeMs < 0 ) ? true : false;
+ FPrintF( stdout, "Lifetime: %?s%?d ms\n", infinite, "∞", !infinite, inContext->lifetimeMs );
+ if( inContext->updateDataPtr )
+ {
+ FPrintF( stdout, "\nUpdate record:\n" );
+ FPrintF( stdout, " Delay: %d ms\n", ( inContext->updateDelayMs >= 0 ) ? inContext->updateDelayMs : 0 );
+ FPrintF( stdout, " TTL: %u%?s\n",
+ inContext->updateTTL, inContext->updateTTL == 0, " (system will use a default value.)" );
+ FPrintF( stdout, " RData: %#H\n", inContext->updateDataPtr, (int) inContext->updateDataLen, INT_MAX );
+ }
+ FPrintF( stdout, "Start time: %s\n", GetTimestampStr( time ) );
+ FPrintF( stdout, "---\n" );
+}
+
+//===========================================================================================================================
+// RegisterRecordContextFree
+//===========================================================================================================================
+
+static void RegisterRecordContextFree( RegisterRecordContext *inContext )
+{
+ DNSServiceForget( &inContext->conRef );
+ ForgetMem( &inContext->dataPtr );
+ ForgetMem( &inContext->updateDataPtr );
+ free( inContext );
+}
+
+//===========================================================================================================================
+// RegisterRecordCallback
+//===========================================================================================================================
+
+static void
+ RegisterRecordCallback(
+ DNSServiceRef inSDRef,
+ DNSRecordRef inRecordRef,
+ DNSServiceFlags inFlags,
+ DNSServiceErrorType inError,
+ void * inContext )
+{
+ RegisterRecordContext * context = (RegisterRecordContext *) inContext;
+ char time[ kTimestampBufLen ];
+
+ Unused( inSDRef );
+ Unused( inRecordRef );
+ Unused( inFlags );
+ Unused( context );
+
+ GetTimestampStr( time );
+ FPrintF( stdout, "%s Record registration result (error %#m)\n", time, inError );
+
+ if( !context->didRegister && !inError )
+ {
+ context->didRegister = true;
+ if( context->updateDataPtr )
+ {
+ if( context->updateDelayMs > 0 )
+ {
+ dispatch_after_f( dispatch_time_milliseconds( context->updateDelayMs ), dispatch_get_main_queue(),
+ context, RegisterRecordUpdate );
+ }
+ else
+ {
+ RegisterRecordUpdate( context );
+ }
+ }
+ if( context->lifetimeMs == 0 )
+ {
+ Exit( kExitReason_TimeLimit );
+ }
+ else if( context->lifetimeMs > 0 )
+ {
+ dispatch_after_f( dispatch_time_milliseconds( context->lifetimeMs ), dispatch_get_main_queue(),
+ kExitReason_TimeLimit, Exit );
+ }
+ }
+}
+
+//===========================================================================================================================
+// RegisterRecordUpdate
+//===========================================================================================================================
+
+static void RegisterRecordUpdate( void *inContext )
+{
+ OSStatus err;
+ RegisterRecordContext * const context = (RegisterRecordContext *) inContext;
+
+ err = DNSServiceUpdateRecord( context->conRef, context->recordRef, 0, (uint16_t) context->updateDataLen,
+ context->updateDataPtr, context->updateTTL );
+ require_noerr( err, exit );
+
+exit:
+ if( err ) exit( 1 );
+}
+
+//===========================================================================================================================
+// ResolveCmd
+//===========================================================================================================================
+
+typedef struct
+{
+ DNSServiceRef mainRef; // Main sdRef for shared connections.
+ DNSServiceRef opRef; // sdRef for the DNSServiceResolve operation.
+ DNSServiceFlags flags; // Flags argument for DNSServiceResolve().
+ const char * name; // Service name argument for DNSServiceResolve().
+ const char * type; // Service type argument for DNSServiceResolve().
+ const char * domain; // Domain argument for DNSServiceResolve().
+ uint32_t ifIndex; // Interface index argument for DNSServiceResolve().
+ int timeLimitSecs; // Time limit for the DNSServiceResolve operation in seconds.
+
+} ResolveContext;
+
+static void ResolvePrintPrologue( const ResolveContext *inContext );
+static void ResolveContextFree( ResolveContext *inContext );
+static void DNSSD_API
+ ResolveCallback(
+ DNSServiceRef inSDRef,
+ DNSServiceFlags inFlags,
+ uint32_t inInterfaceIndex,
+ DNSServiceErrorType inError,
+ const char * inFullName,
+ const char * inHostname,
+ uint16_t inPort,
+ uint16_t inTXTLen,
+ const unsigned char * inTXTPtr,
+ void * inContext );
+
+static void ResolveCmd( void )
+{
+ OSStatus err;
+ DNSServiceRef sdRef;
+ ResolveContext * context = NULL;
+ dispatch_source_t signalSource = NULL;
+ int useMainConnection;
+
+ // Set up SIGINT handler.
+
+ signal( SIGINT, SIG_IGN );
+ err = DispatchSignalSourceCreate( SIGINT, Exit, kExitReason_SIGINT, &signalSource );
+ require_noerr( err, exit );
+ dispatch_resume( signalSource );
+
+ // Create context.
+
+ context = (ResolveContext *) calloc( 1, sizeof( *context ) );
+ require_action( context, exit, err = kNoMemoryErr );
+
+ // Check command parameters.
+
+ if( gResolve_TimeLimitSecs < 0 )
+ {
+ FPrintF( stderr, "Invalid time limit: %d seconds.\n", gResolve_TimeLimitSecs );
+ err = kParamErr;
+ goto exit;
+ }
+
+ // Create main connection.
+
+ if( gConnectionOpt )
+ {
+ err = CreateConnectionFromArgString( gConnectionOpt, dispatch_get_main_queue(), &context->mainRef, NULL );
+ require_noerr_quiet( err, exit );
+ useMainConnection = true;
+ }
+ else
+ {
+ useMainConnection = false;
+ }
+
+ // Get flags.
+
+ context->flags = GetDNSSDFlagsFromOpts();
+ if( useMainConnection ) context->flags |= kDNSServiceFlagsShareConnection;
+
+ // Get interface index.
+
+ err = InterfaceIndexFromArgString( gInterface, &context->ifIndex );
+ require_noerr_quiet( err, exit );
+
+ // Set remaining parameters.
+
+ context->name = gResolve_Name;
+ context->type = gResolve_Type;
+ context->domain = gResolve_Domain;
+ context->timeLimitSecs = gResolve_TimeLimitSecs;
+
+ // Print prologue.
+
+ ResolvePrintPrologue( context );
+
+ // Start operation.
+
+ if( useMainConnection ) sdRef = context->mainRef;
+ err = DNSServiceResolve( &sdRef, context->flags, context->ifIndex, context->name, context->type, context->domain,
+ ResolveCallback, NULL );
+ require_noerr( err, exit );
+
+ context->opRef = sdRef;
+ if( !useMainConnection )
+ {
+ err = DNSServiceSetDispatchQueue( context->opRef, dispatch_get_main_queue() );
+ require_noerr( err, exit );
+ }
+
+ // Set time limit.
+
+ if( context->timeLimitSecs > 0 )
+ {
+ dispatch_after_f( dispatch_time_seconds( context->timeLimitSecs ), dispatch_get_main_queue(),
+ kExitReason_TimeLimit, Exit );
+ }
+ dispatch_main();
+
+exit:
+ dispatch_source_forget( &signalSource );
+ if( context ) ResolveContextFree( context );
+ if( err ) exit( 1 );
+}
+
+//===========================================================================================================================
+// ReconfirmCmd
+//===========================================================================================================================
+
+static void ReconfirmCmd( void )
+{
+ OSStatus err;
+ uint8_t * rdataPtr = NULL;
+ size_t rdataLen = 0;
+ DNSServiceFlags flags;
+ uint32_t ifIndex;
+ uint16_t type, class;
+ char ifName[ kInterfaceNameBufLen ];
+
+ // Get flags.
+
+ flags = GetDNSSDFlagsFromOpts();
+
+ // Get interface index.
+
+ err = InterfaceIndexFromArgString( gInterface, &ifIndex );
+ require_noerr_quiet( err, exit );
+
+ // Get record type.
+
+ err = RecordTypeFromArgString( gReconfirmRecord_Type, &type );
+ require_noerr( err, exit );
+
+ // Get record data.
+
+ if( gReconfirmRecord_Data )
+ {
+ err = RecordDataFromArgString( gReconfirmRecord_Data, &rdataPtr, &rdataLen );
+ require_noerr_quiet( err, exit );
+ }
+
+ // Get record data.
+
+ if( gReconfirmRecord_Class )
+ {
+ err = RecordClassFromArgString( gReconfirmRecord_Class, &class );
+ require_noerr( err, exit );
+ }
+ else
+ {
+ class = kDNSServiceClass_IN;
+ }
+
+ // Print prologue.
+
+ FPrintF( stdout, "Flags: %#{flags}\n", flags, kDNSServiceFlagsDescriptors );
+ FPrintF( stdout, "Interface: %d (%s)\n", (int32_t) ifIndex, InterfaceIndexToName( ifIndex, ifName ) );
+ FPrintF( stdout, "Name: %s\n", gReconfirmRecord_Name );
+ FPrintF( stdout, "Type: %s (%u)\n", RecordTypeToString( type ), type );
+ FPrintF( stdout, "Class: %s (%u)\n", ( class == kDNSServiceClass_IN ) ? "IN" : "???", class );
+ FPrintF( stdout, "Data: %#H\n", rdataPtr, (int) rdataLen, INT_MAX );
+ FPrintF( stdout, "---\n" );
+
+ err = DNSServiceReconfirmRecord( flags, ifIndex, gReconfirmRecord_Name, type, class, (uint16_t) rdataLen, rdataPtr );
+ FPrintF( stdout, "Error: %#m\n", err );
+
+exit:
+ FreeNullSafe( rdataPtr );
+ if( err ) exit( 1 );
+}
+
+//===========================================================================================================================
+// ResolvePrintPrologue
+//===========================================================================================================================
+
+static void ResolvePrintPrologue( const ResolveContext *inContext )
+{
+ const int timeLimitSecs = inContext->timeLimitSecs;
+ char ifName[ kInterfaceNameBufLen ];
+ char time[ kTimestampBufLen ];
+
+ InterfaceIndexToName( inContext->ifIndex, ifName );
+
+ FPrintF( stdout, "Flags: %#{flags}\n", inContext->flags, kDNSServiceFlagsDescriptors );
+ FPrintF( stdout, "Interface: %d (%s)\n", (int32_t) inContext->ifIndex, ifName );
+ FPrintF( stdout, "Name: %s\n", inContext->name );
+ FPrintF( stdout, "Type: %s\n", inContext->type );
+ FPrintF( stdout, "Domain: %s\n", inContext->domain );
+ FPrintF( stdout, "Time limit: " );
+ if( timeLimitSecs > 0 ) FPrintF( stdout, "%d second%?c\n", timeLimitSecs, timeLimitSecs != 1, 's' );
+ else FPrintF( stdout, "∞\n" );
+ FPrintF( stdout, "Start time: %s\n", GetTimestampStr( time ) );
+ FPrintF( stdout, "---\n" );
+}
+
+//===========================================================================================================================
+// ResolveContextFree
+//===========================================================================================================================
+
+static void ResolveContextFree( ResolveContext *inContext )
+{
+ DNSServiceForget( &inContext->opRef );
+ DNSServiceForget( &inContext->mainRef );
+ free( inContext );
+}
+
+//===========================================================================================================================
+// ResolveCallback
+//===========================================================================================================================
+
+static void DNSSD_API
+ ResolveCallback(
+ DNSServiceRef inSDRef,
+ DNSServiceFlags inFlags,
+ uint32_t inInterfaceIndex,
+ DNSServiceErrorType inError,
+ const char * inFullName,
+ const char * inHostname,
+ uint16_t inPort,
+ uint16_t inTXTLen,
+ const unsigned char * inTXTPtr,
+ void * inContext )
+{
+ char time[ kTimestampBufLen ];
+ char errorStr[ 64 ];
+
+ Unused( inSDRef );
+ Unused( inFlags );
+ Unused( inContext );
+
+ GetTimestampStr( time );
+
+ if( inError ) SNPrintF( errorStr, sizeof( errorStr ), " error %#m", inError );
+
+ FPrintF( stdout, "%s: %s can be reached at %s:%u (interface %d)%?s\n",
+ time, inFullName, inHostname, ntohs( inPort ), (int32_t) inInterfaceIndex, inError, errorStr );
+ if( inTXTLen == 1 )
+ {
+ FPrintF( stdout, " TXT record: %#H\n", inTXTPtr, (int) inTXTLen, INT_MAX );
+ }
+ else
+ {
+ FPrintF( stdout, " TXT record: %#{txt}\n", inTXTPtr, (size_t) inTXTLen );
+ }
+}
+
+//===========================================================================================================================
+// GetAddrInfoPOSIXCmd
+//===========================================================================================================================
+
+#define AddressFamilyStr( X ) ( \
+ ( (X) == AF_INET ) ? "inet" : \
+ ( (X) == AF_INET6 ) ? "inet6" : \
+ ( (X) == AF_UNSPEC ) ? "unspec" : \
+ "???" )
+
+static void GetAddrInfoPOSIXCmd( void )
+{
+ OSStatus err;
+ struct addrinfo hints;
+ struct addrinfo * addrInfo;
+ struct addrinfo * addrInfoList = NULL;
+ int addrCount;
+ char time[ kTimestampBufLen ];
+
+ memset( &hints, 0, sizeof( hints ) );
+ hints.ai_socktype = SOCK_STREAM;
+ if( !gGAIPOSIX_Family ) hints.ai_family = AF_UNSPEC;
+ else if( strcasecmp( gGAIPOSIX_Family, "inet" ) == 0 ) hints.ai_family = AF_INET;
+ else if( strcasecmp( gGAIPOSIX_Family, "inet6" ) == 0 ) hints.ai_family = AF_INET6;
+ else if( strcasecmp( gGAIPOSIX_Family, "unspec" ) == 0 ) hints.ai_family = AF_UNSPEC;
+ else
+ {
+ FPrintF( stderr, "Invalid address family: %s.\n", gGAIPOSIX_Family );
+ err = kParamErr;
+ goto exit;
+ }
+
+ FPrintF( stdout, "Name: %s\n", gGAIPOSIX_Name );
+ FPrintF( stdout, "Address family: %s\n", AddressFamilyStr( hints.ai_family ) );
+ FPrintF( stdout, "Start time: %s\n", GetTimestampStr( time ) );
+ FPrintF( stdout, "---\n" );
+
+ err = getaddrinfo( gGAIPOSIX_Name, NULL, &hints, &addrInfoList );
+ FPrintF( stdout, "%s:\n", GetTimestampStr( time ) );
+ if( err )
+ {
+ FPrintF( stderr, "getaddrinfo() error %#m: %s.\n", err, gai_strerror( err ) );
+ goto exit;
+ }
+
+ addrCount = 0;
+ for( addrInfo = addrInfoList; addrInfo; addrInfo = addrInfo->ai_next ) { ++addrCount; }
+
+ FPrintF( stdout, "Addresses (%d total):\n", addrCount );
+ for( addrInfo = addrInfoList; addrInfo; addrInfo = addrInfo->ai_next )
+ {
+ FPrintF( stdout, "%##a\n", addrInfo->ai_addr );
+ }
+
+exit:
+ FPrintF( stdout, "---\n" );
+ FPrintF( stdout, "End time: %s\n", GetTimestampStr( time ) );
+
+ if( addrInfoList ) freeaddrinfo( addrInfoList );
+ if( err ) exit( 1 );
+}
+
+//===========================================================================================================================
+// ReverseLookupCmd
+//===========================================================================================================================
+
+static void ReverseLookupCmd( void )
+{
+ OSStatus err;
+ QueryRecordContext * context = NULL;
+ DNSServiceRef sdRef;
+ dispatch_source_t signalSource = NULL;
+ uint32_t ipv4Addr;
+ uint8_t ipv6Addr[ 16 ];
+ char recordName[ ( 16 * 4 ) + 9 + 1 ];
+ int useMainConnection;
+
+ // Set up SIGINT handler.
+
+ signal( SIGINT, SIG_IGN );
+ err = DispatchSignalSourceCreate( SIGINT, Exit, kExitReason_SIGINT, &signalSource );
+ require_noerr( err, exit );
+ dispatch_resume( signalSource );
+
+ // Create context.
+
+ context = (QueryRecordContext *) calloc( 1, sizeof( *context ) );
+ require_action( context, exit, err = kNoMemoryErr );
+
+ // Check command parameters.
+
+ if( gReverseLookup_TimeLimitSecs < 0 )
+ {
+ FPrintF( stderr, "Invalid time limit: %d s.\n", gReverseLookup_TimeLimitSecs );
+ err = kParamErr;
+ goto exit;
+ }
+
+ // Create main connection.
+
+ if( gConnectionOpt )
+ {
+ err = CreateConnectionFromArgString( gConnectionOpt, dispatch_get_main_queue(), &context->mainRef, NULL );
+ require_noerr_quiet( err, exit );
+ useMainConnection = true;
+ }
+ else
+ {
+ useMainConnection = false;
+ }
+
+ // Get flags.
+
+ context->flags = GetDNSSDFlagsFromOpts();
+ if( useMainConnection ) context->flags |= kDNSServiceFlagsShareConnection;
+
+ // Get interface index.
+
+ err = InterfaceIndexFromArgString( gInterface, &context->ifIndex );
+ require_noerr_quiet( err, exit );
+
+ // Create reverse lookup record name.
+
+ err = StringToIPv4Address( gReverseLookup_IPAddr, kStringToIPAddressFlagsNoPort | kStringToIPAddressFlagsNoPrefix,
+ &ipv4Addr, NULL, NULL, NULL, NULL );
+ if( err )
+ {
+ char * dst;
+ int i;
+
+ err = StringToIPv6Address( gReverseLookup_IPAddr,
+ kStringToIPAddressFlagsNoPort | kStringToIPAddressFlagsNoPrefix | kStringToIPAddressFlagsNoScope,
+ ipv6Addr, NULL, NULL, NULL, NULL );
+ if( err )
+ {
+ FPrintF( stderr, "Invalid IP address: \"%s\".\n", gReverseLookup_IPAddr );
+ err = kParamErr;
+ goto exit;
+ }
+ dst = recordName;
+ for( i = 15; i >= 0; --i )
+ {
+ *dst++ = kHexDigitsLowercase[ ipv6Addr[ i ] & 0x0F ];
+ *dst++ = '.';
+ *dst++ = kHexDigitsLowercase[ ipv6Addr[ i ] >> 4 ];
+ *dst++ = '.';
+ }
+ strcpy( dst, "ip6.arpa." );
+ check( ( strlen( recordName ) + 1 ) <= sizeof( recordName ) );
+ }
+ else
+ {
+ SNPrintF( recordName, sizeof( recordName ), "%u.%u.%u.%u.in-addr.arpa.",
+ ipv4Addr & 0xFF,
+ ( ipv4Addr >> 8 ) & 0xFF,
+ ( ipv4Addr >> 16 ) & 0xFF,
+ ( ipv4Addr >> 24 ) & 0xFF );
+ }
+
+ // Set remaining parameters.
+
+ context->recordName = recordName;
+ context->recordType = kDNSServiceType_PTR;
+ context->timeLimitSecs = gReverseLookup_TimeLimitSecs;
+ context->oneShotMode = gReverseLookup_OneShot ? true : false;
+
+ // Print prologue.
+
+ QueryRecordPrintPrologue( context );
+
+ // Start operation.
+
+ if( useMainConnection ) sdRef = context->mainRef;
+ err = DNSServiceQueryRecord( &sdRef, context->flags, context->ifIndex, context->recordName, context->recordType,
+ kDNSServiceClass_IN, QueryRecordCallback, context );
+ require_noerr( err, exit );
+
+ context->opRef = sdRef;
+ if( !useMainConnection )
+ {
+ err = DNSServiceSetDispatchQueue( context->opRef, dispatch_get_main_queue() );
+ require_noerr( err, exit );
+ }
+
+ // Set time limit.
+
+ if( context->timeLimitSecs > 0 )
+ {
+ dispatch_after_f( dispatch_time_seconds( context->timeLimitSecs ), dispatch_get_main_queue(),
+ kExitReason_TimeLimit, Exit );
+ }
+ dispatch_main();
+
+exit:
+ dispatch_source_forget( &signalSource );
+ if( context ) QueryRecordContextFree( context );
+ if( err ) exit( 1 );
+}
+
+//===========================================================================================================================
+// BrowseAllCmd
+//===========================================================================================================================
+
+typedef struct BrowseDomain BrowseDomain;
+typedef struct BrowseType BrowseType;
+typedef struct BrowseOp BrowseOp;
+typedef struct BrowseInstance BrowseInstance;
+typedef struct BrowseIPAddr BrowseIPAddr;
+
+typedef struct
+{
+ int refCount;
+ DNSServiceRef mainRef;
+ DNSServiceRef domainsQuery;
+ const char * domain;
+ BrowseDomain * domainList;
+ char ** serviceTypes;
+ size_t serviceTypesCount;
+ dispatch_source_t exitTimer;
+ uint32_t ifIndex;
+ int pendingConnectCount;
+ int browseTimeSecs;
+ int connectTimeLimitSecs;
+ Boolean includeAWDL;
+ Boolean useColoredText;
+
+} BrowseAllContext;
+
+struct BrowseDomain
+{
+ BrowseDomain * next;
+ char * name;
+ DNSServiceRef servicesQuery;
+ BrowseAllContext * context;
+ BrowseType * typeList;
+};
+
+struct BrowseType
+{
+ BrowseType * next;
+ char * name;
+ BrowseOp * browseList;
+};
+
+struct BrowseOp
+{
+ BrowseOp * next;
+ BrowseAllContext * context;
+ DNSServiceRef browse;
+ uint64_t startTicks;
+ BrowseInstance * instanceList;
+ uint32_t ifIndex;
+ Boolean isTCP;
+};
+
+struct BrowseInstance
+{
+ BrowseInstance * next;
+ BrowseAllContext * context;
+ char * name;
+ uint64_t foundTicks;
+ DNSServiceRef resolve;
+ uint64_t resolveStartTicks;
+ uint64_t resolveDoneTicks;
+ DNSServiceRef getAddr;
+ uint64_t getAddrStartTicks;
+ BrowseIPAddr * addrList;
+ uint8_t * txtPtr;
+ size_t txtLen;
+ char * hostname;
+ uint32_t ifIndex;
+ uint16_t port;
+ Boolean isTCP;
+};
+
+typedef enum
+{
+ kConnectStatus_None = 0,
+ kConnectStatus_Pending = 1,
+ kConnectStatus_Succeeded = 2,
+ kConnectStatus_Failed = 3
+
+} ConnectStatus;
+
+struct BrowseIPAddr
+{
+ BrowseIPAddr * next;
+ sockaddr_ip sip;
+ int refCount;
+ BrowseAllContext * context;
+ uint64_t foundTicks;
+ AsyncConnectionRef connection;
+ ConnectStatus connectStatus;
+ CFTimeInterval connectTimeSecs;
+ OSStatus connectError;
+};
+
+static void BrowseAllPrintPrologue( const BrowseAllContext *inContext );
+static void DNSSD_API
+ BrowseAllQueryDomainsCallback(
+ DNSServiceRef inSDRef,
+ DNSServiceFlags inFlags,
+ uint32_t inInterfaceIndex,
+ DNSServiceErrorType inError,
+ const char * inFullName,
+ uint16_t inType,
+ uint16_t inClass,
+ uint16_t inRDataLen,
+ const void * inRDataPtr,
+ uint32_t inTTL,
+ void * inContext );
+static void DNSSD_API
+ BrowseAllQueryCallback(
+ DNSServiceRef inSDRef,
+ DNSServiceFlags inFlags,
+ uint32_t inInterfaceIndex,
+ DNSServiceErrorType inError,
+ const char * inFullName,
+ uint16_t inType,
+ uint16_t inClass,
+ uint16_t inRDataLen,
+ const void * inRDataPtr,
+ uint32_t inTTL,
+ void * inContext );
+static void DNSSD_API
+ BrowseAllBrowseCallback(
+ DNSServiceRef inSDRef,
+ DNSServiceFlags inFlags,
+ uint32_t inInterfaceIndex,
+ DNSServiceErrorType inError,
+ const char * inName,
+ const char * inRegType,
+ const char * inDomain,
+ void * inContext );
+static void DNSSD_API
+ BrowseAllResolveCallback(
+ DNSServiceRef inSDRef,
+ DNSServiceFlags inFlags,
+ uint32_t inInterfaceIndex,
+ DNSServiceErrorType inError,
+ const char * inFullName,
+ const char * inHostname,
+ uint16_t inPort,
+ uint16_t inTXTLen,
+ const unsigned char * inTXTPtr,
+ void * inContext );
+static void DNSSD_API
+ BrowseAllGAICallback(
+ DNSServiceRef inSDRef,
+ DNSServiceFlags inFlags,
+ uint32_t inInterfaceIndex,
+ DNSServiceErrorType inError,
+ const char * inHostname,
+ const struct sockaddr * inSockAddr,
+ uint32_t inTTL,
+ void * inContext );
+static void BrowseAllConnectionProgress( int inPhase, const void *inDetails, void *inArg );
+static void BrowseAllConnectionHandler( SocketRef inSock, OSStatus inError, void *inArg );
+static void BrowseAllStop( void *inContext );
+static void BrowseAllExit( void *inContext );
+static OSStatus BrowseAllAddDomain( BrowseAllContext *inContext, const char *inName );
+static OSStatus BrowseAllRemoveDomain( BrowseAllContext *inContext, const char *inName );
+static void BrowseAllContextRelease( BrowseAllContext *inContext );
+static OSStatus
+ BrowseAllAddServiceType(
+ BrowseAllContext * inContext,
+ BrowseDomain * inDomain,
+ const char * inName,
+ uint32_t inIfIndex,
+ Boolean inIncludeAWDL );
+static OSStatus
+ BrowseAllRemoveServiceType(
+ BrowseAllContext * inContext,
+ BrowseDomain * inDomain,
+ const char * inName,
+ uint32_t inIfIndex );
+static OSStatus
+ BrowseAllAddServiceInstance(
+ BrowseAllContext * inContext,
+ BrowseOp * inBrowse,
+ const char * inName,
+ const char * inRegType,
+ const char * inDomain,
+ uint32_t inIfIndex );
+static OSStatus
+ BrowseAllRemoveServiceInstance(
+ BrowseAllContext * inContext,
+ BrowseOp * inBrowse,
+ const char * inName,
+ uint32_t inIfIndex );
+static OSStatus
+ BrowseAllAddIPAddress(
+ BrowseAllContext * inContext,
+ BrowseInstance * inInstance,
+ const struct sockaddr * inSockAddr );
+static OSStatus
+ BrowseAllRemoveIPAddress(
+ BrowseAllContext * inContext,
+ BrowseInstance * inInstance,
+ const struct sockaddr * inSockAddr );
+static void BrowseDomainFree( BrowseDomain *inDomain );
+static void BrowseTypeFree( BrowseType *inType );
+static void BrowseOpFree( BrowseOp *inBrowse );
+static void BrowseInstanceFree( BrowseInstance *inInstance );
+static void BrowseIPAddrRelease( BrowseIPAddr *inAddr );
+static void BrowseIPAddrReleaseList( BrowseIPAddr *inList );
+
+#define ForgetIPAddressList( X ) ForgetCustom( X, BrowseIPAddrReleaseList )
+#define ForgetBrowseAllContext( X ) ForgetCustom( X, BrowseAllContextRelease )
+
+#define kBrowseAllOpenFileMin 4096
+
+static void BrowseAllCmd( void )
+{
+ OSStatus err;
+ BrowseAllContext * context = NULL;
+
+ // Check command parameters.
+
+ if( gBrowseAll_BrowseTimeSecs <= 0 )
+ {
+ FPrintF( stdout, "Invalid browse time: %d seconds.\n", gBrowseAll_BrowseTimeSecs );
+ err = kParamErr;
+ goto exit;
+ }
+
+#if( TARGET_OS_POSIX )
+ // Set open file minimum.
+
+ {
+ struct rlimit fdLimits;
+
+ err = getrlimit( RLIMIT_NOFILE, &fdLimits );
+ err = map_global_noerr_errno( err );
+ require_noerr( err, exit );
+
+ if( fdLimits.rlim_cur < kBrowseAllOpenFileMin )
+ {
+ fdLimits.rlim_cur = kBrowseAllOpenFileMin;
+ err = setrlimit( RLIMIT_NOFILE, &fdLimits );
+ err = map_global_noerr_errno( err );
+ require_noerr( err, exit );
+ }
+ }
+#endif
+
+ context = (BrowseAllContext *) calloc( 1, sizeof( *context ) );
+ require_action( context, exit, err = kNoMemoryErr );
+
+ context->refCount = 1;
+ context->domain = gBrowseAll_Domain;
+ context->serviceTypes = gBrowseAll_ServiceTypes;
+ context->serviceTypesCount = gBrowseAll_ServiceTypesCount;
+ gBrowseAll_ServiceTypes = NULL;
+ gBrowseAll_ServiceTypesCount = 0;
+ context->browseTimeSecs = gBrowseAll_BrowseTimeSecs;
+ context->connectTimeLimitSecs = gBrowseAll_ConnectTimeLimitSecs;
+ context->includeAWDL = gBrowseAll_IncludeAWDL ? true : false;
+#if( TARGET_OS_POSIX )
+ context->useColoredText = isatty( STDOUT_FILENO ) ? true : false;
+#endif
+
+ err = DNSServiceCreateConnection( &context->mainRef );
+ require_noerr( err, exit );
+
+ err = DNSServiceSetDispatchQueue( context->mainRef, dispatch_get_main_queue() );
+ require_noerr( err, exit );
+
+ // Set interface index.
+
+ err = InterfaceIndexFromArgString( gInterface, &context->ifIndex );
+ require_noerr_quiet( err, exit );
+
+ BrowseAllPrintPrologue( context );
+
+ if( context->domain )
+ {
+ err = BrowseAllAddDomain( context, context->domain );
+ require_noerr( err, exit );
+ }
+ else
+ {
+ DNSServiceRef sdRef;
+
+ sdRef = context->mainRef;
+ err = DNSServiceQueryRecord( &sdRef, kDNSServiceFlagsShareConnection, kDNSServiceInterfaceIndexLocalOnly,
+ "b._dns-sd._udp.local.", kDNSServiceType_PTR, kDNSServiceClass_IN, BrowseAllQueryDomainsCallback, context );
+ require_noerr( err, exit );
+
+ context->domainsQuery = sdRef;
+ }
+
+ dispatch_after_f( dispatch_time_seconds( context->browseTimeSecs ), dispatch_get_main_queue(), context, BrowseAllStop );
+ dispatch_main();
+
+exit:
+ if( context ) BrowseAllContextRelease( context );
+ if( err ) exit( 1 );
+}
+
+//===========================================================================================================================
+// BrowseAllPrintPrologue
+//===========================================================================================================================
+
+static void BrowseAllPrintPrologue( const BrowseAllContext *inContext )
+{
+ size_t i;
+ char ifName[ kInterfaceNameBufLen ];
+ char time[ kTimestampBufLen ];
+
+ InterfaceIndexToName( inContext->ifIndex, ifName );
+
+ FPrintF( stdout, "Interface: %d (%s)\n", (int32_t) inContext->ifIndex, ifName );
+ FPrintF( stdout, "Service types: ");
+ if( inContext->serviceTypesCount > 0 )
+ {
+ FPrintF( stdout, "%s", inContext->serviceTypes[ 0 ] );
+ for( i = 1; i < inContext->serviceTypesCount; ++i ) FPrintF( stdout, ", %s", inContext->serviceTypes[ i ] );
+ FPrintF( stdout, "\n" );
+ }
+ else
+ {
+ FPrintF( stdout, "all services\n" );
+ }
+ FPrintF( stdout, "Domain: %s\n", inContext->domain ? inContext->domain : "default domains" );
+ FPrintF( stdout, "Browse time: %d second%?c\n", inContext->browseTimeSecs, inContext->browseTimeSecs != 1, 's' );
+ FPrintF( stdout, "Connect time limit: %d second%?c\n",
+ inContext->connectTimeLimitSecs, inContext->connectTimeLimitSecs != 1, 's' );
+ FPrintF( stdout, "Start time: %s\n", GetTimestampStr( time ) );
+ FPrintF( stdout, "---\n" );
+}
+
+//===========================================================================================================================
+// BrowseAllQueryDomainsCallback
+//===========================================================================================================================
+
+static void DNSSD_API
+ BrowseAllQueryDomainsCallback(
+ DNSServiceRef inSDRef,
+ DNSServiceFlags inFlags,
+ uint32_t inInterfaceIndex,
+ DNSServiceErrorType inError,
+ const char * inFullName,
+ uint16_t inType,
+ uint16_t inClass,
+ uint16_t inRDataLen,
+ const void * inRDataPtr,
+ uint32_t inTTL,
+ void * inContext )
+{
+ OSStatus err;
+ BrowseAllContext * const context = (BrowseAllContext *) inContext;
+ char domainStr[ kDNSServiceMaxDomainName ];
+
+ Unused( inSDRef );
+ Unused( inInterfaceIndex );
+ Unused( inFullName );
+ Unused( inType );
+ Unused( inClass );
+ Unused( inTTL );
+
+ err = inError;
+ require_noerr( err, exit );
+
+ err = DomainNameToString( inRDataPtr, ( (const uint8_t *) inRDataPtr ) + inRDataLen, domainStr, NULL );
+ require_noerr( err, exit );
+
+ if( inFlags & kDNSServiceFlagsAdd )
+ {
+ err = BrowseAllAddDomain( context, domainStr );
+ if( err == kDuplicateErr ) err = kNoErr;
+ require_noerr( err, exit );
+ }
+ else
+ {
+ err = BrowseAllRemoveDomain( context, domainStr );
+ if( err == kNotFoundErr ) err = kNoErr;
+ require_noerr( err, exit );
+ }
+
+exit:
+ if( err ) exit( 1 );
+}
+
+//===========================================================================================================================
+// BrowseAllQueryCallback
+//===========================================================================================================================
+
+static void DNSSD_API
+ BrowseAllQueryCallback(
+ DNSServiceRef inSDRef,
+ DNSServiceFlags inFlags,
+ uint32_t inInterfaceIndex,
+ DNSServiceErrorType inError,
+ const char * inFullName,
+ uint16_t inType,
+ uint16_t inClass,
+ uint16_t inRDataLen,
+ const void * inRDataPtr,
+ uint32_t inTTL,
+ void * inContext )
+{
+ OSStatus err;
+ BrowseDomain * const domain = (BrowseDomain *) inContext;
+ const uint8_t * firstLabel;
+ const uint8_t * secondLabel;
+ char * serviceTypeStr = NULL;
+ const uint8_t * const end = ( (uint8_t * ) inRDataPtr ) + inRDataLen;
+
+ Unused( inSDRef );
+ Unused( inFullName );
+ Unused( inTTL );
+ Unused( inType );
+ Unused( inClass );
+
+ err = inError;
+ require_noerr( err, exit );
+
+ check( inType == kDNSServiceType_PTR );
+ check( inClass == kDNSServiceClass_IN );
+ require_action( inRDataLen > 0, exit, err = kSizeErr );
+
+ firstLabel = inRDataPtr;
+ require_action_quiet( ( firstLabel + 1 + firstLabel[ 0 ] ) < end , exit, err = kUnderrunErr );
+
+ secondLabel = firstLabel + 1 + firstLabel[ 0 ];
+ require_action_quiet( ( secondLabel + 1 + secondLabel[ 0 ] ) < end , exit, err = kUnderrunErr );
+
+ ASPrintF( &serviceTypeStr, "%#s.%#s", firstLabel, secondLabel );
+ require_action( serviceTypeStr, exit, err = kNoMemoryErr );
+
+ if( inFlags & kDNSServiceFlagsAdd )
+ {
+ err = BrowseAllAddServiceType( domain->context, domain, serviceTypeStr, inInterfaceIndex, false );
+ if( err == kDuplicateErr ) err = kNoErr;
+ require_noerr( err, exit );
+ }
+ else
+ {
+ err = BrowseAllRemoveServiceType( domain->context, domain, serviceTypeStr, inInterfaceIndex );
+ if( err == kNotFoundErr ) err = kNoErr;
+ require_noerr( err, exit );
+ }
+
+exit:
+ FreeNullSafe( serviceTypeStr );
+}
+
+//===========================================================================================================================
+// BrowseAllBrowseCallback
+//===========================================================================================================================
+
+static void DNSSD_API
+ BrowseAllBrowseCallback(
+ DNSServiceRef inSDRef,
+ DNSServiceFlags inFlags,
+ uint32_t inInterfaceIndex,
+ DNSServiceErrorType inError,
+ const char * inName,
+ const char * inRegType,
+ const char * inDomain,
+ void * inContext )
+{
+ OSStatus err;
+ BrowseOp * const browse = (BrowseOp *) inContext;
+
+ Unused( inSDRef );
+
+ err = inError;
+ require_noerr( err, exit );
+
+ if( inFlags & kDNSServiceFlagsAdd )
+ {
+ err = BrowseAllAddServiceInstance( browse->context, browse, inName, inRegType, inDomain, inInterfaceIndex );
+ if( err == kDuplicateErr ) err = kNoErr;
+ require_noerr( err, exit );
+ }
+ else
+ {
+ err = BrowseAllRemoveServiceInstance( browse->context, browse, inName, inInterfaceIndex );
+ if( err == kNotFoundErr ) err = kNoErr;
+ require_noerr( err, exit );
+ }
+
+exit:
+ return;
+}
+
+//===========================================================================================================================
+// BrowseAllResolveCallback
+//===========================================================================================================================
+
+static void DNSSD_API
+ BrowseAllResolveCallback(
+ DNSServiceRef inSDRef,
+ DNSServiceFlags inFlags,
+ uint32_t inInterfaceIndex,
+ DNSServiceErrorType inError,
+ const char * inFullName,
+ const char * inHostname,
+ uint16_t inPort,
+ uint16_t inTXTLen,
+ const unsigned char * inTXTPtr,
+ void * inContext )
+{
+ OSStatus err;
+ const uint64_t nowTicks = UpTicks();
+ BrowseInstance * const instance = (BrowseInstance *) inContext;
+
+ Unused( inSDRef );
+ Unused( inFlags );
+ Unused( inInterfaceIndex );
+ Unused( inFullName );
+
+ err = inError;
+ require_noerr( err, exit );
+
+ if( !MemEqual( instance->txtPtr, instance->txtLen, inTXTPtr, inTXTLen ) )
+ {
+ FreeNullSafe( instance->txtPtr );
+ instance->txtPtr = malloc( inTXTLen );
+ require_action( instance->txtPtr, exit, err = kNoMemoryErr );
+
+ memcpy( instance->txtPtr, inTXTPtr, inTXTLen );
+ instance->txtLen = inTXTLen;
+ }
+
+ instance->port = ntohs( inPort );
+
+ if( !instance->hostname || ( strcasecmp( instance->hostname, inHostname ) != 0 ) )
+ {
+ DNSServiceRef sdRef;
+
+ if( !instance->hostname ) instance->resolveDoneTicks = nowTicks;
+ FreeNullSafe( instance->hostname );
+ instance->hostname = strdup( inHostname );
+ require_action( instance->hostname, exit, err = kNoMemoryErr );
+
+ DNSServiceForget( &instance->getAddr );
+ ForgetIPAddressList( &instance->addrList );
+
+ sdRef = instance->context->mainRef;
+ instance->getAddrStartTicks = UpTicks();
+ err = DNSServiceGetAddrInfo( &sdRef, kDNSServiceFlagsShareConnection, instance->ifIndex,
+ kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6, instance->hostname, BrowseAllGAICallback, instance );
+ require_noerr( err, exit );
+
+ instance->getAddr = sdRef;
+ }
+
+exit:
+ if( err ) exit( 1 );
+}
+
+//===========================================================================================================================
+// BrowseAllGAICallback
+//===========================================================================================================================
+
+static void DNSSD_API
+ BrowseAllGAICallback(
+ DNSServiceRef inSDRef,
+ DNSServiceFlags inFlags,
+ uint32_t inInterfaceIndex,
+ DNSServiceErrorType inError,
+ const char * inHostname,
+ const struct sockaddr * inSockAddr,
+ uint32_t inTTL,
+ void * inContext )
+{
+ OSStatus err;
+ BrowseInstance * const instance = (BrowseInstance *) inContext;
+
+ Unused( inSDRef );
+ Unused( inInterfaceIndex );
+ Unused( inHostname );
+ Unused( inTTL );
+
+ err = inError;
+ require_noerr( err, exit );
+
+ if( ( inSockAddr->sa_family != AF_INET ) && ( inSockAddr->sa_family != AF_INET6 ) )
+ {
+ dlogassert( "Unexpected address family: %d", inSockAddr->sa_family );
+ goto exit;
+ }
+
+ if( inFlags & kDNSServiceFlagsAdd )
+ {
+ err = BrowseAllAddIPAddress( instance->context, instance, inSockAddr );
+ if( err == kDuplicateErr ) err = kNoErr;
+ require_noerr( err, exit );
+ }
+ else
+ {
+ err = BrowseAllRemoveIPAddress( instance->context, instance, inSockAddr );
+ if( err == kNotFoundErr ) err = kNoErr;
+ require_noerr( err, exit );
+ }
+
+exit:
+ return;
+}
+
+//===========================================================================================================================
+// BrowseAllConnectionProgress
+//===========================================================================================================================
+
+static void BrowseAllConnectionProgress( int inPhase, const void *inDetails, void *inArg )
+{
+ BrowseIPAddr * const addr = (BrowseIPAddr *) inArg;
+
+ if( inPhase == kAsyncConnectionPhase_Connected )
+ {
+ const AsyncConnectedInfo * const info = (AsyncConnectedInfo *) inDetails;
+
+ addr->connectTimeSecs = info->connectSecs;
+ }
+}
+
+//===========================================================================================================================
+// BrowseAllConnectionHandler
+//===========================================================================================================================
+
+static void BrowseAllConnectionHandler( SocketRef inSock, OSStatus inError, void *inArg )
+{
+ BrowseIPAddr * const addr = (BrowseIPAddr *) inArg;
+ BrowseAllContext * const context = addr->context;
+
+ if( inError )
+ {
+ addr->connectStatus = kConnectStatus_Failed;
+ addr->connectError = inError;
+ }
+ else
+ {
+ addr->connectStatus = kConnectStatus_Succeeded;
+ }
+
+ check( context->pendingConnectCount > 0 );
+ if( --context->pendingConnectCount == 0 )
+ {
+ if( context->exitTimer )
+ {
+ dispatch_source_forget( &context->exitTimer );
+ dispatch_async_f( dispatch_get_main_queue(), context, BrowseAllExit );
+ }
+ }
+
+ ForgetSocket( &inSock );
+ BrowseIPAddrRelease( addr );
+}
+
+//===========================================================================================================================
+// BrowseAllStop
+//===========================================================================================================================
+
+static void BrowseAllStop( void *inContext )
+{
+ OSStatus err;
+ BrowseAllContext * const context = (BrowseAllContext *) inContext;
+ BrowseDomain * domain;
+ BrowseType * type;
+ BrowseOp * browse;
+ BrowseInstance * instance;
+
+ DNSServiceForget( &context->domainsQuery );
+ for( domain = context->domainList; domain; domain = domain->next )
+ {
+ DNSServiceForget( &domain->servicesQuery );
+ for( type = domain->typeList; type; type = type->next )
+ {
+ for( browse = type->browseList; browse; browse = browse->next )
+ {
+ DNSServiceForget( &browse->browse );
+ for( instance = browse->instanceList; instance; instance = instance->next )
+ {
+ DNSServiceForget( &instance->resolve );
+ DNSServiceForget( &instance->getAddr );
+ }
+ }
+ }
+ }
+ DNSServiceForget( &context->mainRef );
+
+ if( ( context->pendingConnectCount > 0 ) && ( context->connectTimeLimitSecs > 0 ) )
+ {
+ check( !context->exitTimer );
+ err = DispatchTimerCreate( dispatch_time_seconds( context->connectTimeLimitSecs ), DISPATCH_TIME_FOREVER,
+ 100 * kNanosecondsPerMillisecond, BrowseAllExit, NULL, context, &context->exitTimer );
+ require_noerr( err, exit );
+ dispatch_resume( context->exitTimer );
+ }
+ else
+ {
+ dispatch_async_f( dispatch_get_main_queue(), context, BrowseAllExit );
+ }
+ err = kNoErr;
+
+exit:
+ if( err ) exit( 1 );
+}
+
+//===========================================================================================================================
+// BrowseAllExit
+//===========================================================================================================================
+
+#define kStatusStr_CouldConnect "connected"
+#define kStatusStr_CouldConnectColored kANSIGreen kStatusStr_CouldConnect kANSINormal
+#define kStatusStr_CouldNotConnect "could not connect"
+#define kStatusStr_CouldNotConnectColored kANSIRed kStatusStr_CouldNotConnect kANSINormal
+#define kStatusStr_NoConnectionAttempted "no connection attempted"
+#define kStatusStr_Unknown "unknown"
+
+#define Indent( X ) ( (X) * 4 ), ""
+
+static void BrowseAllExit( void *inContext )
+{
+ BrowseAllContext * const context = (BrowseAllContext *) inContext;
+ BrowseDomain * domain;
+ BrowseType * type;
+ BrowseOp * browse;
+ BrowseInstance * instance;
+ BrowseIPAddr * addr;
+
+ dispatch_source_forget( &context->exitTimer );
+
+ for( domain = context->domainList; domain; domain = domain->next )
+ {
+ FPrintF( stdout, "%s\n\n", domain->name );
+
+ for( type = domain->typeList; type; type = type->next )
+ {
+ const char * desc;
+
+ desc = ServiceTypeDescription( type->name );
+ if( desc ) FPrintF( stdout, "%*s" "%s (%s)\n\n", Indent( 1 ), desc, type->name );
+ else FPrintF( stdout, "%*s" "%s\n\n", Indent( 1 ), type->name );
+
+ for( browse = type->browseList; browse; browse = browse->next )
+ {
+ for( instance = browse->instanceList; instance; instance = instance->next )
+ {
+ char ifname[ IF_NAMESIZE + 1 ];
+
+ FPrintF( stdout, "%*s" "%s via ", Indent( 2 ), instance->name );
+ FPrintF( stdout, "%*s" "%s via ", Indent( 2 ), instance->name );
+ if( instance->ifIndex == 0 )
+ {
+ FPrintF( stdout, "the Internet" );
+ }
+ else if( if_indextoname( instance->ifIndex, ifname ) )
+ {
+ NetTransportType netType;
+
+ SocketGetInterfaceInfo( kInvalidSocketRef, ifname, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ &netType );
+ FPrintF( stdout, "%s (%s)",
+ ( netType == kNetTransportType_Ethernet ) ? "ethernet" : NetTransportTypeToString( netType ),
+ ifname );
+ }
+ else
+ {
+ FPrintF( stdout, "interface index %u", instance->ifIndex );
+ }
+ FPrintF( stdout, "\n\n" );
+
+ if( instance->hostname )
+ {
+ char buffer[ 256 ];
+
+ SNPrintF( buffer, sizeof( buffer ), "%s:%u", instance->hostname, instance->port );
+ FPrintF( stdout, "%*s" "%-51s %4llu ms\n", Indent( 3 ), buffer,
+ UpTicksToMilliseconds( instance->resolveDoneTicks - instance->resolveStartTicks ) );
+ }
+ else
+ {
+ FPrintF( stdout, "%*s" "%s:%u\n", Indent( 3 ), instance->hostname, instance->port );
+ }
+
+ for( addr = instance->addrList; addr; addr = addr->next )
+ {
+ AsyncConnection_Forget( &addr->connection );
+
+ if( addr->connectStatus == kConnectStatus_Pending )
+ {
+ addr->connectStatus = kConnectStatus_Failed;
+ addr->connectError = kTimeoutErr;
+ }
+
+ FPrintF( stdout, "%*s" "%-##47a %4llu ms (", Indent( 4 ),
+ &addr->sip.sa, UpTicksToMilliseconds( addr->foundTicks - instance->getAddrStartTicks ) );
+ switch( addr->connectStatus )
+ {
+ case kConnectStatus_None:
+ FPrintF( stdout, "%s", kStatusStr_NoConnectionAttempted );
+ break;
+
+ case kConnectStatus_Succeeded:
+ FPrintF( stdout, "%s in %.2f ms",
+ context->useColoredText ? kStatusStr_CouldConnectColored : kStatusStr_CouldConnect,
+ addr->connectTimeSecs * 1000 );
+ break;
+
+ case kConnectStatus_Failed:
+ FPrintF( stdout, "%s: %m",
+ context->useColoredText ? kStatusStr_CouldNotConnectColored : kStatusStr_CouldNotConnect,
+ addr->connectError );
+ break;
+
+ default:
+ FPrintF( stdout, "%s", kStatusStr_Unknown );
+ break;
+ }
+ FPrintF( stdout, ")\n" );
+ }
+
+ FPrintF( stdout, "\n" );
+ if( instance->txtLen == 0 ) continue;
+
+ FPrintF( stdout, "%*s" "TXT record:\n", Indent( 3 ) );
+ if( instance->txtLen > 1 )
+ {
+ FPrintF( stdout, "%3{txt}", instance->txtPtr, instance->txtLen );
+ }
+ else
+ {
+ FPrintF( stdout, "%*s" "%#H\n", Indent( 3 ), instance->txtPtr, (int) instance->txtLen, INT_MAX );
+ }
+ FPrintF( stdout, "\n" );
+ }
+ }
+ FPrintF( stdout, "\n" );
+ }
+ }
+
+ while( ( domain = context->domainList ) != NULL )
+ {
+ context->domainList = domain->next;
+ BrowseDomainFree( domain );
+ }
+
+ BrowseAllContextRelease( context );
+ Exit( NULL );
+}
+
+//===========================================================================================================================
+// BrowseAllAddDomain
+//===========================================================================================================================
+
+static OSStatus BrowseAllAddDomain( BrowseAllContext *inContext, const char *inName )
+{
+ OSStatus err;
+ BrowseDomain * domain;
+ BrowseDomain ** p;
+ BrowseDomain * newDomain = NULL;
+
+ for( p = &inContext->domainList; ( domain = *p ) != NULL; p = &domain->next )
+ {
+ if( strcasecmp( domain->name, inName ) == 0 ) break;
+ }
+ require_action_quiet( !domain, exit, err = kDuplicateErr );
+
+ newDomain = (BrowseDomain *) calloc( 1, sizeof( *newDomain ) );
+ require_action( newDomain, exit, err = kNoMemoryErr );
+
+ ++inContext->refCount;
+ newDomain->context = inContext;
+
+ newDomain->name = strdup( inName );
+ require_action( newDomain->name, exit, err = kNoMemoryErr );
+
+ if( inContext->serviceTypesCount > 0 )
+ {
+ size_t i;
+
+ for( i = 0; i < inContext->serviceTypesCount; ++i )
+ {
+ err = BrowseAllAddServiceType( inContext, newDomain, inContext->serviceTypes[ i ], inContext->ifIndex,
+ inContext->includeAWDL );
+ if( err == kDuplicateErr ) err = kNoErr;
+ require_noerr( err, exit );
+ }
+ }
+ else
+ {
+ char * recordName;
+ DNSServiceFlags flags;
+ DNSServiceRef sdRef;
+
+ ASPrintF( &recordName, "_services._dns-sd._udp.%s", newDomain->name );
+ require_action( recordName, exit, err = kNoMemoryErr );
+
+ flags = kDNSServiceFlagsShareConnection;
+ if( inContext->includeAWDL ) flags |= kDNSServiceFlagsIncludeAWDL;
+
+ sdRef = newDomain->context->mainRef;
+ err = DNSServiceQueryRecord( &sdRef, flags, inContext->ifIndex, recordName, kDNSServiceType_PTR, kDNSServiceClass_IN,
+ BrowseAllQueryCallback, newDomain );
+ free( recordName );
+ require_noerr( err, exit );
+
+ newDomain->servicesQuery = sdRef;
+ }
+
+ *p = newDomain;
+ newDomain = NULL;
+ err = kNoErr;
+
+exit:
+ if( newDomain ) BrowseDomainFree( newDomain );
+ return( err );
+}
+
+//===========================================================================================================================
+// BrowseAllRemoveDomain
+//===========================================================================================================================
+
+static OSStatus BrowseAllRemoveDomain( BrowseAllContext *inContext, const char *inName )
+{
+ OSStatus err;
+ BrowseDomain * domain;
+ BrowseDomain ** p;
+
+ for( p = &inContext->domainList; ( domain = *p ) != NULL; p = &domain->next )
+ {
+ if( strcasecmp( domain->name, inName ) == 0 ) break;
+ }
+
+ if( domain )
+ {
+ *p = domain->next;
+ BrowseDomainFree( domain );
+ err = kNoErr;
+ }
+ else
+ {
+ err = kNotFoundErr;
+ }
+
+ return( err );
+}
+
+//===========================================================================================================================
+// BrowseAllContextRelease
+//===========================================================================================================================
+
+static void BrowseAllContextRelease( BrowseAllContext *inContext )
+{
+ if( --inContext->refCount == 0 )
+ {
+ check( !inContext->domainsQuery );
+ check( !inContext->domainList );
+ check( !inContext->exitTimer );
+ check( !inContext->pendingConnectCount );
+ DNSServiceForget( &inContext->mainRef );
+ if( inContext->serviceTypes )
+ {
+ StringArray_Free( inContext->serviceTypes, inContext->serviceTypesCount );
+ inContext->serviceTypes = NULL;
+ inContext->serviceTypesCount = 0;
+ }
+ free( inContext );
+ }
+}
+
+//===========================================================================================================================
+// BrowseAllAddServiceType
+//===========================================================================================================================
+
+static OSStatus
+ BrowseAllAddServiceType(
+ BrowseAllContext * inContext,
+ BrowseDomain * inDomain,
+ const char * inName,
+ uint32_t inIfIndex,
+ Boolean inIncludeAWDL )
+{
+ OSStatus err;
+ DNSServiceRef sdRef;
+ DNSServiceFlags flags;
+ BrowseType * type;
+ BrowseType ** typePtr;
+ BrowseType * newType = NULL;
+ BrowseOp * browse;
+ BrowseOp ** browsePtr;
+ BrowseOp * newBrowse = NULL;
+
+ for( typePtr = &inDomain->typeList; ( type = *typePtr ) != NULL; typePtr = &type->next )
+ {
+ if( strcasecmp( type->name, inName ) == 0 ) break;
+ }
+ if( !type )
+ {
+ newType = (BrowseType *) calloc( 1, sizeof( *newType ) );
+ require_action( newType, exit, err = kNoMemoryErr );
+
+ newType->name = strdup( inName );
+ require_action( newType->name, exit, err = kNoMemoryErr );
+
+ type = newType;
+ }
+
+ for( browsePtr = &type->browseList; ( browse = *browsePtr ) != NULL; browsePtr = &browse->next )
+ {
+ if( browse->ifIndex == inIfIndex ) break;
+ }
+ require_action_quiet( !browse, exit, err = kDuplicateErr );
+
+ newBrowse = (BrowseOp *) calloc( 1, sizeof( *newBrowse ) );
+ require_action( newBrowse, exit, err = kNoMemoryErr );
+
+ ++inContext->refCount;
+ newBrowse->context = inContext;
+ newBrowse->ifIndex = inIfIndex;
+ if( stricmp_suffix( inName, "._tcp" ) == 0 ) newBrowse->isTCP = true;
+
+ flags = kDNSServiceFlagsShareConnection;
+ if( inIncludeAWDL ) flags |= kDNSServiceFlagsIncludeAWDL;
+
+ newBrowse->startTicks = UpTicks();
+
+ sdRef = inContext->mainRef;
+ err = DNSServiceBrowse( &sdRef, flags, newBrowse->ifIndex, type->name, inDomain->name, BrowseAllBrowseCallback,
+ newBrowse );
+ require_noerr( err, exit );
+
+ newBrowse->browse = sdRef;
+ *browsePtr = newBrowse;
+ newBrowse = NULL;
+
+ if( newType )
+ {
+ *typePtr = newType;
+ newType = NULL;
+ }
+
+exit:
+ if( newBrowse ) BrowseOpFree( newBrowse );
+ if( newType ) BrowseTypeFree( newType );
+ return( err );
+}
+
+//===========================================================================================================================
+// BrowseAllRemoveServiceType
+//===========================================================================================================================
+
+static OSStatus
+ BrowseAllRemoveServiceType(
+ BrowseAllContext * inContext,
+ BrowseDomain * inDomain,
+ const char * inName,
+ uint32_t inIfIndex )
+{
+ OSStatus err;
+ BrowseType * type;
+ BrowseType ** typePtr;
+ BrowseOp * browse;
+ BrowseOp ** browsePtr;
+
+ Unused( inContext );
+
+ for( typePtr = &inDomain->typeList; ( type = *typePtr ) != NULL; typePtr = &type->next )
+ {
+ if( strcasecmp( type->name, inName ) == 0 ) break;
+ }
+ require_action_quiet( type, exit, err = kNotFoundErr );
+
+ for( browsePtr = &type->browseList; ( browse = *browsePtr ) != NULL; browsePtr = &browse->next )
+ {
+ if( browse->ifIndex == inIfIndex ) break;
+ }
+ require_action_quiet( browse, exit, err = kNotFoundErr );
+
+ *browsePtr = browse->next;
+ BrowseOpFree( browse );
+ if( !type->browseList )
+ {
+ *typePtr = type->next;
+ BrowseTypeFree( type );
+ }
+ err = kNoErr;
+
+exit:
+ return( err );
+}
+
+//===========================================================================================================================
+// BrowseAllAddServiceInstance
+//===========================================================================================================================
+
+static OSStatus
+ BrowseAllAddServiceInstance(
+ BrowseAllContext * inContext,
+ BrowseOp * inBrowse,
+ const char * inName,
+ const char * inRegType,
+ const char * inDomain,
+ uint32_t inIfIndex )
+{
+ OSStatus err;
+ DNSServiceRef sdRef;
+ BrowseInstance * instance;
+ BrowseInstance ** p;
+ const uint64_t nowTicks = UpTicks();
+ BrowseInstance * newInstance = NULL;
+
+ for( p = &inBrowse->instanceList; ( instance = *p ) != NULL; p = &instance->next )
+ {
+ if( ( instance->ifIndex == inIfIndex ) && ( strcasecmp( instance->name, inName ) == 0 ) ) break;
+ }
+ require_action_quiet( !instance, exit, err = kDuplicateErr );
+
+ newInstance = (BrowseInstance *) calloc( 1, sizeof( *newInstance ) );
+ require_action( newInstance, exit, err = kNoMemoryErr );
+
+ ++inContext->refCount;
+ newInstance->context = inContext;
+ newInstance->foundTicks = nowTicks;
+ newInstance->ifIndex = inIfIndex;
+ newInstance->isTCP = inBrowse->isTCP;
+
+ newInstance->name = strdup( inName );
+ require_action( newInstance->name, exit, err = kNoMemoryErr );
+
+ sdRef = inContext->mainRef;
+ newInstance->resolveStartTicks = UpTicks();
+ err = DNSServiceResolve( &sdRef, kDNSServiceFlagsShareConnection, newInstance->ifIndex, inName, inRegType, inDomain,
+ BrowseAllResolveCallback, newInstance );
+ require_noerr( err, exit );
+
+ newInstance->resolve = sdRef;
+ *p = newInstance;
+ newInstance = NULL;
+
+exit:
+ if( newInstance ) BrowseInstanceFree( newInstance );
+ return( err );
+}
+
+//===========================================================================================================================
+// BrowseAllRemoveServiceInstance
+//===========================================================================================================================
+
+static OSStatus
+ BrowseAllRemoveServiceInstance(
+ BrowseAllContext * inContext,
+ BrowseOp * inBrowse,
+ const char * inName,
+ uint32_t inIfIndex )
+{
+ OSStatus err;
+ BrowseInstance * instance;
+ BrowseInstance ** p;
+
+ Unused( inContext );
+
+ for( p = &inBrowse->instanceList; ( instance = *p ) != NULL; p = &instance->next )
+ {
+ if( ( instance->ifIndex == inIfIndex ) && ( strcasecmp( instance->name, inName ) == 0 ) ) break;
+ }
+ require_action_quiet( instance, exit, err = kNotFoundErr );
+
+ *p = instance->next;
+ BrowseInstanceFree( instance );
+ err = kNoErr;
+
+exit:
+ return( err );
+}
+
+//===========================================================================================================================
+// BrowseAllAddIPAddress
+//===========================================================================================================================
+
+#define kDiscardProtocolPort 9
+
+static OSStatus
+ BrowseAllAddIPAddress(
+ BrowseAllContext * inContext,
+ BrowseInstance * inInstance,
+ const struct sockaddr * inSockAddr )
+{
+ OSStatus err;
+ BrowseIPAddr * addr;
+ BrowseIPAddr ** p;
+ const uint64_t nowTicks = UpTicks();
+ BrowseIPAddr * newAddr = NULL;
+
+ if( ( inSockAddr->sa_family != AF_INET ) && ( inSockAddr->sa_family != AF_INET6 ) )
+ {
+ dlogassert( "Unexpected address family: %d", inSockAddr->sa_family );
+ err = kTypeErr;
+ goto exit;
+ }
+
+ for( p = &inInstance->addrList; ( addr = *p ) != NULL; p = &addr->next )
+ {
+ if( SockAddrCompareAddr( &addr->sip, inSockAddr ) == 0 ) break;
+ }
+ require_action_quiet( !addr, exit, err = kDuplicateErr );
+
+ newAddr = (BrowseIPAddr *) calloc( 1, sizeof( *newAddr ) );
+ require_action( newAddr, exit, err = kNoMemoryErr );
+
+ ++inContext->refCount;
+ newAddr->refCount = 1;
+ newAddr->context = inContext;
+ newAddr->foundTicks = nowTicks;
+ SockAddrCopy( inSockAddr, &newAddr->sip.sa );
+
+ if( inInstance->isTCP && ( inInstance->port != kDiscardProtocolPort ) )
+ {
+ char destination[ kSockAddrStringMaxSize ];
+
+ err = SockAddrToString( &newAddr->sip, kSockAddrStringFlagsNoPort, destination );
+ require_noerr( err, exit );
+
+ err = AsyncConnection_Connect( &newAddr->connection, destination, -inInstance->port, kAsyncConnectionFlag_P2P,
+ kAsyncConnectionNoTimeout, kSocketBufferSize_DontSet, kSocketBufferSize_DontSet,
+ BrowseAllConnectionProgress, newAddr, BrowseAllConnectionHandler, newAddr, dispatch_get_main_queue() );
+ require_noerr( err, exit );
+
+ ++newAddr->refCount;
+ newAddr->connectStatus = kConnectStatus_Pending;
+ ++inContext->pendingConnectCount;
+ }
+
+ *p = newAddr;
+ newAddr = NULL;
+ err = kNoErr;
+
+exit:
+ if( newAddr ) BrowseIPAddrRelease( newAddr );
+ return( err );
+}
+
+//===========================================================================================================================
+// BrowseAllRemoveIPAddress
+//===========================================================================================================================
+
+static OSStatus
+ BrowseAllRemoveIPAddress(
+ BrowseAllContext * inContext,
+ BrowseInstance * inInstance,
+ const struct sockaddr * inSockAddr )
+{
+ OSStatus err;
+ BrowseIPAddr * addr;
+ BrowseIPAddr ** p;
+
+ Unused( inContext );
+
+ for( p = &inInstance->addrList; ( addr = *p ) != NULL; p = &addr->next )
+ {
+ if( SockAddrCompareAddr( &addr->sip.sa, inSockAddr ) == 0 ) break;
+ }
+ require_action_quiet( addr, exit, err = kNotFoundErr );
+
+ *p = addr->next;
+ BrowseIPAddrRelease( addr );
+ err = kNoErr;
+
+exit:
+ return( err );
+}
+
+//===========================================================================================================================
+// BrowseDomainFree
+//===========================================================================================================================
+
+static void BrowseDomainFree( BrowseDomain *inDomain )
+{
+ BrowseType * type;
+
+ ForgetBrowseAllContext( &inDomain->context );
+ ForgetMem( &inDomain->name );
+ DNSServiceForget( &inDomain->servicesQuery );
+ while( ( type = inDomain->typeList ) != NULL )
+ {
+ inDomain->typeList = type->next;
+ BrowseTypeFree( type );
+ }
+ free( inDomain );
+}
+
+//===========================================================================================================================
+// BrowseTypeFree
+//===========================================================================================================================
+
+static void BrowseTypeFree( BrowseType *inType )
+{
+ BrowseOp * browse;
+
+ ForgetMem( &inType->name );
+ while( ( browse = inType->browseList ) != NULL )
+ {
+ inType->browseList = browse->next;
+ BrowseOpFree( browse );
+ }
+ free( inType );
+}
+
+//===========================================================================================================================
+// BrowseOpFree
+//===========================================================================================================================
+
+static void BrowseOpFree( BrowseOp *inBrowse )
+{
+ BrowseInstance * instance;
+
+ ForgetBrowseAllContext( &inBrowse->context );
+ DNSServiceForget( &inBrowse->browse );
+ while( ( instance = inBrowse->instanceList ) != NULL )
+ {
+ inBrowse->instanceList = instance->next;
+ BrowseInstanceFree( instance );
+ }
+ free( inBrowse );
+}
+
+//===========================================================================================================================
+// BrowseInstanceFree
+//===========================================================================================================================
+
+static void BrowseInstanceFree( BrowseInstance *inInstance )
+{
+ ForgetBrowseAllContext( &inInstance->context );
+ ForgetMem( &inInstance->name );
+ DNSServiceForget( &inInstance->resolve );
+ DNSServiceForget( &inInstance->getAddr );
+ ForgetMem( &inInstance->txtPtr );
+ ForgetMem( &inInstance->hostname );
+ ForgetIPAddressList( &inInstance->addrList );
+ free( inInstance );
+}
+
+//===========================================================================================================================
+// BrowseIPAddrRelease
+//===========================================================================================================================
+
+static void BrowseIPAddrRelease( BrowseIPAddr *inAddr )
+{
+ AsyncConnection_Forget( &inAddr->connection );
+ if( --inAddr->refCount == 0 )
+ {
+ ForgetBrowseAllContext( &inAddr->context );
+ free( inAddr );
+ }
+}
+
+//===========================================================================================================================
+// BrowseIPAddrReleaseList
+//===========================================================================================================================
+
+static void BrowseIPAddrReleaseList( BrowseIPAddr *inList )
+{
+ BrowseIPAddr * addr;
+
+ while( ( addr = inList ) != NULL )
+ {
+ inList = addr->next;
+ BrowseIPAddrRelease( addr );
+ }
+}
+
+//===========================================================================================================================
+// GetAddrInfoStressCmd
+//===========================================================================================================================
+
+typedef struct
+{
+ DNSServiceRef mainRef;
+ DNSServiceRef sdRef;
+ DNSServiceFlags flags;
+ unsigned int interfaceIndex;
+ unsigned int connectionNumber;
+ unsigned int requestCount;
+ unsigned int requestCountMax;
+ unsigned int requestCountLimit;
+ unsigned int durationMinMs;
+ unsigned int durationMaxMs;
+
+} GAIStressContext;
+
+static void GetAddrInfoStressEvent( void *inContext );
+static void DNSSD_API
+ GetAddrInfoStressCallback(
+ DNSServiceRef inSDRef,
+ DNSServiceFlags inFlags,
+ uint32_t inInterfaceIndex,
+ DNSServiceErrorType inError,
+ const char * inHostname,
+ const struct sockaddr * inSockAddr,
+ uint32_t inTTL,
+ void * inContext );
+
+static void GetAddrInfoStressCmd( void )
+{
+ OSStatus err;
+ GAIStressContext * context = NULL;
+ int i;
+ DNSServiceFlags flags;
+ uint32_t ifIndex;
+ char ifName[ kInterfaceNameBufLen ];
+ char time[ kTimestampBufLen ];
+
+ if( gGAIStress_TestDurationSecs < 0 )
+ {
+ FPrintF( stdout, "Invalid test duration: %d s.\n", gGAIStress_TestDurationSecs );
+ err = kParamErr;
+ goto exit;
+ }
+ if( gGAIStress_ConnectionCount <= 0 )
+ {
+ FPrintF( stdout, "Invalid simultaneous connection count: %d.\n", gGAIStress_ConnectionCount );
+ err = kParamErr;
+ goto exit;
+ }
+ if( gGAIStress_DurationMinMs <= 0 )
+ {
+ FPrintF( stdout, "Invalid minimum DNSServiceGetAddrInfo() duration: %d ms.\n", gGAIStress_DurationMinMs );
+ err = kParamErr;
+ goto exit;
+ }
+ if( gGAIStress_DurationMaxMs <= 0 )
+ {
+ FPrintF( stdout, "Invalid maximum DNSServiceGetAddrInfo() duration: %d ms.\n", gGAIStress_DurationMaxMs );
+ err = kParamErr;
+ goto exit;
+ }
+ if( gGAIStress_DurationMinMs > gGAIStress_DurationMaxMs )
+ {
+ FPrintF( stdout, "Invalid minimum and maximum DNSServiceGetAddrInfo() durations: %d ms and %d ms.\n",
+ gGAIStress_DurationMinMs, gGAIStress_DurationMaxMs );
+ err = kParamErr;
+ goto exit;
+ }
+ if( gGAIStress_RequestCountMax <= 0 )
+ {
+ FPrintF( stdout, "Invalid maximum request count: %d.\n", gGAIStress_RequestCountMax );
+ err = kParamErr;
+ goto exit;
+ }
+
+ // Set flags.
+
+ flags = GetDNSSDFlagsFromOpts();
+
+ // Set interface index.
+
+ err = InterfaceIndexFromArgString( gInterface, &ifIndex );
+ require_noerr_quiet( err, exit );
+
+ for( i = 0; i < gGAIStress_ConnectionCount; ++i )
+ {
+ context = (GAIStressContext *) calloc( 1, sizeof( *context ) );
+ require_action( context, exit, err = kNoMemoryErr );
+
+ context->flags = flags;
+ context->interfaceIndex = ifIndex;
+ context->connectionNumber = (unsigned int)( i + 1 );
+ context->requestCountMax = (unsigned int) gGAIStress_RequestCountMax;
+ context->durationMinMs = (unsigned int) gGAIStress_DurationMinMs;
+ context->durationMaxMs = (unsigned int) gGAIStress_DurationMaxMs;
+
+ dispatch_async_f( dispatch_get_main_queue(), context, GetAddrInfoStressEvent );
+ context = NULL;
+ }
+
+ if( gGAIStress_TestDurationSecs > 0 )
+ {
+ dispatch_after_f( dispatch_time_seconds( gGAIStress_TestDurationSecs ), dispatch_get_main_queue(), NULL, Exit );
+ }
+
+ FPrintF( stdout, "Flags: %#{flags}\n", flags, kDNSServiceFlagsDescriptors );
+ FPrintF( stdout, "Interface: %d (%s)\n", (int32_t) ifIndex, InterfaceIndexToName( ifIndex, ifName ) );
+ FPrintF( stdout, "Test duration: " );
+ if( gGAIStress_TestDurationSecs == 0 )
+ {
+ FPrintF( stdout, "∞\n" );
+ }
+ else
+ {
+ FPrintF( stdout, "%d s\n", gGAIStress_TestDurationSecs );
+ }
+ FPrintF( stdout, "Connection count: %d\n", gGAIStress_ConnectionCount );
+ FPrintF( stdout, "Request duration min: %d ms\n", gGAIStress_DurationMinMs );
+ FPrintF( stdout, "Request duration max: %d ms\n", gGAIStress_DurationMaxMs );
+ FPrintF( stdout, "Request count max: %d\n", gGAIStress_RequestCountMax );
+ FPrintF( stdout, "Start time: %s\n", GetTimestampStr( time ) );
+ FPrintF( stdout, "---\n" );
+
+ dispatch_main();
+
+exit:
+ FreeNullSafe( context );
+ if( err ) exit( 1 );
+}
+
+//===========================================================================================================================
+// GetAddrInfoStressEvent
+//===========================================================================================================================
+
+#define kStressRandStrLen 5
+
+#define kLowercaseAlphaCharSet "abcdefghijklmnopqrstuvwxyz"
+
+static void GetAddrInfoStressEvent( void *inContext )
+{
+ GAIStressContext * const context = (GAIStressContext *) inContext;
+ OSStatus err;
+ DNSServiceRef sdRef;
+ unsigned int nextMs;
+ char randomStr[ kStressRandStrLen + 1 ];
+ char hostname[ kStressRandStrLen + 4 + 1 ];
+ char time[ kTimestampBufLen ];
+ Boolean isConnectionNew = false;
+ static Boolean printedHeader = false;
+
+ if( !context->mainRef || ( context->requestCount >= context->requestCountLimit ) )
+ {
+ DNSServiceForget( &context->mainRef );
+ context->sdRef = NULL;
+ context->requestCount = 0;
+ context->requestCountLimit = RandomRange( 1, context->requestCountMax );
+
+ err = DNSServiceCreateConnection( &context->mainRef );
+ require_noerr( err, exit );
+
+ err = DNSServiceSetDispatchQueue( context->mainRef, dispatch_get_main_queue() );
+ require_noerr( err, exit );
+
+ isConnectionNew = true;
+ }
+
+ RandomString( kLowercaseAlphaCharSet, sizeof_string( kLowercaseAlphaCharSet ), 2, kStressRandStrLen, randomStr );
+ SNPrintF( hostname, sizeof( hostname ), "%s.com", randomStr );
+
+ nextMs = RandomRange( context->durationMinMs, context->durationMaxMs );
+
+ if( !printedHeader )
+ {
+ FPrintF( stdout, "%-26s Conn Hostname Dur (ms)\n", "Timestamp" );
+ printedHeader = true;
+ }
+ FPrintF( stdout, "%-26s %3u%c %9s %8u\n",
+ GetTimestampStr( time ), context->connectionNumber, isConnectionNew ? '*': ' ', hostname, nextMs );
+
+ DNSServiceForget( &context->sdRef );
+ sdRef = context->mainRef;
+ err = DNSServiceGetAddrInfo( &sdRef, context->flags | kDNSServiceFlagsShareConnection, context->interfaceIndex,
+ kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6, hostname, GetAddrInfoStressCallback, NULL );
+ require_noerr( err, exit );
+ context->sdRef = sdRef;
+
+ context->requestCount++;
+
+ dispatch_after_f( dispatch_time_milliseconds( nextMs ), dispatch_get_main_queue(), context, GetAddrInfoStressEvent );
+
+exit:
+ if( err ) exit( 1 );
+}
+
+//===========================================================================================================================
+// GetAddrInfoStressCallback
+//===========================================================================================================================
+
+static void DNSSD_API
+ GetAddrInfoStressCallback(
+ DNSServiceRef inSDRef,
+ DNSServiceFlags inFlags,
+ uint32_t inInterfaceIndex,
+ DNSServiceErrorType inError,
+ const char * inHostname,
+ const struct sockaddr * inSockAddr,
+ uint32_t inTTL,
+ void * inContext )
+{
+ Unused( inSDRef );
+ Unused( inFlags );
+ Unused( inInterfaceIndex );
+ Unused( inError );
+ Unused( inHostname );
+ Unused( inSockAddr );
+ Unused( inTTL );
+ Unused( inContext );
+}
+
+//===========================================================================================================================
+// DNSQueryCmd
+//===========================================================================================================================
+
+#define kDNSPort 53
+
+typedef struct
+{
+ sockaddr_ip serverAddr;
+ uint64_t sendTicks;
+ uint8_t * msgPtr;
+ size_t msgLen;
+ size_t msgOffset;
+ const char * name;
+ dispatch_source_t readSource;
+ SocketRef sock;
+ int timeLimitSecs;
+ uint16_t queryID;
+ uint16_t type;
+ Boolean haveTCPLen;
+ Boolean useTCP;
+ Boolean printRawRData; // True if RDATA results are not to be formatted.
+ uint8_t msgBuf[ 512 ];
+
+} DNSQueryContext;
+
+static void DNSQueryPrintPrologue( const DNSQueryContext *inContext );
+static void DNSQueryReadHandler( void *inContext );
+static void DNSQueryCancelHandler( void *inContext );
+
+static void DNSQueryCmd( void )
+{
+ OSStatus err;
+ DNSQueryContext * context = NULL;
+ uint8_t * msgPtr;
+ size_t msgLen, sendLen;
+
+ // Check command parameters.
+
+ if( gDNSQuery_TimeLimitSecs < -1 )
+ {
+ FPrintF( stdout, "Invalid wait period: %d seconds.\n", gDNSQuery_TimeLimitSecs );
+ err = kParamErr;
+ goto exit;
+ }
+ if( ( gDNSQuery_Flags < INT16_MIN ) || ( gDNSQuery_Flags > UINT16_MAX ) )
+ {
+ FPrintF( stdout, "DNS flags-and-codes value is out of the unsigned 16-bit range: 0x%08X.\n", gDNSQuery_Flags );
+ err = kParamErr;
+ goto exit;
+ }
+
+ // Create context.
+
+ context = (DNSQueryContext *) calloc( 1, sizeof( *context ) );
+ require_action( context, exit, err = kNoMemoryErr );
+
+ context->name = gDNSQuery_Name;
+ context->sock = kInvalidSocketRef;
+ context->timeLimitSecs = gDNSQuery_TimeLimitSecs;
+ context->queryID = (uint16_t) Random32();
+ context->useTCP = gDNSQuery_UseTCP ? true : false;
+ context->printRawRData = gDNSQuery_RawRData ? true : false;
+
+ err = StringToSockAddr( gDNSQuery_Server, &context->serverAddr, sizeof( context->serverAddr ), NULL );
+ require_noerr( err, exit );
+ if( SockAddrGetPort( &context->serverAddr ) == 0 ) SockAddrSetPort( &context->serverAddr, kDNSPort );
+
+ err = RecordTypeFromArgString( gDNSQuery_Type, &context->type );
+ require_noerr( err, exit );
+
+ // Write query message.
+
+ check_compile_time_code( sizeof( context->msgBuf ) >= ( kDNSQueryMessageMaxLen + 2 ) );
+
+ msgPtr = context->useTCP ? &context->msgBuf[ 2 ] : context->msgBuf;
+ err = WriteDNSQueryMessage( msgPtr, context->queryID, (uint16_t) gDNSQuery_Flags, context->name, context->type,
+ kDNSServiceClass_IN, &msgLen );
+ require_noerr( err, exit );
+ check( msgLen <= UINT16_MAX );
+
+ if( context->useTCP )
+ {
+ WriteBig16( context->msgBuf, msgLen );
+ sendLen = 2 + msgLen;
+ }
+ else
+ {
+ sendLen = msgLen;
+ }
+
+ DNSQueryPrintPrologue( context );
+
+ if( gDNSQuery_Verbose )
+ {
+ FPrintF( stdout, "DNS message to send:\n\n" );
+ PrintUDNSMessage( msgPtr, msgLen, false );
+ FPrintF( stdout, "---\n" );
+ }
+
+ if( context->useTCP )
+ {
+ // Create TCP socket.
+
+ context->sock = socket( context->serverAddr.sa.sa_family, SOCK_STREAM, IPPROTO_TCP );
+ err = map_socket_creation_errno( context->sock );
+ require_noerr( err, exit );
+
+ err = SocketConnect( context->sock, &context->serverAddr, 5 );
+ require_noerr( err, exit );
+ }
+ else
+ {
+ // Create UDP socket.
+
+ err = UDPClientSocketOpen( AF_UNSPEC, &context->serverAddr, 0, -1, NULL, &context->sock );
+ require_noerr( err, exit );
+ }
+
+ context->sendTicks = UpTicks();
+ err = SocketWriteAll( context->sock, context->msgBuf, sendLen, 5 );
+ require_noerr( err, exit );
+
+ if( context->timeLimitSecs == 0 ) goto exit;
+
+ err = DispatchReadSourceCreate( context->sock, DNSQueryReadHandler, DNSQueryCancelHandler, context,
+ &context->readSource );
+ require_noerr( err, exit );
+ dispatch_resume( context->readSource );
+
+ if( context->timeLimitSecs > 0 )
+ {
+ dispatch_after_f( dispatch_time_seconds( context->timeLimitSecs ), dispatch_get_main_queue(), kExitReason_Timeout,
+ Exit );
+ }
+ dispatch_main();
+
+exit:
+ if( context )
+ {
+ dispatch_source_forget( &context->readSource );
+ ForgetSocket( &context->sock );
+ free( context );
+ }
+ if( err ) exit( 1 );
+}
+
+//===========================================================================================================================
+// DNSQueryPrintPrologue
+//===========================================================================================================================
+
+static void DNSQueryPrintPrologue( const DNSQueryContext *inContext )
+{
+ const int timeLimitSecs = inContext->timeLimitSecs;
+ char time[ kTimestampBufLen ];
+
+ FPrintF( stdout, "Name: %s\n", inContext->name );
+ FPrintF( stdout, "Type: %s (%u)\n", RecordTypeToString( inContext->type ), inContext->type );
+ FPrintF( stdout, "Server: %##a\n", &inContext->serverAddr );
+ FPrintF( stdout, "Transport: %s\n", inContext->useTCP ? "TCP" : "UDP" );
+ FPrintF( stdout, "Time limit: " );
+ if( timeLimitSecs >= 0 ) FPrintF( stdout, "%d second%?c\n", timeLimitSecs, timeLimitSecs != 1, 's' );
+ else FPrintF( stdout, "∞\n" );
+ FPrintF( stdout, "Start time: %s\n", GetTimestampStr( time ) );
+ FPrintF( stdout, "---\n" );
+}
+
+//===========================================================================================================================
+// DNSQueryReadHandler
+//===========================================================================================================================
+
+static void DNSQueryReadHandler( void *inContext )
+{
+ OSStatus err;
+ const uint64_t nowTicks = UpTicks();
+ DNSQueryContext * const context = (DNSQueryContext *) inContext;
+ char time[ kTimestampBufLen ];
+
+ GetTimestampStr( time );
+
+ if( context->useTCP )
+ {
+ if( !context->haveTCPLen )
+ {
+ err = SocketReadData( context->sock, &context->msgBuf, 2, &context->msgOffset );
+ if( err == EWOULDBLOCK ) { err = kNoErr; goto exit; }
+ require_noerr( err, exit );
+
+ context->msgOffset = 0;
+ context->msgLen = ReadBig16( context->msgBuf );
+ context->haveTCPLen = true;
+ if( context->msgLen <= sizeof( context->msgBuf ) )
+ {
+ context->msgPtr = context->msgBuf;
+ }
+ else
+ {
+ context->msgPtr = (uint8_t *) malloc( context->msgLen );
+ require_action( context->msgPtr, exit, err = kNoMemoryErr );
+ }
+ }
+
+ err = SocketReadData( context->sock, context->msgPtr, context->msgLen, &context->msgOffset );
+ if( err == EWOULDBLOCK ) { err = kNoErr; goto exit; }
+ require_noerr( err, exit );
+ context->msgOffset = 0;
+ context->haveTCPLen = false;
+ }
+ else
+ {
+ sockaddr_ip fromAddr;
+
+ context->msgPtr = context->msgBuf;
+ err = SocketRecvFrom( context->sock, context->msgPtr, sizeof( context->msgBuf ), &context->msgLen, &fromAddr,
+ sizeof( fromAddr ), NULL, NULL, NULL, NULL );
+ require_noerr( err, exit );
+
+ check( SockAddrCompareAddr( &fromAddr, &context->serverAddr ) == 0 );
+ }
+
+ FPrintF( stdout, "Receive time: %s\n", time );
+ FPrintF( stdout, "Source: %##a\n", &context->serverAddr );
+ FPrintF( stdout, "Message size: %zu\n", context->msgLen );
+ FPrintF( stdout, "RTT: %llu ms\n\n", UpTicksToMilliseconds( nowTicks - context->sendTicks ) );
+ PrintUDNSMessage( context->msgPtr, context->msgLen, context->printRawRData );
+
+ if( ( context->msgLen >= kDNSHeaderLength ) && ( DNSHeaderGetID( (DNSHeader *) context->msgPtr ) == context->queryID ) )
+ {
+ Exit( kExitReason_ReceivedResponse );
+ }
+
+exit:
+ if( err ) dispatch_source_forget( &context->readSource );
+}
+
+//===========================================================================================================================
+// DNSQueryCancelHandler
+//===========================================================================================================================
+
+static void DNSQueryCancelHandler( void *inContext )
+{
+ DNSQueryContext * const context = (DNSQueryContext *) inContext;
+
+ check( !context->readSource );
+ ForgetSocket( &context->sock );
+ if( context->msgPtr != context->msgBuf ) ForgetMem( &context->msgPtr );
+ free( context );
+ dispatch_async_f( dispatch_get_main_queue(), NULL, Exit );
+}
+
+#if( DNSSDUTIL_INCLUDE_DNSCRYPT )
+//===========================================================================================================================
+// DNSCryptCmd
+//===========================================================================================================================
+
+#define kDNSCryptPort 443
+
+#define kDNSCryptMinPadLength 8
+#define kDNSCryptMaxPadLength 256
+#define kDNSCryptBlockSize 64
+#define kDNSCryptCertMinimumLength 124
+#define kDNSCryptClientMagicLength 8
+#define kDNSCryptResolverMagicLength 8
+#define kDNSCryptHalfNonceLength 12
+#define kDNSCryptCertMagicLength 4
+
+check_compile_time( ( kDNSCryptHalfNonceLength * 2 ) == crypto_box_NONCEBYTES );
+
+static const uint8_t kDNSCryptCertMagic[ kDNSCryptCertMagicLength ] = { 'D', 'N', 'S', 'C' };
+static const uint8_t kDNSCryptResolverMagic[ kDNSCryptResolverMagicLength ] =
+{
+ 0x72, 0x36, 0x66, 0x6e, 0x76, 0x57, 0x6a, 0x38
+};
+
+typedef struct
+{
+ uint8_t certMagic[ kDNSCryptCertMagicLength ];
+ uint8_t esVersion[ 2 ];
+ uint8_t minorVersion[ 2 ];
+ uint8_t signature[ crypto_sign_BYTES ];
+ uint8_t publicKey[ crypto_box_PUBLICKEYBYTES ];
+ uint8_t clientMagic[ kDNSCryptClientMagicLength ];
+ uint8_t serial[ 4 ];
+ uint8_t startTime[ 4 ];
+ uint8_t endTime[ 4 ];
+ uint8_t extensions[ 1 ]; // Variably-sized extension data.
+
+} DNSCryptCert;
+
+check_compile_time( offsetof( DNSCryptCert, extensions ) == kDNSCryptCertMinimumLength );
+
+typedef struct
+{
+ uint8_t clientMagic[ kDNSCryptClientMagicLength ];
+ uint8_t clientPublicKey[ crypto_box_PUBLICKEYBYTES ];
+ uint8_t clientNonce[ kDNSCryptHalfNonceLength ];
+ uint8_t poly1305MAC[ 16 ];
+
+} DNSCryptQueryHeader;
+
+check_compile_time( sizeof( DNSCryptQueryHeader ) == 68 );
+check_compile_time( sizeof( DNSCryptQueryHeader ) >= crypto_box_ZEROBYTES );
+check_compile_time( ( sizeof( DNSCryptQueryHeader ) - crypto_box_ZEROBYTES + crypto_box_BOXZEROBYTES ) ==
+ offsetof( DNSCryptQueryHeader, poly1305MAC ) );
+
+typedef struct
+{
+ uint8_t resolverMagic[ kDNSCryptResolverMagicLength ];
+ uint8_t clientNonce[ kDNSCryptHalfNonceLength ];
+ uint8_t resolverNonce[ kDNSCryptHalfNonceLength ];
+ uint8_t poly1305MAC[ 16 ];
+
+} DNSCryptResponseHeader;
+
+check_compile_time( sizeof( DNSCryptResponseHeader ) == 48 );
+check_compile_time( offsetof( DNSCryptResponseHeader, poly1305MAC ) >= crypto_box_BOXZEROBYTES );
+check_compile_time( ( offsetof( DNSCryptResponseHeader, poly1305MAC ) - crypto_box_BOXZEROBYTES + crypto_box_ZEROBYTES ) ==
+ sizeof( DNSCryptResponseHeader ) );
+
+typedef struct
+{
+ sockaddr_ip serverAddr;
+ uint64_t sendTicks;
+ const char * providerName;
+ const char * qname;
+ const uint8_t * certPtr;
+ size_t certLen;
+ dispatch_source_t readSource;
+ size_t msgLen;
+ int timeLimitSecs;
+ uint16_t queryID;
+ uint16_t qtype;
+ Boolean printRawRData;
+ uint8_t serverPublicSignKey[ crypto_sign_PUBLICKEYBYTES ];
+ uint8_t serverPublicKey[ crypto_box_PUBLICKEYBYTES ];
+ uint8_t clientPublicKey[ crypto_box_PUBLICKEYBYTES ];
+ uint8_t clientSecretKey[ crypto_box_SECRETKEYBYTES ];
+ uint8_t clientMagic[ kDNSCryptClientMagicLength ];
+ uint8_t clientNonce[ kDNSCryptHalfNonceLength ];
+ uint8_t nmKey[ crypto_box_BEFORENMBYTES ];
+ uint8_t msgBuf[ 512 ];
+
+} DNSCryptContext;
+
+static void DNSCryptReceiveCertHandler( void *inContext );
+static void DNSCryptReceiveResponseHandler( void *inContext );
+static void DNSCryptProceed( void *inContext );
+static OSStatus DNSCryptProcessCert( DNSCryptContext *inContext );
+static OSStatus DNSCryptBuildQuery( DNSCryptContext *inContext );
+static OSStatus DNSCryptSendQuery( DNSCryptContext *inContext );
+static void DNSCryptPrintCertificate( const DNSCryptCert *inCert, size_t inLen );
+
+static void DNSCryptCmd( void )
+{
+ OSStatus err;
+ DNSCryptContext * context = NULL;
+ size_t writtenBytes;
+ size_t totalBytes;
+ SocketContext * sockContext;
+ SocketRef sock = kInvalidSocketRef;
+ const char * ptr;
+
+ // Check command parameters.
+
+ if( gDNSQuery_TimeLimitSecs < -1 )
+ {
+ FPrintF( stdout, "Invalid wait period: %d seconds.\n", gDNSQuery_TimeLimitSecs );
+ err = kParamErr;
+ goto exit;
+ }
+
+ // Create context.
+
+ context = (DNSCryptContext *) calloc( 1, sizeof( *context ) );
+ require_action( context, exit, err = kNoMemoryErr );
+
+ context->providerName = gDNSCrypt_ProviderName;
+ context->qname = gDNSCrypt_Name;
+ context->timeLimitSecs = gDNSCrypt_TimeLimitSecs;
+ context->printRawRData = gDNSCrypt_RawRData ? true : false;
+
+ err = crypto_box_keypair( context->clientPublicKey, context->clientSecretKey );
+ require_noerr( err, exit );
+
+ err = HexToData( gDNSCrypt_ProviderKey, kSizeCString, kHexToData_DefaultFlags,
+ context->serverPublicSignKey, sizeof( context->serverPublicSignKey ), &writtenBytes, &totalBytes, &ptr );
+ if( err || ( *ptr != '\0' ) )
+ {
+ FPrintF( stderr, "Failed to parse public signing key hex string (%s).\n", gDNSCrypt_ProviderKey );
+ goto exit;
+ }
+ else if( totalBytes != sizeof( context->serverPublicSignKey ) )
+ {
+ FPrintF( stderr, "Public signing key contains incorrect number of hex bytes (%zu != %zu)\n",
+ totalBytes, sizeof( context->serverPublicSignKey ) );
+ err = kSizeErr;
+ goto exit;
+ }
+ check( writtenBytes == totalBytes );
+
+ err = StringToSockAddr( gDNSCrypt_Server, &context->serverAddr, sizeof( context->serverAddr ), NULL );
+ require_noerr( err, exit );
+ if( SockAddrGetPort( &context->serverAddr ) == 0 ) SockAddrSetPort( &context->serverAddr, kDNSCryptPort );
+
+ err = RecordTypeFromArgString( gDNSCrypt_Type, &context->qtype );
+ require_noerr( err, exit );
+
+ // Write query message.
+
+ context->queryID = (uint16_t) Random32();
+ err = WriteDNSQueryMessage( context->msgBuf, context->queryID, kDNSHeaderFlag_RecursionDesired, context->providerName,
+ kDNSServiceType_TXT, kDNSServiceClass_IN, &context->msgLen );
+ require_noerr( err, exit );
+
+ // Create UDP socket.
+
+ err = UDPClientSocketOpen( AF_UNSPEC, &context->serverAddr, 0, -1, NULL, &sock );
+ require_noerr( err, exit );
+
+ // Send DNS query.
+
+ context->sendTicks = UpTicks();
+ err = SocketWriteAll( sock, context->msgBuf, context->msgLen, 5 );
+ require_noerr( err, exit );
+
+ sockContext = (SocketContext *) calloc( 1, sizeof( *sockContext ) );
+ require_action( sockContext, exit, err = kNoMemoryErr );
+
+ err = DispatchReadSourceCreate( sock, DNSCryptReceiveCertHandler, SocketContextCancelHandler, sockContext,
+ &context->readSource );
+ if( err ) ForgetMem( &sockContext );
+ require_noerr( err, exit );
+
+ sockContext->context = context;
+ sockContext->sock = sock;
+ sock = kInvalidSocketRef;
+ dispatch_resume( context->readSource );
+
+ if( context->timeLimitSecs > 0 )
+ {
+ dispatch_after_f( dispatch_time_seconds( context->timeLimitSecs ), dispatch_get_main_queue(), kExitReason_Timeout,
+ Exit );
+ }
+ dispatch_main();
+
+exit:
+ if( context ) free( context );
+ ForgetSocket( &sock );
+ if( err ) exit( 1 );
+}
+
+//===========================================================================================================================
+// DNSCryptReceiveCertHandler
+//===========================================================================================================================
+
+static void DNSCryptReceiveCertHandler( void *inContext )
+{
+ OSStatus err;
+ const uint64_t nowTicks = UpTicks();
+ SocketContext * const sockContext = (SocketContext *) inContext;
+ DNSCryptContext * const context = (DNSCryptContext *) sockContext->context;
+ const DNSHeader * hdr;
+ sockaddr_ip fromAddr;
+ const uint8_t * ptr;
+ const uint8_t * txtPtr;
+ size_t txtLen;
+ unsigned int answerCount, i;
+ uint8_t targetName[ kDomainNameLengthMax ];
+ char time[ kTimestampBufLen ];
+
+ GetTimestampStr( time );
+
+ dispatch_source_forget( &context->readSource );
+
+ err = SocketRecvFrom( sockContext->sock, context->msgBuf, sizeof( context->msgBuf ), &context->msgLen,
+ &fromAddr, sizeof( fromAddr ), NULL, NULL, NULL, NULL );
+ require_noerr( err, exit );
+ check( SockAddrCompareAddr( &fromAddr, &context->serverAddr ) == 0 );
+
+ FPrintF( stdout, "Receive time: %s\n", time );
+ FPrintF( stdout, "Source: %##a\n", &context->serverAddr );
+ FPrintF( stdout, "Message size: %zu\n", context->msgLen );
+ FPrintF( stdout, "RTT: %llu ms\n\n", UpTicksToMilliseconds( nowTicks - context->sendTicks ) );
+
+ PrintUDNSMessage( context->msgBuf, context->msgLen, context->printRawRData );
+
+ require_action_quiet( context->msgLen >= kDNSHeaderLength, exit, err = kSizeErr );
+
+ hdr = (DNSHeader *) context->msgBuf;
+ require_action_quiet( DNSHeaderGetID( hdr ) == context->queryID, exit, err = kMismatchErr );
+
+ err = DNSMessageGetAnswerSection( context->msgBuf, context->msgLen, &ptr );
+ require_noerr( err, exit );
+
+ targetName[ 0 ] = 0;
+ err = DomainNameAppendString( targetName, context->providerName, NULL );
+ require_noerr( err, exit );
+
+ answerCount = DNSHeaderGetAnswerCount( hdr );
+ for( i = 0; i < answerCount; ++i )
+ {
+ uint16_t type;
+ uint16_t class;
+ uint8_t name[ kDomainNameLengthMax ];
+
+ err = DNSMessageExtractRecord( context->msgBuf, context->msgLen, ptr, name, &type, &class, NULL, &txtPtr, &txtLen,
+ &ptr );
+ require_noerr( err, exit );
+
+ if( ( type == kDNSServiceType_TXT ) && ( class == kDNSServiceClass_IN ) && DomainNameEqual( name, targetName ) )
+ {
+ break;
+ }
+ }
+
+ if( txtLen < ( 1 + kDNSCryptCertMinimumLength ) )
+ {
+ FPrintF( stderr, "TXT record length is too short (%u < %u)\n", txtLen, kDNSCryptCertMinimumLength + 1 );
+ err = kSizeErr;
+ goto exit;
+ }
+ if( txtPtr[ 0 ] < kDNSCryptCertMinimumLength )
+ {
+ FPrintF( stderr, "TXT record value length is too short (%u < %u)\n", txtPtr[ 0 ], kDNSCryptCertMinimumLength );
+ err = kSizeErr;
+ goto exit;
+ }
+
+ context->certLen = txtPtr[ 0 ];
+ context->certPtr = &txtPtr[ 1 ];
+
+ dispatch_async_f( dispatch_get_main_queue(), context, DNSCryptProceed );
+
+exit:
+ if( err ) Exit( NULL );
+}
+
+//===========================================================================================================================
+// DNSCryptReceiveResponseHandler
+//===========================================================================================================================
+
+static void DNSCryptReceiveResponseHandler( void *inContext )
+{
+ OSStatus err;
+ const uint64_t nowTicks = UpTicks();
+ SocketContext * const sockContext = (SocketContext *) inContext;
+ DNSCryptContext * const context = (DNSCryptContext *) sockContext->context;
+ sockaddr_ip fromAddr;
+ DNSCryptResponseHeader * hdr;
+ const uint8_t * end;
+ uint8_t * ciphertext;
+ uint8_t * plaintext;
+ const uint8_t * response;
+ char time[ kTimestampBufLen ];
+ uint8_t nonce[ crypto_box_NONCEBYTES ];
+
+ GetTimestampStr( time );
+
+ dispatch_source_forget( &context->readSource );
+
+ err = SocketRecvFrom( sockContext->sock, context->msgBuf, sizeof( context->msgBuf ), &context->msgLen,
+ &fromAddr, sizeof( fromAddr ), NULL, NULL, NULL, NULL );
+ require_noerr( err, exit );
+ check( SockAddrCompareAddr( &fromAddr, &context->serverAddr ) == 0 );
+
+ FPrintF( stdout, "Receive time: %s\n", time );
+ FPrintF( stdout, "Source: %##a\n", &context->serverAddr );
+ FPrintF( stdout, "Message size: %zu\n", context->msgLen );
+ FPrintF( stdout, "RTT: %llu ms\n\n", UpTicksToMilliseconds( nowTicks - context->sendTicks ) );
+
+ if( context->msgLen < sizeof( DNSCryptResponseHeader ) )
+ {
+ FPrintF( stderr, "DNSCrypt response is too short.\n" );
+ err = kSizeErr;
+ goto exit;
+ }
+
+ hdr = (DNSCryptResponseHeader *) context->msgBuf;
+
+ if( memcmp( hdr->resolverMagic, kDNSCryptResolverMagic, kDNSCryptResolverMagicLength ) != 0 )
+ {
+ FPrintF( stderr, "DNSCrypt response resolver magic %#H != %#H\n",
+ hdr->resolverMagic, kDNSCryptResolverMagicLength, INT_MAX,
+ kDNSCryptResolverMagic, kDNSCryptResolverMagicLength, INT_MAX );
+ err = kValueErr;
+ goto exit;
+ }
+
+ if( memcmp( hdr->clientNonce, context->clientNonce, kDNSCryptHalfNonceLength ) != 0 )
+ {
+ FPrintF( stderr, "DNSCrypt response client nonce mismatch.\n" );
+ err = kValueErr;
+ goto exit;
+ }
+
+ memcpy( nonce, hdr->clientNonce, crypto_box_NONCEBYTES );
+
+ ciphertext = hdr->poly1305MAC - crypto_box_BOXZEROBYTES;
+ memset( ciphertext, 0, crypto_box_BOXZEROBYTES );
+
+ plaintext = (uint8_t *)( hdr + 1 ) - crypto_box_ZEROBYTES;
+ check( plaintext == ciphertext );
+
+ end = context->msgBuf + context->msgLen;
+
+ err = crypto_box_open_afternm( plaintext, ciphertext, (size_t)( end - ciphertext ), nonce, context->nmKey );
+ require_noerr( err, exit );
+
+ response = plaintext + crypto_box_ZEROBYTES;
+ PrintUDNSMessage( response, (size_t)( end - response ), context->printRawRData );
+ Exit( kExitReason_ReceivedResponse );
+
+exit:
+ if( err ) Exit( NULL );
+}
+
+//===========================================================================================================================
+// DNSCryptProceed
+//===========================================================================================================================
+
+static void DNSCryptProceed( void *inContext )
+{
+ OSStatus err;
+ DNSCryptContext * const context = (DNSCryptContext *) inContext;
+
+ err = DNSCryptProcessCert( context );
+ require_noerr_quiet( err, exit );
+
+ err = DNSCryptBuildQuery( context );
+ require_noerr_quiet( err, exit );
+
+ err = DNSCryptSendQuery( context );
+ require_noerr_quiet( err, exit );
+
+exit:
+ if( err ) Exit( NULL );
+}
+
+//===========================================================================================================================
+// DNSCryptProcessCert
+//===========================================================================================================================
+
+static OSStatus DNSCryptProcessCert( DNSCryptContext *inContext )
+{
+ OSStatus err;
+ const DNSCryptCert * const cert = (DNSCryptCert *) inContext->certPtr;
+ const uint8_t * const certEnd = inContext->certPtr + inContext->certLen;
+ struct timeval now;
+ time_t startTimeSecs, endTimeSecs;
+ size_t signedLen;
+ uint8_t * tempBuf;
+ unsigned long long tempLen;
+
+ DNSCryptPrintCertificate( cert, inContext->certLen );
+
+ if( memcmp( cert->certMagic, kDNSCryptCertMagic, kDNSCryptCertMagicLength ) != 0 )
+ {
+ FPrintF( stderr, "DNSCrypt certificate magic %#H != %#H\n",
+ cert->certMagic, kDNSCryptCertMagicLength, INT_MAX,
+ kDNSCryptCertMagic, kDNSCryptCertMagicLength, INT_MAX );
+ err = kValueErr;
+ goto exit;
+ }
+
+ startTimeSecs = (time_t) ReadBig32( cert->startTime );
+ endTimeSecs = (time_t) ReadBig32( cert->endTime );
+
+ gettimeofday( &now, NULL );
+ if( now.tv_sec < startTimeSecs )
+ {
+ FPrintF( stderr, "DNSCrypt certificate start time is in the future.\n" );
+ err = kDateErr;
+ goto exit;
+ }
+ if( now.tv_sec >= endTimeSecs )
+ {
+ FPrintF( stderr, "DNSCrypt certificate has expired.\n" );
+ err = kDateErr;
+ goto exit;
+ }
+
+ signedLen = (size_t)( certEnd - cert->signature );
+ tempBuf = (uint8_t *) malloc( signedLen );
+ require_action( tempBuf, exit, err = kNoMemoryErr );
+ err = crypto_sign_open( tempBuf, &tempLen, cert->signature, signedLen, inContext->serverPublicSignKey );
+ free( tempBuf );
+ if( err )
+ {
+ FPrintF( stderr, "DNSCrypt certificate failed verification.\n" );
+ err = kAuthenticationErr;
+ goto exit;
+ }
+
+ memcpy( inContext->serverPublicKey, cert->publicKey, crypto_box_PUBLICKEYBYTES );
+ memcpy( inContext->clientMagic, cert->clientMagic, kDNSCryptClientMagicLength );
+
+ err = crypto_box_beforenm( inContext->nmKey, inContext->serverPublicKey, inContext->clientSecretKey );
+ require_noerr( err, exit );
+
+ inContext->certPtr = NULL;
+ inContext->certLen = 0;
+ inContext->msgLen = 0;
+
+exit:
+ return( err );
+}
+
+//===========================================================================================================================
+// DNSCryptBuildQuery
+//===========================================================================================================================
+
+static OSStatus DNSCryptPadQuery( uint8_t *inMsgPtr, size_t inMsgLen, size_t inMaxLen, size_t *outPaddedLen );
+
+static OSStatus DNSCryptBuildQuery( DNSCryptContext *inContext )
+{
+ OSStatus err;
+ DNSCryptQueryHeader * const hdr = (DNSCryptQueryHeader *) inContext->msgBuf;
+ uint8_t * const queryPtr = (uint8_t *)( hdr + 1 );
+ size_t queryLen;
+ size_t paddedQueryLen;
+ const uint8_t * const msgLimit = inContext->msgBuf + sizeof( inContext->msgBuf );
+ const uint8_t * padLimit;
+ uint8_t nonce[ crypto_box_NONCEBYTES ];
+
+ check_compile_time_code( sizeof( inContext->msgBuf ) >= ( sizeof( DNSCryptQueryHeader ) + kDNSQueryMessageMaxLen ) );
+
+ inContext->queryID = (uint16_t) Random32();
+ err = WriteDNSQueryMessage( queryPtr, inContext->queryID, kDNSHeaderFlag_RecursionDesired, inContext->qname,
+ inContext->qtype, kDNSServiceClass_IN, &queryLen );
+ require_noerr( err, exit );
+
+ padLimit = &queryPtr[ queryLen + kDNSCryptMaxPadLength ];
+ if( padLimit > msgLimit ) padLimit = msgLimit;
+
+ err = DNSCryptPadQuery( queryPtr, queryLen, (size_t)( padLimit - queryPtr ), &paddedQueryLen );
+ require_noerr( err, exit );
+
+ memset( queryPtr - crypto_box_ZEROBYTES, 0, crypto_box_ZEROBYTES );
+ RandomBytes( inContext->clientNonce, kDNSCryptHalfNonceLength );
+ memcpy( nonce, inContext->clientNonce, kDNSCryptHalfNonceLength );
+ memset( &nonce[ kDNSCryptHalfNonceLength ], 0, kDNSCryptHalfNonceLength );
+
+ err = crypto_box_afternm( queryPtr - crypto_box_ZEROBYTES, queryPtr - crypto_box_ZEROBYTES,
+ paddedQueryLen + crypto_box_ZEROBYTES, nonce, inContext->nmKey );
+ require_noerr( err, exit );
+
+ memcpy( hdr->clientMagic, inContext->clientMagic, kDNSCryptClientMagicLength );
+ memcpy( hdr->clientPublicKey, inContext->clientPublicKey, crypto_box_PUBLICKEYBYTES );
+ memcpy( hdr->clientNonce, nonce, kDNSCryptHalfNonceLength );
+
+ inContext->msgLen = (size_t)( &queryPtr[ paddedQueryLen ] - inContext->msgBuf );
+
+exit:
+ return( err );
+}
+
+static OSStatus DNSCryptPadQuery( uint8_t *inMsgPtr, size_t inMsgLen, size_t inMaxLen, size_t *outPaddedLen )
+{
+ OSStatus err;
+ size_t paddedLen;
+
+ require_action_quiet( ( inMsgLen + kDNSCryptMinPadLength ) <= inMaxLen, exit, err = kSizeErr );
+
+ paddedLen = inMsgLen + kDNSCryptMinPadLength +
+ arc4random_uniform( (uint32_t)( inMaxLen - ( inMsgLen + kDNSCryptMinPadLength ) + 1 ) );
+ paddedLen += ( kDNSCryptBlockSize - ( paddedLen % kDNSCryptBlockSize ) );
+ if( paddedLen > inMaxLen ) paddedLen = inMaxLen;
+
+ inMsgPtr[ inMsgLen ] = 0x80;
+ memset( &inMsgPtr[ inMsgLen + 1 ], 0, paddedLen - ( inMsgLen + 1 ) );
+
+ if( outPaddedLen ) *outPaddedLen = paddedLen;
+ err = kNoErr;
+
+exit:
+ return( err );
+}
+
+//===========================================================================================================================
+// DNSCryptSendQuery
+//===========================================================================================================================
+
+static OSStatus DNSCryptSendQuery( DNSCryptContext *inContext )
+{
+ OSStatus err;
+ SocketContext * sockContext;
+ SocketRef sock = kInvalidSocketRef;
+
+ check( inContext->msgLen > 0 );
+ check( !inContext->readSource );
+
+ err = UDPClientSocketOpen( AF_UNSPEC, &inContext->serverAddr, 0, -1, NULL, &sock );
+ require_noerr( err, exit );
+
+ inContext->sendTicks = UpTicks();
+ err = SocketWriteAll( sock, inContext->msgBuf, inContext->msgLen, 5 );
+ require_noerr( err, exit );
+
+ sockContext = (SocketContext *) calloc( 1, sizeof( *sockContext ) );
+ require_action( sockContext, exit, err = kNoMemoryErr );
+
+ err = DispatchReadSourceCreate( sock, DNSCryptReceiveResponseHandler, SocketContextCancelHandler, sockContext,
+ &inContext->readSource );
+ if( err ) ForgetMem( &sockContext );
+ require_noerr( err, exit );
+
+ sockContext->context = inContext;
+ sockContext->sock = sock;
+ sock = kInvalidSocketRef;
+
+ dispatch_resume( inContext->readSource );
+
+exit:
+ ForgetSocket( &sock );
+ return( err );
+}
+
+//===========================================================================================================================
+// DNSCryptPrintCertificate
+//===========================================================================================================================
+
+#define kCertTimeStrBufLen 32
+
+static char * CertTimeStr( time_t inTime, char inBuffer[ kCertTimeStrBufLen ] );
+
+static void DNSCryptPrintCertificate( const DNSCryptCert *inCert, size_t inLen )
+{
+ time_t startTime, endTime;
+ int extLen;
+ char timeBuf[ kCertTimeStrBufLen ];
+
+ check( inLen >= kDNSCryptCertMinimumLength );
+
+ startTime = (time_t) ReadBig32( inCert->startTime );
+ endTime = (time_t) ReadBig32( inCert->endTime );
+
+ FPrintF( stdout, "DNSCrypt certificate (%zu bytes):\n", inLen );
+ FPrintF( stdout, "Cert Magic: %#H\n", inCert->certMagic, kDNSCryptCertMagicLength, INT_MAX );
+ FPrintF( stdout, "ES Version: %u\n", ReadBig16( inCert->esVersion ) );
+ FPrintF( stdout, "Minor Version: %u\n", ReadBig16( inCert->minorVersion ) );
+ FPrintF( stdout, "Signature: %H\n", inCert->signature, crypto_sign_BYTES / 2, INT_MAX );
+ FPrintF( stdout, " %H\n", &inCert->signature[ crypto_sign_BYTES / 2 ], crypto_sign_BYTES / 2, INT_MAX );
+ FPrintF( stdout, "Public Key: %H\n", inCert->publicKey, sizeof( inCert->publicKey ), INT_MAX );
+ FPrintF( stdout, "Client Magic: %H\n", inCert->clientMagic, kDNSCryptClientMagicLength, INT_MAX );
+ FPrintF( stdout, "Serial: %u\n", ReadBig32( inCert->serial ) );
+ FPrintF( stdout, "Start Time: %u (%s)\n", (uint32_t) startTime, CertTimeStr( startTime, timeBuf ) );
+ FPrintF( stdout, "End Time: %u (%s)\n", (uint32_t) endTime, CertTimeStr( endTime, timeBuf ) );
+
+ if( inLen > kDNSCryptCertMinimumLength )
+ {
+ extLen = (int)( inLen - kDNSCryptCertMinimumLength );
+ FPrintF( stdout, "Extensions: %.1H\n", inCert->extensions, extLen, extLen );
+ }
+ FPrintF( stdout, "\n" );
+}
+
+static char * CertTimeStr( time_t inTime, char inBuffer[ kCertTimeStrBufLen ] )
+{
+ struct tm * tm;
+
+ tm = localtime( &inTime );
+ if( !tm )
+ {
+ dlogassert( "localtime() returned a NULL pointer.\n" );
+ *inBuffer = '\0';
+ }
+ else
+ {
+ strftime( inBuffer, kCertTimeStrBufLen, "%a %b %d %H:%M:%S %Z %Y", tm );
+ }
+
+ return( inBuffer );
+}
+
+#endif // DNSSDUTIL_INCLUDE_DNSCRYPT
+
+//===========================================================================================================================
+// MDNSQueryCmd
+//===========================================================================================================================
+
+#define kMDNSMulticastAddressIPv4 0xE00000FB // 224.0.0.251
+#define kMDNSPort 5353
+
+#define kDefaultMDNSMessageID 0
+#define kDefaultMDNSQueryFlags 0
+
+typedef struct
+{
+ const char * qname;
+ dispatch_source_t readSourceV4;
+ dispatch_source_t readSourceV6;
+ int receivedMsgCount;
+ int localPort;
+ uint32_t ifIndex;
+ uint16_t type;
+ Boolean isQU;
+ Boolean printRawRData;
+ Boolean useIPv4;
+ Boolean useIPv6;
+ Boolean allResponses;
+ char ifName[ IF_NAMESIZE + 1 ];
+ uint8_t domainName[ kDomainNameLengthMax ];
+ uint8_t msgBuf[ 8940 ];
+
+} MDNSQueryContext;
+
+static void MDNSQueryPrintPrologue( const MDNSQueryContext *inContext );
+static void MDNSQueryReadHandler( void *inContext );
+
+static void MDNSQueryCmd( void )
+{
+ OSStatus err;
+ MDNSQueryContext * context;
+ struct sockaddr_in mcastAddr4;
+ struct sockaddr_in6 mcastAddr6;
+ SocketRef sockV4 = kInvalidSocketRef;
+ SocketRef sockV6 = kInvalidSocketRef;
+ ssize_t n;
+ const char * ifNamePtr;
+ SocketContext * sockContext;
+ size_t msgLen;
+
+ context = (MDNSQueryContext *) calloc( 1, sizeof( *context ) );
+ require_action( context, exit, err = kNoMemoryErr );
+
+ context->qname = gMDNSQuery_Name;
+ context->isQU = gMDNSQuery_IsQU ? true : false;
+ context->printRawRData = gMDNSQuery_RawRData ? true : false;
+ context->allResponses = gMDNSQuery_AllResponses ? true : false;
+ context->useIPv4 = ( gMDNSQuery_UseIPv4 || !gMDNSQuery_UseIPv6 ) ? true : false;
+ context->useIPv6 = ( gMDNSQuery_UseIPv6 || !gMDNSQuery_UseIPv4 ) ? true : false;
+
+ err = InterfaceIndexFromArgString( gInterface, &context->ifIndex );
+ require_noerr_quiet( err, exit );
+
+ ifNamePtr = if_indextoname( context->ifIndex, context->ifName );
+ require_action( ifNamePtr, exit, err = kNameErr );
+
+ err = RecordTypeFromArgString( gMDNSQuery_Type, &context->type );
+ require_noerr( err, exit );
+
+ // Set up IPv4 socket.
+
+ if( context->useIPv4 )
+ {
+ err = ServerSocketOpen( AF_INET, SOCK_DGRAM, IPPROTO_UDP,
+ gMDNSQuery_SourcePort ? gMDNSQuery_SourcePort : ( context->isQU ? context->localPort : kMDNSPort ),
+ &context->localPort, kSocketBufferSize_DontSet, &sockV4 );
+ require_noerr( err, exit );
+
+ err = SocketSetMulticastInterface( sockV4, ifNamePtr, context->ifIndex );
+ require_noerr( err, exit );
+
+ err = setsockopt( sockV4, IPPROTO_IP, IP_MULTICAST_LOOP, (char *) &(uint8_t){ 1 }, (socklen_t) sizeof( uint8_t ) );
+ err = map_socket_noerr_errno( sockV4, err );
+ require_noerr( err, exit );
+
+ memset( &mcastAddr4, 0, sizeof( mcastAddr4 ) );
+ SIN_LEN_SET( &mcastAddr4 );
+ mcastAddr4.sin_family = AF_INET;
+ mcastAddr4.sin_port = htons( kMDNSPort );
+ mcastAddr4.sin_addr.s_addr = htonl( kMDNSMulticastAddressIPv4 );
+
+ if( !context->isQU && ( context->localPort == kMDNSPort ) )
+ {
+ SocketJoinMulticast( sockV4, &mcastAddr4, ifNamePtr, context->ifIndex );
+ require_noerr( err, exit );
+ }
+ }
+
+ // Set up IPv6 socket.
+
+ if( context->useIPv6 )
+ {
+ err = ServerSocketOpen( AF_INET6, SOCK_DGRAM, IPPROTO_UDP,
+ gMDNSQuery_SourcePort ? gMDNSQuery_SourcePort : ( context->isQU ? context->localPort : kMDNSPort ),
+ &context->localPort, kSocketBufferSize_DontSet, &sockV6 );
+ require_noerr( err, exit );
+
+ err = SocketSetMulticastInterface( sockV6, ifNamePtr, context->ifIndex );
+ require_noerr( err, exit );
+
+ err = setsockopt( sockV6, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, (char *) &(int){ 1 }, (socklen_t) sizeof( int ) );
+ err = map_socket_noerr_errno( sockV6, err );
+ require_noerr( err, exit );
+
+ memset( &mcastAddr6, 0, sizeof( mcastAddr6 ) );
+ SIN6_LEN_SET( &mcastAddr6 );
+ mcastAddr6.sin6_family = AF_INET6;
+ mcastAddr6.sin6_port = htons( kMDNSPort );
+ mcastAddr6.sin6_addr.s6_addr[ 0 ] = 0xFF; // mDNS IPv6 multicast address FF02::FB
+ mcastAddr6.sin6_addr.s6_addr[ 1 ] = 0x02;
+ mcastAddr6.sin6_addr.s6_addr[ 15 ] = 0xFB;
+
+ if( !context->isQU && ( context->localPort == kMDNSPort ) )
+ {
+ SocketJoinMulticast( sockV6, &mcastAddr6, ifNamePtr, context->ifIndex );
+ require_noerr( err, exit );
+ }
+ }
+
+ // Write mDNS query message.
+
+ check_compile_time_code( sizeof( context->msgBuf ) >= kDNSQueryMessageMaxLen );
+ err = WriteDNSQueryMessage( context->msgBuf, kDefaultMDNSMessageID, kDefaultMDNSQueryFlags, context->qname,
+ context->type,
+ context->isQU ? ( kDNSServiceClass_IN | kQClassUnicastResponseBit ) : kDNSServiceClass_IN, &msgLen );
+ require_noerr( err, exit );
+
+ // Print prologue.
+
+ MDNSQueryPrintPrologue( context );
+
+ if( context->useIPv4 )
+ {
+ n = sendto( sockV4, context->msgBuf, msgLen, 0, (struct sockaddr *) &mcastAddr4, (socklen_t) sizeof( mcastAddr4 ) );
+ err = map_socket_value_errno( sockV4, n == (ssize_t) msgLen, n );
+ require_noerr_quiet( err, exit );
+
+ sockContext = (SocketContext *) calloc( 1, sizeof( *sockContext ) );
+ require_action( sockContext, exit, err = kNoMemoryErr );
+
+ err = DispatchReadSourceCreate( sockV4, MDNSQueryReadHandler, SocketContextCancelHandler, sockContext,
+ &context->readSourceV4 );
+ if( err ) ForgetMem( &sockContext );
+ require_noerr( err, exit );
+
+ sockContext->context = context;
+ sockContext->sock = sockV4;
+ sockV4 = kInvalidSocketRef;
+ dispatch_resume( context->readSourceV4 );
+ }
+
+ if( context->useIPv6 )
+ {
+ n = sendto( sockV6, context->msgBuf, msgLen, 0, (struct sockaddr *) &mcastAddr6, (socklen_t) sizeof( mcastAddr6 ) );
+ err = map_socket_value_errno( sockV6, n == (ssize_t) msgLen, n );
+ require_noerr_quiet( err, exit );
+
+ sockContext = (SocketContext *) calloc( 1, sizeof( *sockContext ) );
+ require_action( sockContext, exit, err = kNoMemoryErr );
+
+ err = DispatchReadSourceCreate( sockV6, MDNSQueryReadHandler, SocketContextCancelHandler, sockContext,
+ &context->readSourceV6 );
+ if( err ) ForgetMem( &sockContext );
+ require_noerr( err, exit );
+
+ sockContext->context = context;
+ sockContext->sock = sockV6;
+ sockV6 = kInvalidSocketRef;
+ dispatch_resume( context->readSourceV6 );
+ }
+
+ dispatch_main();
+
+exit:
+ ForgetSocket( &sockV4 );
+ ForgetSocket( &sockV6 );
+ if( err ) exit( 1 );
+}
+
+//===========================================================================================================================
+// MDNSQueryPrintPrologue
+//===========================================================================================================================
+
+static void MDNSQueryPrintPrologue( const MDNSQueryContext *inContext )
+{
+ char time[ kTimestampBufLen ];
+
+ FPrintF( stdout, "Interface: %d (%s)\n", (int32_t) inContext->ifIndex, inContext->ifName );
+ FPrintF( stdout, "Name: %s\n", inContext->qname );
+ FPrintF( stdout, "Type: %s (%u)\n", RecordTypeToString( inContext->type ), inContext->type );
+ FPrintF( stdout, "Class: IN (%s)\n", inContext->isQU ? "QU" : "QM" );
+ FPrintF( stdout, "Local port: %d\n", inContext->localPort );
+ FPrintF( stdout, "Start time: %s\n", GetTimestampStr( time ) );
+}
+
+//===========================================================================================================================
+// MDNSQueryReadHandler
+//===========================================================================================================================
+
+static void MDNSQueryReadHandler( void *inContext )
+{
+ OSStatus err;
+ SocketContext * const sockContext = (SocketContext *) inContext;
+ MDNSQueryContext * const context = (MDNSQueryContext *) sockContext->context;
+ size_t msgLen;
+ sockaddr_ip fromAddr;
+ char time[ kTimestampBufLen ];
+ Boolean foundAnswer = false;
+
+ GetTimestampStr( time );
+
+ err = SocketRecvFrom( sockContext->sock, context->msgBuf, sizeof( context->msgBuf ), &msgLen, &fromAddr,
+ sizeof( fromAddr ), NULL, NULL, NULL, NULL );
+ require_noerr( err, exit );
+
+ if( !context->allResponses && ( msgLen >= kDNSHeaderLength ) )
+ {
+ const uint8_t * ptr;
+ const DNSHeader * const hdr = (DNSHeader *) context->msgBuf;
+ unsigned int rrCount, i;
+ uint16_t type, class;
+ uint8_t domainName[ kDomainNameLengthMax ];
+
+ err = DNSMessageGetAnswerSection( context->msgBuf, msgLen, &ptr );
+ require_noerr( err, exit );
+
+ if( context->domainName[ 0 ] == 0 )
+ {
+ err = DomainNameAppendString( context->domainName, context->qname, NULL );
+ require_noerr( err, exit );
+ }
+
+ rrCount = DNSHeaderGetAnswerCount( hdr ) + DNSHeaderGetAuthorityCount( hdr ) + DNSHeaderGetAdditionalCount( hdr );
+ for( i = 0; i < rrCount; ++i )
+ {
+ err = DNSMessageExtractRecord( context->msgBuf, msgLen, ptr, domainName, &type, &class, NULL, NULL, NULL, &ptr );
+ require_noerr( err, exit );
+
+ if( ( type == context->type ) && DomainNameEqual( domainName, context->domainName ) )
+ {
+ foundAnswer = true;
+ break;
+ }
+ }
+ }
+ if( context->allResponses || foundAnswer )
+ {
+ FPrintF( stdout, "---\n" );
+ FPrintF( stdout, "Receive time: %s\n", time );
+ FPrintF( stdout, "Source: %##a\n", &fromAddr );
+ FPrintF( stdout, "Message size: %zu\n\n", msgLen );
+
+ PrintMDNSMessage( context->msgBuf, msgLen, context->printRawRData );
+ }
+
+exit:
+ if( err ) exit( 1 );
+}
+
+//===========================================================================================================================
+// PIDToUUIDCmd
+//===========================================================================================================================
+
+static void PIDToUUIDCmd( void )
+{
+ OSStatus err;
+ int n;
+ struct proc_uniqidentifierinfo info;
+
+ n = proc_pidinfo( gPIDToUUID_PID, PROC_PIDUNIQIDENTIFIERINFO, 1, &info, sizeof( info ) );
+ require_action_quiet( n == (int) sizeof( info ), exit, err = kUnknownErr );
+
+ FPrintF( stdout, "%#U\n", info.p_uuid );
+ err = kNoErr;
+
+exit:
+ if( err ) exit( 1 );
+}
+
+//===========================================================================================================================
+// DaemonVersionCmd
+//===========================================================================================================================
+
+static void DaemonVersionCmd( void )
+{
+ OSStatus err;
+ uint32_t size, version;
+ char strBuf[ 16 ];
+
+ size = (uint32_t) sizeof( version );
+ err = DNSServiceGetProperty( kDNSServiceProperty_DaemonVersion, &version, &size );
+ require_noerr( err, exit );
+
+ FPrintF( stdout, "Daemon version: %s\n", SourceVersionToCString( version, strBuf ) );
+
+exit:
+ if( err ) exit( 1 );
+}
+
+//===========================================================================================================================
+// Exit
+//===========================================================================================================================
+
+static void Exit( void *inContext )
+{
+ const char * const reason = (const char *) inContext;
+ char time[ kTimestampBufLen ];
+
+ FPrintF( stdout, "---\n" );
+ FPrintF( stdout, "End time: %s\n", GetTimestampStr( time ) );
+ if( reason ) FPrintF( stdout, "End reason: %s\n", reason );
+ exit( gExitCode );
+}
+
+//===========================================================================================================================
+// GetTimestampStr
+//===========================================================================================================================
+
+static char * GetTimestampStr( char inBuffer[ kTimestampBufLen ] )
+{
+ struct timeval now;
+ struct tm * tm;
+ size_t len;
+
+ gettimeofday( &now, NULL );
+ tm = localtime( &now.tv_sec );
+ require_action( tm, exit, *inBuffer = '\0' );
+
+ len = strftime( inBuffer, kTimestampBufLen, "%Y-%m-%d %H:%M:%S", tm );
+ SNPrintF( &inBuffer[ len ], kTimestampBufLen - len, ".%06u", (unsigned int) now.tv_usec );
+
+exit:
+ return( inBuffer );
+}
+
+//===========================================================================================================================
+// GetDNSSDFlagsFromOpts
+//===========================================================================================================================
+
+static DNSServiceFlags GetDNSSDFlagsFromOpts( void )
+{
+ DNSServiceFlags flags;
+
+ flags = (DNSServiceFlags) gDNSSDFlags;
+ if( flags & kDNSServiceFlagsShareConnection )
+ {
+ FPrintF( stderr, "*** Warning: kDNSServiceFlagsShareConnection (0x%X) is explicitly set in flag parameters.\n",
+ kDNSServiceFlagsShareConnection );
+ }
+
+ if( gDNSSDFlag_BrowseDomains ) flags |= kDNSServiceFlagsBrowseDomains;
+ if( gDNSSDFlag_DenyCellular ) flags |= kDNSServiceFlagsDenyCellular;
+ if( gDNSSDFlag_DenyExpensive ) flags |= kDNSServiceFlagsDenyExpensive;
+ if( gDNSSDFlag_ForceMulticast ) flags |= kDNSServiceFlagsForceMulticast;
+ if( gDNSSDFlag_IncludeAWDL ) flags |= kDNSServiceFlagsIncludeAWDL;
+ if( gDNSSDFlag_NoAutoRename ) flags |= kDNSServiceFlagsNoAutoRename;
+ if( gDNSSDFlag_PathEvaluationDone ) flags |= kDNSServiceFlagsPathEvaluationDone;
+ if( gDNSSDFlag_RegistrationDomains ) flags |= kDNSServiceFlagsRegistrationDomains;
+ if( gDNSSDFlag_ReturnIntermediates ) flags |= kDNSServiceFlagsReturnIntermediates;
+ if( gDNSSDFlag_Shared ) flags |= kDNSServiceFlagsShared;
+ if( gDNSSDFlag_SuppressUnusable ) flags |= kDNSServiceFlagsSuppressUnusable;
+ if( gDNSSDFlag_Timeout ) flags |= kDNSServiceFlagsTimeout;
+ if( gDNSSDFlag_UnicastResponse ) flags |= kDNSServiceFlagsUnicastResponse;
+ if( gDNSSDFlag_Unique ) flags |= kDNSServiceFlagsUnique;
+
+ return( flags );
+}
+
+//===========================================================================================================================
+// CreateConnectionFromArgString
+//===========================================================================================================================
+
+static OSStatus
+ CreateConnectionFromArgString(
+ const char * inString,
+ dispatch_queue_t inQueue,
+ DNSServiceRef * outSDRef,
+ ConnectionDesc * outDesc )
+{
+ OSStatus err;
+ DNSServiceRef sdRef = NULL;
+ ConnectionType type;
+ int32_t pid = -1; // Initializing because the analyzer claims pid may be used uninitialized.
+ uint8_t uuid[ 16 ];
+
+ if( strcasecmp( inString, kConnectionArg_Normal ) == 0 )
+ {
+ err = DNSServiceCreateConnection( &sdRef );
+ require_noerr( err, exit );
+ type = kConnectionType_Normal;
+ }
+ else if( stricmp_prefix( inString, kConnectionArgPrefix_PID ) == 0 )
+ {
+ const char * const pidStr = inString + sizeof_string( kConnectionArgPrefix_PID );
+
+ err = StringToInt32( pidStr, &pid );
+ if( err )
+ {
+ FPrintF( stderr, "Invalid delegate connection PID value: %s\n", pidStr );
+ err = kParamErr;
+ goto exit;
+ }
+
+ memset( uuid, 0, sizeof( uuid ) );
+ err = DNSServiceCreateDelegateConnection( &sdRef, pid, uuid );
+ if( err )
+ {
+ FPrintF( stderr, "DNSServiceCreateDelegateConnection() returned %#m for PID %d\n", err, pid );
+ goto exit;
+ }
+ type = kConnectionType_DelegatePID;
+ }
+ else if( stricmp_prefix( inString, kConnectionArgPrefix_UUID ) == 0 )
+ {
+ const char * const uuidStr = inString + sizeof_string( kConnectionArgPrefix_UUID );
+
+ check_compile_time_code( sizeof( uuid ) == sizeof( uuid_t ) );
+
+ err = StringToUUID( uuidStr, kSizeCString, false, uuid );
+ if( err )
+ {
+ FPrintF( stderr, "Invalid delegate connection UUID value: %s\n", uuidStr );
+ err = kParamErr;
+ goto exit;
+ }
+
+ err = DNSServiceCreateDelegateConnection( &sdRef, 0, uuid );
+ if( err )
+ {
+ FPrintF( stderr, "DNSServiceCreateDelegateConnection() returned %#m for UUID %#U\n", err, uuid );
+ goto exit;
+ }
+ type = kConnectionType_DelegateUUID;
+ }
+ else
+ {
+ FPrintF( stderr, "Unrecognized connection string \"%s\".\n", inString );
+ err = kParamErr;
+ goto exit;
+ }
+
+ err = DNSServiceSetDispatchQueue( sdRef, inQueue );
+ require_noerr( err, exit );
+
+ *outSDRef = sdRef;
+ if( outDesc )
+ {
+ outDesc->type = type;
+ if( type == kConnectionType_DelegatePID ) outDesc->delegate.pid = pid;
+ else if( type == kConnectionType_DelegateUUID ) memcpy( outDesc->delegate.uuid, uuid, 16 );
+ }
+ sdRef = NULL;
+
+exit:
+ if( sdRef ) DNSServiceRefDeallocate( sdRef );
+ return( err );
+}
+
+//===========================================================================================================================
+// InterfaceIndexFromArgString
+//===========================================================================================================================
+
+static OSStatus InterfaceIndexFromArgString( const char *inString, uint32_t *outIndex )
+{
+ OSStatus err;
+ uint32_t ifIndex;
+
+ if( inString )
+ {
+ ifIndex = if_nametoindex( inString );
+ if( ifIndex == 0 )
+ {
+ err = StringToUInt32( inString, &ifIndex );
+ if( err )
+ {
+ FPrintF( stderr, "Invalid interface value: %s\n", inString );
+ err = kParamErr;
+ goto exit;
+ }
+ }
+ }
+ else
+ {
+ ifIndex = 0;
+ }
+
+ *outIndex = ifIndex;
+ err = kNoErr;
+
+exit:
+ return( err );
+}
+
+//===========================================================================================================================
+// RecordDataFromArgString
+//===========================================================================================================================
+
+#define kRDataMaxLen UINT16_C( 0xFFFF )
+
+static OSStatus RecordDataFromArgString( const char *inString, uint8_t **outDataPtr, size_t *outDataLen )
+{
+ OSStatus err;
+ uint8_t * dataPtr = NULL;
+ size_t dataLen;
+ DataBuffer dataBuf;
+
+ DataBuffer_Init( &dataBuf, NULL, 0, kRDataMaxLen );
+
+ if( stricmp_prefix( inString, kRDataArgPrefix_String ) == 0 )
+ {
+ const char * const strPtr = inString + sizeof_string( kRDataArgPrefix_String );
+ const size_t strLen = strlen( strPtr );
+ size_t copiedLen;
+ size_t totalLen;
+
+ if( strLen > 0 )
+ {
+ require_action( strLen <= kRDataMaxLen, exit, err = kSizeErr );
+ dataPtr = (uint8_t *) malloc( strLen );
+ require_action( dataPtr, exit, err = kNoMemoryErr );
+
+ copiedLen = 0;
+ ParseQuotedEscapedString( strPtr, strPtr + strLen, "", (char *) dataPtr, strLen, &copiedLen, &totalLen, NULL );
+ check( copiedLen == totalLen );
+ dataLen = copiedLen;
+ }
+ else
+ {
+ dataPtr = NULL;
+ dataLen = 0;
+ }
+ }
+ else if( stricmp_prefix( inString, kRDataArgPrefix_HexString ) == 0 )
+ {
+ const char * const strPtr = inString + sizeof_string( kRDataArgPrefix_HexString );
+
+ err = HexToDataCopy( strPtr, kSizeCString, kHexToData_DefaultFlags, &dataPtr, &dataLen, NULL );
+ require_noerr( err, exit );
+ require_action( dataLen <= kRDataMaxLen, exit, err = kSizeErr );
+ }
+ else if( stricmp_prefix( inString, kRDataArgPrefix_File ) == 0 )
+ {
+ const char * const path = inString + sizeof_string( kRDataArgPrefix_File );
+
+ err = CopyFileDataByPath( path, (char **) &dataPtr, &dataLen );
+ require_noerr( err, exit );
+ require_action( dataLen <= kRDataMaxLen, exit, err = kSizeErr );
+ }
+ else if( stricmp_prefix( inString, kRDataArgPrefix_TXT ) == 0 )
+ {
+ const char * strPtr = inString + sizeof_string( kRDataArgPrefix_TXT );
+ const char * const strEnd = strPtr + strlen( strPtr );
+
+ while( strPtr < strEnd )
+ {
+ size_t copiedLen, totalLen;
+ uint8_t kvBuf[ 1 + 255 + 1 ]; // Length byte + max key-value length + 1 for NUL terminator.
+
+ err = ParseEscapedString( strPtr, strEnd, ',', (char *) &kvBuf[ 1 ], sizeof( kvBuf ) - 1,
+ &copiedLen, &totalLen, &strPtr );
+ require_noerr_quiet( err, exit );
+ check( copiedLen == totalLen );
+ if( totalLen > 255 )
+ {
+ FPrintF( stderr, "TXT key-value pair length %zu is too long (> 255 bytes).\n", totalLen );
+ err = kParamErr;
+ goto exit;
+ }
+
+ kvBuf[ 0 ] = (uint8_t) copiedLen;
+ err = DataBuffer_Append( &dataBuf, kvBuf, 1 + kvBuf[ 0 ] );
+ require_noerr( err, exit );
+ }
+
+ err = DataBuffer_Commit( &dataBuf, NULL, NULL );
+ require_noerr( err, exit );
+
+ err = DataBuffer_Detach( &dataBuf, &dataPtr, &dataLen );
+ require_noerr( err, exit );
+ }
+ else
+ {
+ FPrintF( stderr, "Unrecognized record data string \"%s\".\n", inString );
+ err = kParamErr;
+ goto exit;
+ }
+ err = kNoErr;
+
+ *outDataLen = dataLen;
+ *outDataPtr = dataPtr;
+ dataPtr = NULL;
+
+exit:
+ DataBuffer_Free( &dataBuf );
+ FreeNullSafe( dataPtr );
+ return( err );
+}
+
+//===========================================================================================================================
+// RecordTypeFromArgString
+//===========================================================================================================================
+
+typedef struct
+{
+ uint16_t value; // Record type's numeric value.
+ const char * name; // Record type's name as a string (e.g., "A", "PTR", "SRV").
+
+} RecordType;
+
+static const RecordType kRecordTypes[] =
+{
+ // Common types.
+
+ { kDNSServiceType_A, "A" },
+ { kDNSServiceType_AAAA, "AAAA" },
+ { kDNSServiceType_PTR, "PTR" },
+ { kDNSServiceType_SRV, "SRV" },
+ { kDNSServiceType_TXT, "TXT" },
+ { kDNSServiceType_CNAME, "CNAME" },
+ { kDNSServiceType_SOA, "SOA" },
+ { kDNSServiceType_NSEC, "NSEC" },
+ { kDNSServiceType_NS, "NS" },
+ { kDNSServiceType_MX, "MX" },
+ { kDNSServiceType_ANY, "ANY" },
+ { kDNSServiceType_OPT, "OPT" },
+
+ // Less common types.
+
+ { kDNSServiceType_MD, "MD" },
+ { kDNSServiceType_NS, "NS" },
+ { kDNSServiceType_MD, "MD" },
+ { kDNSServiceType_MF, "MF" },
+ { kDNSServiceType_MB, "MB" },
+ { kDNSServiceType_MG, "MG" },
+ { kDNSServiceType_MR, "MR" },
+ { kDNSServiceType_NULL, "NULL" },
+ { kDNSServiceType_WKS, "WKS" },
+ { kDNSServiceType_HINFO, "HINFO" },
+ { kDNSServiceType_MINFO, "MINFO" },
+ { kDNSServiceType_RP, "RP" },
+ { kDNSServiceType_AFSDB, "AFSDB" },
+ { kDNSServiceType_X25, "X25" },
+ { kDNSServiceType_ISDN, "ISDN" },
+ { kDNSServiceType_RT, "RT" },
+ { kDNSServiceType_NSAP, "NSAP" },
+ { kDNSServiceType_NSAP_PTR, "NSAP_PTR" },
+ { kDNSServiceType_SIG, "SIG" },
+ { kDNSServiceType_KEY, "KEY" },
+ { kDNSServiceType_PX, "PX" },
+ { kDNSServiceType_GPOS, "GPOS" },
+ { kDNSServiceType_LOC, "LOC" },
+ { kDNSServiceType_NXT, "NXT" },
+ { kDNSServiceType_EID, "EID" },
+ { kDNSServiceType_NIMLOC, "NIMLOC" },
+ { kDNSServiceType_ATMA, "ATMA" },
+ { kDNSServiceType_NAPTR, "NAPTR" },
+ { kDNSServiceType_KX, "KX" },
+ { kDNSServiceType_CERT, "CERT" },
+ { kDNSServiceType_A6, "A6" },
+ { kDNSServiceType_DNAME, "DNAME" },
+ { kDNSServiceType_SINK, "SINK" },
+ { kDNSServiceType_APL, "APL" },
+ { kDNSServiceType_DS, "DS" },
+ { kDNSServiceType_SSHFP, "SSHFP" },
+ { kDNSServiceType_IPSECKEY, "IPSECKEY" },
+ { kDNSServiceType_RRSIG, "RRSIG" },
+ { kDNSServiceType_DNSKEY, "DNSKEY" },
+ { kDNSServiceType_DHCID, "DHCID" },
+ { kDNSServiceType_NSEC3, "NSEC3" },
+ { kDNSServiceType_NSEC3PARAM, "NSEC3PARAM" },
+ { kDNSServiceType_HIP, "HIP" },
+ { kDNSServiceType_SPF, "SPF" },
+ { kDNSServiceType_UINFO, "UINFO" },
+ { kDNSServiceType_UID, "UID" },
+ { kDNSServiceType_GID, "GID" },
+ { kDNSServiceType_UNSPEC, "UNSPEC" },
+ { kDNSServiceType_TKEY, "TKEY" },
+ { kDNSServiceType_TSIG, "TSIG" },
+ { kDNSServiceType_IXFR, "IXFR" },
+ { kDNSServiceType_AXFR, "AXFR" },
+ { kDNSServiceType_MAILB, "MAILB" },
+ { kDNSServiceType_MAILA, "MAILA" }
+};
+
+static OSStatus RecordTypeFromArgString( const char *inString, uint16_t *outValue )
+{
+ OSStatus err;
+ int32_t i32;
+ const RecordType * type;
+ const RecordType * const end = kRecordTypes + countof( kRecordTypes );
+
+ for( type = kRecordTypes; type < end; ++type )
+ {
+ if( strcasecmp( type->name, inString ) == 0 )
+ {
+ *outValue = type->value;
+ return( kNoErr );
+ }
+ }
+
+ err = StringToInt32( inString, &i32 );
+ require_noerr_quiet( err, exit );
+ require_action_quiet( ( i32 >= 0 ) && ( i32 <= UINT16_MAX ), exit, err = kParamErr );
+
+ *outValue = (uint16_t) i32;
+
+exit:
+ return( err );
+}
+
+//===========================================================================================================================
+// RecordClassFromArgString
+//===========================================================================================================================
+
+static OSStatus RecordClassFromArgString( const char *inString, uint16_t *outValue )
+{
+ OSStatus err;
+ int32_t i32;
+
+ if( strcasecmp( inString, "IN" ) == 0 )
+ {
+ *outValue = kDNSServiceClass_IN;
+ err = kNoErr;
+ goto exit;
+ }
+
+ err = StringToInt32( inString, &i32 );
+ require_noerr_quiet( err, exit );
+ require_action_quiet( ( i32 >= 0 ) && ( i32 <= UINT16_MAX ), exit, err = kParamErr );
+
+ *outValue = (uint16_t) i32;
+
+exit:
+ return( err );
+}
+
+//===========================================================================================================================
+// InterfaceIndexToName
+//===========================================================================================================================
+
+static char * InterfaceIndexToName( uint32_t inIfIndex, char inNameBuf[ kInterfaceNameBufLen ] )
+{
+ switch( inIfIndex )
+ {
+ case kDNSServiceInterfaceIndexAny:
+ strlcpy( inNameBuf, "Any", kInterfaceNameBufLen );
+ break;
+
+ case kDNSServiceInterfaceIndexLocalOnly:
+ strlcpy( inNameBuf, "LocalOnly", kInterfaceNameBufLen );
+ break;
+
+ case kDNSServiceInterfaceIndexUnicast:
+ strlcpy( inNameBuf, "Unicast", kInterfaceNameBufLen );
+ break;
+
+ case kDNSServiceInterfaceIndexP2P:
+ strlcpy( inNameBuf, "P2P", kInterfaceNameBufLen );
+ break;
+
+ #if( defined( kDNSServiceInterfaceIndexBLE ) )
+ case kDNSServiceInterfaceIndexBLE:
+ strlcpy( inNameBuf, "BLE", kInterfaceNameBufLen );
+ break;
+ #endif
+
+ default:
+ {
+ const char * name;
+
+ name = if_indextoname( inIfIndex, inNameBuf );
+ if( !name ) strlcpy( inNameBuf, "NO NAME", kInterfaceNameBufLen );
+ break;
+ }
+ }
+
+ return( inNameBuf );
+}
+
+//===========================================================================================================================
+// RecordTypeToString
+//===========================================================================================================================
+
+static const char * RecordTypeToString( unsigned int inValue )
+{
+ const RecordType * type;
+ const RecordType * const end = kRecordTypes + countof( kRecordTypes );
+
+ for( type = kRecordTypes; type < end; ++type )
+ {
+ if( type->value == inValue ) return( type->name );
+ }
+ return( "???" );
+}
+
+//===========================================================================================================================
+// DNSMessageExtractDomainName
+//===========================================================================================================================
+
+#define IsCompressionByte( X ) ( ( ( X ) & 0xC0 ) == 0xC0 )
+
+static OSStatus
+ DNSMessageExtractDomainName(
+ const uint8_t * inMsgPtr,
+ size_t inMsgLen,
+ const uint8_t * inNamePtr,
+ uint8_t inBuf[ kDomainNameLengthMax ],
+ const uint8_t ** outNextPtr )
+{
+ OSStatus err;
+ const uint8_t * label;
+ uint8_t labelLen;
+ const uint8_t * nextLabel;
+ const uint8_t * const msgEnd = inMsgPtr + inMsgLen;
+ uint8_t * dst = inBuf;
+ const uint8_t * const dstLim = inBuf ? ( inBuf + kDomainNameLengthMax ) : NULL;
+ const uint8_t * nameEnd = NULL;
+
+ require_action( ( inNamePtr >= inMsgPtr ) && ( inNamePtr < msgEnd ), exit, err = kRangeErr );
+
+ for( label = inNamePtr; ( labelLen = label[ 0 ] ) != 0; label = nextLabel )
+ {
+ if( labelLen <= kDomainLabelLengthMax )
+ {
+ nextLabel = label + 1 + labelLen;
+ require_action( nextLabel < msgEnd, exit, err = kUnderrunErr );
+ if( dst )
+ {
+ require_action( ( dstLim - dst ) > ( 1 + labelLen ), exit, err = kOverrunErr );
+ memcpy( dst, label, 1 + labelLen );
+ dst += ( 1 + labelLen );
+ }
+ }
+ else if( IsCompressionByte( labelLen ) )
+ {
+ uint16_t offset;
+
+ require_action( ( msgEnd - label ) >= 2, exit, err = kUnderrunErr );
+ if( !nameEnd )
+ {
+ nameEnd = label + 2;
+ if( !dst ) break;
+ }
+ offset = (uint16_t)( ( ( label[ 0 ] & 0x3F ) << 8 ) | label[ 1 ] );
+ nextLabel = inMsgPtr + offset;
+ require_action( nextLabel < msgEnd, exit, err = kUnderrunErr );
+ require_action( !IsCompressionByte( nextLabel[ 0 ] ), exit, err = kMalformedErr );
+ }
+ else
+ {
+ dlogassert( "Unhandled label length 0x%02X\n", labelLen );
+ err = kMalformedErr;
+ goto exit;
+ }
+ }
+
+ if( dst ) *dst = 0;
+ if( !nameEnd ) nameEnd = label + 1;
+
+ if( outNextPtr ) *outNextPtr = nameEnd;
+ err = kNoErr;
+
+exit:
+ return( err );
+}
+
+//===========================================================================================================================
+// DNSMessageExtractDomainNameString
+//===========================================================================================================================
+
+static OSStatus
+ DNSMessageExtractDomainNameString(
+ const void * inMsgPtr,
+ size_t inMsgLen,
+ const void * inNamePtr,
+ char inBuf[ kDNSServiceMaxDomainName ],
+ const uint8_t ** outNextPtr )
+{
+ OSStatus err;
+ const uint8_t * nextPtr;
+ uint8_t domainName[ kDomainNameLengthMax ];
+
+ err = DNSMessageExtractDomainName( inMsgPtr, inMsgLen, inNamePtr, domainName, &nextPtr );
+ require_noerr( err, exit );
+
+ err = DomainNameToString( domainName, NULL, inBuf, NULL );
+ require_noerr( err, exit );
+
+ if( outNextPtr ) *outNextPtr = nextPtr;
+
+exit:
+ return( err );
+}
+
+//===========================================================================================================================
+// DNSMessageExtractRecord
+//===========================================================================================================================
+
+typedef struct
+{
+ uint8_t type[ 2 ];
+ uint8_t class[ 2 ];
+ uint8_t ttl[ 4 ];
+ uint8_t rdLength[ 2 ];
+ uint8_t rdata[ 1 ];
+
+} DNSRecordFields;
+
+check_compile_time( offsetof( DNSRecordFields, rdata ) == 10 );
+
+static OSStatus
+ DNSMessageExtractRecord(
+ const uint8_t * inMsgPtr,
+ size_t inMsgLen,
+ const uint8_t * inPtr,
+ uint8_t inNameBuf[ kDomainNameLengthMax ],
+ uint16_t * outType,
+ uint16_t * outClass,
+ uint32_t * outTTL,
+ const uint8_t ** outRDataPtr,
+ size_t * outRDataLen,
+ const uint8_t ** outPtr )
+{
+ OSStatus err;
+ const uint8_t * const msgEnd = inMsgPtr + inMsgLen;
+ const uint8_t * ptr;
+ const DNSRecordFields * record;
+ size_t rdLength;
+
+ err = DNSMessageExtractDomainName( inMsgPtr, inMsgLen, inPtr, inNameBuf, &ptr );
+ require_noerr_quiet( err, exit );
+ require_action_quiet( (size_t)( msgEnd - ptr ) >= offsetof( DNSRecordFields, rdata ), exit, err = kUnderrunErr );
+
+ record = (DNSRecordFields *) ptr;
+ rdLength = ReadBig16( record->rdLength );
+ require_action_quiet( (size_t)( msgEnd - record->rdata ) >= rdLength , exit, err = kUnderrunErr );
+
+ if( outType ) *outType = ReadBig16( record->type );
+ if( outClass ) *outClass = ReadBig16( record->class );
+ if( outTTL ) *outTTL = ReadBig32( record->ttl );
+ if( outRDataPtr ) *outRDataPtr = record->rdata;
+ if( outRDataLen ) *outRDataLen = rdLength;
+ if( outPtr ) *outPtr = record->rdata + rdLength;
+
+exit:
+ return( err );
+}
+
+//===========================================================================================================================
+// DNSMessageGetAnswerSection
+//===========================================================================================================================
+
+static OSStatus DNSMessageGetAnswerSection( const uint8_t *inMsgPtr, size_t inMsgLen, const uint8_t **outPtr )
+{
+ OSStatus err;
+ const uint8_t * const msgEnd = inMsgPtr + inMsgLen;
+ unsigned int questionCount, i;
+ const DNSHeader * hdr;
+ const uint8_t * ptr;
+
+ require_action_quiet( inMsgLen >= kDNSHeaderLength, exit, err = kSizeErr );
+
+ hdr = (DNSHeader *) inMsgPtr;
+ questionCount = DNSHeaderGetQuestionCount( hdr );
+
+ ptr = (uint8_t *)( hdr + 1 );
+ for( i = 0; i < questionCount; ++i )
+ {
+ err = DNSMessageExtractDomainName( inMsgPtr, inMsgLen, ptr, NULL, &ptr );
+ require_noerr( err, exit );
+ require_action_quiet( ( msgEnd - ptr ) >= 4, exit, err = kUnderrunErr );
+ ptr += 4;
+ }
+
+ if( outPtr ) *outPtr = ptr;
+ err = kNoErr;
+
+exit:
+ return( err );
+}
+
+//===========================================================================================================================
+// DNSRecordDataToString
+//===========================================================================================================================
+
+static OSStatus
+ DNSRecordDataToString(
+ const void * inRDataPtr,
+ size_t inRDataLen,
+ unsigned int inRDataType,
+ const void * inMsgPtr,
+ size_t inMsgLen,
+ char ** outString )
+{
+ OSStatus err;
+ const uint8_t * const rdataPtr = (uint8_t *) inRDataPtr;
+ const uint8_t * const rdataEnd = rdataPtr + inRDataLen;
+ char * rdataStr;
+ const uint8_t * ptr;
+ int n;
+ char domainNameStr[ kDNSServiceMaxDomainName ];
+
+ rdataStr = NULL;
+ if( inRDataType == kDNSServiceType_A )
+ {
+ require_action_quiet( inRDataLen == 4, exit, err = kMalformedErr );
+
+ ASPrintF( &rdataStr, "%.4a", rdataPtr );
+ require_action( rdataStr, exit, err = kNoMemoryErr );
+ }
+ else if( inRDataType == kDNSServiceType_AAAA )
+ {
+ require_action_quiet( inRDataLen == 16, exit, err = kMalformedErr );
+
+ ASPrintF( &rdataStr, "%.16a", rdataPtr );
+ require_action( rdataStr, exit, err = kNoMemoryErr );
+ }
+ else if( ( inRDataType == kDNSServiceType_PTR ) || ( inRDataType == kDNSServiceType_CNAME ) ||
+ ( inRDataType == kDNSServiceType_NS ) )
+ {
+ if( inMsgPtr )
+ {
+ err = DNSMessageExtractDomainNameString( inMsgPtr, inMsgLen, rdataPtr, domainNameStr, NULL );
+ require_noerr( err, exit );
+ }
+ else
+ {
+ err = DomainNameToString( rdataPtr, rdataEnd, domainNameStr, NULL );
+ require_noerr( err, exit );
+ }
+
+ rdataStr = strdup( domainNameStr );
+ require_action( rdataStr, exit, err = kNoMemoryErr );
+ }
+ else if( inRDataType == kDNSServiceType_SRV )
+ {
+ uint16_t priority, weight, port;
+ const uint8_t * target;
+
+ require_action_quiet( ( rdataPtr + 6 ) < rdataEnd, exit, err = kMalformedErr );
+
+ priority = ReadBig16( rdataPtr );
+ weight = ReadBig16( rdataPtr + 2 );
+ port = ReadBig16( rdataPtr + 4 );
+ target = rdataPtr + 6;
+
+ if( inMsgPtr )
+ {
+ err = DNSMessageExtractDomainNameString( inMsgPtr, inMsgLen, target, domainNameStr, NULL );
+ require_noerr( err, exit );
+ }
+ else
+ {
+ err = DomainNameToString( target, rdataEnd, domainNameStr, NULL );
+ require_noerr( err, exit );
+ }
+
+ ASPrintF( &rdataStr, "%u %u %u %s", priority, weight, port, domainNameStr );
+ require_action( rdataStr, exit, err = kNoMemoryErr );
+ }
+ else if( inRDataType == kDNSServiceType_TXT )
+ {
+ require_action_quiet( inRDataLen > 0, exit, err = kMalformedErr );
+
+ if( inRDataLen == 1 )
+ {
+ ASPrintF( &rdataStr, "%#H", rdataPtr, (int) inRDataLen, INT_MAX );
+ require_action( rdataStr, exit, err = kNoMemoryErr );
+ }
+ else
+ {
+ ASPrintF( &rdataStr, "%#{txt}", rdataPtr, inRDataLen );
+ require_action( rdataStr, exit, err = kNoMemoryErr );
+ }
+ }
+ else if( inRDataType == kDNSServiceType_SOA )
+ {
+ uint32_t serial, refresh, retry, expire, minimum;
+
+ if( inMsgPtr )
+ {
+ err = DNSMessageExtractDomainNameString( inMsgPtr, inMsgLen, rdataPtr, domainNameStr, &ptr );
+ require_noerr( err, exit );
+
+ require_action_quiet( ptr < rdataEnd, exit, err = kMalformedErr );
+
+ rdataStr = strdup( domainNameStr );
+ require_action( rdataStr, exit, err = kNoMemoryErr );
+
+ err = DNSMessageExtractDomainNameString( inMsgPtr, inMsgLen, ptr, domainNameStr, &ptr );
+ require_noerr( err, exit );
+ }
+ else
+ {
+ err = DomainNameToString( rdataPtr, rdataEnd, domainNameStr, &ptr );
+ require_noerr( err, exit );
+
+ rdataStr = strdup( domainNameStr );
+ require_action( rdataStr, exit, err = kNoMemoryErr );
+
+ err = DomainNameToString( ptr, rdataEnd, domainNameStr, &ptr );
+ require_noerr( err, exit );
+ }
+
+ require_action_quiet( ( ptr + 20 ) == rdataEnd, exit, err = kMalformedErr );
+
+ serial = ReadBig32( ptr );
+ refresh = ReadBig32( ptr + 4 );
+ retry = ReadBig32( ptr + 8 );
+ expire = ReadBig32( ptr + 12 );
+ minimum = ReadBig32( ptr + 16 );
+
+ n = AppendPrintF( &rdataStr, " %s %u %u %u %u %u\n", domainNameStr, serial, refresh, retry, expire, minimum );
+ require_action( n > 0, exit, err = kUnknownErr );
+ }
+ else if( inRDataType == kDNSServiceType_NSEC )
+ {
+ unsigned int windowBlock, bitmapLen, i, recordType;
+ const uint8_t * bitmapPtr;
+
+ if( inMsgPtr )
+ {
+ err = DNSMessageExtractDomainNameString( inMsgPtr, inMsgLen, rdataPtr, domainNameStr, &ptr );
+ require_noerr( err, exit );
+ }
+ else
+ {
+ err = DomainNameToString( rdataPtr, rdataEnd, domainNameStr, &ptr );
+ require_noerr( err, exit );
+ }
+
+ require_action_quiet( ptr < rdataEnd, exit, err = kMalformedErr );
+
+ rdataStr = strdup( domainNameStr );
+ require_action( rdataStr, exit, err = kNoMemoryErr );
+
+ for( ; ptr < rdataEnd; ptr += ( 2 + bitmapLen ) )
+ {
+ require_action_quiet( ( ptr + 2 ) < rdataEnd, exit, err = kMalformedErr );
+
+ windowBlock = ptr[ 0 ];
+ bitmapLen = ptr[ 1 ];
+ bitmapPtr = &ptr[ 2 ];
+
+ require_action_quiet( ( bitmapLen >= 1 ) && ( bitmapLen <= 32 ) , exit, err = kMalformedErr );
+ require_action_quiet( ( bitmapPtr + bitmapLen ) <= rdataEnd, exit, err = kMalformedErr );
+
+ for( i = 0; i < BitArray_MaxBits( bitmapLen ); ++i )
+ {
+ if( BitArray_GetBit( bitmapPtr, bitmapLen, i ) )
+ {
+ recordType = ( windowBlock * 256 ) + i;
+ n = AppendPrintF( &rdataStr, " %s", RecordTypeToString( recordType ) );
+ require_action( n > 0, exit, err = kUnknownErr );
+ }
+ }
+ }
+ }
+ else if( inRDataType == kDNSServiceType_MX )
+ {
+ uint16_t preference;
+ const uint8_t * exchange;
+
+ require_action_quiet( ( rdataPtr + 2 ) < rdataEnd, exit, err = kMalformedErr );
+
+ preference = ReadBig16( rdataPtr );
+ exchange = &rdataPtr[ 2 ];
+
+ if( inMsgPtr )
+ {
+ err = DNSMessageExtractDomainNameString( inMsgPtr, inMsgLen, exchange, domainNameStr, NULL );
+ require_noerr( err, exit );
+ }
+ else
+ {
+ err = DomainNameToString( exchange, rdataEnd, domainNameStr, NULL );
+ require_noerr( err, exit );
+ }
+
+ n = ASPrintF( &rdataStr, "%u %s", preference, domainNameStr );
+ require_action( n > 0, exit, err = kUnknownErr );
+ }
+ else
+ {
+ err = kNotHandledErr;
+ goto exit;
+ }
+
+ check( rdataStr );
+ *outString = rdataStr;
+ rdataStr = NULL;
+ err = kNoErr;
+
+exit:
+ FreeNullSafe( rdataStr );
+ return( err );
+}
+
+//===========================================================================================================================
+// DomainNameAppendString
+//===========================================================================================================================
+
+static OSStatus
+ DomainNameAppendString(
+ uint8_t inDomainName[ kDomainNameLengthMax ],
+ const char * inString,
+ uint8_t ** outEndPtr )
+{
+ OSStatus err;
+ const char * src;
+ uint8_t * dst;
+ const uint8_t * const nameLimit = inDomainName + kDomainNameLengthMax;
+
+ // Find the root label.
+
+ for( dst = inDomainName; ( dst < nameLimit ) && *dst; dst += ( 1 + *dst ) ) {}
+ require_action_quiet( dst < nameLimit, exit, err = kMalformedErr );
+
+ // Append the string's labels one label at a time.
+
+ src = inString;
+ while( *src )
+ {
+ uint8_t * const label = dst++;
+ const uint8_t * const labelLimit = Min( dst + kDomainLabelLengthMax, nameLimit - 1 );
+
+ // If the first character is a label separator, then the label is empty. Empty non-root labels are not allowed.
+
+ require_action_quiet( *src != '.', exit, err = kMalformedErr );
+
+ // Write the label characters until the end of the label, a separator or NUL character, is encountered, or until no
+ // more space is available.
+
+ while( ( *src != '.' ) && ( *src != '\0' ) && ( dst < labelLimit ) )
+ {
+ uint8_t value;
+
+ value = (uint8_t) *src++;
+ if( value == '\\' )
+ {
+ if( *src == '\0' ) break;
+ value = (uint8_t) *src++;
+ if( isdigit_safe( value ) && isdigit_safe( src[ 0 ] ) && isdigit_safe( src[ 1 ] ) )
+ {
+ int decimalValue;
+
+ decimalValue = ( ( value - '0' ) * 100 ) + ( ( src[ 0 ] - '0' ) * 10 ) + ( src[ 1 ] - '0' );
+ if( decimalValue <= 255 )
+ {
+ value = (uint8_t) decimalValue;
+ src += 2;
+ }
+ }
+ }
+ *dst++ = value;
+ }
+ if( ( *src == '.' ) || ( *src == '\0' ) )
+ {
+ label[ 0 ] = (uint8_t)( dst - &label[ 1 ] ); // Write the label length.
+ if( *src == '.' ) ++src; // Advance the pointer past the label separator.
+ }
+ else
+ {
+ label[ 0 ] = 0;
+ err = kOverrunErr;
+ goto exit;
+ }
+ }
+
+ *dst++ = 0; // Write the empty root label.
+ if( outEndPtr ) *outEndPtr = dst;
+ err = kNoErr;
+
+exit:
+ return( err );
+}
+
+//===========================================================================================================================
+// DomainNameEqual
+//===========================================================================================================================
+
+static Boolean DomainNameEqual( const uint8_t *inName1, const uint8_t *inName2 )
+{
+ const uint8_t * p1 = inName1;
+ const uint8_t * p2 = inName2;
+ unsigned int len;
+
+ for( ;; )
+ {
+ if( ( len = *p1++ ) != *p2++ ) return( false );
+ if( len == 0 ) break;
+ for( ; len > 0; ++p1, ++p2, --len )
+ {
+ if( tolower_safe( *p1 ) != tolower_safe( *p2 ) ) return( false );
+ }
+ }
+ return( true );
+}
+
+//===========================================================================================================================
+// DomainNameToString
+//===========================================================================================================================
+
+static OSStatus
+ DomainNameToString(
+ const uint8_t * inDomainName,
+ const uint8_t * inEnd,
+ char inBuf[ kDNSServiceMaxDomainName ],
+ const uint8_t ** outNextPtr )
+{
+ OSStatus err;
+ const uint8_t * label;
+ uint8_t labelLen;
+ const uint8_t * nextLabel;
+ char * dst;
+ const uint8_t * src;
+
+ require_action( !inEnd || ( inDomainName < inEnd ), exit, err = kUnderrunErr );
+
+ // Convert each label up until the root label, i.e., the zero-length label.
+
+ dst = inBuf;
+ for( label = inDomainName; ( labelLen = label[ 0 ] ) != 0; label = nextLabel )
+ {
+ require_action( labelLen <= kDomainLabelLengthMax, exit, err = kMalformedErr );
+
+ nextLabel = &label[ 1 ] + labelLen;
+ require_action( ( nextLabel - inDomainName ) < kDomainNameLengthMax, exit, err = kMalformedErr );
+ require_action( !inEnd || ( nextLabel < inEnd ), exit, err = kUnderrunErr );
+
+ for( src = &label[ 1 ]; src < nextLabel; ++src )
+ {
+ if( isprint_safe( *src ) )
+ {
+ if( ( *src == '.' ) || ( *src == '\\' ) || ( *src == ' ' ) ) *dst++ = '\\';
+ *dst++ = (char) *src;
+ }
+ else
+ {
+ *dst++ = '\\';
+ *dst++ = '0' + ( *src / 100 );
+ *dst++ = '0' + ( ( *src / 10 ) % 10 );
+ *dst++ = '0' + ( *src % 10 );
+ }
+ }
+ *dst++ = '.';
+ }
+
+ // At this point, label points to the root label.
+ // If the root label was the only label, then write a dot for it.
+
+ if( label == inDomainName ) *dst++ = '.';
+ *dst = '\0';
+ if( outNextPtr ) *outNextPtr = label + 1;
+ err = kNoErr;
+
+exit:
+ return( err );
+}
+
+//===========================================================================================================================
+// PrintDNSMessage
+//===========================================================================================================================
+
+#define DNSFlagsOpCodeToString( X ) ( \
+ ( (X) == kDNSOpCode_Query ) ? "Query" : \
+ ( (X) == kDNSOpCode_InverseQuery ) ? "IQuery" : \
+ ( (X) == kDNSOpCode_Status ) ? "Status" : \
+ ( (X) == kDNSOpCode_Notify ) ? "Notify" : \
+ ( (X) == kDNSOpCode_Update ) ? "Update" : \
+ "Unassigned" )
+
+#define DNSFlagsRCodeToString( X ) ( \
+ ( (X) == kDNSRCode_NoError ) ? "NoError" : \
+ ( (X) == kDNSRCode_FormatError ) ? "FormErr" : \
+ ( (X) == kDNSRCode_ServerFailure ) ? "ServFail" : \
+ ( (X) == kDNSRCode_NXDomain ) ? "NXDomain" : \
+ ( (X) == kDNSRCode_NotImplemented ) ? "NotImp" : \
+ ( (X) == kDNSRCode_Refused ) ? "Refused" : \
+ "???" )
+
+#define DNSFlagsGetOpCode( X ) ( ( (X) >> 11 ) & 0x0F )
+#define DNSFlagsGetRCode( X ) ( (X) & 0x0F )
+
+static OSStatus PrintDNSMessage( const uint8_t *inMsgPtr, size_t inMsgLen, const Boolean inIsMDNS, const Boolean inPrintRaw )
+{
+ OSStatus err;
+ const DNSHeader * hdr;
+ const uint8_t * const msgEnd = inMsgPtr + inMsgLen;
+ const uint8_t * ptr;
+ unsigned int id, flags, opcode, rcode;
+ unsigned int questionCount, answerCount, authorityCount, additionalCount, i, totalRRCount;
+ char nameStr[ kDNSServiceMaxDomainName ];
+
+ require_action_quiet( inMsgLen >= kDNSHeaderLength, exit, err = kSizeErr );
+
+ hdr = (DNSHeader *) inMsgPtr;
+ id = DNSHeaderGetID( hdr );
+ flags = DNSHeaderGetFlags( hdr );
+ questionCount = DNSHeaderGetQuestionCount( hdr );
+ answerCount = DNSHeaderGetAnswerCount( hdr );
+ authorityCount = DNSHeaderGetAuthorityCount( hdr );
+ additionalCount = DNSHeaderGetAdditionalCount( hdr );
+ opcode = DNSFlagsGetOpCode( flags );
+ rcode = DNSFlagsGetRCode( flags );
+
+ FPrintF( stdout, "ID: 0x%04X\n", id );
+ FPrintF( stdout, "Flags: 0x%04X %c/%s %cAA%cTC%cRD%cRA %s\n",
+ flags,
+ ( flags & kDNSHeaderFlag_Response ) ? 'R' : 'Q', DNSFlagsOpCodeToString( opcode ),
+ ( flags & kDNSHeaderFlag_AuthAnswer ) ? ' ' : '!',
+ ( flags & kDNSHeaderFlag_Truncation ) ? ' ' : '!',
+ ( flags & kDNSHeaderFlag_RecursionDesired ) ? ' ' : '!',
+ ( flags & kDNSHeaderFlag_RecursionAvailable ) ? ' ' : '!',
+ DNSFlagsRCodeToString( rcode ) );
+ FPrintF( stdout, "Question count: %u\n", questionCount );
+ FPrintF( stdout, "Answer count: %u\n", answerCount );
+ FPrintF( stdout, "Authority count: %u\n", authorityCount );
+ FPrintF( stdout, "Additional count: %u\n", additionalCount );
+
+ ptr = (uint8_t *)( hdr + 1 );
+ for( i = 0; i < questionCount; ++i )
+ {
+ unsigned int qType, qClass;
+ Boolean isQU;
+
+ err = DNSMessageExtractDomainNameString( inMsgPtr, inMsgLen, ptr, nameStr, &ptr );
+ require_noerr( err, exit );
+
+ if( ( msgEnd - ptr ) < 4 )
+ {
+ err = kUnderrunErr;
+ goto exit;
+ }
+
+ qType = ReadBig16( ptr );
+ ptr += 2;
+ qClass = ReadBig16( ptr );
+ ptr += 2;
+
+ isQU = ( inIsMDNS && ( qClass & kQClassUnicastResponseBit ) ) ? true : false;
+ if( inIsMDNS ) qClass &= ~kQClassUnicastResponseBit;
+
+ if( i == 0 ) FPrintF( stdout, "\nQUESTION SECTION\n" );
+
+ FPrintF( stdout, "%s %2s %?2s%?2u %-5s\n",
+ nameStr, inIsMDNS ? ( isQU ? "QU" : "QM" ) : "",
+ ( qClass == kDNSServiceClass_IN ), "IN", ( qClass != kDNSServiceClass_IN ), qClass,
+ RecordTypeToString( qType ) );
+ }
+
+ totalRRCount = answerCount + authorityCount + additionalCount;
+ for( i = 0; i < totalRRCount; ++i )
+ {
+ uint16_t type;
+ uint16_t class;
+ uint32_t ttl;
+ const uint8_t * rdataPtr;
+ size_t rdataLen;
+ char * rdataStr;
+ Boolean cacheFlush;
+ uint8_t name[ kDomainNameLengthMax ];
+
+ err = DNSMessageExtractRecord( inMsgPtr, inMsgLen, ptr, name, &type, &class, &ttl, &rdataPtr, &rdataLen, &ptr );
+ require_noerr( err, exit );
+
+ err = DomainNameToString( name, NULL, nameStr, NULL );
+ require_noerr( err, exit );
+
+ cacheFlush = ( inIsMDNS && ( class & kRRClassCacheFlushBit ) ) ? true : false;
+ if( inIsMDNS ) class &= ~kRRClassCacheFlushBit;
+
+ rdataStr = NULL;
+ if( !inPrintRaw ) DNSRecordDataToString( rdataPtr, rdataLen, type, inMsgPtr, inMsgLen, &rdataStr );
+ if( !rdataStr )
+ {
+ ASPrintF( &rdataStr, "%#H", rdataPtr, (int) rdataLen, INT_MAX );
+ require_action( rdataStr, exit, err = kNoMemoryErr );
+ }
+
+ if( answerCount && ( i == 0 ) ) FPrintF( stdout, "\nANSWER SECTION\n" );
+ else if( authorityCount && ( i == answerCount ) ) FPrintF( stdout, "\nAUTHORITY SECTION\n" );
+ else if( additionalCount && ( i == ( answerCount + authorityCount ) ) ) FPrintF( stdout, "\nADDITIONAL SECTION\n" );
+
+ FPrintF( stdout, "%-42s %6u %2s %?2s%?2u %-5s %s\n",
+ nameStr, ttl, cacheFlush ? "CF" : "",
+ ( class == kDNSServiceClass_IN ), "IN", ( class != kDNSServiceClass_IN ), class,
+ RecordTypeToString( type ), rdataStr );
+ free( rdataStr );
+ }
+ FPrintF( stdout, "\n" );
+ err = kNoErr;
+
+exit:
+ return( err );
+}
+
+//===========================================================================================================================
+// WriteDNSQueryMessage
+//===========================================================================================================================
+
+static OSStatus
+ WriteDNSQueryMessage(
+ uint8_t inMsg[ kDNSQueryMessageMaxLen ],
+ uint16_t inMsgID,
+ uint16_t inFlags,
+ const char * inQName,
+ uint16_t inQType,
+ uint16_t inQClass,
+ size_t * outMsgLen )
+{
+ OSStatus err;
+ DNSHeader * const hdr = (DNSHeader *) inMsg;
+ uint8_t * ptr;
+ size_t msgLen;
+
+ WriteBig16( hdr->id, inMsgID );
+ WriteBig16( hdr->flags, inFlags );
+ WriteBig16( hdr->questionCount, 1 );
+ WriteBig16( hdr->answerCount, 0 );
+ WriteBig16( hdr->authorityCount, 0 );
+ WriteBig16( hdr->additionalCount, 0 );
+
+ ptr = (uint8_t *)( hdr + 1 );
+ ptr[ 0 ] = 0;
+ err = DomainNameAppendString( ptr, inQName, &ptr );
+ require_noerr_quiet( err, exit );
+
+ WriteBig16( ptr, inQType );
+ ptr += 2;
+ WriteBig16( ptr, inQClass );
+ ptr += 2;
+ msgLen = (size_t)( ptr - inMsg );
+ check( msgLen <= kDNSQueryMessageMaxLen );
+
+ if( outMsgLen ) *outMsgLen = msgLen;
+
+exit:
+ return( err );
+}
+
+//===========================================================================================================================
+// DispatchSignalSourceCreate
+//===========================================================================================================================
+
+static OSStatus
+ DispatchSignalSourceCreate(
+ int inSignal,
+ DispatchHandler inEventHandler,
+ void * inContext,
+ dispatch_source_t * outSource )
+{
+ OSStatus err;
+ dispatch_source_t source;
+
+ source = dispatch_source_create( DISPATCH_SOURCE_TYPE_SIGNAL, (uintptr_t) inSignal, 0, dispatch_get_main_queue() );
+ require_action( source, exit, err = kUnknownErr );
+
+ dispatch_set_context( source, inContext );
+ dispatch_source_set_event_handler_f( source, inEventHandler );
+
+ *outSource = source;
+ err = kNoErr;
+
+exit:
+ return( err );
+}
+
+//===========================================================================================================================
+// DispatchReadSourceCreate
+//===========================================================================================================================
+
+static OSStatus
+ DispatchReadSourceCreate(
+ SocketRef inSock,
+ DispatchHandler inEventHandler,
+ DispatchHandler inCancelHandler,
+ void * inContext,
+ dispatch_source_t * outSource )
+{
+ OSStatus err;
+ dispatch_source_t source;
+
+ source = dispatch_source_create( DISPATCH_SOURCE_TYPE_READ, (uintptr_t) inSock, 0, dispatch_get_main_queue() );
+ require_action( source, exit, err = kUnknownErr );
+
+ dispatch_set_context( source, inContext );
+ dispatch_source_set_event_handler_f( source, inEventHandler );
+ dispatch_source_set_cancel_handler_f( source, inCancelHandler );
+
+ *outSource = source;
+ err = kNoErr;
+
+exit:
+ return( err );
+}
+
+//===========================================================================================================================
+// DispatchTimerCreate
+//===========================================================================================================================
+
+static OSStatus
+ DispatchTimerCreate(
+ dispatch_time_t inStart,
+ uint64_t inIntervalNs,
+ uint64_t inLeewayNs,
+ DispatchHandler inEventHandler,
+ DispatchHandler inCancelHandler,
+ void * inContext,
+ dispatch_source_t * outTimer )
+{
+ OSStatus err;
+ dispatch_source_t timer;
+
+ timer = dispatch_source_create( DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue() );
+ require_action( timer, exit, err = kUnknownErr );
+
+ dispatch_source_set_timer( timer, inStart, inIntervalNs, inLeewayNs );
+ dispatch_set_context( timer, inContext );
+ dispatch_source_set_event_handler_f( timer, inEventHandler );
+ dispatch_source_set_cancel_handler_f( timer, inCancelHandler );
+
+ *outTimer = timer;
+ err = kNoErr;
+
+exit:
+ return( err );
+}
+
+//===========================================================================================================================
+// ServiceTypeDescription
+//===========================================================================================================================
+
+typedef struct
+{
+ const char * name; // Name of the service type in two-label "_service._proto" format.
+ const char * description; // Description of the service type.
+
+} ServiceType;
+
+// A Non-comprehensive table of DNS-SD service types
+
+static const ServiceType kServiceTypes[] =
+{
+ { "_acp-sync._tcp", "AirPort Base Station Sync" },
+ { "_adisk._tcp", "Automatic Disk Discovery" },
+ { "_afpovertcp._tcp", "Apple File Sharing" },
+ { "_airdrop._tcp", "AirDrop" },
+ { "_airplay._tcp", "AirPlay" },
+ { "_airport._tcp", "AirPort Base Station" },
+ { "_daap._tcp", "Digital Audio Access Protocol (iTunes)" },
+ { "_eppc._tcp", "Remote AppleEvents" },
+ { "_ftp._tcp", "File Transfer Protocol" },
+ { "_home-sharing._tcp", "Home Sharing" },
+ { "_homekit._tcp", "HomeKit" },
+ { "_http._tcp", "World Wide Web HTML-over-HTTP" },
+ { "_https._tcp", "HTTP over SSL/TLS" },
+ { "_ipp._tcp", "Internet Printing Protocol" },
+ { "_ldap._tcp", "Lightweight Directory Access Protocol" },
+ { "_mediaremotetv._tcp", "Media Remote" },
+ { "_net-assistant._tcp", "Apple Remote Desktop" },
+ { "_od-master._tcp", "OpenDirectory Master" },
+ { "_nfs._tcp", "Network File System" },
+ { "_presence._tcp", "Peer-to-peer messaging / Link-Local Messaging" },
+ { "_pdl-datastream._tcp", "Printer Page Description Language Data Stream" },
+ { "_raop._tcp", "Remote Audio Output Protocol" },
+ { "_rfb._tcp", "Remote Frame Buffer" },
+ { "_scanner._tcp", "Bonjour Scanning" },
+ { "_smb._tcp", "Server Message Block over TCP/IP" },
+ { "_sftp-ssh._tcp", "Secure File Transfer Protocol over SSH" },
+ { "_sleep-proxy._udp", "Sleep Proxy Server" },
+ { "_ssh._tcp", "SSH Remote Login Protocol" },
+ { "_teleport._tcp", "teleport" },
+ { "_tftp._tcp", "Trivial File Transfer Protocol" },
+ { "_workstation._tcp", "Workgroup Manager" },
+ { "_webdav._tcp", "World Wide Web Distributed Authoring and Versioning (WebDAV)" },
+ { "_webdavs._tcp", "WebDAV over SSL/TLS" }
+};
+
+static const char * ServiceTypeDescription( const char *inName )
+{
+ const ServiceType * serviceType;
+ const ServiceType * const end = kServiceTypes + countof( kServiceTypes );
+
+ for( serviceType = kServiceTypes; serviceType < end; ++serviceType )
+ {
+ if( strcasecmp( inName, serviceType->name ) == 0 ) return( serviceType->description );
+ }
+ return( NULL );
+}
+
+//===========================================================================================================================
+// SocketContextCancelHandler
+//===========================================================================================================================
+
+static void SocketContextCancelHandler( void *inContext )
+{
+ SocketContext * const context = (SocketContext *) inContext;
+
+ ForgetSocket( &context->sock );
+ free( context );
+}
+
+//===========================================================================================================================
+// StringToInt32
+//===========================================================================================================================
+
+static OSStatus StringToInt32( const char *inString, int32_t *outValue )
+{
+ OSStatus err;
+ long value;
+ char * endPtr;
+
+ value = strtol( inString, &endPtr, 0 );
+ require_action_quiet( ( *endPtr == '\0' ) && ( endPtr != inString ), exit, err = kParamErr );
+ require_action_quiet( ( value >= INT32_MIN ) && ( value <= INT32_MAX ), exit, err = kRangeErr );
+
+ *outValue = (int32_t) value;
+ err = kNoErr;
+
+exit:
+ return( err );
+}
+
+//===========================================================================================================================
+// StringToUInt32
+//===========================================================================================================================
+
+static OSStatus StringToUInt32( const char *inString, uint32_t *outValue )
+{
+ OSStatus err;
+ uint32_t value;
+ char * endPtr;
+
+ value = (uint32_t) strtol( inString, &endPtr, 0 );
+ require_action_quiet( ( *endPtr == '\0' ) && ( endPtr != inString ), exit, err = kParamErr );
+
+ *outValue = value;
+ err = kNoErr;
+
+exit:
+ return( err );
+}
+
+//===========================================================================================================================
+// SocketWriteAll
+//
+// Note: This was copied from CoreUtils because the SocketWriteAll function is currently not exported in the framework.
+//===========================================================================================================================
+
+OSStatus SocketWriteAll( SocketRef inSock, const void *inData, size_t inSize, int32_t inTimeoutSecs )
+{
+ OSStatus err;
+ const uint8_t * src;
+ const uint8_t * end;
+ fd_set writeSet;
+ struct timeval timeout;
+ ssize_t n;
+
+ FD_ZERO( &writeSet );
+ src = (const uint8_t *) inData;
+ end = src + inSize;
+ while( src < end )
+ {
+ FD_SET( inSock, &writeSet );
+ timeout.tv_sec = inTimeoutSecs;
+ timeout.tv_usec = 0;
+ n = select( (int)( inSock + 1 ), NULL, &writeSet, NULL, &timeout );
+ if( n == 0 ) { err = kTimeoutErr; goto exit; }
+ err = map_socket_value_errno( inSock, n > 0, n );
+ require_noerr( err, exit );
+
+ n = send( inSock, (char *) src, (size_t)( end - src ), 0 );
+ err = map_socket_value_errno( inSock, n >= 0, n );
+ if( err == EINTR ) continue;
+ require_noerr( err, exit );
+
+ src += n;
+ }
+ err = kNoErr;
+
+exit:
+ return( err );
+}
+
+//===========================================================================================================================
+// ParseEscapedString
+//
+// Note: This was copied from CoreUtils because the ParseEscapedString function is currently not exported in the framework.
+//===========================================================================================================================
+
+OSStatus
+ ParseEscapedString(
+ const char * inSrc,
+ const char * inEnd,
+ char inDelimiter,
+ char * inBuf,
+ size_t inMaxLen,
+ size_t * outCopiedLen,
+ size_t * outTotalLen,
+ const char ** outSrc )
+{
+ OSStatus err;
+ char c;
+ char * dst;
+ char * lim;
+ size_t len;
+
+ dst = inBuf;
+ lim = dst + ( ( inMaxLen > 0 ) ? ( inMaxLen - 1 ) : 0 ); // Leave room for null terminator.
+ len = 0;
+ while( ( inSrc < inEnd ) && ( ( c = *inSrc++ ) != inDelimiter ) )
+ {
+ if( c == '\\' )
+ {
+ require_action_quiet( inSrc < inEnd, exit, err = kUnderrunErr );
+ c = *inSrc++;
+ }
+ if( dst < lim )
+ {
+ if( inBuf ) *dst = c;
+ ++dst;
+ }
+ ++len;
+ }
+ if( inBuf && ( inMaxLen > 0 ) ) *dst = '\0';
+ err = kNoErr;
+
+exit:
+ if( outCopiedLen ) *outCopiedLen = (size_t)( dst - inBuf );
+ if( outTotalLen ) *outTotalLen = len;
+ if( outSrc ) *outSrc = inSrc;
+ return( err );
+}
+
+//===========================================================================================================================
+// ParseIPv4Address
+//
+// Warning: "inBuffer" may be modified even in error cases.
+//
+// Note: This was copied from CoreUtils because the StringToIPv4Address function is currently not exported in the framework.
+//===========================================================================================================================
+
+static OSStatus ParseIPv4Address( const char *inStr, uint8_t inBuffer[ 4 ], const char **outStr )
+{
+ OSStatus err;
+ uint8_t * dst;
+ int segments;
+ int sawDigit;
+ int c;
+ int v;
+
+ check( inBuffer );
+ check( outStr );
+
+ dst = inBuffer;
+ *dst = 0;
+ sawDigit = 0;
+ segments = 0;
+ for( ; ( c = *inStr ) != '\0'; ++inStr )
+ {
+ if( isdigit_safe( c ) )
+ {
+ v = ( *dst * 10 ) + ( c - '0' );
+ require_action_quiet( v <= 255, exit, err = kRangeErr );
+ *dst = (uint8_t) v;
+ if( !sawDigit )
+ {
+ ++segments;
+ require_action_quiet( segments <= 4, exit, err = kOverrunErr );
+ sawDigit = 1;
+ }
+ }
+ else if( ( c == '.' ) && sawDigit )
+ {
+ require_action_quiet( segments < 4, exit, err = kMalformedErr );
+ *++dst = 0;
+ sawDigit = 0;
+ }
+ else
+ {
+ break;
+ }
+ }
+ require_action_quiet( segments == 4, exit, err = kUnderrunErr );
+
+ *outStr = inStr;
+ err = kNoErr;
+
+exit:
+ return( err );
+}
+
+//===========================================================================================================================
+// StringToIPv4Address
+//
+// Note: This was copied from CoreUtils because the StringToIPv4Address function is currently not exported in the framework.
+//===========================================================================================================================
+
+OSStatus
+ StringToIPv4Address(
+ const char * inStr,
+ StringToIPAddressFlags inFlags,
+ uint32_t * outIP,
+ int * outPort,
+ uint32_t * outSubnet,
+ uint32_t * outRouter,
+ const char ** outStr )
+{
+ OSStatus err;
+ uint8_t buf[ 4 ];
+ int c;
+ uint32_t ip;
+ int hasPort;
+ int port;
+ int hasPrefix;
+ int prefix;
+ uint32_t subnetMask;
+ uint32_t router;
+
+ require_action( inStr, exit, err = kParamErr );
+
+ // Parse the address-only part of the address (e.g. "1.2.3.4").
+
+ err = ParseIPv4Address( inStr, buf, &inStr );
+ require_noerr_quiet( err, exit );
+ ip = (uint32_t)( ( buf[ 0 ] << 24 ) | ( buf[ 1 ] << 16 ) | ( buf[ 2 ] << 8 ) | buf[ 3 ] );
+ c = *inStr;
+
+ // Parse the port (if any).
+
+ hasPort = 0;
+ port = 0;
+ if( c == ':' )
+ {
+ require_action_quiet( !( inFlags & kStringToIPAddressFlagsNoPort ), exit, err = kUnexpectedErr );
+ while( ( ( c = *( ++inStr ) ) != '\0' ) && ( ( c >= '0' ) && ( c <= '9' ) ) ) port = ( port * 10 ) + ( c - '0' );
+ require_action_quiet( port <= 65535, exit, err = kRangeErr );
+ hasPort = 1;
+ }
+
+ // Parse the prefix length (if any).
+
+ hasPrefix = 0;
+ prefix = 0;
+ subnetMask = 0;
+ router = 0;
+ if( c == '/' )
+ {
+ require_action_quiet( !( inFlags & kStringToIPAddressFlagsNoPrefix ), exit, err = kUnexpectedErr );
+ while( ( ( c = *( ++inStr ) ) != '\0' ) && ( ( c >= '0' ) && ( c <= '9' ) ) ) prefix = ( prefix * 10 ) + ( c - '0' );
+ require_action_quiet( ( prefix >= 0 ) && ( prefix <= 32 ), exit, err = kRangeErr );
+ hasPrefix = 1;
+
+ subnetMask = ( prefix > 0 ) ? ( UINT32_C( 0xFFFFFFFF ) << ( 32 - prefix ) ) : 0;
+ router = ( ip & subnetMask ) | 1;
+ }
+
+ // Return the results. Only fill in port/prefix/router results if the info was found to allow for defaults.
+
+ if( outIP ) *outIP = ip;
+ if( outPort && hasPort ) *outPort = port;
+ if( outSubnet && hasPrefix ) *outSubnet = subnetMask;
+ if( outRouter && hasPrefix ) *outRouter = router;
+ if( outStr ) *outStr = inStr;
+ err = kNoErr;
+
+exit:
+ return( err );
+}
+
+//===========================================================================================================================
+// ParseIPv6Address
+//
+// Note: Parsed according to the rules specified in RFC 3513.
+// Warning: "inBuffer" may be modified even in error cases.
+//
+// Note: This was copied from CoreUtils because the StringToIPv6Address function is currently not exported in the framework.
+//===========================================================================================================================
+
+static OSStatus ParseIPv6Address( const char *inStr, int inAllowV4Mapped, uint8_t inBuffer[ 16 ], const char **outStr )
+{
+ // Table to map uppercase hex characters - '0' to their numeric values.
+ // 0 1 2 3 4 5 6 7 8 9 : ; < = > ? @ A B C D E F
+ static const uint8_t kASCIItoHexTable[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, 0, 10, 11, 12, 13, 14, 15 };
+ OSStatus err;
+ const char * ptr;
+ uint8_t * dst;
+ uint8_t * lim;
+ uint8_t * colonPtr;
+ int c;
+ int sawDigit;
+ unsigned int v;
+ int i;
+ int n;
+
+ // Pre-zero the address to simplify handling of compressed addresses (e.g. "::1").
+
+ for( i = 0; i < 16; ++i ) inBuffer[ i ] = 0;
+
+ // Special case leading :: (e.g. "::1") to simplify processing later.
+
+ if( *inStr == ':' )
+ {
+ ++inStr;
+ require_action_quiet( *inStr == ':', exit, err = kMalformedErr );
+ }
+
+ // Parse the address.
+
+ ptr = inStr;
+ dst = inBuffer;
+ lim = dst + 16;
+ colonPtr = NULL;
+ sawDigit = 0;
+ v = 0;
+ while( ( ( c = *inStr++ ) != '\0' ) && ( c != '%' ) && ( c != '/' ) && ( c != ']' ) )
+ {
+ if( ( c >= 'a' ) && ( c <= 'f' ) ) c -= ( 'a' - 'A' );
+ if( ( ( c >= '0' ) && ( c <= '9' ) ) || ( ( c >= 'A' ) && ( c <= 'F' ) ) )
+ {
+ c -= '0';
+ check( c < (int) countof( kASCIItoHexTable ) );
+ v = ( v << 4 ) | kASCIItoHexTable[ c ];
+ require_action_quiet( v <= 0xFFFF, exit, err = kRangeErr );
+ sawDigit = 1;
+ continue;
+ }
+ if( c == ':' )
+ {
+ ptr = inStr;
+ if( !sawDigit )
+ {
+ require_action_quiet( !colonPtr, exit, err = kMalformedErr );
+ colonPtr = dst;
+ continue;
+ }
+ require_action_quiet( *inStr != '\0', exit, err = kUnderrunErr );
+ require_action_quiet( ( dst + 2 ) <= lim, exit, err = kOverrunErr );
+ *dst++ = (uint8_t)( ( v >> 8 ) & 0xFF );
+ *dst++ = (uint8_t)( v & 0xFF );
+ sawDigit = 0;
+ v = 0;
+ continue;
+ }
+
+ // Handle IPv4-mapped/compatible addresses (e.g. ::FFFF:1.2.3.4).
+
+ if( inAllowV4Mapped && ( c == '.' ) && ( ( dst + 4 ) <= lim ) )
+ {
+ err = ParseIPv4Address( ptr, dst, &inStr );
+ require_noerr_quiet( err, exit );
+ dst += 4;
+ sawDigit = 0;
+ ++inStr; // Increment because the code below expects the end to be at "inStr - 1".
+ }
+ break;
+ }
+ if( sawDigit )
+ {
+ require_action_quiet( ( dst + 2 ) <= lim, exit, err = kOverrunErr );
+ *dst++ = (uint8_t)( ( v >> 8 ) & 0xFF );
+ *dst++ = (uint8_t)( v & 0xFF );
+ }
+ check( dst <= lim );
+ if( colonPtr )
+ {
+ require_action_quiet( dst < lim, exit, err = kOverrunErr );
+ n = (int)( dst - colonPtr );
+ for( i = 1; i <= n; ++i )
+ {
+ lim[ -i ] = colonPtr[ n - i ];
+ colonPtr[ n - i ] = 0;
+ }
+ dst = lim;
+ }
+ require_action_quiet( dst == lim, exit, err = kUnderrunErr );
+
+ *outStr = inStr - 1;
+ err = kNoErr;
+
+exit:
+ return( err );
+}
+
+//===========================================================================================================================
+// ParseIPv6Scope
+//
+// Note: This was copied from CoreUtils because the StringToIPv6Address function is currently not exported in the framework.
+//===========================================================================================================================
+
+static OSStatus ParseIPv6Scope( const char *inStr, uint32_t *outScope, const char **outStr )
+{
+#if( TARGET_OS_POSIX )
+ OSStatus err;
+ char scopeStr[ 64 ];
+ char * dst;
+ char * lim;
+ int c;
+ uint32_t scope;
+ const char * ptr;
+
+ // Copy into a local NULL-terminated string since that is what if_nametoindex expects.
+
+ dst = scopeStr;
+ lim = dst + ( countof( scopeStr ) - 1 );
+ while( ( ( c = *inStr ) != '\0' ) && ( c != ':' ) && ( c != '/' ) && ( c != ']' ) && ( dst < lim ) )
+ {
+ *dst++ = *inStr++;
+ }
+ *dst = '\0';
+ check( dst <= lim );
+
+ // First try to map as a name and if that fails, treat it as a numeric scope.
+
+ scope = if_nametoindex( scopeStr );
+ if( scope == 0 )
+ {
+ for( ptr = scopeStr; ( ( c = *ptr ) >= '0' ) && ( c <= '9' ); ++ptr )
+ {
+ scope = ( scope * 10 ) + ( ( (uint8_t) c ) - '0' );
+ }
+ require_action_quiet( c == '\0', exit, err = kMalformedErr );
+ require_action_quiet( ( ptr != scopeStr ) && ( ( (int)( ptr - scopeStr ) ) <= 10 ), exit, err = kMalformedErr );
+ }
+
+ *outScope = scope;
+ *outStr = inStr;
+ err = kNoErr;
+
+exit:
+ return( err );
+#else
+ OSStatus err;
+ uint32_t scope;
+ const char * start;
+ int c;
+
+ scope = 0;
+ for( start = inStr; ( ( c = *inStr ) >= '0' ) && ( c <= '9' ); ++inStr )
+ {
+ scope = ( scope * 10 ) + ( c - '0' );
+ }
+ require_action_quiet( ( inStr != start ) && ( ( (int)( inStr - start ) ) <= 10 ), exit, err = kMalformedErr );
+
+ *outScope = scope;
+ *outStr = inStr;
+ err = kNoErr;
+
+exit:
+ return( err );
+#endif
+}
+
+//===========================================================================================================================
+// StringToIPv6Address
+//
+// Note: This was copied from CoreUtils because the StringToIPv6Address function is currently not exported in the framework.
+//===========================================================================================================================
+
+OSStatus
+ StringToIPv6Address(
+ const char * inStr,
+ StringToIPAddressFlags inFlags,
+ uint8_t outIPv6[ 16 ],
+ uint32_t * outScope,
+ int * outPort,
+ int * outPrefix,
+ const char ** outStr )
+{
+ OSStatus err;
+ uint8_t ipv6[ 16 ];
+ int c;
+ int hasScope;
+ uint32_t scope;
+ int hasPort;
+ int port;
+ int hasPrefix;
+ int prefix;
+ int hasBracket;
+ int i;
+
+ require_action( inStr, exit, err = kParamErr );
+
+ if( *inStr == '[' ) ++inStr; // Skip a leading bracket for []-wrapped addresses (e.g. "[::1]:80").
+
+ // Parse the address-only part of the address (e.g. "1::1").
+
+ err = ParseIPv6Address( inStr, !( inFlags & kStringToIPAddressFlagsNoIPv4Mapped ), ipv6, &inStr );
+ require_noerr_quiet( err, exit );
+ c = *inStr;
+
+ // Parse the scope, port, or prefix length.
+
+ hasScope = 0;
+ scope = 0;
+ hasPort = 0;
+ port = 0;
+ hasPrefix = 0;
+ prefix = 0;
+ hasBracket = 0;
+ for( ;; )
+ {
+ if( c == '%' ) // Scope (e.g. "%en0" or "%5")
+ {
+ require_action_quiet( !hasScope, exit, err = kMalformedErr );
+ require_action_quiet( !( inFlags & kStringToIPAddressFlagsNoScope ), exit, err = kUnexpectedErr );
+ ++inStr;
+ err = ParseIPv6Scope( inStr, &scope, &inStr );
+ require_noerr_quiet( err, exit );
+ hasScope = 1;
+ c = *inStr;
+ }
+ else if( c == ':' ) // Port (e.g. ":80")
+ {
+ require_action_quiet( !hasPort, exit, err = kMalformedErr );
+ require_action_quiet( !( inFlags & kStringToIPAddressFlagsNoPort ), exit, err = kUnexpectedErr );
+ while( ( ( c = *( ++inStr ) ) != '\0' ) && ( ( c >= '0' ) && ( c <= '9' ) ) ) port = ( port * 10 ) + ( c - '0' );
+ require_action_quiet( port <= 65535, exit, err = kRangeErr );
+ hasPort = 1;
+ }
+ else if( c == '/' ) // Prefix Length (e.g. "/64")
+ {
+ require_action_quiet( !hasPrefix, exit, err = kMalformedErr );
+ require_action_quiet( !( inFlags & kStringToIPAddressFlagsNoPrefix ), exit, err = kUnexpectedErr );
+ while( ( ( c = *( ++inStr ) ) != '\0' ) && ( ( c >= '0' ) && ( c <= '9' ) ) ) prefix = ( prefix * 10 ) + ( c - '0' );
+ require_action_quiet( ( prefix >= 0 ) && ( prefix <= 128 ), exit, err = kRangeErr );
+ hasPrefix = 1;
+ }
+ else if( c == ']' )
+ {
+ require_action_quiet( !hasBracket, exit, err = kMalformedErr );
+ hasBracket = 1;
+ c = *( ++inStr );
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ // Return the results. Only fill in scope/port/prefix results if the info was found to allow for defaults.
+
+ if( outIPv6 ) for( i = 0; i < 16; ++i ) outIPv6[ i ] = ipv6[ i ];
+ if( outScope && hasScope ) *outScope = scope;
+ if( outPort && hasPort ) *outPort = port;
+ if( outPrefix && hasPrefix ) *outPrefix = prefix;
+ if( outStr ) *outStr = inStr;
+ err = kNoErr;
+
+exit:
+ return( err );
+}
+
+//===========================================================================================================================
+// StringArray_Append
+//
+// Note: This was copied from CoreUtils because the StringArray_Append function is currently not exported in the framework.
+//===========================================================================================================================
+
+OSStatus StringArray_Append( char ***ioArray, size_t *ioCount, const char *inStr )
+{
+ OSStatus err;
+ char * newStr;
+ size_t oldCount;
+ size_t newCount;
+ char ** oldArray;
+ char ** newArray;
+
+ newStr = strdup( inStr );
+ require_action( newStr, exit, err = kNoMemoryErr );
+
+ oldCount = *ioCount;
+ newCount = oldCount + 1;
+ newArray = (char **) malloc( newCount * sizeof( *newArray ) );
+ require_action( newArray, exit, err = kNoMemoryErr );
+
+ if( oldCount > 0 )
+ {
+ oldArray = *ioArray;
+ memcpy( newArray, oldArray, oldCount * sizeof( *oldArray ) );
+ free( oldArray );
+ }
+ newArray[ oldCount ] = newStr;
+ newStr = NULL;
+
+ *ioArray = newArray;
+ *ioCount = newCount;
+ err = kNoErr;
+
+exit:
+ if( newStr ) free( newStr );
+ return( err );
+}
+
+//===========================================================================================================================
+// StringArray_Free
+//
+// Note: This was copied from CoreUtils because the StringArray_Free function is currently not exported in the framework.
+//===========================================================================================================================
+
+void StringArray_Free( char **inArray, size_t inCount )
+{
+ size_t i;
+
+ for( i = 0; i < inCount; ++i )
+ {
+ free( inArray[ i ] );
+ }
+ if( inCount > 0 ) free( inArray );
+}
+
+//===========================================================================================================================
+// ParseQuotedEscapedString
+//
+// Note: This was copied from CoreUtils because it's currently not exported in the framework.
+//===========================================================================================================================
+
+Boolean
+ ParseQuotedEscapedString(
+ const char * inSrc,
+ const char * inEnd,
+ const char * inDelimiters,
+ char * inBuf,
+ size_t inMaxLen,
+ size_t * outCopiedLen,
+ size_t * outTotalLen,
+ const char ** outSrc )
+{
+ const unsigned char * src;
+ const unsigned char * end;
+ unsigned char * dst;
+ unsigned char * lim;
+ unsigned char c;
+ unsigned char c2;
+ size_t totalLen;
+ Boolean singleQuote;
+ Boolean doubleQuote;
+
+ if( inEnd == NULL ) inEnd = inSrc + strlen( inSrc );
+ src = (const unsigned char *) inSrc;
+ end = (const unsigned char *) inEnd;
+ dst = (unsigned char *) inBuf;
+ lim = dst + inMaxLen;
+ while( ( src < end ) && isspace_safe( *src ) ) ++src; // Skip leading spaces.
+ if( src >= end ) return( false );
+
+ // Parse each argument from the string.
+ //
+ // See <http://resources.mpi-inf.mpg.de/departments/rg1/teaching/unixffb-ss98/quoting-guide.html> for details.
+
+ totalLen = 0;
+ singleQuote = false;
+ doubleQuote = false;
+ while( src < end )
+ {
+ c = *src++;
+ if( singleQuote )
+ {
+ // Single quotes protect everything (even backslashes, newlines, etc.) except single quotes.
+
+ if( c == '\'' )
+ {
+ singleQuote = false;
+ continue;
+ }
+ }
+ else if( doubleQuote )
+ {
+ // Double quotes protect everything except double quotes and backslashes. A backslash can be
+ // used to protect " or \ within double quotes. A backslash-newline pair disappears completely.
+ // A backslash followed by x or X and 2 hex digits (e.g. "\x1f") is stored as that hex byte.
+ // A backslash followed by 3 octal digits (e.g. "\377") is stored as that octal byte.
+ // A backslash that does not precede ", \, x, X, or a newline is taken literally.
+
+ if( c == '"' )
+ {
+ doubleQuote = false;
+ continue;
+ }
+ else if( c == '\\' )
+ {
+ if( src < end )
+ {
+ c2 = *src;
+ if( ( c2 == '"' ) || ( c2 == '\\' ) )
+ {
+ ++src;
+ c = c2;
+ }
+ else if( c2 == '\n' )
+ {
+ ++src;
+ continue;
+ }
+ else if( ( c2 == 'x' ) || ( c2 == 'X' ) )
+ {
+ ++src;
+ c = c2;
+ if( ( ( end - src ) >= 2 ) && IsHexPair( src ) )
+ {
+ c = HexPairToByte( src );
+ src += 2;
+ }
+ }
+ else if( isoctal_safe( c2 ) )
+ {
+ if( ( ( end - src ) >= 3 ) && IsOctalTriple( src ) )
+ {
+ c = OctalTripleToByte( src );
+ src += 3;
+ }
+ }
+ }
+ }
+ }
+ else if( strchr( inDelimiters, c ) )
+ {
+ break;
+ }
+ else if( c == '\\' )
+ {
+ // A backslash protects the next character, except a newline, x, X and 2 hex bytes or 3 octal bytes.
+ // A backslash followed by a newline disappears completely.
+ // A backslash followed by x or X and 2 hex digits (e.g. "\x1f") is stored as that hex byte.
+ // A backslash followed by 3 octal digits (e.g. "\377") is stored as that octal byte.
+
+ if( src < end )
+ {
+ c = *src;
+ if( c == '\n' )
+ {
+ ++src;
+ continue;
+ }
+ else if( ( c == 'x' ) || ( c == 'X' ) )
+ {
+ ++src;
+ if( ( ( end - src ) >= 2 ) && IsHexPair( src ) )
+ {
+ c = HexPairToByte( src );
+ src += 2;
+ }
+ }
+ else if( isoctal_safe( c ) )
+ {
+ if( ( ( end - src ) >= 3 ) && IsOctalTriple( src ) )
+ {
+ c = OctalTripleToByte( src );
+ src += 3;
+ }
+ else
+ {
+ ++src;
+ }
+ }
+ else
+ {
+ ++src;
+ }
+ }
+ }
+ else if( c == '\'' )
+ {
+ singleQuote = true;
+ continue;
+ }
+ else if( c == '"' )
+ {
+ doubleQuote = true;
+ continue;
+ }
+
+ if( dst < lim )
+ {
+ if( inBuf ) *dst = c;
+ ++dst;
+ }
+ ++totalLen;
+ }
+
+ if( outCopiedLen ) *outCopiedLen = (size_t)( dst - ( (unsigned char *) inBuf ) );
+ if( outTotalLen ) *outTotalLen = totalLen;
+ if( outSrc ) *outSrc = (const char *) src;
+ return( true );
+}