[rtems-libbsd commit] mDNSResponder: Update to v878.250.4
Sebastian Huber
sebh at rtems.org
Tue Jun 23 16:21:35 UTC 2020
Module: rtems-libbsd
Branch: master
Commit: 1350d527b841ca6cac92448ea5f6bc7bc31a0853
Changeset: http://git.rtems.org/rtems-libbsd/commit/?id=1350d527b841ca6cac92448ea5f6bc7bc31a0853
Author: Sebastian Huber <sebastian.huber at embedded-brains.de>
Date: Thu Jun 18 13:12:03 2020 +0200
mDNSResponder: Update to v878.250.4
The sources can be obtained via:
https://opensource.apple.com/tarballs/mDNSResponder/mDNSResponder-878.250.4.tar.gz
Update #4010.
---
mDNSResponder/Clients/dnssdutil.c | 20886 +++++++++++++------
mDNSResponder/Makefile | 2 +-
mDNSResponder/mDNSMacOSX/BATS/mDNSResponder.plist | 627 +
.../mDNSMacOSX/dnssdutil-entitlements.plist | 12 +
mDNSResponder/mDNSMacOSX/mDNSMacOSX.c | 19 +-
.../mDNSResponder.xcodeproj/project.pbxproj | 29 +-
mDNSResponder/mDNSShared/dns_sd.h | 2 +-
7 files changed, 14821 insertions(+), 6756 deletions(-)
diff --git a/mDNSResponder/Clients/dnssdutil.c b/mDNSResponder/Clients/dnssdutil.c
index 206e2b6..16b4875 100644
--- a/mDNSResponder/Clients/dnssdutil.c
+++ b/mDNSResponder/Clients/dnssdutil.c
@@ -6,6 +6,7 @@
#include <CoreUtils/CommonServices.h> // Include early.
#include <CoreUtils/AsyncConnection.h>
+#include <CoreUtils/AtomicUtils.h>
#include <CoreUtils/CFUtils.h>
#include <CoreUtils/CommandLineUtils.h>
#include <CoreUtils/DataBufferUtils.h>
@@ -20,6 +21,7 @@
#include <CoreUtils/SoftLinking.h>
#include <CoreUtils/StringUtils.h>
#include <CoreUtils/TickUtils.h>
+#include <CoreUtils/TimeUtils.h>
#include <dns_sd.h>
#include <dns_sd_private.h>
@@ -93,6 +95,7 @@
"\x1C" "ServiceIndex\0" \
"\x1D" "DenyExpensive\0" \
"\x1E" "PathEvaluationDone\0" \
+ "\x1F" "AllowExpiredAnswers\0" \
"\x00"
#define kDNSServiceProtocolDescriptors \
@@ -108,14 +111,15 @@
// DNS
//===========================================================================================================================
-#define kDNSPort 53
-#define kDNSCompressionOffsetMax 0x3FFF
-#define kDNSMaxUDPMessageSize 512
-#define kDNSMaxTCPMessageSize UINT16_MAX
+#define kDNSPort 53
+#define kDNSMaxUDPMessageSize 512
+#define kDNSMaxTCPMessageSize UINT16_MAX
#define kDomainLabelLengthMax 63
#define kDomainNameLengthMax 256
+#define kDNSRecordDataLengthMax UINT16_MAX
+
typedef struct
{
uint8_t id[ 2 ];
@@ -206,16 +210,47 @@ typedef struct
check_compile_time( sizeof( DNSRecordFixedFields ) == 10 );
-#define DNSRecordFixedFieldsInit( FIELDS, TYPE, CLASS, TTL, RDLENGTH ) \
- do \
- { \
- WriteBig16( (FIELDS)->type, TYPE ); \
- WriteBig16( (FIELDS)->class, CLASS ); \
- WriteBig32( (FIELDS)->ttl, TTL ); \
- WriteBig16( (FIELDS)->rdlength, RDLENGTH ); \
- \
+// SRV RDATA fixed-length fields. See <https://tools.ietf.org/html/rfc2782>.
+
+typedef struct
+{
+ uint8_t priority[ 2 ];
+ uint8_t weight[ 2 ];
+ uint8_t port[ 2 ];
+
+} SRVRecordDataFixedFields;
+
+check_compile_time( sizeof( SRVRecordDataFixedFields ) == 6 );
+
+// SOA RDATA fixed-length fields. See <https://tools.ietf.org/html/rfc1035#section-3.3.13>.
+
+typedef struct
+{
+ uint8_t serial[ 4 ];
+ uint8_t refresh[ 4 ];
+ uint8_t retry[ 4 ];
+ uint8_t expire[ 4 ];
+ uint8_t minimum[ 4 ];
+
+} SOARecordDataFixedFields;
+
+check_compile_time( sizeof( SOARecordDataFixedFields ) == 20 );
+
+// DNS message compression. See <https://tools.ietf.org/html/rfc1035#section-4.1.4>.
+
+#define kDNSCompressionOffsetMax 0x3FFF
+
+#define IsCompressionByte( X ) ( ( ( X ) & 0xC0 ) == 0xC0 )
+#define WriteDNSCompressionPtr( PTR, OFFSET ) \
+ do \
+ { \
+ ( (uint8_t *)(PTR) )[ 0 ] = (uint8_t)( ( ( (OFFSET) >> 8 ) & 0x3F ) | 0xC0 ); \
+ ( (uint8_t *)(PTR) )[ 1 ] = (uint8_t)( (OFFSET) & 0xFF ); \
+ \
} while( 0 )
+#define NextLabel( LABEL ) ( ( (LABEL)[ 0 ] == 0 ) ? NULL : ( (LABEL) + 1 + (LABEL)[ 0 ] ) )
+
//===========================================================================================================================
// mDNS
//===========================================================================================================================
@@ -228,18 +263,76 @@ check_compile_time( sizeof( DNSRecordFixedFields ) == 10 );
#define kQClassUnicastResponseBit ( 1U << 15 )
#define kRRClassCacheFlushBit ( 1U << 15 )
+// Recommended Resource Record TTL values. See <https://tools.ietf.org/html/rfc6762#section-10>.
+
+#define kMDNSRecordTTL_Host 120 // TTL for resource records related to a host name, e.g., A, AAAA, SRV, etc.
+#define kMDNSRecordTTL_Other 4500 // TTL for other resource records.
+
+// Maximum mDNS Message Size. See <https://tools.ietf.org/html/rfc6762#section-17>.
+
+#define kMDNSMessageSizeMax 8952 // 9000 B (Ethernet jumbo frame max size) - 40 B (IPv6 header) - 8 B (UDP header)
+
+#define kLocalStr "\x05" "local"
+#define kLocalName ( (const uint8_t *) kLocalStr )
+#define kLocalNameLen sizeof( kLocalStr )
+
//===========================================================================================================================
-// Test DNS Server
+// Test Address Blocks
//===========================================================================================================================
// IPv4 address block 203.0.113.0/24 (TEST-NET-3) is reserved for documentation. See <https://tools.ietf.org/html/rfc5737>.
-#define kTestDNSServerBaseAddrV4 UINT32_C( 0xCB007100 ) // 203.0.113.0
+#define kDNSServerBaseAddrV4 UINT32_C( 0xCB007100 ) // 203.0.113.0/24
// IPv6 address block 2001:db8::/32 is reserved for documentation. See <https://tools.ietf.org/html/rfc3849>.
-#define kTestDNSServerBaseAddrV6 \
- ( (const uint8_t *) "\x20\x01\x0D\xB8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" )
+static const uint8_t kDNSServerBaseAddrV6[] =
+{
+ 0x20, 0x01, 0x0D, 0xB8, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // 2001:db8:1::/120
+};
+
+static const uint8_t kMDNSReplierBaseAddrV6[] =
+{
+ 0x20, 0x01, 0x0D, 0xB8, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // 2001:db8:2::/96
+};
+
+check_compile_time( sizeof( kDNSServerBaseAddrV6 ) == 16 );
+check_compile_time( sizeof( kMDNSReplierBaseAddrV6 ) == 16 );
+
+// Bad IPv4 and IPv6 Address Blocks
+// Used by the DNS server when it needs to respond with intentionally "bad" A/AAAA record data, i.e., IP addresses neither
+// in 203.0.113.0/24 nor 2001:db8:1::/120.
+
+#define kDNSServerBadBaseAddrV4 UINT32_C( 0x00000000 ) // 0.0.0.0/24
+
+static const uint8_t kDNSServerBadBaseAddrV6[] =
+{
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00 // ::ffff:0:0/120
+};
+
+check_compile_time( sizeof( kDNSServerBadBaseAddrV6 ) == 16 );
+
+//===========================================================================================================================
+// Misc.
+//===========================================================================================================================
+
+#define kLowerAlphaNumericCharSet "abcdefghijklmnopqrstuvwxyz0123456789"
+#define kLowerAlphaNumericCharSetSize sizeof_string( kLowerAlphaNumericCharSet )
+
+// Note: strcpy_literal() appears in CoreUtils code, but isn't currently defined in framework headers.
+
+#if( !defined( strcpy_literal ) )
+ #define strcpy_literal( DST, SRC ) memcpy( DST, SRC, sizeof( SRC ) )
+#endif
+
+#define _RandomStringExact( CHAR_SET, CHAR_SET_SIZE, CHAR_COUNT, OUT_STRING ) \
+ RandomString( CHAR_SET, CHAR_SET_SIZE, CHAR_COUNT, CHAR_COUNT, OUT_STRING )
+
+#define kNoSuchRecordStr "No Such Record"
+#define kNoSuchRecordAStr "No Such Record (A)"
+#define kNoSuchRecordAAAAStr "No Such Record (AAAA)"
+
+#define kRootLabel ( (const uint8_t *) "" )
//===========================================================================================================================
// Gerneral Command Options
@@ -266,6 +359,11 @@ check_compile_time( sizeof( DNSRecordFixedFields ) == 10 );
(IS_REQUIRED) ? SHORT_HELP kRequiredOptionSuffix : SHORT_HELP, \
(IS_REQUIRED) ? kCLIOptionFlags_Required : kCLIOptionFlags_None, NULL )
+#define DoubleOption( SHORT_CHAR, LONG_NAME, VAL_PTR, ARG_HELP, SHORT_HELP, IS_REQUIRED ) \
+ CLI_OPTION_DOUBLE_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 )
@@ -285,6 +383,7 @@ check_compile_time( sizeof( DNSRecordFixedFields ) == 10 );
// DNS-SD API flag options
static int gDNSSDFlags = 0;
+static int gDNSSDFlag_AllowExpiredAnswers = false;
static int gDNSSDFlag_BrowseDomains = false;
static int gDNSSDFlag_DenyCellular = false;
static int gDNSSDFlag_DenyExpensive = false;
@@ -308,19 +407,20 @@ static int gDNSSDFlag_WakeOnResolve = false;
#define DNSSDFlagOption( SHORT_CHAR, FLAG_NAME ) \
BooleanOption( SHORT_CHAR, # FLAG_NAME, &gDNSSDFlag_ ## FLAG_NAME, "Use kDNSServiceFlags" # 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 )
-#define DNSSDFlagsOption_WakeOnResolve() DNSSDFlagOption( 'W', WakeOnResolve )
+#define DNSSDFlagsOption_AllowExpiredAnswers() DNSSDFlagOption( 'X', AllowExpiredAnswers )
+#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 )
+#define DNSSDFlagsOption_WakeOnResolve() DNSSDFlagOption( 'W', WakeOnResolve )
// Interface option
@@ -395,6 +495,32 @@ static const char * gConnectionOpt = kConnectionArg_Normal;
#define RecordDataSection() CLI_SECTION( kRecordDataSection_Name, kRecordDataSection_Text )
//===========================================================================================================================
+// Output Formatting
+//===========================================================================================================================
+
+#define kOutputFormatStr_JSON "json"
+#define kOutputFormatStr_XML "xml"
+#define kOutputFormatStr_Binary "binary"
+
+typedef enum
+{
+ kOutputFormatType_Invalid = 0,
+ kOutputFormatType_JSON = 1,
+ kOutputFormatType_XML = 2,
+ kOutputFormatType_Binary = 3
+
+} OutputFormatType;
+
+#define FormatOption( SHORT_CHAR, LONG_NAME, VAL_PTR, SHORT_HELP, IS_REQUIRED ) \
+ StringOptionEx( SHORT_CHAR, LONG_NAME, VAL_PTR, "format", SHORT_HELP, IS_REQUIRED, \
+ "\n" \
+ "Use '" kOutputFormatStr_JSON "' for JavaScript Object Notation (JSON).\n" \
+ "Use '" kOutputFormatStr_XML "' for property list XML version 1.0.\n" \
+ "Use '" kOutputFormatStr_Binary "' for property list binary version 1.0.\n" \
+ "\n" \
+ )
+
+//===========================================================================================================================
// Browse Command Options
//===========================================================================================================================
@@ -444,6 +570,7 @@ static CLIOption kGetAddrInfoOpts[] =
CLI_OPTION_GROUP( "Flags" ),
DNSSDFlagsOption(),
+ DNSSDFlagsOption_AllowExpiredAnswers(),
DNSSDFlagsOption_DenyCellular(),
DNSSDFlagsOption_DenyExpensive(),
DNSSDFlagsOption_PathEvalDone(),
@@ -478,15 +605,16 @@ static CLIOption kQueryRecordOpts[] =
CLI_OPTION_GROUP( "Flags" ),
DNSSDFlagsOption(),
- DNSSDFlagsOption_IncludeAWDL(),
+ DNSSDFlagsOption_AllowExpiredAnswers(),
+ DNSSDFlagsOption_DenyCellular(),
+ DNSSDFlagsOption_DenyExpensive(),
DNSSDFlagsOption_ForceMulticast(),
- DNSSDFlagsOption_Timeout(),
+ DNSSDFlagsOption_IncludeAWDL(),
+ DNSSDFlagsOption_PathEvalDone(),
DNSSDFlagsOption_ReturnIntermediates(),
DNSSDFlagsOption_SuppressUnusable(),
+ DNSSDFlagsOption_Timeout(),
DNSSDFlagsOption_UnicastResponse(),
- DNSSDFlagsOption_DenyCellular(),
- DNSSDFlagsOption_DenyExpensive(),
- DNSSDFlagsOption_PathEvalDone(),
CLI_OPTION_GROUP( "Operation" ),
ConnectionOptions(),
@@ -672,7 +800,7 @@ static CLIOption kGetAddrInfoPOSIXOpts[] =
StringOption( 'n', "hostname", &gGAIPOSIX_HostName, "hostname", "Domain name to resolve or an IPv4 or IPv6 address.", true ),
StringOption( 's', "servname", &gGAIPOSIX_ServName, "servname", "Port number in decimal or service name from services(5).", false ),
- CLI_OPTION_GROUP( "Hints " ),
+ CLI_OPTION_GROUP( "Hints" ),
StringOptionEx( 'f', "family", &gGAIPOSIX_Family, "address family", "Address family to use for hints ai_family field.", false,
"\n"
"Possible address family values are 'inet' for AF_INET, 'inet6' for AF_INET6, or 'unspec' for AF_UNSPEC. If no\n"
@@ -761,23 +889,23 @@ static CLIOption kPortMappingOpts[] =
//===========================================================================================================================
static const char * gBrowseAll_Domain = NULL;
-static char ** gBrowseAll_ServiceTypes = NULL;
+static const char ** gBrowseAll_ServiceTypes = NULL;
static size_t gBrowseAll_ServiceTypesCount = 0;
static int gBrowseAll_BrowseTimeSecs = 5;
-static int gBrowseAll_MaxConnectTimeSecs = 0;
+static int gBrowseAll_ConnectTimeout = 0;
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\". All services are browsed for if none is specified.", false ),
+ 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\". All services are browsed for if none is specified.", false ),
CLI_OPTION_GROUP( "Flags" ),
DNSSDFlagsOption_IncludeAWDL(),
CLI_OPTION_GROUP( "Operation" ),
- IntegerOption( 'b', "browseTime", &gBrowseAll_BrowseTimeSecs, "seconds", "Amount of time to spend browsing. (Default: 5 seconds)", false ),
- IntegerOption( 'c', "maxConnectTime", &gBrowseAll_MaxConnectTimeSecs, "seconds", "Max duration of connection attempts. If <= 0, then no connections are attempted. (Default: 0 seconds)", false ),
+ IntegerOption( 'b', "browseTime", &gBrowseAll_BrowseTimeSecs, "seconds", "Amount of time to spend browsing in seconds. (default: 5)", false ),
+ IntegerOption( 'c', "connectTimeout", &gBrowseAll_ConnectTimeout, "seconds", "Timeout for connection attempts. If <= 0, no connections are attempted. (default: 0)", false ),
CLI_OPTION_END()
};
@@ -928,6 +1056,119 @@ static CLIOption kMDNSQueryOpts[] =
};
//===========================================================================================================================
+// MDNSCollider Command Options
+//===========================================================================================================================
+
+#define kMDNSColliderProgramSection_Intro \
+ "Programs dictate when the collider sends out unsolicited response messages for its record and how the collider\n" \
+ "ought to react to probe queries that match its record's name, if at all.\n" \
+ "\n" \
+ "For example, suppose that the goal is to cause a specific unique record in the verified state to be renamed.\n" \
+ "The collider should be invoked such that its record's name is equal to that of the record being targeted. Also,\n" \
+ "the record's type and data should be such that no record with that name, type, and data combination currently\n" \
+ "exists. If the mDNS responder that owns the record follows sections 8.1 and 9 of RFC 6762, then the goal can be\n" \
+ "accomplished with the following program:\n" \
+ "\n" \
+ " probes 3r; send; wait 5000\n" \
+ "\n" \
+ "The first command, 'probes 3r', tells the collider to respond to the next three probe queries that match its\n" \
+ "record's name. The second command, makes the collider send an unsolicited response message that contains its\n" \
+ "record in the answer section. The third command makes the collider wait for five seconds before exiting, which\n" \
+ "is more than enough time for the collider to respond to probe queries.\n" \
+ "\n" \
+ "The send command will cause the targeted record to go into the probing state per section 9 since the collider's\n" \
+ "record conflicts with target record. Per the probes command, the subsequent probe query sent during the probing\n" \
+ "state will be answered by the collider, which will cause the record to be renamed per section 8.1.\n"
+
+#define kMDNSColliderProgramSection_Probes \
+ "The probes command defines how the collider ought to react to probe queries that match its record's name.\n" \
+ "\n" \
+ "Usage: probes [<action-string>]\n" \
+ "\n" \
+ "The syntax for an action-string is\n" \
+ "\n" \
+ " <action-string> ::= <action> | <action-string> \"-\" <action>\n" \
+ " <action> ::= [<repeat-count>] <action-code>\n" \
+ " <repeat-count> ::= \"1\" | \"2\" | ... | \"10\"\n" \
+ " <action-code> ::= \"n\" | \"r\" | \"u\" | \"m\" | \"p\"\n" \
+ "\n" \
+ "An expanded action-string is defined as\n" \
+ "\n" \
+ " <expanded-action-string> ::= <action-code> | <expanded-action-string> \"-\" <action-code>\n" \
+ "\n" \
+ "The action-string argument is converted into an expanded-action-string by expanding each action with a\n" \
+ "repeat-count into an expanded-action-string consisting of exactly <repeat-count> <action-code>s. For example,\n" \
+ "2n-r expands to n-n-r. Action-strings that expand to expanded-action-strings with more than 10 action-codes\n" \
+ "are not allowed.\n" \
+ "\n" \
+ "When the probes command is executed, it does two things. Firstly, it resets to zero the collider's count of\n" \
+ "probe queries that match its record's name. Secondly, it defines how the collider ought to react to such probe\n" \
+ "queries based on the action-string argument. Specifically, the nth action-code in the expanded version of the\n" \
+ "action-string argument defines how the collider ought to react to the nth received probe query:\n" \
+ "\n" \
+ " Code Action\n" \
+ " ---- ------\n" \
+ " n Do nothing.\n" \
+ " r Respond to the probe query.\n" \
+ " u Respond to the probe query via unicast.\n" \
+ " m Respond to the probe query via multicast.\n" \
+ " p Multicast own probe query. (Useful for causing simultaneous probe scenarios.)\n" \
+ "\n" \
+ "Note: If no action is defined for a received probe query, then the collider does nothing, i.e., it doesn't send\n" \
+ "a response nor does it multicast its own probe query.\n"
+
+#define kMDNSColliderProgramSection_Send \
+ "The send command multicasts an unsolicited mDNS response containing the collider's record in the answer\n" \
+ "section, which can be used to force unique records with the same record name into the probing state.\n" \
+ "\n" \
+ "Usage: send\n"
+
+#define kMDNSColliderProgramSection_Wait \
+ "The wait command pauses program execution for the interval of time specified by its argument.\n" \
+ "\n" \
+ "Usage: wait <milliseconds>\n"
+
+#define kMDNSColliderProgramSection_Loop \
+ "The loop command starts a counting loop. The done statement marks the end of the loop body. The loop command's\n" \
+ "argument specifies the number of loop iterations. Note: Loop nesting is supported up to a depth of 16.\n" \
+ "\n" \
+ "Usage: loop <non-zero count>; ... ; done\n" \
+ "\n" \
+ "For example, the following program sends three unsolicited responses at an approximate rate of one per second:\n" \
+ "\n" \
+ " loop 3; wait 1000; send; done"
+
+#define ConnectionSection() CLI_SECTION( kConnectionSection_Name, kConnectionSection_Text )
+
+static const char * gMDNSCollider_Name = NULL;
+static const char * gMDNSCollider_Type = NULL;
+static const char * gMDNSCollider_RecordData = NULL;
+static int gMDNSCollider_UseIPv4 = false;
+static int gMDNSCollider_UseIPv6 = false;
+static const char * gMDNSCollider_Program = NULL;
+
+static CLIOption kMDNSColliderOpts[] =
+{
+ StringOption( 'i', "interface", &gInterface, "name or index", "Network interface by name or index.", true ),
+ StringOption( 'n', "name", &gMDNSCollider_Name, "name", "Collider's record name.", true ),
+ StringOption( 't', "type", &gMDNSCollider_Type, "type", "Collider's record type.", true ),
+ StringOption( 'd', "data", &gMDNSCollider_RecordData, "record data", "Collider's record data. See " kRecordDataSection_Name " below.", true ),
+ StringOption( 'p', "program", &gMDNSCollider_Program, "program", "Program to execute. See Program section below.", true ),
+ BooleanOption( 0 , "ipv4", &gMDNSCollider_UseIPv4, "Use IPv4." ),
+ BooleanOption( 0 , "ipv6", &gMDNSCollider_UseIPv6, "Use IPv6." ),
+
+ RecordDataSection(),
+ CLI_SECTION( "Program", kMDNSColliderProgramSection_Intro ),
+ CLI_SECTION( "Program Command: probes", kMDNSColliderProgramSection_Probes ),
+ CLI_SECTION( "Program Command: send", kMDNSColliderProgramSection_Send ),
+ CLI_SECTION( "Program Command: wait", kMDNSColliderProgramSection_Wait ),
+ CLI_SECTION( "Program Command: loop", kMDNSColliderProgramSection_Loop ),
+ CLI_OPTION_END()
+};
+
+static void MDNSColliderCmd( void );
+
+//===========================================================================================================================
// PIDToUUID Command Options
//===========================================================================================================================
@@ -945,59 +1186,72 @@ static CLIOption kPIDToUUIDOpts[] =
#define kDNSServerInfoText_Intro \
"The DNS server answers certain queries in the d.test. domain. Responses are dynamically generated based on the\n" \
- "presence of special labels in the query's QNAME. There are currently seven types of special labels that can be\n" \
+ "presence of special labels in the query's QNAME. There are currently eight types of special labels that can be\n" \
"used to generate specific responses: Alias labels, Alias-TTL labels, Count labels, Tag labels, TTL labels, the\n" \
- "IPv4 label, and the IPv6 label.\n"
+ "IPv4 label, the IPv6 label, and SRV labels.\n" \
+ "\n" \
+ "Note: Sub-strings representing integers in domain name labels are in decimal notation and without leading zeros.\n"
#define kDNSServerInfoText_NameExistence \
- "A name is considered to exist if and only if it ends in d.test., and the other labels, if\n" \
- "any, consist of\n" \
+ "A name is considered to exist if it's an Address name or an SRV name.\n" \
+ "\n" \
+ "An Address name is defined as a name that ends with d.test., and the other labels, if any, and in no particular\n" \
+ "order, unless otherwise noted, consist of\n" \
"\n" \
" 1. at most one Alias or Alias-TTL label as the first label;\n" \
" 2. at most one Count label;\n" \
- " 3. zero or more Tag labels;\n" \
+ " 3. zero or more Tag labels;\n" \
" 4. at most one TTL label; and\n" \
- " 5. at most one IPv4 or IPv6 label.\n"
+ " 5. at most one IPv4 or IPv6 label.\n" \
+ "\n" \
+ "An SRV name is defined as a name with the following form:\n" \
+ "\n" \
+ " _<service>._<proto>[.<parent domain>][.<SRV label 1>[.<target 1>][.<SRV label 2>[.<target 2>][...]]].d.test.\n" \
+ "\n" \
+ "See \"SRV Names\" for details.\n"
#define kDNSServerInfoText_ResourceRecords \
- "Currently, the server only provides CNAME, A, and AAAA records.\n" \
+ "Currently, the server only supports CNAME, A, AAAA, and SRV records.\n" \
"\n" \
- "Names that exist and begin with an Alias or Alias-TTL label are aliases of canonical names, i.e., they're the\n" \
+ "Address names that begin with an Alias or Alias-TTL label are aliases of canonical names, i.e., they're the\n" \
"names of CNAME records. See \"Alias Labels\" and \"Alias-TTL Labels\" for details.\n" \
"\n" \
- "Names that exist and have an IPv4 label have at least one A record, but no AAAA records. Names that exist and\n" \
- "have an IPv6 label, have at least one AAAA record, but no A records. All other names that exist have at least\n" \
- "one A record and at least one AAAA record. See \"Count Labels\" for how the number of address records for a\n" \
- "given name is determined.\n" \
+ "A canonical Address name can exclusively be the name of one or more A records, can exclusively be the name or\n" \
+ "one or more AAAA records, or can be the name of both A and AAAA records. Address names that contain an IPv4\n" \
+ "label have at least one A record, but no AAAA records. Address names that contain an IPv6 label, have at least\n" \
+ "one AAAA record, but no A records. All other Address names have at least one A record and at least one AAAA\n" \
+ "record. See \"Count Labels\" for how the number of address records for a given Address name is determined.\n" \
"\n" \
"A records contain IPv4 addresses in the 203.0.113.0/24 block, while AAAA records contain IPv6 addresses in the\n" \
- "2001:db8::/32 block. Both of these address blocks are reserved for documentation.\n" \
- "See <https://tools.ietf.org/html/rfc5737> and <https://tools.ietf.org/html/rfc3849>.\n" \
+ "2001:db8:1::/120 block. Both of these address blocks are reserved for documentation. See\n" \
+ "<https://tools.ietf.org/html/rfc5737> and <https://tools.ietf.org/html/rfc3849>.\n" \
+ "\n" \
+ "SRV names are names of SRV records.\n" \
"\n" \
"Unless otherwise specified, all resource records will use a default TTL. The default TTL can be set with the\n" \
- "--defaultTTL option. See \"Alias-TTL Labels\" and \"TTL Labels\" for details on how to query for records with\n" \
- "specific TTL values.\n"
+ "--defaultTTL option. See \"Alias-TTL Labels\" and \"TTL Labels\" for details on how to query for CNAME, A, and\n" \
+ "AAAA records with specific TTL values.\n"
#define kDNSServerInfoText_AliasLabel \
- "Alias labels are of the form \"alias\" or \"alias-N\", where N is an integer in [2 .. 2^31 - 1].\n" \
+ "Alias labels are of the form \"alias\" or \"alias-N\", where N is an integer in [2, 2^31 - 1].\n" \
"\n" \
- "If QNAME exist and its first label is Alias label \"alias-N\", then the response will contain exactly N CNAME\n" \
- "records:\n" \
+ "If QNAME is an Address name and its first label is Alias label \"alias-N\", then the response will contain\n" \
+ "exactly N CNAME records:\n" \
"\n" \
- " 1. For each i in [3 .. N], the response will contain a CNAME record whose name is identical to QNAME,\n" \
- " except that the first label is \"alias-i\" instead, and whose RDATA is the name of the other CNAME\n" \
- " record whose name has \"alias-(i - 1)\" as its first label.\n" \
+ " 1. For each i in [3, N], the response will contain a CNAME record whose name is identical to QNAME, except\n" \
+ " that the first label is \"alias-i\" instead, and whose RDATA is the name of the other CNAME record whose\n" \
+ " name has \"alias-(i - 1)\" as its first label.\n" \
"\n" \
" 2. The response will contain a CNAME record whose name is identical to QNAME, except that the first label\n" \
" is \"alias-2\" instead, and whose RDATA is the name identical to QNAME, except that the first label is\n" \
" \"alias\" instead.\n" \
"\n" \
" 3. The response will contain a CNAME record whose name is identical to QNAME, except that the first label\n" \
- " is \"alias\" instead, and whose RDATA is the name identical to QNAME stripped of its first label.\n" \
+ " is \"alias\" instead, and whose RDATA is the name identical to QNAME minus its first label.\n" \
"\n" \
- "If QNAME exist and its first label is Alias label \"alias\", then the response will contain a single CNAME\n" \
- "record. The CNAME record's name will be equal to QNAME and its RDATA will be the name identical to QNAME\n" \
- "stripped of its first label.\n" \
+ "If QNAME is an Address name and its first label is Alias label \"alias\", then the response will contain a\n" \
+ "single CNAME record. The CNAME record's name will be equal to QNAME and its RDATA will be the name identical to\n" \
+ "QNAME minus its first label.\n" \
"\n" \
"Example. A response to a query with a QNAME of alias-3.count-5.d.test will contain the following CNAME\n" \
"records:\n" \
@@ -1009,12 +1263,12 @@ static CLIOption kPIDToUUIDOpts[] =
#define kDNSServerInfoText_AliasTTLLabel \
"Alias-TTL labels are of the form \"alias-ttl-T_1[-T_2[...-T_N]]\", where each T_i is an integer in\n" \
- "[0 .. 2^31 - 1] and N is a positive integer bounded by the size of the maximum legal label length (63 octets).\n" \
+ "[0, 2^31 - 1] and N is a positive integer bounded by the size of the maximum legal label length (63 octets).\n" \
"\n" \
- "If QNAME exists and its first label is Alias-TTL label \"alias-ttl-T_1...-T_N\", then the response will contain\n" \
- "exactly N CNAME records:\n" \
+ "If QNAME is an Address name and its first label is Alias-TTL label \"alias-ttl-T_1...-T_N\", then the response\n" \
+ "will contain exactly N CNAME records:\n" \
"\n" \
- " 1. For each i in [1 .. N - 1], the response will contain a CNAME record whose name is identical to QNAME,\n" \
+ " 1. For each i in [1, N - 1], the response will contain a CNAME record whose name is identical to QNAME,\n" \
" except that the first label is \"alias-ttl-T_i...-T_N\" instead, whose TTL value is T_i, and whose RDATA\n" \
" is the name of the other CNAME record whose name has \"alias-ttl-T_(i+1)...-T_N\" as its first label.\n" \
"\n" \
@@ -1030,14 +1284,14 @@ static CLIOption kPIDToUUIDOpts[] =
" alias-ttl-80.count-5.d.test. 80 IN CNAME count-5.d.test.\n"
#define kDNSServerInfoText_CountLabel \
- "Count labels are of the form \"count-N_1\" or \"count-N_1-N_2\", where N_1 is an integer in [1 .. 255] and N_2\n" \
- "is an integer in [N_1 .. 255].\n" \
+ "Count labels are of the form \"count-N_1\" or \"count-N_1-N_2\", where N_1 is an integer in [1, 255] and N_2 is\n" \
+ "an integer in [N_1, 255].\n" \
"\n" \
- "If QNAME exists, contains Count label \"count-N\", and has the type of address records specified by QTYPE, then\n" \
- "the response will contain exactly N address records:\n" \
+ "If QNAME is an Address name, contains Count label \"count-N\", and has the type of address records specified by\n" \
+ "QTYPE, then the response will contain exactly N address records:\n" \
"\n" \
- " 1. For i in [1 .. N], the response will contain an address record of type QTYPE whose name is equal to\n" \
- " QNAME and whose RDATA is an address equal to a constant base address + i.\n" \
+ " 1. For i in [1, N], the response will contain an address record of type QTYPE whose name is equal to QNAME\n" \
+ " and whose RDATA is an address equal to a constant base address + i.\n" \
"\n" \
" 2. The address records will be ordered by the address contained in RDATA in ascending order.\n" \
"\n" \
@@ -1048,26 +1302,26 @@ static CLIOption kPIDToUUIDOpts[] =
" count-3.d.test. 60 IN A 203.0.113.2\n" \
" count-3.d.test. 60 IN A 203.0.113.3\n" \
"\n" \
- "If QNAME exists, contains Count label \"count-N_1-N_2\", and has the type of address records specified by\n" \
- "QTYPE, then the response will contain exactly N_1 address records:\n" \
+ "If QNAME is an Address name, contains Count label \"count-N_1-N_2\", and has the type of address records\n" \
+ "specified by QTYPE, then the response will contain exactly N_1 address records:\n" \
"\n" \
" 1. Each of the address records will be of type QTYPE, have name equal to QNAME, and have as its RDATA a\n" \
- " unique address equal to a constant base address + i, where i is a randomly chosen integer in [1 .. N_2].\n" \
+ " unique address equal to a constant base address + i, where i is a randomly chosen integer in [1, N_2].\n" \
"\n" \
" 2. The order of the address records will be random.\n" \
"\n" \
"Example. A response to a AAAA record query with a QNAME of count-3-100.ttl-20.d.test could contain the\n" \
"following AAAA records:\n" \
"\n" \
- " count-3-100.ttl-20.d.test. 20 IN AAAA 2001:db8::c\n" \
- " count-3-100.ttl-20.d.test. 20 IN AAAA 2001:db8::3a\n" \
- " count-3-100.ttl-20.d.test. 20 IN AAAA 2001:db8::4f\n" \
+ " count-3-100.ttl-20.d.test. 20 IN AAAA 2001:db8:1::c\n" \
+ " count-3-100.ttl-20.d.test. 20 IN AAAA 2001:db8:1::3a\n" \
+ " count-3-100.ttl-20.d.test. 20 IN AAAA 2001:db8:1::4f\n" \
"\n" \
- "If QNAME exists, but doesn't have the type of address records specified by QTYPE, then the response will\n" \
- "contain no address records, regardless of whether it contains a Count label.\n" \
+ "If QNAME is an Address name, but doesn't have the type of address records specified by QTYPE, then the response\n" \
+ "will contain no address records, regardless of whether it contains a Count label.\n" \
"\n" \
- "QNAMEs that exist, but don't have a Count label are treated as though they contain a count label equal to\n" \
- "\"count-1\".\n"
+ "Address names that don't have a Count label are treated as though they contain a count label equal to\n" \
+ "count-1\".\n"
#define kDNSServerInfoText_TagLabel \
"Tag labels are labels prefixed with \"tag-\" and contain zero or more arbitrary octets after the prefix.\n" \
@@ -1076,10 +1330,10 @@ static CLIOption kPIDToUUIDOpts[] =
"to increase the sizes of domain names.\n"
#define kDNSServerInfoText_TTLLabel \
- "TTL labels are of the form \"ttl-T\", where T is an integer in [0 .. 2^31 - 1].\n" \
+ "TTL labels are of the form \"ttl-T\", where T is an integer in [0, 2^31 - 1].\n" \
"\n" \
- "If the name specified by QNAME exists, and contains TTL label \"ttl-T\", then all non-CNAME records contained\n" \
- "in the response will have a TTL value equal to T.\n"
+ "If QNAME is an Address name and contains TTL label \"ttl-T\", then all non-CNAME records contained in the\n" \
+ "response will have a TTL value equal to T.\n"
#define kDNSServerInfoText_IPv4Label \
"The IPv4 label is \"ipv4\". See \"Resource Records\" for the affect of this label.\n"
@@ -1087,25 +1341,58 @@ static CLIOption kPIDToUUIDOpts[] =
#define kDNSServerInfoText_IPv6Label \
"The IPv6 label is \"ipv6\". See \"Resource Records\" for the affect of this label.\n"
-#define kDNSServerDefaultTTL 60
+#define kDNSServerInfoText_SRVNames \
+ "SRV labels are of the form \"srv-R-W-P\", where R, W, and P are integers in [0, 2^16 - 1].\n" \
+ "\n" \
+ "After the first two labels, i.e., the service and protocol labels, the sequence of labels, which may be empty,\n" \
+ "leading up to the the first SRV label, if one exists, or the d.test. labels will be used as a parent domain for\n" \
+ "the target hostname of each of the SRV name's SRV records.\n" \
+ "\n" \
+ "If QNAME is an SRV name and QTYPE is SRV, then for each SRV label, the response will contain an SRV record with\n" \
+ "priority R, weight W, port P, and target hostname <target>[.<parent domain>]., where <target> is the sequence\n" \
+ "of labels, which may be empty, that follows the SRV label leading up to either the next SRV label or the\n" \
+ "d.test. labels, whichever comes first.\n" \
+ "\n" \
+ "Example. A response to an SRV record query with a QNAME of\n" \
+ "_http._tcp.example.com.srv-0-0-80.www.srv-1-0-8080.www.d.test. will contain the following SRV records:\n" \
+ "\n" \
+ "_http._tcp.example.com.srv-0-0-80.www.srv-1-0-8080.www.d.test. 60 IN SRV 0 0 80 www.example.com.\n" \
+ "_http._tcp.example.com.srv-0-0-80.www.srv-1-0-8080.www.d.test. 60 IN SRV 1 0 8080 www.example.com.\n"
+
+#define kDNSServerInfoText_BadUDPMode \
+ "The purpose of Bad UDP mode is to test mDNSResponder's TCP fallback mechanism by which mDNSResponder reissues a\n" \
+ "UDP query as a TCP query if the UDP response contains the expected QNAME, QTYPE, and QCLASS, but a message ID\n" \
+ "that's not equal to the query's message ID.\n" \
+ "\n" \
+ "This mode is identical to the normal mode except that all responses sent via UDP have a message ID equal to the\n" \
+ "query's message ID plus one. Also, in this mode, to aid in debugging, A records in responses sent via UDP have\n" \
+ "IPv4 addresses in the 0.0.0.0/24 block instead of the 203.0.113.0/24 block, i.e., 0.0.0.0 is used as the IPv4\n" \
+ "base address, and AAAA records in responses sent via UDP have IPv6 addresses in the ::ffff:0:0/120 block\n" \
+ "instead of the 2001:db8:1::/120 block, i.e., ::ffff:0:0 is used as the IPv6 base address.\n"
static int gDNSServer_LoopbackOnly = false;
static int gDNSServer_Foreground = false;
static int gDNSServer_ResponseDelayMs = 0;
-static int gDNSServer_DefaultTTL = kDNSServerDefaultTTL;
+static int gDNSServer_DefaultTTL = 60;
+static int gDNSServer_Port = kDNSPort;
+static const char * gDNSServer_DomainOverride = NULL;
#if( TARGET_OS_DARWIN )
static const char * gDNSServer_FollowPID = NULL;
#endif
+static int gDNSServer_BadUDPMode = false;
static CLIOption kDNSServerOpts[] =
{
BooleanOption( 'l', "loopback", &gDNSServer_LoopbackOnly, "Bind to to the loopback interface." ),
- BooleanOption( 'f', "foreground", &gDNSServer_Foreground, "Directlog output to stdout instead of system logging." ),
+ BooleanOption( 'f', "foreground", &gDNSServer_Foreground, "Direct log output to stdout instead of system logging." ),
IntegerOption( 'd', "responseDelay", &gDNSServer_ResponseDelayMs, "ms", "The amount of additional delay in milliseconds to apply to responses. (default: 0)", false ),
IntegerOption( 0 , "defaultTTL", &gDNSServer_DefaultTTL, "seconds", "Resource record TTL value to use when unspecified. (default: 60)", false ),
+ IntegerOption( 'p', "port", &gDNSServer_Port, "port number", "UDP/TCP port number to use. Use 0 for any port. (default: 53)", false ),
+ StringOption( 0 , "domain", &gDNSServer_DomainOverride, "domain", "Used to override 'd.test.' as the server's domain.", false ),
#if( TARGET_OS_DARWIN )
- StringOption( 0 , "followPID", &gDNSServer_FollowPID, "pid", "Exit when the process (usually the parent proccess) specified by PID exits.", false ),
+ StringOption( 0 , "follow", &gDNSServer_FollowPID, "pid", "Exit when the process, usually the parent process, specified by PID exits.", false ),
#endif
+ BooleanOption( 0 , "badUDPMode", &gDNSServer_BadUDPMode, "Run in Bad UDP mode to trigger mDNSResponder's TCP fallback mechanism." ),
CLI_SECTION( "Intro", kDNSServerInfoText_Intro ),
CLI_SECTION( "Name Existence", kDNSServerInfoText_NameExistence ),
@@ -1117,26 +1404,248 @@ static CLIOption kDNSServerOpts[] =
CLI_SECTION( "TTL Labels", kDNSServerInfoText_TTLLabel ),
CLI_SECTION( "IPv4 Label", kDNSServerInfoText_IPv4Label ),
CLI_SECTION( "IPv6 Label", kDNSServerInfoText_IPv6Label ),
+ CLI_SECTION( "SRV Names", kDNSServerInfoText_SRVNames ),
+ CLI_SECTION( "Bad UDP Mode", kDNSServerInfoText_BadUDPMode ),
CLI_OPTION_END()
};
static void DNSServerCmd( void );
//===========================================================================================================================
+// MDNSReplier Command Options
+//===========================================================================================================================
+
+#define kMDNSReplierPortBase 50000
+
+#define kMDNSReplierInfoText_Intro \
+ "The mDNS replier answers mDNS queries for its authoritative records. These records are of class IN and of types\n" \
+ "PTR, SRV, TXT, A, and AAAA as described below.\n" \
+ "\n" \
+ "Note: Sub-strings representing integers in domain name labels are in decimal notation and without leading zeros.\n"
+
+#define kMDNSReplierInfoText_Parameters \
+ "There are five parameters that control the replier's set of authoritative records.\n" \
+ "\n" \
+ " 1. <hostname> is the base name used for service instance names and the names of A and AAAA records. This\n" \
+ " parameter is specified with the --hostname option.\n" \
+ " 2. <tag> is an arbitrary string used to uniquify service types. This parameter is specified with the --tag\n" \
+ " option.\n" \
+ " 3. N_max in an integer in [1, 65535] and limits service types to those that have no more than N_max\n" \
+ " instances. It also limits the number of hostnames to N_max, i.e., <hostname>.local.,\n" \
+ " <hostname>-1.local., ..., <hostname>-N_max.local. This parameter is specified with the\n" \
+ " --maxInstanceCount option.\n" \
+ " 4. N_a is an integer in [1, 255] and the number of A records per hostname. This parameter is specified\n" \
+ " with the --countA option.\n" \
+ " 5. N_aaaa is an integer in [1, 255] and the number of AAAA records per hostname. This parameter is\n" \
+ " specified with the --countAAAA option.\n"
+
+#define kMDNSReplierInfoText_PTR \
+ "The replier's authoritative PTR records have names of the form _t-<tag>-<L>-<N>._tcp.local., where L is an\n" \
+ "integer in [1, 65535], and N is an integer in [1, N_max].\n" \
+ "\n" \
+ "For a given L and N, the replier has exactly N authoritative PTR records:\n" \
+ "\n" \
+ " 1. The first PTR record is defined as\n" \
+ "\n" \
+ " NAME: _t-<tag>-<L>-<N>._tcp.local.\n" \
+ " TYPE: PTR\n" \
+ " CLASS: IN\n" \
+ " TTL: 4500\n" \
+ " RDATA: <hostname>._t-<tag>-<L>-<N>._tcp.local.\n" \
+ "\n" \
+ " 2. For each i in [2, N], there is one PTR record defined as\n" \
+ "\n" \
+ " NAME: _t-<tag>-<L>-<N>._tcp.local.\n" \
+ " TYPE: PTR\n" \
+ " CLASS: IN\n" \
+ " TTL: 4500\n" \
+ " RDATA: \"<hostname> (<i>)._t-<tag>-<L>-<N>._tcp.local.\"\n"
+
+#define kMDNSReplierInfoText_SRV \
+ "The replier's authoritative SRV records have names of the form <instance name>._t-<tag>-<L>-<N>._tcp.local.,\n" \
+ "where L is an integer in [1, 65535], N is an integer in [1, N_max], and <instance name> is <hostname> or\n" \
+ "\"<hostname> (<i>)\", where i is in [2, N].\n" \
+ "\n" \
+ "For a given L and N, the replier has exactly N authoritative SRV records:\n" \
+ "\n" \
+ " 1. The first SRV record is defined as\n" \
+ "\n" \
+ " NAME: <hostname>._t-<tag>-<L>-<N>._tcp.local.\n" \
+ " TYPE: SRV\n" \
+ " CLASS: IN\n" \
+ " TTL: 120\n" \
+ " RDATA:\n" \
+ " Priority: 0\n" \
+ " Weight: 0\n" \
+ " Port: (50000 + L) mod 2^16\n" \
+ " Target: <hostname>.local.\n" \
+ "\n" \
+ " 2. For each i in [2, N], there is one SRV record defined as:\n" \
+ "\n" \
+ " NAME: \"<hostname> (<i>)._t-<tag>-<L>-<N>._tcp.local.\"\n" \
+ " TYPE: SRV\n" \
+ " CLASS: IN\n" \
+ " TTL: 120\n" \
+ " RDATA:\n" \
+ " Priority: 0\n" \
+ " Weight: 0\n" \
+ " Port: (50000 + L) mod 2^16\n" \
+ " Target: <hostname>-<i>.local.\n"
+
+#define kMDNSReplierInfoText_TXT \
+ "The replier's authoritative TXT records have names of the form <instance name>._t-<tag>-<L>-<N>._tcp.local.,\n" \
+ "where L is an integer in [1, 65535], N is an integer in [1, N_max], and <instance name> is <hostname> or\n" \
+ "\"<hostname> (<i>)\", where i is in [2, N].\n" \
+ "\n" \
+ "For a given L and N, the replier has exactly N authoritative TXT records:\n" \
+ "\n" \
+ " 1. The first TXT record is defined as\n" \
+ "\n" \
+ " NAME: <hostname>._t-<tag>-<L>-<N>._tcp.local.\n" \
+ " TYPE: TXT\n" \
+ " CLASS: IN\n" \
+ " TTL: 4500\n" \
+ " RDLENGTH: L\n" \
+ " RDATA: <one or more strings with an aggregate length of L octets>\n" \
+ "\n" \
+ " 2. For each i in [2, N], there is one TXT record:\n" \
+ "\n" \
+ " NAME: \"<hostname> (<i>)._t-<tag>-<L>-<N>._tcp.local.\"\n" \
+ " TYPE: TXT\n" \
+ " CLASS: IN\n" \
+ " TTL: 4500\n" \
+ " RDLENGTH: L\n" \
+ " RDATA: <one or more strings with an aggregate length of L octets>\n" \
+ "\n" \
+ "The RDATA of each TXT record is exactly L octets and consists of a repeating series of the 15-byte string\n" \
+ "\"hash=0x<32-bit FNV-1 hash of the record name as an 8-character hexadecimal string>\". The last instance of\n" \
+ "the string may be truncated to satisfy the TXT record data's size requirement.\n"
+
+#define kMDNSReplierInfoText_A \
+ "The replier has exactly N_max x N_a authoritative A records:\n" \
+ "\n" \
+ " 1. For each j in [1, N_a], an A record is defined as\n" \
+ "\n" \
+ " NAME: <hostname>.local.\n" \
+ " TYPE: A\n" \
+ " CLASS: IN\n" \
+ " TTL: 120\n" \
+ " RDLENGTH: 4\n" \
+ " RDATA: 0.0.1.<j>\n" \
+ "\n" \
+ " 2. For each i in [2, N_max], for each j in [1, N_a], an A record is defined as\n" \
+ "\n" \
+ " NAME: <hostname>-<i>.local.\n" \
+ " TYPE: A\n" \
+ " CLASS: IN\n" \
+ " TTL: 120\n" \
+ " RDLENGTH: 4\n" \
+ " RDATA: 0.<ceil(i / 256)>.<i mod 256>.<j>\n"
+
+#define kMDNSReplierInfoText_AAAA \
+ "The replier has exactly N_max x N_aaaa authoritative AAAA records:\n" \
+ "\n" \
+ " 1. For each j in [1, N_aaaa], a AAAA record is defined as\n" \
+ "\n" \
+ " NAME: <hostname>.local.\n" \
+ " TYPE: AAAA\n" \
+ " CLASS: IN\n" \
+ " TTL: 120\n" \
+ " RDLENGTH: 16\n" \
+ " RDATA: 2001:db8:2::1:<j>\n" \
+ "\n" \
+ " 2. For each i in [2, N_max], for each j in [1, N_aaaa], a AAAA record is defined as\n" \
+ "\n" \
+ " NAME: <hostname>-<i>.local.\n" \
+ " TYPE: AAAA\n" \
+ " CLASS: IN\n" \
+ " TTL: 120\n" \
+ " RDLENGTH: 16\n" \
+ " RDATA: 2001:db8:2::<i>:<j>\n"
+
+#define kMDNSReplierInfoText_Responses \
+ "When generating answers for a query message, any two records pertaining to the same hostname will be grouped\n" \
+ "together in the same response message, and any two records pertaining to different hostnames will be in\n" \
+ "separate response messages.\n"
+
+static const char * gMDNSReplier_Hostname = NULL;
+static const char * gMDNSReplier_ServiceTypeTag = NULL;
+static int gMDNSReplier_MaxInstanceCount = 1000;
+static int gMDNSReplier_NoAdditionals = false;
+static int gMDNSReplier_RecordCountA = 1;
+static int gMDNSReplier_RecordCountAAAA = 1;
+static double gMDNSReplier_UnicastDropRate = 0.0;
+static double gMDNSReplier_MulticastDropRate = 0.0;
+static int gMDNSReplier_MaxDropCount = 0;
+static int gMDNSReplier_UseIPv4 = false;
+static int gMDNSReplier_UseIPv6 = false;
+static int gMDNSReplier_Foreground = false;
+static const char * gMDNSReplier_FollowPID = NULL;
+
+static CLIOption kMDNSReplierOpts[] =
+{
+ StringOption( 'i', "interface", &gInterface, "name or index", "Network interface by name or index.", true ),
+ StringOption( 'n', "hostname", &gMDNSReplier_Hostname, "string", "Base name to use for hostnames and service instance names.", true ),
+ StringOption( 't', "tag", &gMDNSReplier_ServiceTypeTag, "string", "Tag to use for service types, e.g., _t-<tag>-<TXT size>-<count>._tcp.", true ),
+ IntegerOption( 'c', "maxInstanceCount", &gMDNSReplier_MaxInstanceCount, "count", "Maximum number of service instances. (default: 1000)", false ),
+ BooleanOption( 0 , "noAdditionals", &gMDNSReplier_NoAdditionals, "When answering queries, don't include any additional records." ),
+ IntegerOption( 0 , "countA", &gMDNSReplier_RecordCountA, "count", "Number of A records per hostname. (default: 1)", false ),
+ IntegerOption( 0 , "countAAAA", &gMDNSReplier_RecordCountAAAA, "count", "Number of AAAA records per hostname. (default: 1)", false ),
+ DoubleOption( 0 , "udrop", &gMDNSReplier_UnicastDropRate, "probability", "Probability of dropping a unicast response. (default: 0.0)", false ),
+ DoubleOption( 0 , "mdrop", &gMDNSReplier_MulticastDropRate, "probability", "Probability of dropping a multicast query or response. (default: 0.0)", false ),
+ IntegerOption( 0 , "maxDropCount", &gMDNSReplier_MaxDropCount, "count", "If > 0, drop probabilities are limted to first <count> responses from each instance. (default: 0)", false ),
+ BooleanOption( 0 , "ipv4", &gMDNSReplier_UseIPv4, "Use IPv4." ),
+ BooleanOption( 0 , "ipv6", &gMDNSReplier_UseIPv6, "Use IPv6." ),
+ BooleanOption( 'f', "foreground", &gMDNSReplier_Foreground, "Direct log output to stdout instead of system logging." ),
+#if( TARGET_OS_DARWIN )
+ StringOption( 0 , "follow", &gMDNSReplier_FollowPID, "pid", "Exit when the process, usually the parent process, specified by PID exits.", false ),
+#endif
+
+ CLI_SECTION( "Intro", kMDNSReplierInfoText_Intro ),
+ CLI_SECTION( "Authoritative Record Parameters", kMDNSReplierInfoText_Parameters ),
+ CLI_SECTION( "Authoritative PTR Records", kMDNSReplierInfoText_PTR ),
+ CLI_SECTION( "Authoritative SRV Records", kMDNSReplierInfoText_SRV ),
+ CLI_SECTION( "Authoritative TXT Records", kMDNSReplierInfoText_TXT ),
+ CLI_SECTION( "Authoritative A Records", kMDNSReplierInfoText_A ),
+ CLI_SECTION( "Authoritative AAAA Records", kMDNSReplierInfoText_AAAA ),
+ CLI_SECTION( "Responses", kMDNSReplierInfoText_Responses ),
+ CLI_OPTION_END()
+};
+
+static void MDNSReplierCmd( void );
+
+//===========================================================================================================================
// Test Command Options
//===========================================================================================================================
+#define kTestExitStatusSection_Name "Exit Status"
+#define kTestExitStatusSection_Text \
+ "This test command can exit with one of three status codes:\n" \
+ "\n" \
+ "0 - The test ran to completion and passed.\n" \
+ "1 - A fatal error prevented the test from completing.\n" \
+ "2 - The test ran to completion, but it or a subtest failed. See test output for details.\n" \
+ "\n" \
+ "Note: The pass/fail status applies to the correctness or results. It does not necessarily imply anything about\n" \
+ "performance.\n"
+
+#define TestExitStatusSection() CLI_SECTION( kTestExitStatusSection_Name, kTestExitStatusSection_Text )
+
+#define kGAIPerfTestSuiteName_Basic "basic"
+#define kGAIPerfTestSuiteName_Advanced "advanced"
+
static const char * gGAIPerf_TestSuite = NULL;
static int gGAIPerf_CallDelayMs = 10;
static int gGAIPerf_ServerDelayMs = 10;
-static int gGAIPerf_DefaultIterCount = 100;
+static int gGAIPerf_SkipPathEvalulation = false;
+static int gGAIPerf_BadUDPMode = false;
+static int gGAIPerf_IterationCount = 100;
static const char * gGAIPerf_OutputFilePath = NULL;
-static const char * gGAIPerf_OutputFormat = "json";
-static int gGAIPerf_OutputAppendNewLine = false;
+static const char * gGAIPerf_OutputFormat = kOutputFormatStr_JSON;
+static int gGAIPerf_OutputAppendNewline = false;
static void GAIPerfCmd( void );
-#define kGAIPerfSectionTitle_TestSuiteBasic "Test Suite \"Basic\""
#define kGAIPerfSectionText_TestSuiteBasic \
"This test suite consists of the following three test cases:\n" \
"\n" \
@@ -1161,7 +1670,6 @@ static void GAIPerfCmd( void );
"\n" \
"Test Case #3: Each iteration resolves localhost to its IPv4 and IPv6 addresses.\n"
-#define kGAIPerfSectionTitle_TestSuiteAdvanced "Test Suite \"Advanced\""
#define kGAIPerfSectionText_TestSuiteAdvanced \
"This test suite consists of 33 test cases. Test cases 1 through 32 can be described in the following way\n" \
"\n" \
@@ -1204,30 +1712,150 @@ static CLIOption kGAIPerfOpts[] =
{
StringOptionEx( 's', "suite", &gGAIPerf_TestSuite, "name", "Name of the predefined test suite to run.", true,
"\n"
- "There are currently two predefined test suites, 'basic' and 'advanced', which are described below.\n"
+ "There are currently two predefined test suites, '" kGAIPerfTestSuiteName_Basic "' and '" kGAIPerfTestSuiteName_Advanced "', which are described below.\n"
"\n"
),
StringOption( 'o', "output", &gGAIPerf_OutputFilePath, "path", "Path of the file to write test results to instead of standard output (stdout).", false ),
- StringOptionEx( 'f', "format", &gGAIPerf_OutputFormat, "format", "Specifies the test results output format. (default: json)", false,
- "\n"
- "Use 'json' for JavaScript Object Notation (JSON).\n"
- "Use 'xml' for property list XML version 1.0.\n"
- "Use 'binary' for property list binary version 1.0.\n"
- "\n"
- ),
- BooleanOption( 'n', "appendNewline", &gGAIPerf_OutputAppendNewLine, "If the output format is JSON, output a trailing newline character." ),
+ FormatOption( 'f', "format", &gGAIPerf_OutputFormat, "Specifies the test results output format. (default: " kOutputFormatStr_JSON ")", false ),
+ BooleanOption( 'n', "appendNewline", &gGAIPerf_OutputAppendNewline, "If the output format is JSON, output a trailing newline character." ),
IntegerOption( 0 , "callDelay", &gGAIPerf_CallDelayMs, "ms", "Time to wait before calling DNSServiceGetAddrInfo() in milliseconds. (default: 10)", false ),
- IntegerOption( 0 , "responseDelay", &gGAIPerf_ServerDelayMs, "ms", "Additional delay in milliseconds to have the test DNS server apply to responses. (default: 0)", false ),
- IntegerOption( 'i', "iterations", &gGAIPerf_DefaultIterCount, "count", "The default number of test case iterations. (default: 100)", false ),
+ BooleanOption( 0 , "skipPathEval", &gGAIPerf_SkipPathEvalulation, "Use kDNSServiceFlagsPathEvaluationDone when calling DNSServiceGetAddrInfo()." ),
+ IntegerOption( 'i', "iterations", &gGAIPerf_IterationCount, "count", "The number of iterations per test case. (default: 100)", false ),
+
+ CLI_OPTION_GROUP( "DNS Server Options" ),
+ IntegerOption( 0 , "responseDelay", &gGAIPerf_ServerDelayMs, "ms", "Additional delay in milliseconds to have the server apply to responses. (default: 10)", false ),
+ BooleanOption( 0 , "badUDPMode", &gGAIPerf_BadUDPMode, "Run server in Bad UDP mode to trigger mDNSResponder's TCP fallback mechanism." ),
+
+ CLI_SECTION( "Test Suite \"Basic\"", kGAIPerfSectionText_TestSuiteBasic ),
+ CLI_SECTION( "Test Suite \"Advanced\"", kGAIPerfSectionText_TestSuiteAdvanced ),
+ TestExitStatusSection(),
+ CLI_OPTION_END()
+};
+
+static void MDNSDiscoveryTestCmd( void );
+
+static int gMDNSDiscoveryTest_InstanceCount = 100;
+static int gMDNSDiscoveryTest_TXTSize = 100;
+static int gMDNSDiscoveryTest_BrowseTimeSecs = 2;
+static int gMDNSDiscoveryTest_FlushCache = false;
+static char * gMDNSDiscoveryTest_Interface = NULL;
+static int gMDNSDiscoveryTest_NoAdditionals = false;
+static int gMDNSDiscoveryTest_RecordCountA = 1;
+static int gMDNSDiscoveryTest_RecordCountAAAA = 1;
+static double gMDNSDiscoveryTest_UnicastDropRate = 0.0;
+static double gMDNSDiscoveryTest_MulticastDropRate = 0.0;
+static int gMDNSDiscoveryTest_MaxDropCount = 0;
+static int gMDNSDiscoveryTest_UseIPv4 = false;
+static int gMDNSDiscoveryTest_UseIPv6 = false;
+static const char * gMDNSDiscoveryTest_OutputFormat = kOutputFormatStr_JSON;
+static int gMDNSDiscoveryTest_OutputAppendNewline = false;
+static const char * gMDNSDiscoveryTest_OutputFilePath = NULL;
+
+static CLIOption kMDNSDiscoveryTestOpts[] =
+{
+ IntegerOption( 'c', "instanceCount", &gMDNSDiscoveryTest_InstanceCount, "count", "Number of service instances to discover. (default: 100)", false ),
+ IntegerOption( 's', "txtSize", &gMDNSDiscoveryTest_TXTSize, "bytes", "Desired size of each service instance's TXT record data. (default: 100)", false ),
+ IntegerOption( 'b', "browseTime", &gMDNSDiscoveryTest_BrowseTimeSecs, "seconds", "Amount of time to spend browsing in seconds. (default: 2)", false ),
+ BooleanOption( 0 , "flushCache", &gMDNSDiscoveryTest_FlushCache, "Flush mDNSResponder's record cache before browsing. Requires root privileges." ),
+
+ CLI_OPTION_GROUP( "mDNS Replier Parameters" ),
+ StringOption( 'i', "interface", &gMDNSDiscoveryTest_Interface, "name or index", "Network interface. If unspecified, any available mDNS-capable interface will be used.", false ),
+ BooleanOption( 0 , "noAdditionals", &gMDNSDiscoveryTest_NoAdditionals, "When answering queries, don't include any additional records." ),
+ IntegerOption( 0 , "countA", &gMDNSDiscoveryTest_RecordCountA, "count", "Number of A records per hostname. (default: 1)", false ),
+ IntegerOption( 0 , "countAAAA", &gMDNSDiscoveryTest_RecordCountAAAA, "count", "Number of AAAA records per hostname. (default: 1)", false ),
+ DoubleOption( 0 , "udrop", &gMDNSDiscoveryTest_UnicastDropRate, "probability", "Probability of dropping a unicast response. (default: 0.0)", false ),
+ DoubleOption( 0 , "mdrop", &gMDNSDiscoveryTest_MulticastDropRate, "probability", "Probability of dropping a multicast query or response. (default: 0.0)", false ),
+ IntegerOption( 0 , "maxDropCount", &gMDNSDiscoveryTest_MaxDropCount, "count", "If > 0, drop probabilities are limted to first <count> responses from each instance. (default: 0)", false ),
+ BooleanOption( 0 , "ipv4", &gMDNSDiscoveryTest_UseIPv4, "Use IPv4." ),
+ BooleanOption( 0 , "ipv6", &gMDNSDiscoveryTest_UseIPv6, "Use IPv6." ),
+
+ CLI_OPTION_GROUP( "Results" ),
+ FormatOption( 'f', "format", &gMDNSDiscoveryTest_OutputFormat, "Specifies the test results output format. (default: " kOutputFormatStr_JSON ")", false ),
+ StringOption( 'o', "output", &gMDNSDiscoveryTest_OutputFilePath, "path", "Path of the file to write test results to instead of standard output (stdout).", false ),
+ BooleanOption( 'n', "appendNewline", &gMDNSDiscoveryTest_OutputAppendNewline, "If the output format is JSON, output a trailing newline character." ),
+
+ TestExitStatusSection(),
+ CLI_OPTION_END()
+};
+
+static void DotLocalTestCmd( void );
+
+static const char * gDotLocalTest_Interface = NULL;
+static const char * gDotLocalTest_OutputFormat = kOutputFormatStr_JSON;
+static int gDotLocalTest_OutputAppendNewline = false;
+static const char * gDotLocalTest_OutputFilePath = NULL;
+
+#define kDotLocalTestSubtestDesc_GAIMDNSOnly "GAI for a dotlocal name that has only MDNS A and AAAA records."
+#define kDotLocalTestSubtestDesc_GAIDNSOnly "GAI for a dotlocal name that has only DNS A and AAAA records."
+#define kDotLocalTestSubtestDesc_GAIBoth "GAI for a dotlocal name that has both mDNS and DNS A and AAAA records."
+#define kDotLocalTestSubtestDesc_GAINeither "GAI for a dotlocal name that has no A or AAAA records."
+#define kDotLocalTestSubtestDesc_GAINoSuchRecord \
+ "GAI for a dotlocal name that has no A or AAAA records, but is a subdomain name of a search domain."
+#define kDotLocalTestSubtestDesc_QuerySRV "SRV query for a dotlocal name that has only a DNS SRV record."
+
+#define kDotLocalTestSectionText_Description \
+ "The goal of the dotlocal test is to verify that mDNSResponder properly handles queries for domain names in the\n" \
+ "local domain when a local SOA record exists. As part of the test setup, a test DNS server and an mdnsreplier are\n" \
+ "spawned, and a dummy local SOA record is registered with DNSServiceRegisterRecord(). The server is invoked such\n" \
+ "that its domain is a second-level subdomain of the local domain, i.e., <some label>.local, while the mdnsreplier is\n" \
+ "invoked such that its base hostname is equal to the server's domain, e.g., if the server's domain is test.local.,\n" \
+ "then the mdnsreplier's base hostname is test.local.\n" \
+ "\n" \
+ "The dotlocal test consists of six subtests that perform either a DNSServiceGetAddrInfo (GAI) operation for a\n" \
+ "hostname in the local domain or a DNSServiceQueryRecord operation to query for an SRV record in the local domain:\n" \
+ "\n" \
+ "1. " kDotLocalTestSubtestDesc_GAIMDNSOnly "\n" \
+ "2. " kDotLocalTestSubtestDesc_GAIDNSOnly "\n" \
+ "3. " kDotLocalTestSubtestDesc_GAIBoth "\n" \
+ "4. " kDotLocalTestSubtestDesc_GAINeither "\n" \
+ "5. " kDotLocalTestSubtestDesc_GAINoSuchRecord "\n" \
+ "6. " kDotLocalTestSubtestDesc_QuerySRV "\n" \
+ "\n" \
+ "Each subtest runs for five seconds.\n"
+
+static CLIOption kDotLocalTestOpts[] =
+{
+ StringOption( 'i', "interface", &gDotLocalTest_Interface, "name or index", "mdnsreplier's network interface. If not set, any mDNS-capable interface will be used.", false ),
+
+ CLI_OPTION_GROUP( "Results" ),
+ FormatOption( 'f', "format", &gDotLocalTest_OutputFormat, "Specifies the test results output format. (default: " kOutputFormatStr_JSON ")", false ),
+ StringOption( 'o', "output", &gDotLocalTest_OutputFilePath, "path", "Path of the file to write test results to instead of standard output (stdout).", false ),
+ BooleanOption( 'n', "appendNewline", &gDotLocalTest_OutputAppendNewline, "If the output format is JSON, output a trailing newline character." ),
+
+ CLI_SECTION( "Description", kDotLocalTestSectionText_Description ),
+ TestExitStatusSection(),
+ CLI_OPTION_END()
+};
+
+static void ProbeConflictTestCmd( void );
+
+static const char * gProbeConflictTest_Interface = NULL;
+static int gProbeConflictTest_UseComputerName = false;
+static const char * gProbeConflictTest_OutputFormat = kOutputFormatStr_JSON;
+static int gProbeConflictTest_OutputAppendNewline = false;
+static const char * gProbeConflictTest_OutputFilePath = NULL;
+
+static CLIOption kProbeConflictTestOpts[] =
+{
+ StringOption( 'i', "interface", &gProbeConflictTest_Interface, "name or index", "mdnsreplier's network interface. If not set, any mDNS-capable interface will be used.", false ),
+ BooleanOption( 'c', "useComputerName", &gProbeConflictTest_UseComputerName, "Use the device's \"computer name\" for the test service's name." ),
- CLI_SECTION( kGAIPerfSectionTitle_TestSuiteBasic, kGAIPerfSectionText_TestSuiteBasic ),
- CLI_SECTION( kGAIPerfSectionTitle_TestSuiteAdvanced, kGAIPerfSectionText_TestSuiteAdvanced ),
+ CLI_OPTION_GROUP( "Results" ),
+ FormatOption( 'f', "format", &gProbeConflictTest_OutputFormat, "Specifies the test report output format. (default: " kOutputFormatStr_JSON ")", false ),
+ StringOption( 'o', "output", &gProbeConflictTest_OutputFilePath, "path", "Path of the file to write test report to instead of standard output (stdout).", false ),
+ BooleanOption( 'n', "appendNewline", &gProbeConflictTest_OutputAppendNewline, "If the output format is JSON, output a trailing newline character." ),
+
+ TestExitStatusSection(),
CLI_OPTION_END()
};
static CLIOption kTestOpts[] =
{
- Command( "gaiperf", GAIPerfCmd, kGAIPerfOpts, "Run DNSServiceGetAddrInfo() performance tests.", false ),
+ Command( "gaiperf", GAIPerfCmd, kGAIPerfOpts, "Runs DNSServiceGetAddrInfo() performance tests.", false ),
+ Command( "mdnsdiscovery", MDNSDiscoveryTestCmd, kMDNSDiscoveryTestOpts, "Tests mDNS service discovery for correctness.", false ),
+ Command( "dotlocal", DotLocalTestCmd, kDotLocalTestOpts, "Tests DNS and mDNS queries for domain names in the local domain.", false ),
+ Command( "probeconflicts", ProbeConflictTestCmd, kProbeConflictTestOpts, "Tests various probing conflict scenarios.", false ),
+
CLI_OPTION_END()
};
@@ -1429,9 +2057,11 @@ static CLIOption kGlobalOpts[] =
#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( "mdnsquery", MDNSQueryCmd, kMDNSQueryOpts, "Crafts and sends an mDNS query over the specified interface.", true ),
+ Command( "mdnscollider", MDNSColliderCmd, kMDNSColliderOpts, "Creates record name collision scenarios.", true ),
Command( "pid2uuid", PIDToUUIDCmd, kPIDToUUIDOpts, "Prints the UUID of a process.", true ),
Command( "server", DNSServerCmd, kDNSServerOpts, "DNS server for testing.", true ),
+ Command( "mdnsreplier", MDNSReplierCmd, kMDNSReplierOpts, "Responds to mDNS queries for a set of authoritative resource records.", true ),
Command( "test", NULL, kTestOpts, "Commands for testing DNS-SD.", true ),
Command( "ssdp", NULL, kSSDPOpts, "Commands for testing Simple Service Discovery Protocol (SSDP).", true ),
#if( TARGET_OS_DARWIN )
@@ -1468,6 +2098,12 @@ static int
PrintFFormat * inFormat,
PrintFVAList * inArgs,
void * inUserContext );
+static int
+ PrintFAddRmvFlagsHandler(
+ PrintFContext * inContext,
+ PrintFFormat * inFormat,
+ PrintFVAList * inArgs,
+ void * inUserContext );
static DNSServiceFlags GetDNSSDFlagsFromOpts( void );
@@ -1523,6 +2159,15 @@ static OSStatus
char inBuf[ kDNSServiceMaxDomainName ],
const uint8_t ** outNextPtr );
static OSStatus
+ DNSMessageExtractQuestion(
+ const uint8_t * inMsgPtr,
+ size_t inMsgLen,
+ const uint8_t * inPtr,
+ uint8_t inNameBuf[ kDomainNameLengthMax ],
+ uint16_t * outType,
+ uint16_t * outClass,
+ const uint8_t ** outPtr );
+static OSStatus
DNSMessageExtractRecord(
const uint8_t * inMsgPtr,
size_t inMsgLen,
@@ -1550,6 +2195,10 @@ static OSStatus
uint8_t ** outEndPtr );
static Boolean DomainNameEqual( const uint8_t *inName1, const uint8_t *inName2 );
static size_t DomainNameLength( const uint8_t *inName );
+static OSStatus DomainNameDupEx( const uint8_t *inName, Boolean inLower, uint8_t **outNamePtr, size_t *outNameLen );
+#define DomainNameDup( IN_NAME, OUT_NAME, OUT_LEN ) DomainNameDupEx( IN_NAME, false, OUT_NAME, OUT_LEN )
+#define DomainNameDupLower( IN_NAME, OUT_NAME, OUT_LEN ) DomainNameDupEx( IN_NAME, true, OUT_NAME, OUT_LEN )
+
static OSStatus
DomainNameFromString(
uint8_t inDomainName[ kDomainNameLengthMax ],
@@ -1618,6 +2267,10 @@ static OSStatus
DispatchHandler inCancelHandler,
void * inContext,
dispatch_source_t * outTimer );
+
+#define DispatchTimerOneShotCreate( IN_START, IN_LEEWAY, IN_QUEUE, IN_EVENT_HANDLER, IN_CONTEXT, OUT_TIMER ) \
+ DispatchTimerCreate( IN_START, DISPATCH_TIME_FOREVER, IN_LEEWAY, IN_QUEUE, IN_EVENT_HANDLER, NULL, IN_CONTEXT, OUT_TIMER )
+
static OSStatus
DispatchProcessMonitorCreate(
pid_t inPID,
@@ -1647,7 +2300,9 @@ static void SocketContextCancelHandler( void *inContext );
static OSStatus StringToInt32( const char *inString, int32_t *outValue );
static OSStatus StringToUInt32( const char *inString, uint32_t *outValue );
-static OSStatus StringToLongLong( const char *inString, long long *outValue );
+#if( TARGET_OS_DARWIN )
+static OSStatus StringToPID( const char *inString, pid_t *outPID );
+#endif
static OSStatus StringToARecordData( const char *inString, uint8_t **outPtr, size_t *outLen );
static OSStatus StringToAAAARecordData( const char *inString, uint8_t **outPtr, size_t *outLen );
static OSStatus StringToDomainName( const char *inString, uint8_t **outPtr, size_t *outLen );
@@ -1666,45 +2321,263 @@ static OSStatus
Boolean inNoPortReuse,
SocketRef * outSock );
-typedef uint64_t MicroTime64;
+static const struct sockaddr * GetMDNSMulticastAddrV4( void );
+static const struct sockaddr * GetMDNSMulticastAddrV6( void );
+static OSStatus GetAnyMDNSInterface( char inNameBuf[ IF_NAMESIZE + 1 ], uint32_t *outIndex );
-static MicroTime64 GetCurrentMicroTime( void ); // Gets the number of milliseconds since 1970-01-01T00:00:00Z
+static OSStatus
+ CreateMulticastSocket(
+ const struct sockaddr * inAddr,
+ int inPort,
+ const char * inIfName,
+ uint32_t inIfIndex,
+ Boolean inJoin,
+ int * outPort,
+ SocketRef * outSock );
+
+static OSStatus DecimalTextToUInt32( const char *inSrc, const char *inEnd, uint32_t *outValue, const char **outPtr );
+static OSStatus CheckIntegerArgument( int inArgValue, const char *inArgName, int inMin, int inMax );
+static OSStatus CheckDoubleArgument( double inArgValue, const char *inArgName, double inMin, double inMax );
+static OSStatus CheckRootUser( void );
+static OSStatus SpawnCommand( pid_t *outPID, const char *inFormat, ... );
+static OSStatus
+ OutputPropertyList(
+ CFPropertyListRef inPList,
+ OutputFormatType inType,
+ Boolean inAppendNewline,
+ const char * inOutputFilePath );
+static void
+ DNSRecordFixedFieldsSet(
+ DNSRecordFixedFields * inFields,
+ uint16_t inType,
+ uint16_t inClass,
+ uint32_t inTTL,
+ uint16_t inRDLength );
+static void
+ SRVRecordDataFixedFieldsGet(
+ const SRVRecordDataFixedFields * inFields,
+ unsigned int * outPriority,
+ unsigned int * outWeight,
+ unsigned int * outPort );
+static void
+ SRVRecordDataFixedFieldsSet(
+ SRVRecordDataFixedFields * inFields,
+ uint16_t inPriority,
+ uint16_t inWeight,
+ uint16_t inPort );
+static void
+ SOARecordDataFixedFieldsGet(
+ const SOARecordDataFixedFields * inFields,
+ uint32_t * outSerial,
+ uint32_t * outRefresh,
+ uint32_t * outRetry,
+ uint32_t * outExpire,
+ uint32_t * outMinimum );
+static void
+ SOARecordDataFixedFieldsSet(
+ SOARecordDataFixedFields * inFields,
+ uint32_t inSerial,
+ uint32_t inRefresh,
+ uint32_t inRetry,
+ uint32_t inExpire,
+ uint32_t inMinimum );
+static OSStatus CreateSRVRecordDataFromString( const char *inString, uint8_t **outPtr, size_t *outLen );
+static OSStatus CreateTXTRecordDataFromString( const char *inString, int inDelimiter, uint8_t **outPtr, size_t *outLen );
+static OSStatus
+ CreateNSECRecordData(
+ const uint8_t * inNextDomainName,
+ uint8_t ** outPtr,
+ size_t * outLen,
+ unsigned int inTypeCount,
+ ... );
+static OSStatus
+ AppendSOARecord(
+ DataBuffer * inDB,
+ const uint8_t * inNamePtr,
+ size_t inNameLen,
+ uint16_t inType,
+ uint16_t inClass,
+ uint32_t inTTL,
+ const uint8_t * inMName,
+ const uint8_t * inRName,
+ uint32_t inSerial,
+ uint32_t inRefresh,
+ uint32_t inRetry,
+ uint32_t inExpire,
+ uint32_t inMinimumTTL,
+ size_t * outLen );
+static OSStatus
+ CreateSOARecordData(
+ const uint8_t * inMName,
+ const uint8_t * inRName,
+ uint32_t inSerial,
+ uint32_t inRefresh,
+ uint32_t inRetry,
+ uint32_t inExpire,
+ uint32_t inMinimumTTL,
+ uint8_t ** outPtr,
+ size_t * outLen );
+static OSStatus
+ _DataBuffer_AppendDNSQuestion(
+ DataBuffer * inDB,
+ const uint8_t * inNamePtr,
+ size_t inNameLen,
+ uint16_t inType,
+ uint16_t inClass );
+static OSStatus
+ _DataBuffer_AppendDNSRecord(
+ DataBuffer * inDB,
+ const uint8_t * inNamePtr,
+ size_t inNameLen,
+ uint16_t inType,
+ uint16_t inClass,
+ uint32_t inTTL,
+ const uint8_t * inRDataPtr,
+ size_t inRDataLen );
+static char * _NanoTime64ToDateString( NanoTime64 inTime, char *inBuf, size_t inMaxLen );
-#define AddRmvString( X ) ( ( (X) & kDNSServiceFlagsAdd ) ? "Add" : "Rmv" )
-#define Unused( X ) (void)(X)
+#define Unused( X ) (void)(X)
//===========================================================================================================================
-// main
+// MDNSCollider
//===========================================================================================================================
-int main( int argc, const char **argv )
-{
- OSStatus err;
-
- // Route DebugServices logging output to stderr.
-
- dlog_control( "DebugServices:output=file;stderr" );
-
- PrintFRegisterExtension( "du:time", PrintFTimestampHandler, NULL );
- PrintFRegisterExtension( "du:dnsmsg", PrintFDNSMessageHandler, NULL );
- CLIInit( argc, argv );
- err = CLIParse( kGlobalOpts, kCLIFlags_None );
- if( err ) exit( 1 );
-
- return( gExitCode );
-}
+typedef struct MDNSColliderPrivate * MDNSColliderRef;
+
+typedef uint32_t MDNSColliderProtocols;
+#define kMDNSColliderProtocol_None 0
+#define kMDNSColliderProtocol_IPv4 ( 1 << 0 )
+#define kMDNSColliderProtocol_IPv6 ( 1 << 1 )
+
+typedef void ( *MDNSColliderStopHandler_f )( void *inContext, OSStatus inError );
+
+static OSStatus MDNSColliderCreate( dispatch_queue_t inQueue, MDNSColliderRef *outCollider );
+static OSStatus MDNSColliderStart( MDNSColliderRef inCollider );
+static void MDNSColliderStop( MDNSColliderRef inCollider );
+static void MDNSColliderSetProtocols( MDNSColliderRef inCollider, MDNSColliderProtocols inProtocols );
+static void MDNSColliderSetInterfaceIndex( MDNSColliderRef inCollider, uint32_t inInterfaceIndex );
+static OSStatus MDNSColliderSetProgram( MDNSColliderRef inCollider, const char *inProgramStr );
+static void
+ MDNSColliderSetStopHandler(
+ MDNSColliderRef inCollider,
+ MDNSColliderStopHandler_f inStopHandler,
+ void * inStopContext );
+static OSStatus
+ MDNSColliderSetRecord(
+ MDNSColliderRef inCollider,
+ const uint8_t * inName,
+ uint16_t inType,
+ const void * inRDataPtr,
+ size_t inRDataLen );
+static CFTypeID MDNSColliderGetTypeID( void );
//===========================================================================================================================
-// VersionOptionCallback
+// ServiceBrowser
//===========================================================================================================================
-static OSStatus VersionOptionCallback( CLIOption *inOption, const char *inArg, int inUnset )
+typedef struct ServiceBrowserPrivate * ServiceBrowserRef;
+typedef struct ServiceBrowserResults ServiceBrowserResults;
+typedef struct SBRDomain SBRDomain;
+typedef struct SBRServiceType SBRServiceType;
+typedef struct SBRServiceInstance SBRServiceInstance;
+typedef struct SBRIPAddress SBRIPAddress;
+
+typedef void ( *ServiceBrowserCallback_f )( ServiceBrowserResults *inResults, OSStatus inError, void *inContext );
+
+struct ServiceBrowserResults
{
- const char * srcVers;
-#if( MDNSRESPONDER_PROJECT )
- char srcStr[ 16 ];
-#endif
-
+ SBRDomain * domainList; // List of domains in which services were found.
+};
+
+struct SBRDomain
+{
+ SBRDomain * next; // Next domain in list.
+ char * name; // Name of domain represented by this object.
+ SBRServiceType * typeList; // List of service types in this domain.
+};
+
+struct SBRServiceType
+{
+ SBRServiceType * next; // Next service type in list.
+ char * name; // Name of service type represented by this object.
+ SBRServiceInstance * instanceList; // List of service instances of this service type.
+};
+
+struct SBRServiceInstance
+{
+ SBRServiceInstance * next; // Next service instance in list.
+ char * name; // Name of service instance represented by this object.
+ char * hostname; // Target from service instance's SRV record.
+ uint32_t ifIndex; // Index of interface over which this service instance was discovered.
+ uint16_t port; // Port from service instance's SRV record.
+ uint8_t * txtPtr; // Service instance's TXT record data.
+ size_t txtLen; // Service instance's TXT record data length.
+ SBRIPAddress * ipaddrList; // List of IP addresses that the hostname resolved to.
+ uint64_t discoverTimeUs; // Time it took to discover this service instance in microseconds.
+ uint64_t resolveTimeUs; // Time it took to resolve this service instance in microseconds.
+};
+
+struct SBRIPAddress
+{
+ SBRIPAddress * next; // Next IP address in list.
+ sockaddr_ip sip; // IPv4 or IPv6 address.
+ uint64_t resolveTimeUs; // Time it took to resolve this IP address in microseconds.
+};
+
+static CFTypeID ServiceBrowserGetTypeID( void );
+static OSStatus
+ ServiceBrowserCreate(
+ dispatch_queue_t inQueue,
+ uint32_t inInterfaceIndex,
+ const char * inDomain,
+ unsigned int inBrowseTimeSecs,
+ Boolean inIncludeAWDL,
+ ServiceBrowserRef * outBrowser );
+static void ServiceBrowserStart( ServiceBrowserRef inBrowser );
+static OSStatus ServiceBrowserAddServiceType( ServiceBrowserRef inBrowser, const char *inServiceType );
+static void
+ ServiceBrowserSetCallback(
+ ServiceBrowserRef inBrowser,
+ ServiceBrowserCallback_f inCallback,
+ void * inContext );
+static void ServiceBrowserResultsRetain( ServiceBrowserResults *inResults );
+static void ServiceBrowserResultsRelease( ServiceBrowserResults *inResults );
+
+#define ForgetServiceBrowserResults( X ) ForgetCustom( X, ServiceBrowserResultsRelease )
+
+//===========================================================================================================================
+// main
+//===========================================================================================================================
+
+int main( int argc, const char **argv )
+{
+ OSStatus err;
+
+ // Route DebugServices logging output to stderr.
+
+ dlog_control( "DebugServices:output=file;stderr" );
+
+ PrintFRegisterExtension( "du:time", PrintFTimestampHandler, NULL );
+ PrintFRegisterExtension( "du:dnsmsg", PrintFDNSMessageHandler, NULL );
+ PrintFRegisterExtension( "du:arflags", PrintFAddRmvFlagsHandler, NULL );
+ CLIInit( argc, argv );
+ err = CLIParse( kGlobalOpts, kCLIFlags_None );
+ if( err ) exit( 1 );
+
+ 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 );
@@ -2010,11 +2883,11 @@ static void DNSSD_API
if( !context->printedHeader )
{
- FPrintF( stdout, "%-26s A/R Flags IF %-20s %-20s Instance Name\n", "Timestamp", "Domain", "Service Type" );
+ FPrintF( stdout, "%-26s %-14s IF %-20s %-20s Instance Name\n", "Timestamp", "Flags", "Domain", "Service Type" );
context->printedHeader = true;
}
- FPrintF( stdout, "%{du:time} %-3s %5X %2d %-20s %-20s %s\n",
- &now, AddRmvString( inFlags ), inFlags, (int32_t) inInterfaceIndex, inDomain, inRegType, inName );
+ FPrintF( stdout, "%{du:time} %{du:arflags} %2d %-20s %-20s %s\n",
+ &now, inFlags, (int32_t) inInterfaceIndex, inDomain, inRegType, inName );
if( !context->doResolve && !context->doResolveTXTOnly ) goto exit;
@@ -2117,7 +2990,8 @@ static void DNSSD_API
require_action( inType == kDNSServiceType_TXT, exit, err = kTypeErr );
FPrintF( stdout, "%{du:time} %s %s TXT on interface %d\n TXT: %#{txt}\n",
- &now, AddRmvString( inFlags ), inFullName, (int32_t) inInterfaceIndex, inRDataPtr, (size_t) inRDataLen );
+ &now, ( inFlags & kDNSServiceFlagsAdd ) ? "Add" : "Rmv", inFullName, (int32_t) inInterfaceIndex,
+ inRDataPtr, (size_t) inRDataLen );
exit:
if( err ) exit( 1 );
@@ -2383,16 +3257,16 @@ static void DNSSD_API
}
else
{
- addrStr = ( inSockAddr->sa_family == AF_INET ) ? "No Such Record (A)" : "No Such Record (AAAA)";
+ addrStr = ( inSockAddr->sa_family == AF_INET ) ? kNoSuchRecordAStr : kNoSuchRecordAAAAStr;
}
if( !context->printedHeader )
{
- FPrintF( stdout, "%-26s A/R Flags IF %-32s %-38s %6s\n", "Timestamp", "Hostname", "Address", "TTL" );
+ FPrintF( stdout, "%-26s %-14s IF %-32s %-38s %6s\n", "Timestamp", "Flags", "Hostname", "Address", "TTL" );
context->printedHeader = true;
}
- FPrintF( stdout, "%{du:time} %s %5X %2d %-32s %-38s %6u\n",
- &now, AddRmvString( inFlags ), inFlags, (int32_t) inInterfaceIndex, inHostname, addrStr, inTTL );
+ FPrintF( stdout, "%{du:time} %{du:arflags} %2d %-32s %-38s %6u\n",
+ &now, inFlags, (int32_t) inInterfaceIndex, inHostname, addrStr, inTTL );
if( context->oneShotMode )
{
@@ -2621,11 +3495,7 @@ static void DNSSD_API
goto exit;
}
- if( inError == kDNSServiceErr_NoSuchRecord )
- {
- ASPrintF( &rdataStr, "No Such Record" );
- }
- else
+ if( inError != kDNSServiceErr_NoSuchRecord )
{
if( !context->printRawRData ) DNSRecordDataToString( inRDataPtr, inRDataLen, inType, NULL, 0, &rdataStr );
if( !rdataStr )
@@ -2637,12 +3507,14 @@ static void DNSSD_API
if( !context->printedHeader )
{
- FPrintF( stdout, "%-26s A/R Flags IF %-32s %-5s %-5s %6s RData\n", "Timestamp", "Name", "Type", "Class", "TTL" );
+ FPrintF( stdout, "%-26s %-14s IF %-32s %-5s %-5s %6s RData\n",
+ "Timestamp", "Flags", "Name", "Type", "Class", "TTL" );
context->printedHeader = true;
}
- FPrintF( stdout, "%{du:time} %-3s %5X %2d %-32s %-5s %?-5s%?5u %6u %s\n",
- &now, AddRmvString( inFlags ), inFlags, (int32_t) inInterfaceIndex, inFullName, RecordTypeToString( inType ),
- ( inClass == kDNSServiceClass_IN ), "IN", ( inClass != kDNSServiceClass_IN ), inClass, inTTL, rdataStr );
+ FPrintF( stdout, "%{du:time} %{du:arflags} %2d %-32s %-5s %?-5s%?5u %6u %s\n",
+ &now, inFlags, (int32_t) inInterfaceIndex, inFullName, RecordTypeToString( inType ),
+ ( inClass == kDNSServiceClass_IN ), "IN", ( inClass != kDNSServiceClass_IN ), inClass, inTTL,
+ rdataStr ? rdataStr : kNoSuchRecordStr );
if( context->oneShotMode )
{
@@ -2916,11 +3788,10 @@ static void DNSSD_API
if( !context->printedHeader )
{
- FPrintF( stdout, "%-26s A/R Flags Service\n", "Timestamp" );
+ FPrintF( stdout, "%-26s %-14s Service\n", "Timestamp", "Flags" );
context->printedHeader = true;
}
- FPrintF( stdout, "%{du:time} %-3s %5X %s.%s%s %?#m\n",
- &now, AddRmvString( inFlags ), inFlags, inName, inType, inDomain, inError, inError );
+ FPrintF( stdout, "%{du:time} %{du:arflags} %s.%s%s %?#m\n", &now, inFlags, inName, inType, inDomain, inError, inError );
require_noerr_action_quiet( inError, exit, err = inError );
@@ -3605,6 +4476,8 @@ exit:
// ReverseLookupCmd
//===========================================================================================================================
+#define kIP6ARPADomainStr "ip6.arpa."
+
static void ReverseLookupCmd( void )
{
OSStatus err;
@@ -3613,7 +4486,7 @@ static void ReverseLookupCmd( void )
dispatch_source_t signalSource = NULL;
uint32_t ipv4Addr;
uint8_t ipv6Addr[ 16 ];
- char recordName[ ( 16 * 4 ) + 9 + 1 ];
+ char recordName[ ( 16 * 4 ) + sizeof( kIP6ARPADomainStr ) ];
int useMainConnection;
const char * endPtr;
@@ -3687,7 +4560,7 @@ static void ReverseLookupCmd( void )
*dst++ = kHexDigitsLowercase[ ipv6Addr[ i ] >> 4 ];
*dst++ = '.';
}
- strcpy( dst, "ip6.arpa." );
+ strcpy_literal( dst, kIP6ARPADomainStr );
check( ( strlen( recordName ) + 1 ) <= sizeof( recordName ) );
}
else
@@ -3939,221 +4812,56 @@ static void DNSSD_API
// BrowseAllCmd
//===========================================================================================================================
-typedef struct BrowseDomain BrowseDomain;
-typedef struct BrowseType BrowseType;
-typedef struct BrowseOp BrowseOp;
-typedef struct BrowseInstance BrowseInstance;
-typedef struct BrowseIPAddr BrowseIPAddr;
+typedef struct BrowseAllConnection BrowseAllConnection;
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 maxConnectTimeSecs;
- Boolean includeAWDL;
- Boolean useColoredText;
+ ServiceBrowserRef browser; // Service browser.
+ ServiceBrowserResults * results; // Results from the service browser.
+ BrowseAllConnection * connectionList; // List of connections.
+ dispatch_source_t connectionTimer; // Timer for connection timeout.
+ int connectionPendingCount; // Number of pending connections.
+ int connectionTimeoutSecs; // Timeout value for connections in seconds.
} 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
+struct BrowseAllConnection
{
- BrowseIPAddr * next;
- sockaddr_ip sip;
- int refCount;
- BrowseAllContext * context;
- uint64_t foundTicks;
- AsyncConnectionRef connection;
- ConnectStatus connectStatus;
- CFTimeInterval connectTimeSecs;
- OSStatus connectError;
+ BrowseAllConnection * next; // Next connection object in list.
+ sockaddr_ip sip; // IPv4 or IPv6 address to connect to.
+ uint16_t port; // TCP port to connect to.
+ AsyncConnectionRef asyncCnx; // AsyncConnection object to handle the actual connection.
+ OSStatus status; // Status of connection. NoErr means connection succeeded.
+ CFTimeInterval connectTimeSecs; // Time it took to connect in seconds.
+ int32_t refCount; // This object's reference count.
+ BrowseAllContext * context; // Back pointer to parent context.
};
-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 void _BrowseAllContextFree( BrowseAllContext *inContext );
+static void _BrowseAllServiceBrowserCallback( ServiceBrowserResults *inResults, OSStatus inError, void *inContext );
static OSStatus
- BrowseAllRemoveIPAddress(
+ _BrowseAllConnectionCreate(
+ const struct sockaddr * inSockAddr,
+ uint16_t inPort,
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 )
+ BrowseAllConnection ** outConnection );
+static void _BrowseAllConnectionRetain( BrowseAllConnection *inConnection );
+static void _BrowseAllConnectionRelease( BrowseAllConnection *inConnection );
+static void _BrowseAllConnectionProgress( int inPhase, const void *inDetails, void *inArg );
+static void _BrowseAllConnectionHandler( SocketRef inSock, OSStatus inError, void *inArg );
+static void _BrowseAllExit( void *inContext );
-#define kBrowseAllOpenFileMin 4096
+static Boolean _IsServiceTypeTCP( const char *inServiceType );
static void BrowseAllCmd( void )
{
OSStatus err;
BrowseAllContext * context = NULL;
+ size_t i;
+ uint32_t ifIndex;
+ char ifName[ kInterfaceNameBufLen ];
- // Check command parameters.
+ // Check parameters.
if( gBrowseAll_BrowseTimeSecs <= 0 )
{
@@ -4162,9 +4870,14 @@ static void BrowseAllCmd( void )
goto exit;
}
+ context = (BrowseAllContext *) calloc( 1, sizeof( *context ) );
+ require_action( context, exit, err = kNoMemoryErr );
+
+ context->connectionTimeoutSecs = gBrowseAll_ConnectTimeout;
#if( TARGET_OS_POSIX )
- // Set open file minimum.
+ // Increase the open file descriptor limit for connection sockets.
+ if( context->connectionTimeoutSecs > 0 )
{
struct rlimit fdLimits;
@@ -4172,9 +4885,9 @@ static void BrowseAllCmd( void )
err = map_global_noerr_errno( err );
require_noerr( err, exit );
- if( fdLimits.rlim_cur < kBrowseAllOpenFileMin )
+ if( fdLimits.rlim_cur < 4096 )
{
- fdLimits.rlim_cur = kBrowseAllOpenFileMin;
+ fdLimits.rlim_cur = 4096;
err = setrlimit( RLIMIT_NOFILE, &fdLimits );
err = map_global_noerr_errno( err );
require_noerr( err, exit );
@@ -4182,3071 +4895,2423 @@ static void BrowseAllCmd( void )
}
#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->maxConnectTimeSecs = gBrowseAll_MaxConnectTimeSecs;
- context->includeAWDL = gDNSSDFlag_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.
+ // Get interface index.
- err = InterfaceIndexFromArgString( gInterface, &context->ifIndex );
+ err = InterfaceIndexFromArgString( gInterface, &ifIndex );
require_noerr_quiet( err, exit );
- BrowseAllPrintPrologue( context );
+ // Print prologue.
- if( context->domain )
+ FPrintF( stdout, "Interface: %d (%s)\n", (int32_t) ifIndex, InterfaceIndexToName( ifIndex, ifName ) );
+ FPrintF( stdout, "Service types: ");
+ if( gBrowseAll_ServiceTypesCount > 0 )
{
- err = BrowseAllAddDomain( context, context->domain );
- require_noerr( err, exit );
+ FPrintF( stdout, "%s", gBrowseAll_ServiceTypes[ 0 ] );
+ for( i = 1; i < gBrowseAll_ServiceTypesCount; ++i )
+ {
+ FPrintF( stdout, ", %s", gBrowseAll_ServiceTypes[ i ] );
+ }
+ FPrintF( stdout, "\n" );
}
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;
+ FPrintF( stdout, "all services\n" );
}
+ FPrintF( stdout, "Domain: %s\n", gBrowseAll_Domain ? gBrowseAll_Domain : "default domains" );
+ FPrintF( stdout, "Browse time: %d second%?c\n", gBrowseAll_BrowseTimeSecs, gBrowseAll_BrowseTimeSecs != 1, 's' );
+ FPrintF( stdout, "Connect timeout: %d second%?c\n",
+ context->connectionTimeoutSecs, context->connectionTimeoutSecs != 1, 's' );
+ FPrintF( stdout, "IncludeAWDL: %s\n", gDNSSDFlag_IncludeAWDL ? "yes" : "no" );
+ FPrintF( stdout, "Start time: %{du:time}\n", NULL );
+ FPrintF( stdout, "---\n" );
+
+ err = ServiceBrowserCreate( dispatch_get_main_queue(), ifIndex, gBrowseAll_Domain,
+ (unsigned int) gBrowseAll_BrowseTimeSecs, gDNSSDFlag_IncludeAWDL ? true : false, &context->browser );
+ require_noerr( err, exit );
- dispatch_after_f( dispatch_time_seconds( context->browseTimeSecs ), dispatch_get_main_queue(), context, BrowseAllStop );
+ for( i = 0; i < gBrowseAll_ServiceTypesCount; ++i )
+ {
+ err = ServiceBrowserAddServiceType( context->browser, gBrowseAll_ServiceTypes[ i ] );
+ require_noerr( err, exit );
+ }
+ ServiceBrowserSetCallback( context->browser, _BrowseAllServiceBrowserCallback, context );
+ ServiceBrowserStart( context->browser );
dispatch_main();
exit:
- if( context ) BrowseAllContextRelease( context );
+ if( context ) _BrowseAllContextFree( context );
if( err ) exit( 1 );
}
//===========================================================================================================================
-// BrowseAllPrintPrologue
+// _BrowseAllContextFree
//===========================================================================================================================
-static void BrowseAllPrintPrologue( const BrowseAllContext *inContext )
+static void _BrowseAllContextFree( BrowseAllContext *inContext )
{
- size_t i;
- char ifName[ kInterfaceNameBufLen ];
-
- 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, "Max connect time: %d second%?c\n",
- inContext->maxConnectTimeSecs, inContext->maxConnectTimeSecs != 1, 's' );
- FPrintF( stdout, "IncludeAWDL: %s\n", inContext->includeAWDL ? "YES" : "NO" );
- FPrintF( stdout, "Start time: %{du:time}\n", NULL );
- FPrintF( stdout, "---\n" );
+ check( !inContext->browser );
+ check( !inContext->connectionTimer );
+ check( !inContext->connectionList );
+ ForgetServiceBrowserResults( &inContext->results );
+ free( inContext );
}
//===========================================================================================================================
-// BrowseAllQueryDomainsCallback
+// _BrowseAllServiceBrowserCallback
//===========================================================================================================================
-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 )
+#define kDiscardProtocolPort 9
+
+static void _BrowseAllServiceBrowserCallback( ServiceBrowserResults *inResults, OSStatus inError, void *inContext )
{
OSStatus err;
BrowseAllContext * const context = (BrowseAllContext *) inContext;
- char domainStr[ kDNSServiceMaxDomainName ];
+ SBRDomain * domain;
+ SBRServiceType * type;
+ SBRServiceInstance * instance;
+ SBRIPAddress * ipaddr;
- Unused( inSDRef );
- Unused( inInterfaceIndex );
- Unused( inFullName );
- Unused( inType );
- Unused( inClass );
- Unused( inTTL );
+ Unused( inError );
- err = inError;
- require_noerr( err, exit );
+ require_action( inResults, exit, err = kUnexpectedErr );
- err = DomainNameToString( inRDataPtr, ( (const uint8_t *) inRDataPtr ) + inRDataLen, domainStr, NULL );
- require_noerr( err, exit );
+ check( !context->results );
+ context->results = inResults;
+ ServiceBrowserResultsRetain( context->results );
- if( inFlags & kDNSServiceFlagsAdd )
+ check( context->connectionPendingCount == 0 );
+ if( context->connectionTimeoutSecs > 0 )
{
- err = BrowseAllAddDomain( context, domainStr );
- if( err == kDuplicateErr ) err = kNoErr;
+ BrowseAllConnection * connection;
+ BrowseAllConnection ** connectionPtr = &context->connectionList;
+ char destination[ kSockAddrStringMaxSize ];
+
+ for( domain = context->results->domainList; domain; domain = domain->next )
+ {
+ for( type = domain->typeList; type; type = type->next )
+ {
+ if( !_IsServiceTypeTCP( type->name ) ) continue;
+ for( instance = type->instanceList; instance; instance = instance->next )
+ {
+ if( instance->port == kDiscardProtocolPort ) continue;
+ for( ipaddr = instance->ipaddrList; ipaddr; ipaddr = ipaddr->next )
+ {
+ err = _BrowseAllConnectionCreate( &ipaddr->sip.sa, instance->port, context, &connection );
+ require_noerr( err, exit );
+
+ *connectionPtr = connection;
+ connectionPtr = &connection->next;
+
+ err = SockAddrToString( &ipaddr->sip, kSockAddrStringFlagsNoPort, destination );
+ check_noerr( err );
+ if( !err )
+ {
+ err = AsyncConnection_Connect( &connection->asyncCnx, destination, -instance->port,
+ kAsyncConnectionFlag_P2P, kAsyncConnectionNoTimeout,
+ kSocketBufferSize_DontSet, kSocketBufferSize_DontSet,
+ _BrowseAllConnectionProgress, connection, _BrowseAllConnectionHandler, connection,
+ dispatch_get_main_queue() );
+ check_noerr( err );
+ }
+ if( !err )
+ {
+ _BrowseAllConnectionRetain( connection );
+ connection->status = kInProgressErr;
+ ++context->connectionPendingCount;
+ }
+ else
+ {
+ connection->status = err;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if( context->connectionPendingCount > 0 )
+ {
+ check( !context->connectionTimer );
+ err = DispatchTimerCreate( dispatch_time_seconds( context->connectionTimeoutSecs ), DISPATCH_TIME_FOREVER,
+ 100 * kNanosecondsPerMillisecond, NULL, _BrowseAllExit, NULL, context, &context->connectionTimer );
require_noerr( err, exit );
+ dispatch_resume( context->connectionTimer );
}
else
{
- err = BrowseAllRemoveDomain( context, domainStr );
- if( err == kNotFoundErr ) err = kNoErr;
- require_noerr( err, exit );
+ dispatch_async_f( dispatch_get_main_queue(), context, _BrowseAllExit );
}
+ err = kNoErr;
exit:
+ ForgetCF( &context->browser );
if( err ) exit( 1 );
}
//===========================================================================================================================
-// BrowseAllQueryCallback
+// _BrowseAllConnectionCreate
//===========================================================================================================================
-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 OSStatus
+ _BrowseAllConnectionCreate(
+ const struct sockaddr * inSockAddr,
+ uint16_t inPort,
+ BrowseAllContext * inContext,
+ BrowseAllConnection ** outConnection )
{
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;
+ BrowseAllConnection * obj;
- Unused( inSDRef );
+ obj = (BrowseAllConnection *) calloc( 1, sizeof( *obj ) );
+ require_action( obj, exit, err = kNoMemoryErr );
- err = inError;
- require_noerr( err, exit );
+ obj->refCount = 1;
+ SockAddrCopy( inSockAddr, &obj->sip );
+ obj->port = inPort;
+ obj->context = inContext;
- 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 );
- }
+ *outConnection = obj;
+ err = kNoErr;
exit:
- return;
+ return( err );
}
//===========================================================================================================================
-// BrowseAllResolveCallback
+// _BrowseAllConnectionRetain
//===========================================================================================================================
-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 _BrowseAllConnectionRetain( BrowseAllConnection *inConnection )
{
- 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 );
+ ++inConnection->refCount;
}
//===========================================================================================================================
-// BrowseAllGAICallback
+// _BrowseAllConnectionRelease
//===========================================================================================================================
-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 _BrowseAllConnectionRelease( BrowseAllConnection *inConnection )
{
- 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;
+ if( --inConnection->refCount == 0 ) free( inConnection );
}
//===========================================================================================================================
-// BrowseAllConnectionProgress
+// _BrowseAllConnectionProgress
//===========================================================================================================================
-static void BrowseAllConnectionProgress( int inPhase, const void *inDetails, void *inArg )
+static void _BrowseAllConnectionProgress( int inPhase, const void *inDetails, void *inArg )
{
- BrowseIPAddr * const addr = (BrowseIPAddr *) inArg;
+ BrowseAllConnection * const connection = (BrowseAllConnection *) inArg;
if( inPhase == kAsyncConnectionPhase_Connected )
{
const AsyncConnectedInfo * const info = (AsyncConnectedInfo *) inDetails;
- addr->connectTimeSecs = info->connectSecs;
+ connection->connectTimeSecs = info->connectSecs;
}
}
//===========================================================================================================================
-// BrowseAllConnectionHandler
+// _BrowseAllConnectionHandler
//===========================================================================================================================
-static void BrowseAllConnectionHandler( SocketRef inSock, OSStatus inError, void *inArg )
+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 );
- }
- }
+ BrowseAllConnection * const connection = (BrowseAllConnection *) inArg;
+ BrowseAllContext * const context = connection->context;
+ connection->status = inError;
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 )
+ if( context )
{
- DNSServiceForget( &domain->servicesQuery );
- for( type = domain->typeList; type; type = type->next )
+ check( context->connectionPendingCount > 0 );
+ if( ( --context->connectionPendingCount == 0 ) && context->connectionTimer )
{
- 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 );
- }
- }
+ dispatch_source_forget( &context->connectionTimer );
+ dispatch_async_f( dispatch_get_main_queue(), context, _BrowseAllExit );
}
}
- DNSServiceForget( &context->mainRef );
-
- if( ( context->pendingConnectCount > 0 ) && ( context->maxConnectTimeSecs > 0 ) )
- {
- check( !context->exitTimer );
- err = DispatchTimerCreate( dispatch_time_seconds( context->maxConnectTimeSecs ), DISPATCH_TIME_FOREVER,
- 100 * kNanosecondsPerMillisecond, NULL, 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 );
+ _BrowseAllConnectionRelease( connection );
}
//===========================================================================================================================
-// BrowseAllExit
+// _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 )
+static void _BrowseAllExit( void *inContext )
{
- BrowseAllContext * const context = (BrowseAllContext *) inContext;
- BrowseDomain * domain;
- BrowseType * type;
- BrowseOp * browse;
- BrowseInstance * instance;
- BrowseIPAddr * addr;
+ BrowseAllContext * const context = (BrowseAllContext *) inContext;
+ SBRDomain * domain;
+ SBRServiceType * type;
+ SBRServiceInstance * instance;
+ SBRIPAddress * ipaddr;
+ char textBuf[ 512 ];
+#if( TARGET_OS_POSIX )
+ const Boolean useColor = isatty( STDOUT_FILENO ) ? true : false;
+#endif
- dispatch_source_forget( &context->exitTimer );
+ dispatch_source_forget( &context->connectionTimer );
- for( domain = context->domainList; domain; domain = domain->next )
+ for( domain = context->results->domainList; domain; domain = domain->next )
{
FPrintF( stdout, "%s\n\n", domain->name );
for( type = domain->typeList; type; type = type->next )
{
- const char * desc;
+ const char * description;
+ const Boolean serviceTypeIsTCP = _IsServiceTypeTCP( type->name );
- 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 );
+ description = ServiceTypeDescription( type->name );
+ if( description ) FPrintF( stdout, "%*s" "%s (%s)\n\n", Indent( 1 ), description, type->name );
+ else FPrintF( stdout, "%*s" "%s\n\n", Indent( 1 ), type->name );
- for( browse = type->browseList; browse; browse = browse->next )
+ for( instance = type->instanceList; instance; instance = instance->next )
{
- for( instance = browse->instanceList; instance; instance = instance->next )
+ char * dst = textBuf;
+ char * const lim = &textBuf[ countof( textBuf ) ];
+ char ifname[ IF_NAMESIZE + 1 ];
+
+ SNPrintF_Add( &dst, lim, "%s via ", instance->name );
+ if( instance->ifIndex == 0 )
{
- char ifname[ IF_NAMESIZE + 1 ];
+ SNPrintF_Add( &dst, lim, "the Internet" );
+ }
+ else if( if_indextoname( instance->ifIndex, ifname ) )
+ {
+ NetTransportType netType;
- 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" );
+ SocketGetInterfaceInfo( kInvalidSocketRef, ifname, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &netType );
+ SNPrintF_Add( &dst, lim, "%s (%s)",
+ ( netType == kNetTransportType_Ethernet ) ? "Ethernet" : NetTransportTypeToString( netType ),
+ ifname );
+ }
+ else
+ {
+ SNPrintF_Add( &dst, lim, "interface index %u", instance->ifIndex );
+ }
+ FPrintF( stdout, "%*s" "%-55s %4llu.%03llu ms\n\n",
+ Indent( 2 ), textBuf, instance->discoverTimeUs / 1000, instance->discoverTimeUs % 1000 );
+
+ if( instance->hostname )
+ {
+ SNPrintF( textBuf, sizeof( textBuf ), "%s:%u", instance->hostname, instance->port );
+ FPrintF( stdout, "%*s" "%-51s %4llu.%03llu ms\n",
+ Indent( 3 ), textBuf, instance->resolveTimeUs / 1000, instance->resolveTimeUs % 1000 );
+ }
+ else
+ {
+ FPrintF( stdout, "%*s" "%s:%u\n", Indent( 3 ), instance->hostname, instance->port );
+ }
+
+ for( ipaddr = instance->ipaddrList; ipaddr; ipaddr = ipaddr->next )
+ {
+ BrowseAllConnection * conn;
+ BrowseAllConnection ** connPtr;
- 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 );
- }
+ FPrintF( stdout, "%*s" "%-##47a %4llu.%03llu ms",
+ Indent( 4 ), &ipaddr->sip.sa, ipaddr->resolveTimeUs / 1000, ipaddr->resolveTimeUs % 1000 );
- for( addr = instance->addrList; addr; addr = addr->next )
+ conn = NULL;
+ if( serviceTypeIsTCP && ( instance->port != kDiscardProtocolPort ) )
{
- 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 ) );
- if( context->maxConnectTimeSecs <= 0 )
+ for( connPtr = &context->connectionList; ( conn = *connPtr ) != NULL; connPtr = &conn->next )
{
- FPrintF( stdout, "\n" );
- continue;
+ if( ( conn->port == instance->port ) &&
+ ( SockAddrCompareAddr( &conn->sip, &ipaddr->sip ) == 0 ) ) break;
}
- switch( addr->connectStatus )
+ if( conn )
{
- case kConnectStatus_None:
- FPrintF( stdout, " (%s)\n", kStatusStr_NoConnectionAttempted );
- break;
-
- case kConnectStatus_Succeeded:
- FPrintF( stdout, " (%s in %.2f ms)\n",
- context->useColoredText ? kStatusStr_CouldConnectColored : kStatusStr_CouldConnect,
- addr->connectTimeSecs * 1000 );
- break;
-
- case kConnectStatus_Failed:
- FPrintF( stdout, " (%s: %m)\n",
- context->useColoredText ? kStatusStr_CouldNotConnectColored : kStatusStr_CouldNotConnect,
- addr->connectError );
- break;
-
- default:
- FPrintF( stdout, " (%s)\n", kStatusStr_Unknown );
- break;
+ if( conn->status == kInProgressErr ) conn->status = kTimeoutErr;
+ *connPtr = conn->next;
+ conn->context = NULL;
+ AsyncConnection_Forget( &conn->asyncCnx );
}
}
- FPrintF( stdout, "\n" );
- if( instance->txtLen == 0 ) continue;
-
- FPrintF( stdout, "%*s" "TXT record:\n", Indent( 3 ) );
- if( instance->txtLen > 1 )
+ if( conn )
{
- FPrintF( stdout, "%3{txt}", instance->txtPtr, instance->txtLen );
+ if( conn->status == kNoErr )
+ {
+ FPrintF( stdout, " (%sconnected%s in %.3f ms)\n",
+ useColor ? kANSIGreen : "", useColor ? kANSINormal : "", conn->connectTimeSecs * 1000 );
+ }
+ else
+ {
+ FPrintF( stdout, " (%scould not connect%s: %m)\n",
+ useColor ? kANSIRed : "", useColor ? kANSINormal : "", conn->status );
+ }
+ _BrowseAllConnectionRelease( conn );
}
else
{
- FPrintF( stdout, "%*s" "%#H\n", Indent( 3 ), instance->txtPtr, (int) instance->txtLen, INT_MAX );
+ FPrintF( stdout, " (no connection attempted)\n" );
}
- FPrintF( stdout, "\n" );
}
+
+ FPrintF( stdout, "\n" );
+ if( instance->txtLen == 0 ) continue;
+
+ FPrintF( stdout, "%*s" "TXT record (%zu byte%?c):\n",
+ Indent( 3 ), instance->txtLen, instance->txtLen != 1, 's' );
+ 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 );
+ _BrowseAllContextFree( context );
Exit( NULL );
}
//===========================================================================================================================
-// BrowseAllAddDomain
+// _IsServiceTypeTCP
//===========================================================================================================================
-static OSStatus BrowseAllAddDomain( BrowseAllContext *inContext, const char *inName )
+static Boolean _IsServiceTypeTCP( const char *inServiceType )
{
OSStatus err;
- BrowseDomain * domain;
- BrowseDomain ** p;
- BrowseDomain * newDomain = NULL;
+ const uint8_t * secondLabel;
+ uint8_t name[ kDomainNameLengthMax ];
- for( p = &inContext->domainList; ( domain = *p ) != NULL; p = &domain->next )
+ err = DomainNameFromString( name, inServiceType, NULL );
+ if( !err )
{
- if( strcasecmp( domain->name, inName ) == 0 ) break;
+ secondLabel = NextLabel( name );
+ if( secondLabel && DomainNameEqual( secondLabel, (const uint8_t *) "\x04" "_tcp" ) ) return( true );
}
- 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 );
+ return( false );
}
//===========================================================================================================================
-// BrowseAllRemoveDomain
+// GetNameInfoCmd
//===========================================================================================================================
-static OSStatus BrowseAllRemoveDomain( BrowseAllContext *inContext, const char *inName )
+const FlagStringPair kGetNameInfoFlagStringPairs[] =
{
- OSStatus err;
- BrowseDomain * domain;
- BrowseDomain ** p;
+ CaseFlagStringify( NI_NUMERICSCOPE ),
+ CaseFlagStringify( NI_DGRAM ),
+ CaseFlagStringify( NI_NUMERICSERV ),
+ CaseFlagStringify( NI_NAMEREQD ),
+ CaseFlagStringify( NI_NUMERICHOST ),
+ CaseFlagStringify( NI_NOFQDN ),
+ { 0, NULL }
+};
+
+static void GetNameInfoCmd( void )
+{
+ OSStatus err;
+ sockaddr_ip sip;
+ size_t sockAddrLen;
+ unsigned int flags;
+ const FlagStringPair * pair;
+ struct timeval now;
+ char host[ NI_MAXHOST ];
+ char serv[ NI_MAXSERV ];
- for( p = &inContext->domainList; ( domain = *p ) != NULL; p = &domain->next )
+ err = StringToSockAddr( gGetNameInfo_IPAddress, &sip, sizeof( sip ), &sockAddrLen );
+ check_noerr( err );
+ if( err )
{
- if( strcasecmp( domain->name, inName ) == 0 ) break;
+ FPrintF( stderr, "Failed to convert \"%s\" to a sockaddr.\n", gGetNameInfo_IPAddress );
+ goto exit;
}
- if( domain )
+ flags = 0;
+ if( gGetNameInfoFlag_DGram ) flags |= NI_DGRAM;
+ if( gGetNameInfoFlag_NameReqd ) flags |= NI_NAMEREQD;
+ if( gGetNameInfoFlag_NoFQDN ) flags |= NI_NOFQDN;
+ if( gGetNameInfoFlag_NumericHost ) flags |= NI_NUMERICHOST;
+ if( gGetNameInfoFlag_NumericScope ) flags |= NI_NUMERICSCOPE;
+ if( gGetNameInfoFlag_NumericServ ) flags |= NI_NUMERICSERV;
+
+ // Print prologue.
+
+ FPrintF( stdout, "SockAddr: %##a\n", &sip.sa );
+ FPrintF( stdout, "Flags: 0x%X < ", flags );
+ for( pair = kGetNameInfoFlagStringPairs; pair->str != NULL; ++pair )
{
- *p = domain->next;
- BrowseDomainFree( domain );
- err = kNoErr;
+ if( flags & pair->flag ) FPrintF( stdout, "%s ", pair->str );
+ }
+ FPrintF( stdout, ">\n" );
+ FPrintF( stdout, "Start time: %{du:time}\n", NULL );
+ FPrintF( stdout, "---\n" );
+
+ // Call getnameinfo().
+
+ err = getnameinfo( &sip.sa, (socklen_t) sockAddrLen, host, (socklen_t) sizeof( host ), serv, (socklen_t) sizeof( serv ),
+ (int) flags );
+ gettimeofday( &now, NULL );
+ if( err )
+ {
+ FPrintF( stderr, "Error %d: %s.\n", err, gai_strerror( err ) );
}
else
{
- err = kNotFoundErr;
+ FPrintF( stdout, "host: %s\n", host );
+ FPrintF( stdout, "serv: %s\n", serv );
}
+ FPrintF( stdout, "---\n" );
+ FPrintF( stdout, "End time: %{du:time}\n", &now );
- return( err );
+exit:
+ gExitCode = err ? 1 : 0;
}
//===========================================================================================================================
-// BrowseAllContextRelease
+// GetAddrInfoStressCmd
//===========================================================================================================================
-static void BrowseAllContextRelease( BrowseAllContext *inContext )
+typedef struct
{
- 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 );
- }
-}
+ 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;
-//===========================================================================================================================
-// BrowseAllAddServiceType
-//===========================================================================================================================
+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 OSStatus
- BrowseAllAddServiceType(
- BrowseAllContext * inContext,
- BrowseDomain * inDomain,
- const char * inName,
- uint32_t inIfIndex,
- Boolean inIncludeAWDL )
+static void GetAddrInfoStressCmd( void )
{
- OSStatus err;
- DNSServiceRef sdRef;
- DNSServiceFlags flags;
- BrowseType * type;
- BrowseType ** typePtr;
- BrowseType * newType = NULL;
- BrowseOp * browse;
- BrowseOp ** browsePtr;
- BrowseOp * newBrowse = NULL;
+ OSStatus err;
+ GAIStressContext * context = NULL;
+ int i;
+ DNSServiceFlags flags;
+ uint32_t ifIndex;
+ char ifName[ kInterfaceNameBufLen ];
- for( typePtr = &inDomain->typeList; ( type = *typePtr ) != NULL; typePtr = &type->next )
+ if( gGAIStress_TestDurationSecs < 0 )
{
- if( strcasecmp( type->name, inName ) == 0 ) break;
+ FPrintF( stdout, "Invalid test duration: %d s.\n", gGAIStress_TestDurationSecs );
+ err = kParamErr;
+ goto exit;
}
- if( !type )
+ if( gGAIStress_ConnectionCount <= 0 )
{
- 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;
+ FPrintF( stdout, "Invalid simultaneous connection count: %d.\n", gGAIStress_ConnectionCount );
+ err = kParamErr;
+ goto exit;
}
-
- for( browsePtr = &type->browseList; ( browse = *browsePtr ) != NULL; browsePtr = &browse->next )
+ if( gGAIStress_DurationMinMs <= 0 )
{
- if( browse->ifIndex == inIfIndex ) break;
+ 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;
}
- 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;
+ // Set flags.
- newBrowse->startTicks = UpTicks();
+ flags = GetDNSSDFlagsFromOpts();
- sdRef = inContext->mainRef;
- err = DNSServiceBrowse( &sdRef, flags, newBrowse->ifIndex, type->name, inDomain->name, BrowseAllBrowseCallback,
- newBrowse );
- require_noerr( err, exit );
+ // Set interface index.
- newBrowse->browse = sdRef;
- *browsePtr = newBrowse;
- newBrowse = NULL;
+ err = InterfaceIndexFromArgString( gInterface, &ifIndex );
+ require_noerr_quiet( err, exit );
- if( newType )
+ for( i = 0; i < gGAIStress_ConnectionCount; ++i )
{
- *typePtr = newType;
- newType = NULL;
+ 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;
}
-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( gGAIStress_TestDurationSecs > 0 )
{
- if( strcasecmp( type->name, inName ) == 0 ) break;
+ dispatch_after_f( dispatch_time_seconds( gGAIStress_TestDurationSecs ), dispatch_get_main_queue(), NULL, Exit );
}
- require_action_quiet( type, exit, err = kNotFoundErr );
- for( browsePtr = &type->browseList; ( browse = *browsePtr ) != NULL; browsePtr = &browse->next )
+ 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 )
{
- if( browse->ifIndex == inIfIndex ) break;
+ FPrintF( stdout, "â\n" );
}
- require_action_quiet( browse, exit, err = kNotFoundErr );
-
- *browsePtr = browse->next;
- BrowseOpFree( browse );
- if( !type->browseList )
+ else
{
- *typePtr = type->next;
- BrowseTypeFree( type );
+ FPrintF( stdout, "%d s\n", gGAIStress_TestDurationSecs );
}
- err = kNoErr;
+ 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: %{du:time}\n", NULL);
+ FPrintF( stdout, "---\n" );
+
+ dispatch_main();
exit:
- return( err );
+ FreeNullSafe( context );
+ if( err ) exit( 1 );
}
//===========================================================================================================================
-// BrowseAllAddServiceInstance
+// GetAddrInfoStressEvent
//===========================================================================================================================
-static OSStatus
- BrowseAllAddServiceInstance(
- BrowseAllContext * inContext,
- BrowseOp * inBrowse,
- const char * inName,
- const char * inRegType,
- const char * inDomain,
- uint32_t inIfIndex )
+#define kStressRandStrLen 5
+
+#define kLowercaseAlphaCharSet "abcdefghijklmnopqrstuvwxyz"
+
+static void GetAddrInfoStressEvent( void *inContext )
{
- OSStatus err;
- DNSServiceRef sdRef;
- BrowseInstance * instance;
- BrowseInstance ** p;
- const uint64_t nowTicks = UpTicks();
- BrowseInstance * newInstance = NULL;
+ GAIStressContext * const context = (GAIStressContext *) inContext;
+ OSStatus err;
+ DNSServiceRef sdRef;
+ unsigned int nextMs;
+ char randomStr[ kStressRandStrLen + 1 ];
+ char hostname[ kStressRandStrLen + 4 + 1 ];
+ Boolean isConnectionNew = false;
+ static Boolean printedHeader = false;
- for( p = &inBrowse->instanceList; ( instance = *p ) != NULL; p = &instance->next )
+ if( !context->mainRef || ( context->requestCount >= context->requestCountLimit ) )
{
- if( ( instance->ifIndex == inIfIndex ) && ( strcasecmp( instance->name, inName ) == 0 ) ) break;
+ 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;
}
- require_action_quiet( !instance, exit, err = kDuplicateErr );
- newInstance = (BrowseInstance *) calloc( 1, sizeof( *newInstance ) );
- require_action( newInstance, exit, err = kNoMemoryErr );
+ RandomString( kLowercaseAlphaCharSet, sizeof_string( kLowercaseAlphaCharSet ), 2, kStressRandStrLen, randomStr );
+ SNPrintF( hostname, sizeof( hostname ), "%s.com", randomStr );
- ++inContext->refCount;
- newInstance->context = inContext;
- newInstance->foundTicks = nowTicks;
- newInstance->ifIndex = inIfIndex;
- newInstance->isTCP = inBrowse->isTCP;
+ nextMs = RandomRange( context->durationMinMs, context->durationMaxMs );
- newInstance->name = strdup( inName );
- require_action( newInstance->name, exit, err = kNoMemoryErr );
+ if( !printedHeader )
+ {
+ FPrintF( stdout, "%-26s Conn Hostname Dur (ms)\n", "Timestamp" );
+ printedHeader = true;
+ }
+ FPrintF( stdout, "%{du:time} %3u%c %9s %8u\n",
+ NULL, context->connectionNumber, isConnectionNew ? '*': ' ', hostname, nextMs );
- sdRef = inContext->mainRef;
- newInstance->resolveStartTicks = UpTicks();
- err = DNSServiceResolve( &sdRef, kDNSServiceFlagsShareConnection, newInstance->ifIndex, inName, inRegType, inDomain,
- BrowseAllResolveCallback, newInstance );
+ 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;
- newInstance->resolve = sdRef;
- *p = newInstance;
- newInstance = NULL;
+ context->requestCount++;
+
+ dispatch_after_f( dispatch_time_milliseconds( nextMs ), dispatch_get_main_queue(), context, GetAddrInfoStressEvent );
exit:
- if( newInstance ) BrowseInstanceFree( newInstance );
- return( err );
+ if( err ) exit( 1 );
}
//===========================================================================================================================
-// BrowseAllRemoveServiceInstance
+// GetAddrInfoStressCallback
//===========================================================================================================================
-static OSStatus
- BrowseAllRemoveServiceInstance(
- BrowseAllContext * inContext,
- BrowseOp * inBrowse,
- const char * inName,
- uint32_t inIfIndex )
+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 )
{
- OSStatus err;
- BrowseInstance * instance;
- BrowseInstance ** p;
-
+ Unused( inSDRef );
+ Unused( inFlags );
+ Unused( inInterfaceIndex );
+ Unused( inError );
+ Unused( inHostname );
+ Unused( inSockAddr );
+ Unused( inTTL );
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
+// DNSQueryCmd
//===========================================================================================================================
-#define kDiscardProtocolPort 9
+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 OSStatus
- BrowseAllAddIPAddress(
- BrowseAllContext * inContext,
- BrowseInstance * inInstance,
- const struct sockaddr * inSockAddr )
+static void DNSQueryCmd( void )
{
- OSStatus err;
- BrowseIPAddr * addr;
- BrowseIPAddr ** p;
- const uint64_t nowTicks = UpTicks();
- BrowseIPAddr * newAddr = NULL;
+ OSStatus err;
+ DNSQueryContext * context = NULL;
+ uint8_t * msgPtr;
+ size_t msgLen, sendLen;
- if( ( inSockAddr->sa_family != AF_INET ) && ( inSockAddr->sa_family != AF_INET6 ) )
+ // Check command parameters.
+
+ if( gDNSQuery_TimeLimitSecs < -1 )
{
- dlogassert( "Unexpected address family: %d", inSockAddr->sa_family );
- err = kTypeErr;
+ FPrintF( stdout, "Invalid time limit: %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;
}
- for( p = &inInstance->addrList; ( addr = *p ) != NULL; p = &addr->next )
+ // 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;
+
+#if( TARGET_OS_DARWIN )
+ if( gDNSQuery_Server )
+#endif
+ {
+ err = StringToSockAddr( gDNSQuery_Server, &context->serverAddr, sizeof( context->serverAddr ), NULL );
+ require_noerr( err, exit );
+ }
+#if( TARGET_OS_DARWIN )
+ else
+ {
+ err = GetDefaultDNSServer( &context->serverAddr );
+ require_noerr( err, exit );
+ }
+#endif
+ 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
{
- if( SockAddrCompareAddr( &addr->sip, inSockAddr ) == 0 ) break;
+ sendLen = msgLen;
}
- require_action_quiet( !addr, exit, err = kDuplicateErr );
- newAddr = (BrowseIPAddr *) calloc( 1, sizeof( *newAddr ) );
- require_action( newAddr, exit, err = kNoMemoryErr );
+ DNSQueryPrintPrologue( context );
- ++inContext->refCount;
- newAddr->refCount = 1;
- newAddr->context = inContext;
- newAddr->foundTicks = nowTicks;
- SockAddrCopy( inSockAddr, &newAddr->sip.sa );
+ if( gDNSQuery_Verbose )
+ {
+ FPrintF( stdout, "DNS message to send:\n\n%{du:dnsmsg}", msgPtr, msgLen );
+ FPrintF( stdout, "---\n" );
+ }
- if( ( inContext->maxConnectTimeSecs > 0 ) && inInstance->isTCP && ( inInstance->port != kDiscardProtocolPort ) )
+ if( context->useTCP )
{
- char destination[ kSockAddrStringMaxSize ];
+ // Create TCP socket.
- err = SockAddrToString( &newAddr->sip, kSockAddrStringFlagsNoPort, destination );
+ context->sock = socket( context->serverAddr.sa.sa_family, SOCK_STREAM, IPPROTO_TCP );
+ err = map_socket_creation_errno( context->sock );
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() );
+ err = SocketConnect( context->sock, &context->serverAddr, 5 );
require_noerr( err, exit );
+ }
+ else
+ {
+ // Create UDP socket.
- ++newAddr->refCount;
- newAddr->connectStatus = kConnectStatus_Pending;
- ++inContext->pendingConnectCount;
+ err = UDPClientSocketOpen( AF_UNSPEC, &context->serverAddr, 0, -1, NULL, &context->sock );
+ require_noerr( err, exit );
}
- *p = newAddr;
- newAddr = NULL;
- err = kNoErr;
+ context->sendTicks = UpTicks();
+ err = SocketWriteAll( context->sock, context->msgBuf, sendLen, 5 );
+ require_noerr( err, exit );
-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;
+ if( context->timeLimitSecs == 0 ) goto exit;
- Unused( inContext );
+ err = DispatchReadSourceCreate( context->sock, NULL, DNSQueryReadHandler, DNSQueryCancelHandler, context,
+ &context->readSource );
+ require_noerr( err, exit );
+ dispatch_resume( context->readSource );
- for( p = &inInstance->addrList; ( addr = *p ) != NULL; p = &addr->next )
+ if( context->timeLimitSecs > 0 )
{
- if( SockAddrCompareAddr( &addr->sip.sa, inSockAddr ) == 0 ) break;
+ dispatch_after_f( dispatch_time_seconds( context->timeLimitSecs ), dispatch_get_main_queue(), kExitReason_Timeout,
+ Exit );
}
- require_action_quiet( addr, exit, err = kNotFoundErr );
-
- *p = addr->next;
- BrowseIPAddrRelease( addr );
- err = kNoErr;
+ dispatch_main();
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 )
+ if( context )
{
- inDomain->typeList = type->next;
- BrowseTypeFree( type );
+ dispatch_source_forget( &context->readSource );
+ ForgetSocket( &context->sock );
+ free( context );
}
- free( inDomain );
+ if( err ) exit( 1 );
}
//===========================================================================================================================
-// BrowseTypeFree
+// DNSQueryPrintPrologue
//===========================================================================================================================
-static void BrowseTypeFree( BrowseType *inType )
+static void DNSQueryPrintPrologue( const DNSQueryContext *inContext )
{
- BrowseOp * browse;
+ const int timeLimitSecs = inContext->timeLimitSecs;
- ForgetMem( &inType->name );
- while( ( browse = inType->browseList ) != NULL )
- {
- inType->browseList = browse->next;
- BrowseOpFree( browse );
- }
- free( inType );
+ 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: %{du:time}\n", NULL );
+ FPrintF( stdout, "---\n" );
}
//===========================================================================================================================
-// BrowseOpFree
+// DNSQueryReadHandler
//===========================================================================================================================
-static void BrowseOpFree( BrowseOp *inBrowse )
+static void DNSQueryReadHandler( void *inContext )
{
- BrowseInstance * instance;
+ OSStatus err;
+ struct timeval now;
+ const uint64_t nowTicks = UpTicks();
+ DNSQueryContext * const context = (DNSQueryContext *) inContext;
- ForgetBrowseAllContext( &inBrowse->context );
- DNSServiceForget( &inBrowse->browse );
- while( ( instance = inBrowse->instanceList ) != NULL )
+ gettimeofday( &now, NULL );
+
+ if( context->useTCP )
{
- inBrowse->instanceList = instance->next;
- BrowseInstanceFree( instance );
+ 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;
}
- free( inBrowse );
+ 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: %{du:time}\n", &now );
+ 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 ) );
+ FPrintF( stdout, "%.*{du:dnsmsg}", context->printRawRData ? 1 : 0, context->msgPtr, context->msgLen );
+
+ if( ( context->msgLen >= kDNSHeaderLength ) && ( DNSHeaderGetID( (DNSHeader *) context->msgPtr ) == context->queryID ) )
+ {
+ Exit( kExitReason_ReceivedResponse );
+ }
+
+exit:
+ if( err ) dispatch_source_forget( &context->readSource );
}
//===========================================================================================================================
-// BrowseInstanceFree
+// DNSQueryCancelHandler
//===========================================================================================================================
-static void BrowseInstanceFree( BrowseInstance *inInstance )
+static void DNSQueryCancelHandler( void *inContext )
{
- ForgetBrowseAllContext( &inInstance->context );
- ForgetMem( &inInstance->name );
- DNSServiceForget( &inInstance->resolve );
- DNSServiceForget( &inInstance->getAddr );
- ForgetMem( &inInstance->txtPtr );
- ForgetMem( &inInstance->hostname );
- ForgetIPAddressList( &inInstance->addrList );
- free( inInstance );
+ 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 )
//===========================================================================================================================
-// BrowseIPAddrRelease
+// DNSCryptCmd
//===========================================================================================================================
-static void BrowseIPAddrRelease( BrowseIPAddr *inAddr )
+#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 ] =
{
- AsyncConnection_Forget( &inAddr->connection );
- if( --inAddr->refCount == 0 )
- {
- ForgetBrowseAllContext( &inAddr->context );
- free( inAddr );
- }
-}
+ 0x72, 0x36, 0x66, 0x6e, 0x76, 0x57, 0x6a, 0x38
+};
-//===========================================================================================================================
-// BrowseIPAddrReleaseList
-//===========================================================================================================================
+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;
-static void BrowseIPAddrReleaseList( BrowseIPAddr *inList )
+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
{
- BrowseIPAddr * addr;
+ uint8_t resolverMagic[ kDNSCryptResolverMagicLength ];
+ uint8_t clientNonce[ kDNSCryptHalfNonceLength ];
+ uint8_t resolverNonce[ kDNSCryptHalfNonceLength ];
+ uint8_t poly1305MAC[ 16 ];
- while( ( addr = inList ) != NULL )
- {
- inList = addr->next;
- BrowseIPAddrRelease( addr );
- }
-}
+} DNSCryptResponseHeader;
-//===========================================================================================================================
-// GetNameInfoCmd
-//===========================================================================================================================
+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 ) );
-const FlagStringPair kGetNameInfoFlagStringPairs[] =
+typedef struct
{
- CaseFlagStringify( NI_NUMERICSCOPE ),
- CaseFlagStringify( NI_DGRAM ),
- CaseFlagStringify( NI_NUMERICSERV ),
- CaseFlagStringify( NI_NAMEREQD ),
- CaseFlagStringify( NI_NUMERICHOST ),
- CaseFlagStringify( NI_NOFQDN ),
- { 0, NULL }
-};
+ 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 GetNameInfoCmd( void )
+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;
- sockaddr_ip sip;
- size_t sockAddrLen;
- unsigned int flags;
- const FlagStringPair * pair;
- struct timeval now;
- char host[ NI_MAXHOST ];
- char serv[ NI_MAXSERV ];
+ OSStatus err;
+ DNSCryptContext * context = NULL;
+ size_t writtenBytes;
+ size_t totalBytes;
+ SocketContext * sockCtx;
+ SocketRef sock = kInvalidSocketRef;
+ const char * ptr;
- err = StringToSockAddr( gGetNameInfo_IPAddress, &sip, sizeof( sip ), &sockAddrLen );
- check_noerr( err );
- if( err )
+ // Check command parameters.
+
+ if( gDNSCrypt_TimeLimitSecs < -1 )
{
- FPrintF( stderr, "Failed to convert \"%s\" to a sockaddr.\n", gGetNameInfo_IPAddress );
+ FPrintF( stdout, "Invalid time limit: %d seconds.\n", gDNSCrypt_TimeLimitSecs );
+ err = kParamErr;
goto exit;
}
- flags = 0;
- if( gGetNameInfoFlag_DGram ) flags |= NI_DGRAM;
- if( gGetNameInfoFlag_NameReqd ) flags |= NI_NAMEREQD;
- if( gGetNameInfoFlag_NoFQDN ) flags |= NI_NOFQDN;
- if( gGetNameInfoFlag_NumericHost ) flags |= NI_NUMERICHOST;
- if( gGetNameInfoFlag_NumericScope ) flags |= NI_NUMERICSCOPE;
- if( gGetNameInfoFlag_NumericServ ) flags |= NI_NUMERICSERV;
+ // Create context.
- // Print prologue.
+ context = (DNSCryptContext *) calloc( 1, sizeof( *context ) );
+ require_action( context, exit, err = kNoMemoryErr );
- FPrintF( stdout, "SockAddr: %##a\n", &sip.sa );
- FPrintF( stdout, "Flags: 0x%X < ", flags );
- for( pair = kGetNameInfoFlagStringPairs; pair->str != NULL; ++pair )
- {
- if( flags & pair->flag ) FPrintF( stdout, "%s ", pair->str );
- }
- FPrintF( stdout, ">\n" );
- FPrintF( stdout, "Start time: %{du:time}\n", NULL );
- FPrintF( stdout, "---\n" );
+ context->providerName = gDNSCrypt_ProviderName;
+ context->qname = gDNSCrypt_Name;
+ context->timeLimitSecs = gDNSCrypt_TimeLimitSecs;
+ context->printRawRData = gDNSCrypt_RawRData ? true : false;
- // Call getnameinfo().
+ err = crypto_box_keypair( context->clientPublicKey, context->clientSecretKey );
+ require_noerr( err, exit );
- err = getnameinfo( &sip.sa, (socklen_t) sockAddrLen, host, (socklen_t) sizeof( host ), serv, (socklen_t) sizeof( serv ),
- (int) flags );
- gettimeofday( &now, NULL );
- if( err )
- {
- FPrintF( stderr, "Error %d: %s.\n", err, gai_strerror( err ) );
+ 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
+ else if( totalBytes != sizeof( context->serverPublicSignKey ) )
{
- FPrintF( stdout, "host: %s\n", host );
- FPrintF( stdout, "serv: %s\n", serv );
+ FPrintF( stderr, "Public signing key contains incorrect number of hex bytes (%zu != %zu)\n",
+ totalBytes, sizeof( context->serverPublicSignKey ) );
+ err = kSizeErr;
+ goto exit;
}
- FPrintF( stdout, "---\n" );
- FPrintF( stdout, "End time: %{du:time}\n", &now );
+ 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 );
+
+ err = SocketContextCreate( sock, context, &sockCtx );
+ require_noerr( err, exit );
+ sock = kInvalidSocketRef;
+
+ err = DispatchReadSourceCreate( sockCtx->sock, NULL, DNSCryptReceiveCertHandler, SocketContextCancelHandler, sockCtx,
+ &context->readSource );
+ if( err ) ForgetSocketContext( &sockCtx );
+ 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:
- gExitCode = err ? 1 : 0;
+ if( context ) free( context );
+ ForgetSocket( &sock );
+ if( err ) exit( 1 );
}
//===========================================================================================================================
-// GetAddrInfoStressCmd
+// DNSCryptReceiveCertHandler
//===========================================================================================================================
-typedef struct
+static void DNSCryptReceiveCertHandler( void *inContext )
{
- 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;
+ OSStatus err;
+ struct timeval now;
+ const uint64_t nowTicks = UpTicks();
+ SocketContext * const sockCtx = (SocketContext *) inContext;
+ DNSCryptContext * const context = (DNSCryptContext *) sockCtx->userContext;
+ 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 ];
-} 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 ];
+ gettimeofday( &now, NULL );
- 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;
- }
+ dispatch_source_forget( &context->readSource );
- // Set flags.
+ err = SocketRecvFrom( sockCtx->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 );
- flags = GetDNSSDFlagsFromOpts();
+ FPrintF( stdout, "Receive time: %{du:time}\n", &now );
+ 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 ) );
+ FPrintF( stdout, "%.*{du:dnsmsg}", context->printRawRData ? 1 : 0, context->msgBuf, context->msgLen );
- // Set interface index.
+ require_action_quiet( context->msgLen >= kDNSHeaderLength, exit, err = kSizeErr );
- err = InterfaceIndexFromArgString( gInterface, &ifIndex );
- require_noerr_quiet( err, exit );
+ hdr = (DNSHeader *) context->msgBuf;
+ require_action_quiet( DNSHeaderGetID( hdr ) == context->queryID, exit, err = kMismatchErr );
- for( i = 0; i < gGAIStress_ConnectionCount; ++i )
+ err = DNSMessageGetAnswerSection( context->msgBuf, context->msgLen, &ptr );
+ require_noerr( err, exit );
+
+ err = DomainNameFromString( targetName, context->providerName, NULL );
+ require_noerr( err, exit );
+
+ answerCount = DNSHeaderGetAnswerCount( hdr );
+ for( i = 0; i < answerCount; ++i )
{
- context = (GAIStressContext *) calloc( 1, sizeof( *context ) );
- require_action( context, exit, err = kNoMemoryErr );
+ uint16_t type;
+ uint16_t class;
+ uint8_t name[ kDomainNameLengthMax ];
- 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;
+ err = DNSMessageExtractRecord( context->msgBuf, context->msgLen, ptr, name, &type, &class, NULL, &txtPtr, &txtLen,
+ &ptr );
+ require_noerr( err, exit );
- 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 );
+ if( ( type == kDNSServiceType_TXT ) && ( class == kDNSServiceClass_IN ) && DomainNameEqual( name, targetName ) )
+ {
+ break;
+ }
}
- 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 )
+ if( txtLen < ( 1 + kDNSCryptCertMinimumLength ) )
{
- FPrintF( stdout, "â\n" );
+ FPrintF( stderr, "TXT record length is too short (%u < %u)\n", txtLen, kDNSCryptCertMinimumLength + 1 );
+ err = kSizeErr;
+ goto exit;
}
- else
+ if( txtPtr[ 0 ] < kDNSCryptCertMinimumLength )
{
- FPrintF( stdout, "%d s\n", gGAIStress_TestDurationSecs );
+ FPrintF( stderr, "TXT record value length is too short (%u < %u)\n", txtPtr[ 0 ], kDNSCryptCertMinimumLength );
+ err = kSizeErr;
+ goto exit;
}
- 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: %{du:time}\n", NULL);
- FPrintF( stdout, "---\n" );
- dispatch_main();
+ context->certLen = txtPtr[ 0 ];
+ context->certPtr = &txtPtr[ 1 ];
+
+ dispatch_async_f( dispatch_get_main_queue(), context, DNSCryptProceed );
exit:
- FreeNullSafe( context );
- if( err ) exit( 1 );
+ if( err ) Exit( NULL );
}
//===========================================================================================================================
-// GetAddrInfoStressEvent
+// DNSCryptReceiveResponseHandler
//===========================================================================================================================
-#define kStressRandStrLen 5
-
-#define kLowercaseAlphaCharSet "abcdefghijklmnopqrstuvwxyz"
-
-static void GetAddrInfoStressEvent( void *inContext )
+static void DNSCryptReceiveResponseHandler( void *inContext )
{
- GAIStressContext * const context = (GAIStressContext *) inContext;
OSStatus err;
- DNSServiceRef sdRef;
- unsigned int nextMs;
- char randomStr[ kStressRandStrLen + 1 ];
- char hostname[ kStressRandStrLen + 4 + 1 ];
- 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;
+ struct timeval now;
+ const uint64_t nowTicks = UpTicks();
+ SocketContext * const sockCtx = (SocketContext *) inContext;
+ DNSCryptContext * const context = (DNSCryptContext *) sockCtx->userContext;
+ sockaddr_ip fromAddr;
+ DNSCryptResponseHeader * hdr;
+ const uint8_t * end;
+ uint8_t * ciphertext;
+ uint8_t * plaintext;
+ const uint8_t * response;
+ uint8_t nonce[ crypto_box_NONCEBYTES ];
+
+ gettimeofday( &now, NULL );
+
+ dispatch_source_forget( &context->readSource );
+
+ err = SocketRecvFrom( sockCtx->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: %{du:time}\n", &now );
+ 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;
}
- RandomString( kLowercaseAlphaCharSet, sizeof_string( kLowercaseAlphaCharSet ), 2, kStressRandStrLen, randomStr );
- SNPrintF( hostname, sizeof( hostname ), "%s.com", randomStr );
+ hdr = (DNSCryptResponseHeader *) context->msgBuf;
- nextMs = RandomRange( context->durationMinMs, context->durationMaxMs );
+ 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( !printedHeader )
+ if( memcmp( hdr->clientNonce, context->clientNonce, kDNSCryptHalfNonceLength ) != 0 )
{
- FPrintF( stdout, "%-26s Conn Hostname Dur (ms)\n", "Timestamp" );
- printedHeader = true;
+ FPrintF( stderr, "DNSCrypt response client nonce mismatch.\n" );
+ err = kValueErr;
+ goto exit;
}
- FPrintF( stdout, "%{du:time} %3u%c %9s %8u\n",
- NULL, 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;
+ memcpy( nonce, hdr->clientNonce, crypto_box_NONCEBYTES );
- context->requestCount++;
+ ciphertext = hdr->poly1305MAC - crypto_box_BOXZEROBYTES;
+ memset( ciphertext, 0, crypto_box_BOXZEROBYTES );
- dispatch_after_f( dispatch_time_milliseconds( nextMs ), dispatch_get_main_queue(), context, GetAddrInfoStressEvent );
+ 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;
+ FPrintF( stdout, "%.*{du:dnsmsg}", context->printRawRData ? 1 : 0, response, (size_t)( end - response ) );
+ Exit( kExitReason_ReceivedResponse );
exit:
- if( err ) exit( 1 );
+ if( err ) Exit( NULL );
}
//===========================================================================================================================
-// GetAddrInfoStressCallback
+// DNSCryptProceed
//===========================================================================================================================
-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 DNSCryptProceed( void *inContext )
{
- Unused( inSDRef );
- Unused( inFlags );
- Unused( inInterfaceIndex );
- Unused( inError );
- Unused( inHostname );
- Unused( inSockAddr );
- Unused( inTTL );
- Unused( 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 );
}
//===========================================================================================================================
-// DNSQueryCmd
+// DNSCryptProcessCert
//===========================================================================================================================
-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 )
+static OSStatus DNSCryptProcessCert( DNSCryptContext *inContext )
{
- OSStatus err;
- DNSQueryContext * context = NULL;
- uint8_t * msgPtr;
- size_t msgLen, sendLen;
+ 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;
- // Check command parameters.
+ DNSCryptPrintCertificate( cert, inContext->certLen );
- if( gDNSQuery_TimeLimitSecs < -1 )
+ if( memcmp( cert->certMagic, kDNSCryptCertMagic, kDNSCryptCertMagicLength ) != 0 )
{
- FPrintF( stdout, "Invalid time limit: %d seconds.\n", gDNSQuery_TimeLimitSecs );
- err = kParamErr;
+ FPrintF( stderr, "DNSCrypt certificate magic %#H != %#H\n",
+ cert->certMagic, kDNSCryptCertMagicLength, INT_MAX,
+ kDNSCryptCertMagic, kDNSCryptCertMagicLength, INT_MAX );
+ err = kValueErr;
goto exit;
}
- if( ( gDNSQuery_Flags < INT16_MIN ) || ( gDNSQuery_Flags > UINT16_MAX ) )
+
+ startTimeSecs = (time_t) ReadBig32( cert->startTime );
+ endTimeSecs = (time_t) ReadBig32( cert->endTime );
+
+ gettimeofday( &now, NULL );
+ if( now.tv_sec < startTimeSecs )
{
- FPrintF( stdout, "DNS flags-and-codes value is out of the unsigned 16-bit range: 0x%08X.\n", gDNSQuery_Flags );
- err = kParamErr;
+ FPrintF( stderr, "DNSCrypt certificate start time is in the future.\n" );
+ err = kDateErr;
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;
-
-#if( TARGET_OS_DARWIN )
- if( gDNSQuery_Server )
-#endif
+ if( now.tv_sec >= endTimeSecs )
{
- err = StringToSockAddr( gDNSQuery_Server, &context->serverAddr, sizeof( context->serverAddr ), NULL );
- require_noerr( err, exit );
+ FPrintF( stderr, "DNSCrypt certificate has expired.\n" );
+ err = kDateErr;
+ goto exit;
}
-#if( TARGET_OS_DARWIN )
- else
+
+ 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 )
{
- err = GetDefaultDNSServer( &context->serverAddr );
- require_noerr( err, exit );
+ FPrintF( stderr, "DNSCrypt certificate failed verification.\n" );
+ err = kAuthenticationErr;
+ goto exit;
}
-#endif
- if( SockAddrGetPort( &context->serverAddr ) == 0 ) SockAddrSetPort( &context->serverAddr, kDNSPort );
- err = RecordTypeFromArgString( gDNSQuery_Type, &context->type );
+ 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 );
- // Write query message.
+ inContext->certPtr = NULL;
+ inContext->certLen = 0;
+ inContext->msgLen = 0;
- check_compile_time_code( sizeof( context->msgBuf ) >= ( kDNSQueryMessageMaxLen + 2 ) );
+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 ];
- msgPtr = context->useTCP ? &context->msgBuf[ 2 ] : context->msgBuf;
- err = WriteDNSQueryMessage( msgPtr, context->queryID, (uint16_t) gDNSQuery_Flags, context->name, context->type,
- kDNSServiceClass_IN, &msgLen );
+ 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 );
- check( msgLen <= UINT16_MAX );
- if( context->useTCP )
- {
- WriteBig16( context->msgBuf, msgLen );
- sendLen = 2 + msgLen;
- }
- else
- {
- sendLen = msgLen;
- }
-
- DNSQueryPrintPrologue( context );
+ padLimit = &queryPtr[ queryLen + kDNSCryptMaxPadLength ];
+ if( padLimit > msgLimit ) padLimit = msgLimit;
- if( gDNSQuery_Verbose )
- {
- FPrintF( stdout, "DNS message to send:\n\n%{du:dnsmsg}", msgPtr, msgLen );
- FPrintF( stdout, "---\n" );
- }
+ err = DNSCryptPadQuery( queryPtr, queryLen, (size_t)( padLimit - queryPtr ), &paddedQueryLen );
+ require_noerr( err, exit );
- 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 );
- }
+ memset( queryPtr - crypto_box_ZEROBYTES, 0, crypto_box_ZEROBYTES );
+ RandomBytes( inContext->clientNonce, kDNSCryptHalfNonceLength );
+ memcpy( nonce, inContext->clientNonce, kDNSCryptHalfNonceLength );
+ memset( &nonce[ kDNSCryptHalfNonceLength ], 0, kDNSCryptHalfNonceLength );
- context->sendTicks = UpTicks();
- err = SocketWriteAll( context->sock, context->msgBuf, sendLen, 5 );
+ err = crypto_box_afternm( queryPtr - crypto_box_ZEROBYTES, queryPtr - crypto_box_ZEROBYTES,
+ paddedQueryLen + crypto_box_ZEROBYTES, nonce, inContext->nmKey );
require_noerr( err, exit );
- if( context->timeLimitSecs == 0 ) goto exit;
+ memcpy( hdr->clientMagic, inContext->clientMagic, kDNSCryptClientMagicLength );
+ memcpy( hdr->clientPublicKey, inContext->clientPublicKey, crypto_box_PUBLICKEYBYTES );
+ memcpy( hdr->clientNonce, nonce, kDNSCryptHalfNonceLength );
- err = DispatchReadSourceCreate( context->sock, NULL, DNSQueryReadHandler, DNSQueryCancelHandler, context,
- &context->readSource );
- require_noerr( err, exit );
- dispatch_resume( context->readSource );
+ inContext->msgLen = (size_t)( &queryPtr[ paddedQueryLen ] - inContext->msgBuf );
- if( context->timeLimitSecs > 0 )
- {
- dispatch_after_f( dispatch_time_seconds( context->timeLimitSecs ), dispatch_get_main_queue(), kExitReason_Timeout,
- Exit );
- }
- dispatch_main();
+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:
- if( context )
- {
- dispatch_source_forget( &context->readSource );
- ForgetSocket( &context->sock );
- free( context );
- }
- if( err ) exit( 1 );
+ return( err );
}
//===========================================================================================================================
-// DNSQueryPrintPrologue
+// DNSCryptSendQuery
//===========================================================================================================================
-static void DNSQueryPrintPrologue( const DNSQueryContext *inContext )
+static OSStatus DNSCryptSendQuery( DNSCryptContext *inContext )
{
- const int timeLimitSecs = inContext->timeLimitSecs;
+ OSStatus err;
+ SocketContext * sockCtx;
+ SocketRef sock = kInvalidSocketRef;
- 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: %{du:time}\n", NULL );
- FPrintF( stdout, "---\n" );
+ 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 );
+
+ err = SocketContextCreate( sock, inContext, &sockCtx );
+ require_noerr( err, exit );
+ sock = kInvalidSocketRef;
+
+ err = DispatchReadSourceCreate( sockCtx->sock, NULL, DNSCryptReceiveResponseHandler, SocketContextCancelHandler, sockCtx,
+ &inContext->readSource );
+ if( err ) ForgetSocketContext( &sockCtx );
+ require_noerr( err, exit );
+
+ dispatch_resume( inContext->readSource );
+
+exit:
+ ForgetSocket( &sock );
+ return( err );
}
//===========================================================================================================================
-// DNSQueryReadHandler
+// DNSCryptPrintCertificate
//===========================================================================================================================
-static void DNSQueryReadHandler( void *inContext )
+#define kCertTimeStrBufLen 32
+
+static char * CertTimeStr( time_t inTime, char inBuffer[ kCertTimeStrBufLen ] );
+
+static void DNSCryptPrintCertificate( const DNSCryptCert *inCert, size_t inLen )
{
- OSStatus err;
- struct timeval now;
- const uint64_t nowTicks = UpTicks();
- DNSQueryContext * const context = (DNSQueryContext *) inContext;
+ time_t startTime, endTime;
+ int extLen;
+ char timeBuf[ kCertTimeStrBufLen ];
- gettimeofday( &now, NULL );
+ check( inLen >= kDNSCryptCertMinimumLength );
- if( context->useTCP )
+ 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 )
{
- 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;
+ extLen = (int)( inLen - kDNSCryptCertMinimumLength );
+ FPrintF( stdout, "Extensions: %.1H\n", inCert->extensions, extLen, extLen );
}
- else
+ FPrintF( stdout, "\n" );
+}
+
+static char * CertTimeStr( time_t inTime, char inBuffer[ kCertTimeStrBufLen ] )
+{
+ struct tm * tm;
+
+ tm = localtime( &inTime );
+ if( !tm )
{
- 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 );
+ dlogassert( "localtime() returned a NULL pointer.\n" );
+ *inBuffer = '\0';
}
-
- FPrintF( stdout, "Receive time: %{du:time}\n", &now );
- 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 ) );
- FPrintF( stdout, "%.*{du:dnsmsg}", context->printRawRData ? 1 : 0, context->msgPtr, context->msgLen );
-
- if( ( context->msgLen >= kDNSHeaderLength ) && ( DNSHeaderGetID( (DNSHeader *) context->msgPtr ) == context->queryID ) )
+ else
{
- Exit( kExitReason_ReceivedResponse );
+ strftime( inBuffer, kCertTimeStrBufLen, "%a %b %d %H:%M:%S %Z %Y", tm );
}
-exit:
- if( err ) dispatch_source_forget( &context->readSource );
+ return( inBuffer );
}
+#endif // DNSSDUTIL_INCLUDE_DNSCRYPT
+
//===========================================================================================================================
-// DNSQueryCancelHandler
+// MDNSQueryCmd
//===========================================================================================================================
-static void DNSQueryCancelHandler( void *inContext )
+typedef struct
{
- DNSQueryContext * const context = (DNSQueryContext *) inContext;
+ const char * qnameStr; // Name (QNAME) of the record being queried as a C string.
+ dispatch_source_t readSourceV4; // Read dispatch source for IPv4 socket.
+ dispatch_source_t readSourceV6; // Read dispatch source for IPv6 socket.
+ int localPort; // The port number to which the sockets are bound.
+ int receiveSecs; // After send, the amount of time to spend receiving.
+ uint32_t ifIndex; // Index of the interface over which to send the query.
+ uint16_t qtype; // The type (QTYPE) of the record being queried.
+ Boolean isQU; // True if the query is QU, i.e., requests unicast responses.
+ Boolean allResponses; // True if all mDNS messages received should be printed.
+ Boolean printRawRData; // True if RDATA should be printed as hexdumps.
+ Boolean useIPv4; // True if the query should be sent via IPv4 multicast.
+ Boolean useIPv6; // True if the query should be sent via IPv6 multicast.
+ char ifName[ IF_NAMESIZE + 1 ]; // Name of the interface over which to send the query.
+ uint8_t qname[ kDomainNameLengthMax ]; // Buffer to hold the QNAME in DNS label format.
+ uint8_t msgBuf[ kMDNSMessageSizeMax ]; // mDNS message buffer.
- 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 );
-}
+} MDNSQueryContext;
-#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 MDNSQueryPrintPrologue( const MDNSQueryContext *inContext );
+static void MDNSQueryReadHandler( void *inContext );
-static void DNSCryptCmd( void )
+static void MDNSQueryCmd( void )
{
OSStatus err;
- DNSCryptContext * context = NULL;
- size_t writtenBytes;
- size_t totalBytes;
- SocketContext * sockCtx;
- SocketRef sock = kInvalidSocketRef;
- const char * ptr;
+ MDNSQueryContext * context;
+ SocketRef sockV4 = kInvalidSocketRef;
+ SocketRef sockV6 = kInvalidSocketRef;
+ ssize_t n;
+ const char * ifname;
+ size_t msgLen;
+ unsigned int sendCount;
// Check command parameters.
- if( gDNSCrypt_TimeLimitSecs < -1 )
+ if( gMDNSQuery_ReceiveSecs < -1 )
{
- FPrintF( stdout, "Invalid time limit: %d seconds.\n", gDNSCrypt_TimeLimitSecs );
+ FPrintF( stdout, "Invalid receive time value: %d seconds.\n", gMDNSQuery_ReceiveSecs );
err = kParamErr;
goto exit;
}
- // Create context.
-
- context = (DNSCryptContext *) calloc( 1, sizeof( *context ) );
+ context = (MDNSQueryContext *) 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 );
+ context->qnameStr = gMDNSQuery_Name;
+ context->receiveSecs = gMDNSQuery_ReceiveSecs;
+ context->isQU = gMDNSQuery_IsQU ? true : false;
+ context->allResponses = gMDNSQuery_AllResponses ? true : false;
+ context->printRawRData = gMDNSQuery_RawRData ? true : false;
+ context->useIPv4 = ( gMDNSQuery_UseIPv4 || !gMDNSQuery_UseIPv6 ) ? true : false;
+ context->useIPv6 = ( gMDNSQuery_UseIPv6 || !gMDNSQuery_UseIPv4 ) ? true : false;
- 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 = InterfaceIndexFromArgString( gInterface, &context->ifIndex );
+ require_noerr_quiet( err, exit );
- err = StringToSockAddr( gDNSCrypt_Server, &context->serverAddr, sizeof( context->serverAddr ), NULL );
- require_noerr( err, exit );
- if( SockAddrGetPort( &context->serverAddr ) == 0 ) SockAddrSetPort( &context->serverAddr, kDNSCryptPort );
+ ifname = if_indextoname( context->ifIndex, context->ifName );
+ require_action( ifname, exit, err = kNameErr );
- err = RecordTypeFromArgString( gDNSCrypt_Type, &context->qtype );
+ err = RecordTypeFromArgString( gMDNSQuery_Type, &context->qtype );
require_noerr( err, exit );
- // Write query message.
+ // Set up IPv4 socket.
- 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 );
+ if( context->useIPv4 )
+ {
+ err = CreateMulticastSocket( GetMDNSMulticastAddrV4(),
+ gMDNSQuery_SourcePort ? gMDNSQuery_SourcePort : ( context->isQU ? context->localPort : kMDNSPort ),
+ ifname, context->ifIndex, !context->isQU, &context->localPort, &sockV4 );
+ require_noerr( err, exit );
+ }
- // Create UDP socket.
+ // Set up IPv6 socket.
- err = UDPClientSocketOpen( AF_UNSPEC, &context->serverAddr, 0, -1, NULL, &sock );
- require_noerr( err, exit );
+ if( context->useIPv6 )
+ {
+ err = CreateMulticastSocket( GetMDNSMulticastAddrV6(),
+ gMDNSQuery_SourcePort ? gMDNSQuery_SourcePort : ( context->isQU ? context->localPort : kMDNSPort ),
+ ifname, context->ifIndex, !context->isQU, &context->localPort, &sockV6 );
+ require_noerr( err, exit );
+ }
- // Send DNS query.
+ // Craft mDNS query message.
- context->sendTicks = UpTicks();
- err = SocketWriteAll( sock, context->msgBuf, context->msgLen, 5 );
+ check_compile_time_code( sizeof( context->msgBuf ) >= kDNSQueryMessageMaxLen );
+ err = WriteDNSQueryMessage( context->msgBuf, kDefaultMDNSMessageID, kDefaultMDNSQueryFlags, context->qnameStr,
+ context->qtype, context->isQU ? ( kDNSServiceClass_IN | kQClassUnicastResponseBit ) : kDNSServiceClass_IN, &msgLen );
require_noerr( err, exit );
- err = SocketContextCreate( sock, context, &sockCtx );
- require_noerr( err, exit );
- sock = kInvalidSocketRef;
+ // Print prologue.
- err = DispatchReadSourceCreate( sockCtx->sock, NULL, DNSCryptReceiveCertHandler, SocketContextCancelHandler, sockCtx,
- &context->readSource );
- if( err ) ForgetSocketContext( &sockCtx );
- require_noerr( err, exit );
+ MDNSQueryPrintPrologue( context );
- dispatch_resume( context->readSource );
+ // Send mDNS query message.
- if( context->timeLimitSecs > 0 )
+ sendCount = 0;
+ if( IsValidSocket( sockV4 ) )
{
- dispatch_after_f( dispatch_time_seconds( context->timeLimitSecs ), dispatch_get_main_queue(), kExitReason_Timeout,
- Exit );
+ const struct sockaddr * const mcastAddr4 = GetMDNSMulticastAddrV4();
+
+ n = sendto( sockV4, context->msgBuf, msgLen, 0, mcastAddr4, SockAddrGetSize( mcastAddr4 ) );
+ err = map_socket_value_errno( sockV4, n == (ssize_t) msgLen, n );
+ if( err )
+ {
+ FPrintF( stderr, "*** Failed to send query on IPv4 socket with error %#m\n", err );
+ ForgetSocket( &sockV4 );
+ }
+ else
+ {
+ ++sendCount;
+ }
}
- dispatch_main();
+ if( IsValidSocket( sockV6 ) )
+ {
+ const struct sockaddr * const mcastAddr6 = GetMDNSMulticastAddrV6();
+
+ n = sendto( sockV6, context->msgBuf, msgLen, 0, mcastAddr6, SockAddrGetSize( mcastAddr6 ) );
+ err = map_socket_value_errno( sockV6, n == (ssize_t) msgLen, n );
+ if( err )
+ {
+ FPrintF( stderr, "*** Failed to send query on IPv6 socket with error %#m\n", err );
+ ForgetSocket( &sockV6 );
+ }
+ else
+ {
+ ++sendCount;
+ }
+ }
+ require_action_quiet( sendCount > 0, exit, err = kUnexpectedErr );
-exit:
- if( context ) free( context );
- ForgetSocket( &sock );
- if( err ) exit( 1 );
-}
-
-//===========================================================================================================================
-// DNSCryptReceiveCertHandler
-//===========================================================================================================================
-
-static void DNSCryptReceiveCertHandler( void *inContext )
-{
- OSStatus err;
- struct timeval now;
- const uint64_t nowTicks = UpTicks();
- SocketContext * const sockCtx = (SocketContext *) inContext;
- DNSCryptContext * const context = (DNSCryptContext *) sockCtx->userContext;
- 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 ];
+ // If there's no wait period after the send, then exit.
- gettimeofday( &now, NULL );
+ if( context->receiveSecs == 0 ) goto exit;
- dispatch_source_forget( &context->readSource );
+ // Create dispatch read sources for socket(s).
- err = SocketRecvFrom( sockCtx->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: %{du:time}\n", &now );
- 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 ) );
- FPrintF( stdout, "%.*{du:dnsmsg}", context->printRawRData ? 1 : 0, context->msgBuf, context->msgLen );
-
- 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 );
-
- err = DomainNameFromString( targetName, context->providerName, NULL );
- require_noerr( err, exit );
-
- answerCount = DNSHeaderGetAnswerCount( hdr );
- for( i = 0; i < answerCount; ++i )
+ if( IsValidSocket( sockV4 ) )
{
- uint16_t type;
- uint16_t class;
- uint8_t name[ kDomainNameLengthMax ];
+ SocketContext * sockCtx;
- err = DNSMessageExtractRecord( context->msgBuf, context->msgLen, ptr, name, &type, &class, NULL, &txtPtr, &txtLen,
- &ptr );
+ err = SocketContextCreate( sockV4, context, &sockCtx );
require_noerr( err, exit );
+ sockV4 = kInvalidSocketRef;
- if( ( type == kDNSServiceType_TXT ) && ( class == kDNSServiceClass_IN ) && DomainNameEqual( name, targetName ) )
- {
- break;
- }
+ err = DispatchReadSourceCreate( sockCtx->sock, NULL, MDNSQueryReadHandler, SocketContextCancelHandler, sockCtx,
+ &context->readSourceV4 );
+ if( err ) ForgetSocketContext( &sockCtx );
+ require_noerr( err, exit );
+
+ dispatch_resume( context->readSourceV4 );
}
- if( txtLen < ( 1 + kDNSCryptCertMinimumLength ) )
+ if( IsValidSocket( sockV6 ) )
{
- FPrintF( stderr, "TXT record length is too short (%u < %u)\n", txtLen, kDNSCryptCertMinimumLength + 1 );
- err = kSizeErr;
- goto exit;
+ SocketContext * sockCtx;
+
+ err = SocketContextCreate( sockV6, context, &sockCtx );
+ require_noerr( err, exit );
+ sockV6 = kInvalidSocketRef;
+
+ err = DispatchReadSourceCreate( sockCtx->sock, NULL, MDNSQueryReadHandler, SocketContextCancelHandler, sockCtx,
+ &context->readSourceV6 );
+ if( err ) ForgetSocketContext( &sockCtx );
+ require_noerr( err, exit );
+
+ dispatch_resume( context->readSourceV6 );
}
- if( txtPtr[ 0 ] < kDNSCryptCertMinimumLength )
+
+ if( context->receiveSecs > 0 )
{
- FPrintF( stderr, "TXT record value length is too short (%u < %u)\n", txtPtr[ 0 ], kDNSCryptCertMinimumLength );
- err = kSizeErr;
- goto exit;
+ dispatch_after_f( dispatch_time_seconds( context->receiveSecs ), dispatch_get_main_queue(), kExitReason_Timeout,
+ Exit );
}
-
- context->certLen = txtPtr[ 0 ];
- context->certPtr = &txtPtr[ 1 ];
-
- dispatch_async_f( dispatch_get_main_queue(), context, DNSCryptProceed );
+ dispatch_main();
exit:
- if( err ) Exit( NULL );
+ ForgetSocket( &sockV4 );
+ ForgetSocket( &sockV6 );
+ if( err ) exit( 1 );
}
//===========================================================================================================================
-// DNSCryptReceiveResponseHandler
+// MDNSColliderCmd
//===========================================================================================================================
-static void DNSCryptReceiveResponseHandler( void *inContext )
+static void _MDNSColliderCmdStopHandler( void *inContext, OSStatus inError );
+
+static void MDNSColliderCmd( void )
{
- OSStatus err;
- struct timeval now;
- const uint64_t nowTicks = UpTicks();
- SocketContext * const sockCtx = (SocketContext *) inContext;
- DNSCryptContext * const context = (DNSCryptContext *) sockCtx->userContext;
- sockaddr_ip fromAddr;
- DNSCryptResponseHeader * hdr;
- const uint8_t * end;
- uint8_t * ciphertext;
- uint8_t * plaintext;
- const uint8_t * response;
- uint8_t nonce[ crypto_box_NONCEBYTES ];
-
- gettimeofday( &now, NULL );
-
- dispatch_source_forget( &context->readSource );
-
- err = SocketRecvFrom( sockCtx->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 );
+ OSStatus err;
+ MDNSColliderRef collider = NULL;
+ uint8_t * rdataPtr = NULL;
+ size_t rdataLen = 0;
+ const char * ifname;
+ uint32_t ifIndex;
+ MDNSColliderProtocols protocols;
+ uint16_t type;
+ char ifName[ IF_NAMESIZE + 1 ];
+ uint8_t name[ kDomainNameLengthMax ];
- FPrintF( stdout, "Receive time: %{du:time}\n", &now );
- 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 ) );
+ err = InterfaceIndexFromArgString( gInterface, &ifIndex );
+ require_noerr_quiet( err, exit );
- if( context->msgLen < sizeof( DNSCryptResponseHeader ) )
+ ifname = if_indextoname( ifIndex, ifName );
+ if( !ifname )
{
- FPrintF( stderr, "DNSCrypt response is too short.\n" );
- err = kSizeErr;
+ FPrintF( stderr, "error: Invalid interface name or index: %s\n", gInterface );
+ err = kNameErr;
goto exit;
}
- hdr = (DNSCryptResponseHeader *) context->msgBuf;
-
- if( memcmp( hdr->resolverMagic, kDNSCryptResolverMagic, kDNSCryptResolverMagicLength ) != 0 )
+ err = DomainNameFromString( name, gMDNSCollider_Name, NULL );
+ if( err )
{
- FPrintF( stderr, "DNSCrypt response resolver magic %#H != %#H\n",
- hdr->resolverMagic, kDNSCryptResolverMagicLength, INT_MAX,
- kDNSCryptResolverMagic, kDNSCryptResolverMagicLength, INT_MAX );
- err = kValueErr;
+ FPrintF( stderr, "error: Invalid record name: %s\n", gMDNSCollider_Name );
goto exit;
}
- if( memcmp( hdr->clientNonce, context->clientNonce, kDNSCryptHalfNonceLength ) != 0 )
+ err = RecordTypeFromArgString( gMDNSCollider_Type, &type );
+ require_noerr_quiet( err, exit );
+
+ if( gMDNSCollider_RecordData )
{
- FPrintF( stderr, "DNSCrypt response client nonce mismatch.\n" );
- err = kValueErr;
- goto exit;
+ err = RecordDataFromArgString( gMDNSCollider_RecordData, &rdataPtr, &rdataLen );
+ require_noerr_quiet( err, exit );
}
- memcpy( nonce, hdr->clientNonce, crypto_box_NONCEBYTES );
+ err = MDNSColliderCreate( dispatch_get_main_queue(), &collider );
+ require_noerr( err, exit );
- ciphertext = hdr->poly1305MAC - crypto_box_BOXZEROBYTES;
- memset( ciphertext, 0, crypto_box_BOXZEROBYTES );
+ err = MDNSColliderSetProgram( collider, gMDNSCollider_Program );
+ if( err )
+ {
+ FPrintF( stderr, "error: Failed to set program string: '%s'\n", gMDNSCollider_Program );
+ goto exit;
+ }
- plaintext = (uint8_t *)( hdr + 1 ) - crypto_box_ZEROBYTES;
- check( plaintext == ciphertext );
+ err = MDNSColliderSetRecord( collider, name, type, rdataPtr, rdataLen );
+ require_noerr( err, exit );
+ ForgetMem( &rdataPtr );
- end = context->msgBuf + context->msgLen;
+ protocols = kMDNSColliderProtocol_None;
+ if( gMDNSCollider_UseIPv4 || !gMDNSCollider_UseIPv6 ) protocols |= kMDNSColliderProtocol_IPv4;
+ if( gMDNSCollider_UseIPv6 || !gMDNSCollider_UseIPv4 ) protocols |= kMDNSColliderProtocol_IPv6;
+ MDNSColliderSetProtocols( collider, protocols );
+ MDNSColliderSetInterfaceIndex( collider, ifIndex );
+ MDNSColliderSetStopHandler( collider, _MDNSColliderCmdStopHandler, collider );
- err = crypto_box_open_afternm( plaintext, ciphertext, (size_t)( end - ciphertext ), nonce, context->nmKey );
+ err = MDNSColliderStart( collider );
require_noerr( err, exit );
- response = plaintext + crypto_box_ZEROBYTES;
- FPrintF( stdout, "%.*{du:dnsmsg}", context->printRawRData ? 1 : 0, response, (size_t)( end - response ) );
- Exit( kExitReason_ReceivedResponse );
+ dispatch_main();
exit:
- if( err ) Exit( NULL );
+ FreeNullSafe( rdataPtr );
+ CFReleaseNullSafe( collider );
+ if( err ) exit( 1 );
+}
+
+static void _MDNSColliderCmdStopHandler( void *inContext, OSStatus inError )
+{
+ MDNSColliderRef const collider = (MDNSColliderRef) inContext;
+
+ CFRelease( collider );
+ exit( inError ? 1 : 0 );
}
//===========================================================================================================================
-// DNSCryptProceed
+// MDNSQueryPrintPrologue
//===========================================================================================================================
-static void DNSCryptProceed( void *inContext )
+static void MDNSQueryPrintPrologue( const MDNSQueryContext *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 );
+ const int receiveSecs = inContext->receiveSecs;
-exit:
- if( err ) Exit( NULL );
+ FPrintF( stdout, "Interface: %d (%s)\n", (int32_t) inContext->ifIndex, inContext->ifName );
+ FPrintF( stdout, "Name: %s\n", inContext->qnameStr );
+ FPrintF( stdout, "Type: %s (%u)\n", RecordTypeToString( inContext->qtype ), inContext->qtype );
+ FPrintF( stdout, "Class: IN (%s)\n", inContext->isQU ? "QU" : "QM" );
+ FPrintF( stdout, "Local port: %d\n", inContext->localPort );
+ FPrintF( stdout, "IP protocols: %?s%?s%?s\n",
+ inContext->useIPv4, "IPv4", ( inContext->useIPv4 && inContext->useIPv6 ), ", ", inContext->useIPv6, "IPv6" );
+ FPrintF( stdout, "Receive duration: " );
+ if( receiveSecs >= 0 ) FPrintF( stdout, "%d second%?c\n", receiveSecs, receiveSecs != 1, 's' );
+ else FPrintF( stdout, "â\n" );
+ FPrintF( stdout, "Start time: %{du:time}\n", NULL );
}
//===========================================================================================================================
-// DNSCryptProcessCert
+// MDNSQueryReadHandler
//===========================================================================================================================
-static OSStatus DNSCryptProcessCert( DNSCryptContext *inContext )
+static void MDNSQueryReadHandler( void *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 );
+ SocketContext * const sockCtx = (SocketContext *) inContext;
+ MDNSQueryContext * const context = (MDNSQueryContext *) sockCtx->userContext;
+ size_t msgLen;
+ sockaddr_ip fromAddr;
+ Boolean foundAnswer = false;
- 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;
- }
+ gettimeofday( &now, NULL );
- startTimeSecs = (time_t) ReadBig32( cert->startTime );
- endTimeSecs = (time_t) ReadBig32( cert->endTime );
+ err = SocketRecvFrom( sockCtx->sock, context->msgBuf, sizeof( context->msgBuf ), &msgLen, &fromAddr,
+ sizeof( fromAddr ), NULL, NULL, NULL, NULL );
+ require_noerr( err, exit );
- gettimeofday( &now, NULL );
- if( now.tv_sec < startTimeSecs )
+ if( !context->allResponses && ( msgLen >= kDNSHeaderLength ) )
{
- FPrintF( stderr, "DNSCrypt certificate start time is in the future.\n" );
- err = kDateErr;
- goto exit;
+ const uint8_t * ptr;
+ const DNSHeader * const hdr = (DNSHeader *) context->msgBuf;
+ unsigned int rrCount, i;
+ uint16_t type, class;
+ uint8_t name[ kDomainNameLengthMax ];
+
+ err = DNSMessageGetAnswerSection( context->msgBuf, msgLen, &ptr );
+ require_noerr( err, exit );
+
+ if( context->qname[ 0 ] == 0 )
+ {
+ err = DomainNameAppendString( context->qname, context->qnameStr, NULL );
+ require_noerr( err, exit );
+ }
+
+ rrCount = DNSHeaderGetAnswerCount( hdr ) + DNSHeaderGetAuthorityCount( hdr ) + DNSHeaderGetAdditionalCount( hdr );
+ for( i = 0; i < rrCount; ++i )
+ {
+ err = DNSMessageExtractRecord( context->msgBuf, msgLen, ptr, name, &type, &class, NULL, NULL, NULL, &ptr );
+ require_noerr( err, exit );
+
+ if( ( ( context->qtype == kDNSServiceType_ANY ) || ( type == context->qtype ) ) &&
+ DomainNameEqual( name, context->qname ) )
+ {
+ foundAnswer = true;
+ break;
+ }
+ }
}
- if( now.tv_sec >= endTimeSecs )
+ if( context->allResponses || foundAnswer )
{
- FPrintF( stderr, "DNSCrypt certificate has expired.\n" );
- err = kDateErr;
- goto exit;
+ FPrintF( stdout, "---\n" );
+ FPrintF( stdout, "Receive time: %{du:time}\n", &now );
+ FPrintF( stdout, "Source: %##a\n", &fromAddr );
+ FPrintF( stdout, "Message size: %zu\n\n%#.*{du:dnsmsg}",
+ msgLen, context->printRawRData ? 1 : 0, context->msgBuf, msgLen );
}
- 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 );
+ if( err ) exit( 1 );
}
//===========================================================================================================================
-// DNSCryptBuildQuery
+// PIDToUUIDCmd
//===========================================================================================================================
-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 )
+static void PIDToUUIDCmd( void )
{
- 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;
+ OSStatus err;
+ int n;
+ struct proc_uniqidentifierinfo info;
- inMsgPtr[ inMsgLen ] = 0x80;
- memset( &inMsgPtr[ inMsgLen + 1 ], 0, paddedLen - ( inMsgLen + 1 ) );
+ n = proc_pidinfo( gPIDToUUID_PID, PROC_PIDUNIQIDENTIFIERINFO, 1, &info, sizeof( info ) );
+ require_action_quiet( n == (int) sizeof( info ), exit, err = kUnknownErr );
- if( outPaddedLen ) *outPaddedLen = paddedLen;
+ FPrintF( stdout, "%#U\n", info.p_uuid );
err = kNoErr;
exit:
- return( err );
+ if( err ) exit( 1 );
}
//===========================================================================================================================
-// DNSCryptSendQuery
+// DNSServerCmd
//===========================================================================================================================
-static OSStatus DNSCryptSendQuery( DNSCryptContext *inContext )
+typedef struct DNSServerPrivate * DNSServerRef;
+
+typedef struct
{
- OSStatus err;
- SocketContext * sockCtx;
- 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 );
-
- err = SocketContextCreate( sock, inContext, &sockCtx );
- require_noerr( err, exit );
- sock = kInvalidSocketRef;
-
- err = DispatchReadSourceCreate( sockCtx->sock, NULL, DNSCryptReceiveResponseHandler, SocketContextCancelHandler, sockCtx,
- &inContext->readSource );
- if( err ) ForgetSocketContext( &sockCtx );
- require_noerr( err, exit );
+ DNSServerRef server; // Reference to the DNS server.
+ dispatch_source_t sigIntSource; // Dispatch SIGINT source.
+ dispatch_source_t sigTermSource; // Dispatch SIGTERM source.
+ const char * domainOverride; // If non-NULL, the server is to use this domain instead of "d.test.".
+#if( TARGET_OS_DARWIN )
+ dispatch_source_t processMonitor; // Process monitor source for process being followed, if any.
+ pid_t followPID; // PID of process being followed, if any. (If it exits, we exit).
+ Boolean addedResolver; // True if system DNS settings contains a resolver entry for server.
+#endif
+ Boolean loopbackOnly; // True if the server should be bound to the loopback interface.
- dispatch_resume( inContext->readSource );
+} DNSServerCmdContext;
+
+typedef enum
+{
+ kDNSServerEvent_Started = 1,
+ kDNSServerEvent_Stopped = 2
-exit:
- ForgetSocket( &sock );
- return( err );
-}
+} DNSServerEventType;
-//===========================================================================================================================
-// DNSCryptPrintCertificate
-//===========================================================================================================================
+typedef void ( *DNSServerEventHandler_f )( DNSServerEventType inType, uintptr_t inEventData, void *inContext );
-#define kCertTimeStrBufLen 32
+CFTypeID DNSServerGetTypeID( void );
+static OSStatus
+ DNSServerCreate(
+ dispatch_queue_t inQueue,
+ DNSServerEventHandler_f inEventHandler,
+ void * inEventContext,
+ unsigned int inResponseDelayMs,
+ uint32_t inDefaultTTL,
+ int inPort,
+ Boolean inLoopbackOnly,
+ const char * inDomain,
+ Boolean inBadUDPMode,
+ DNSServerRef * outServer );
+static void DNSServerStart( DNSServerRef inServer );
+static void DNSServerStop( DNSServerRef inServer );
-static char * CertTimeStr( time_t inTime, char inBuffer[ kCertTimeStrBufLen ] );
+#define ForgetDNSServer( X ) ForgetCustomEx( X, DNSServerStop, CFRelease )
-static void DNSCryptPrintCertificate( const DNSCryptCert *inCert, size_t inLen )
+static void DNSServerCmdContextFree( DNSServerCmdContext *inContext );
+static void DNSServerCmdEventHandler( DNSServerEventType inType, uintptr_t inEventData, void *inContext );
+static void DNSServerCmdSigIntHandler( void *inContext );
+static void DNSServerCmdSigTermHandler( void *inContext );
+#if( TARGET_OS_DARWIN )
+static void DNSServerCmdFollowedProcessHandler( void *inContext );
+#endif
+
+ulog_define_ex( "com.apple.dnssdutil", DNSServer, kLogLevelInfo, kLogFlags_None, "DNSServer", NULL );
+#define ds_ulog( LEVEL, ... ) ulog( &log_category_from_name( DNSServer ), (LEVEL), __VA_ARGS__ )
+
+static void DNSServerCmd( void )
{
- time_t startTime, endTime;
- int extLen;
- char timeBuf[ kCertTimeStrBufLen ];
+ OSStatus err;
+ DNSServerCmdContext * context = NULL;
- check( inLen >= kDNSCryptCertMinimumLength );
+ if( gDNSServer_Foreground )
+ {
+ LogControl( "DNSServer:output=file;stdout,DNSServer:flags=time;prefix" );
+ }
- startTime = (time_t) ReadBig32( inCert->startTime );
- endTime = (time_t) ReadBig32( inCert->endTime );
+ err = CheckIntegerArgument( gDNSServer_ResponseDelayMs, "response delay (ms)", 0, INT_MAX );
+ require_noerr_quiet( err, exit );
- 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 ) );
+ err = CheckIntegerArgument( gDNSServer_DefaultTTL, "default TTL", 0, INT32_MAX );
+ require_noerr_quiet( err, exit );
- 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;
+ err = CheckIntegerArgument( gDNSServer_Port, "port number", -UINT16_MAX, UINT16_MAX );
+ require_noerr_quiet( err, exit );
- tm = localtime( &inTime );
- if( !tm )
+ context = (DNSServerCmdContext *) calloc( 1, sizeof( *context ) );
+ require_action( context, exit, err = kNoMemoryErr );
+
+ context->domainOverride = gDNSServer_DomainOverride;
+ context->loopbackOnly = gDNSServer_LoopbackOnly ? true : false;
+
+#if( TARGET_OS_DARWIN )
+ if( gDNSServer_FollowPID )
{
- dlogassert( "localtime() returned a NULL pointer.\n" );
- *inBuffer = '\0';
+ err = StringToPID( gDNSServer_FollowPID, &context->followPID );
+ if( err || ( context->followPID < 0 ) )
+ {
+ FPrintF( stderr, "error: Invalid follow PID: %s\n", gDNSServer_FollowPID );
+ err = kParamErr;
+ goto exit;
+ }
+
+ err = DispatchProcessMonitorCreate( context->followPID, DISPATCH_PROC_EXIT, dispatch_get_main_queue(),
+ DNSServerCmdFollowedProcessHandler, NULL, context, &context->processMonitor );
+ require_noerr( err, exit );
+ dispatch_resume( context->processMonitor );
}
else
{
- strftime( inBuffer, kCertTimeStrBufLen, "%a %b %d %H:%M:%S %Z %Y", tm );
+ context->followPID = -1;
}
+#endif
- return( inBuffer );
+ signal( SIGINT, SIG_IGN );
+ err = DispatchSignalSourceCreate( SIGINT, DNSServerCmdSigIntHandler, context, &context->sigIntSource );
+ require_noerr( err, exit );
+ dispatch_resume( context->sigIntSource );
+
+ signal( SIGTERM, SIG_IGN );
+ err = DispatchSignalSourceCreate( SIGTERM, DNSServerCmdSigTermHandler, context, &context->sigTermSource );
+ require_noerr( err, exit );
+ dispatch_resume( context->sigTermSource );
+
+ err = DNSServerCreate( dispatch_get_main_queue(), DNSServerCmdEventHandler, context,
+ (unsigned int) gDNSServer_ResponseDelayMs, (uint32_t) gDNSServer_DefaultTTL, gDNSServer_Port, context->loopbackOnly,
+ context->domainOverride, gDNSServer_BadUDPMode ? true : false, &context->server );
+ require_noerr( err, exit );
+
+ DNSServerStart( context->server );
+ dispatch_main();
+
+exit:
+ FPrintF( stderr, "Failed to start DNS server: %#m\n", err );
+ if( context ) DNSServerCmdContextFree( context );
+ if( err ) exit( 1 );
}
-#endif // DNSSDUTIL_INCLUDE_DNSCRYPT
+//===========================================================================================================================
+// DNSServerCmdContextFree
+//===========================================================================================================================
+
+static void DNSServerCmdContextFree( DNSServerCmdContext *inContext )
+{
+ ForgetCF( &inContext->server );
+ dispatch_source_forget( &inContext->sigIntSource );
+ dispatch_source_forget( &inContext->sigTermSource );
+#if( TARGET_OS_DARWIN )
+ dispatch_source_forget( &inContext->processMonitor );
+#endif
+ free( inContext );
+}
//===========================================================================================================================
-// MDNSQueryCmd
+// DNSServerCmdEventHandler
//===========================================================================================================================
-typedef struct
+#if( TARGET_OS_DARWIN )
+static OSStatus _DNSServerCmdLoopbackResolverAdd( const char *inDomain, int inPort );
+static OSStatus _DNSServerCmdLoopbackResolverRemove( void );
+#endif
+
+static void DNSServerCmdEventHandler( DNSServerEventType inType, uintptr_t inEventData, void *inContext )
{
- const char * qnameStr; // Name (QNAME) of the record being queried as a C string.
- dispatch_source_t readSourceV4; // Read dispatch source for IPv4 socket.
- dispatch_source_t readSourceV6; // Read dispatch source for IPv6 socket.
- int localPort; // The port number to which the sockets are bound.
- int receiveSecs; // After send, the amount of time to spend receiving.
- uint32_t ifIndex; // Index of the interface over which to send the query.
- uint16_t qtype; // The type (QTYPE) of the record being queried.
- Boolean isQU; // True if the query is QU, i.e., requests unicast responses.
- Boolean allResponses; // True if all mDNS messages received should be printed.
- Boolean printRawRData; // True if RDATA should be printed as hexdumps.
- Boolean useIPv4; // True if the query should be sent via IPv4 multicast.
- Boolean useIPv6; // True if the query should be sent via IPv6 multicast.
- char ifName[ IF_NAMESIZE + 1 ]; // Name of the interface over which to send the query.
- uint8_t qname[ kDomainNameLengthMax ]; // Buffer to hold the QNAME in DNS label format.
- uint8_t msgBuf[ 8940 ]; // Message buffer. 8940 is max size used by mDNSResponder.
-
-} 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;
- size_t msgLen;
- unsigned int sendCount;
-
- // Check command parameters.
-
- if( gMDNSQuery_ReceiveSecs < -1 )
- {
- FPrintF( stdout, "Invalid receive time value: %d seconds.\n", gMDNSQuery_ReceiveSecs );
- err = kParamErr;
- goto exit;
- }
-
- context = (MDNSQueryContext *) calloc( 1, sizeof( *context ) );
- require_action( context, exit, err = kNoMemoryErr );
-
- context->qnameStr = gMDNSQuery_Name;
- context->receiveSecs = gMDNSQuery_ReceiveSecs;
- context->isQU = gMDNSQuery_IsQU ? true : false;
- context->allResponses = gMDNSQuery_AllResponses ? true : false;
- context->printRawRData = gMDNSQuery_RawRData ? 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->qtype );
- 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( 0xE00000FB ); // The mDNS IPv4 multicast address is 224.0.0.251
-
- if( !context->isQU && ( context->localPort == kMDNSPort ) )
- {
- err = SocketJoinMulticast( sockV4, &mcastAddr4, ifNamePtr, context->ifIndex );
- require_noerr( err, exit );
- }
- }
-
- // Set up IPv6 socket.
+ OSStatus err;
+ DNSServerCmdContext * const context = (DNSServerCmdContext *) inContext;
- if( context->useIPv6 )
+ if( inType == kDNSServerEvent_Started )
{
- 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( TARGET_OS_DARWIN )
+ const int port = (int) inEventData;
- if( !context->isQU && ( context->localPort == kMDNSPort ) )
- {
- err = SocketJoinMulticast( sockV6, &mcastAddr6, ifNamePtr, context->ifIndex );
- require_noerr( err, exit );
- }
- }
-
- // Craft mDNS query message.
-
- check_compile_time_code( sizeof( context->msgBuf ) >= kDNSQueryMessageMaxLen );
- err = WriteDNSQueryMessage( context->msgBuf, kDefaultMDNSMessageID, kDefaultMDNSQueryFlags, context->qnameStr,
- context->qtype, context->isQU ? ( kDNSServiceClass_IN | kQClassUnicastResponseBit ) : kDNSServiceClass_IN, &msgLen );
- require_noerr( err, exit );
-
- // Print prologue.
-
- MDNSQueryPrintPrologue( context );
-
- // Send mDNS query message.
-
- sendCount = 0;
- if( IsValidSocket( sockV4 ) )
- {
- 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 );
+ err = _DNSServerCmdLoopbackResolverAdd( context->domainOverride ? context->domainOverride : "d.test.", port );
if( err )
{
- FPrintF( stderr, "*** Failed to send query on IPv4 socket with error %#m\n", err );
- ForgetSocket( &sockV4 );
+ ds_ulog( kLogLevelError, "Failed to add loopback resolver to DNS configuration for \"d.test.\" domain: %#m\n",
+ err );
+ if( context->loopbackOnly ) ForgetDNSServer( &context->server );
}
else
{
- ++sendCount;
+ context->addedResolver = true;
}
+ #endif
}
- if( IsValidSocket( sockV6 ) )
+ else if( inType == kDNSServerEvent_Stopped )
{
- 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 );
- if( err )
+ const OSStatus stopError = (OSStatus) inEventData;
+
+ if( stopError ) ds_ulog( kLogLevelError, "The server stopped unexpectedly with error: %#m.\n", stopError );
+
+ err = kNoErr;
+ #if( TARGET_OS_DARWIN )
+ if( context->addedResolver )
{
- FPrintF( stderr, "*** Failed to send query on IPv6 socket with error %#m\n", err );
- ForgetSocket( &sockV6 );
+ err = _DNSServerCmdLoopbackResolverRemove();
+ if( err )
+ {
+ ds_ulog( kLogLevelError, "Failed to remove loopback resolver from DNS configuration: %#m\n", err );
+ }
+ else
+ {
+ context->addedResolver = false;
+ }
}
- else
+ else if( context->loopbackOnly )
{
- ++sendCount;
+ err = kUnknownErr;
}
+ #endif
+ DNSServerCmdContextFree( context );
+ exit( ( stopError || err ) ? 1 : 0 );
}
- require_action_quiet( sendCount > 0, exit, err = kUnexpectedErr );
+}
+
+#if( TARGET_OS_DARWIN )
+//===========================================================================================================================
+// _DNSServerCmdLoopbackResolverAdd
+//===========================================================================================================================
+
+static OSStatus _DNSServerCmdLoopbackResolverAdd( const char *inDomain, int inPort )
+{
+ OSStatus err;
+ SCDynamicStoreRef store;
+ CFPropertyListRef plist = NULL;
+ CFStringRef key = NULL;
+ const uint32_t loopbackV4 = htonl( INADDR_LOOPBACK );
+ Boolean success;
- // If there's no wait period after the send, then exit.
+ store = SCDynamicStoreCreate( NULL, CFSTR( "com.apple.dnssdutil" ), NULL, NULL );
+ err = map_scerror( store );
+ require_noerr( err, exit );
- if( context->receiveSecs == 0 ) goto exit;
+ err = CFPropertyListCreateFormatted( kCFAllocatorDefault, &plist,
+ "{"
+ "%kO="
+ "["
+ "%s"
+ "]"
+ "%kO="
+ "["
+ "%.4a"
+ "%.16a"
+ "]"
+ "%kO=%i"
+ "}",
+ kSCPropNetDNSSupplementalMatchDomains, inDomain,
+ kSCPropNetDNSServerAddresses, &loopbackV4, in6addr_loopback.s6_addr,
+ kSCPropNetDNSServerPort, inPort );
+ require_noerr( err, exit );
- // Create dispatch read sources for socket(s).
+ key = SCDynamicStoreKeyCreateNetworkServiceEntity( NULL, kSCDynamicStoreDomainState,
+ CFSTR( "com.apple.dnssdutil.server" ), kSCEntNetDNS );
+ require_action( key, exit, err = kUnknownErr );
- if( IsValidSocket( sockV4 ) )
- {
- SocketContext * sockCtx;
-
- err = SocketContextCreate( sockV4, context, &sockCtx );
- require_noerr( err, exit );
- sockV4 = kInvalidSocketRef;
-
- err = DispatchReadSourceCreate( sockCtx->sock, NULL, MDNSQueryReadHandler, SocketContextCancelHandler, sockCtx,
- &context->readSourceV4 );
- if( err ) ForgetSocketContext( &sockCtx );
- require_noerr( err, exit );
-
- dispatch_resume( context->readSourceV4 );
- }
+ success = SCDynamicStoreSetValue( store, key, plist );
+ require_action( success, exit, err = kUnknownErr );
- if( IsValidSocket( sockV6 ) )
- {
- SocketContext * sockCtx;
-
- err = SocketContextCreate( sockV6, context, &sockCtx );
- require_noerr( err, exit );
- sockV6 = kInvalidSocketRef;
-
- err = DispatchReadSourceCreate( sockCtx->sock, NULL, MDNSQueryReadHandler, SocketContextCancelHandler, sockCtx,
- &context->readSourceV6 );
- if( err ) ForgetSocketContext( &sockCtx );
- require_noerr( err, exit );
-
- dispatch_resume( context->readSourceV6 );
- }
+exit:
+ CFReleaseNullSafe( store );
+ CFReleaseNullSafe( plist );
+ CFReleaseNullSafe( key );
+ return( err );
+}
+
+//===========================================================================================================================
+// _DNSServerCmdLoopbackResolverRemove
+//===========================================================================================================================
+
+static OSStatus _DNSServerCmdLoopbackResolverRemove( void )
+{
+ OSStatus err;
+ SCDynamicStoreRef store;
+ CFStringRef key = NULL;
+ Boolean success;
- if( context->receiveSecs > 0 )
- {
- dispatch_after_f( dispatch_time_seconds( context->receiveSecs ), dispatch_get_main_queue(), kExitReason_Timeout,
- Exit );
- }
- dispatch_main();
+ store = SCDynamicStoreCreate( NULL, CFSTR( "com.apple.dnssdutil" ), NULL, NULL );
+ err = map_scerror( store );
+ require_noerr( err, exit );
+
+ key = SCDynamicStoreKeyCreateNetworkServiceEntity( NULL, kSCDynamicStoreDomainState,
+ CFSTR( "com.apple.dnssdutil.server" ), kSCEntNetDNS );
+ require_action( key, exit, err = kUnknownErr );
+
+ success = SCDynamicStoreRemoveValue( store, key );
+ require_action( success, exit, err = kUnknownErr );
exit:
- ForgetSocket( &sockV4 );
- ForgetSocket( &sockV6 );
- if( err ) exit( 1 );
+ CFReleaseNullSafe( store );
+ CFReleaseNullSafe( key );
+ return( err );
}
+#endif
//===========================================================================================================================
-// MDNSQueryPrintPrologue
+// DNSServerCmdSigIntHandler
//===========================================================================================================================
-static void MDNSQueryPrintPrologue( const MDNSQueryContext *inContext )
+static void _DNSServerCmdShutdown( DNSServerCmdContext *inContext, int inSignal );
+
+static void DNSServerCmdSigIntHandler( void *inContext )
{
- const int receiveSecs = inContext->receiveSecs;
-
- FPrintF( stdout, "Interface: %d (%s)\n", (int32_t) inContext->ifIndex, inContext->ifName );
- FPrintF( stdout, "Name: %s\n", inContext->qnameStr );
- FPrintF( stdout, "Type: %s (%u)\n", RecordTypeToString( inContext->qtype ), inContext->qtype );
- FPrintF( stdout, "Class: IN (%s)\n", inContext->isQU ? "QU" : "QM" );
- FPrintF( stdout, "Local port: %d\n", inContext->localPort );
- FPrintF( stdout, "IP protocols: %?s%?s%?s\n",
- inContext->useIPv4, "IPv4", ( inContext->useIPv4 && inContext->useIPv6 ), ", ", inContext->useIPv6, "IPv6" );
- FPrintF( stdout, "Receive duration: " );
- if( receiveSecs >= 0 ) FPrintF( stdout, "%d second%?c\n", receiveSecs, receiveSecs != 1, 's' );
- else FPrintF( stdout, "â\n" );
- FPrintF( stdout, "Start time: %{du:time}\n", NULL );
+ _DNSServerCmdShutdown( (DNSServerCmdContext *) inContext, SIGINT );
}
//===========================================================================================================================
-// MDNSQueryReadHandler
+// DNSServerCmdSigTermHandler
//===========================================================================================================================
-static void MDNSQueryReadHandler( void *inContext )
+static void DNSServerCmdSigTermHandler( void *inContext )
{
- OSStatus err;
- struct timeval now;
- SocketContext * const sockCtx = (SocketContext *) inContext;
- MDNSQueryContext * const context = (MDNSQueryContext *) sockCtx->userContext;
- size_t msgLen;
- sockaddr_ip fromAddr;
- Boolean foundAnswer = false;
-
- gettimeofday( &now, NULL );
-
- err = SocketRecvFrom( sockCtx->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 name[ kDomainNameLengthMax ];
-
- err = DNSMessageGetAnswerSection( context->msgBuf, msgLen, &ptr );
- require_noerr( err, exit );
-
- if( context->qname[ 0 ] == 0 )
- {
- err = DomainNameAppendString( context->qname, context->qnameStr, NULL );
- require_noerr( err, exit );
- }
-
- rrCount = DNSHeaderGetAnswerCount( hdr ) + DNSHeaderGetAuthorityCount( hdr ) + DNSHeaderGetAdditionalCount( hdr );
- for( i = 0; i < rrCount; ++i )
- {
- err = DNSMessageExtractRecord( context->msgBuf, msgLen, ptr, name, &type, &class, NULL, NULL, NULL, &ptr );
- require_noerr( err, exit );
-
- if( ( ( context->qtype == kDNSServiceType_ANY ) || ( type == context->qtype ) ) &&
- DomainNameEqual( name, context->qname ) )
- {
- foundAnswer = true;
- break;
- }
- }
- }
- if( context->allResponses || foundAnswer )
- {
- FPrintF( stdout, "---\n" );
- FPrintF( stdout, "Receive time: %{du:time}\n", &now );
- FPrintF( stdout, "Source: %##a\n", &fromAddr );
- FPrintF( stdout, "Message size: %zu\n\n%#.*{du:dnsmsg}",
- msgLen, context->printRawRData ? 1 : 0, context->msgBuf, msgLen );
- }
-
-exit:
- if( err ) exit( 1 );
+ _DNSServerCmdShutdown( (DNSServerCmdContext *) inContext, SIGTERM );
}
+#if( TARGET_OS_DARWIN )
//===========================================================================================================================
-// PIDToUUIDCmd
+// DNSServerCmdFollowedProcessHandler
//===========================================================================================================================
-static void PIDToUUIDCmd( void )
+static void DNSServerCmdFollowedProcessHandler( void *inContext )
{
- 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;
+ DNSServerCmdContext * const context = (DNSServerCmdContext *) inContext;
-exit:
- if( err ) exit( 1 );
+ if( dispatch_source_get_data( context->processMonitor ) & DISPATCH_PROC_EXIT ) _DNSServerCmdShutdown( context, 0 );
}
+#endif
//===========================================================================================================================
-// DNSServerCmd
+// _DNSServerCmdExternalExit
//===========================================================================================================================
-typedef uint32_t DNSServerEventType;
-#define kDNSServerEvent_Started 1
-#define kDNSServerEvent_Stopped 2
-
-typedef struct DNSServerPrivate * DNSServerRef;
+#define SignalNumberToString( X ) ( \
+ ( (X) == SIGINT ) ? "SIGINT" : \
+ ( (X) == SIGTERM ) ? "SIGTERM" : \
+ "???" )
-typedef struct
+static void _DNSServerCmdShutdown( DNSServerCmdContext *inContext, int inSignal )
{
- DNSServerRef server; // Reference to the DNS server.
- dispatch_source_t sigIntSource; // Dispatch SIGINT source.
- dispatch_source_t sigTermSource; // Dispatch SIGTERM source.
+ dispatch_source_forget( &inContext->sigIntSource );
+ dispatch_source_forget( &inContext->sigTermSource );
#if( TARGET_OS_DARWIN )
- dispatch_source_t processMonitor; // Process monitor source for process being followed, if any.
- pid_t followPID; // PID of process being followed (we exit when they exit), if any.
- Boolean resolverRegistered; // True if system DNS settings contains a resolver entry for server.
+ dispatch_source_forget( &inContext->processMonitor );
+
+ if( inSignal == 0 )
+ {
+ ds_ulog( kLogLevelNotice, "Exiting: followed process (%lld) exited\n", (int64_t) inContext->followPID );
+ }
+ else
#endif
- Boolean loopbackOnly; // True if the server should be bound to the loopback interface.
- Boolean serverStarted; // True if the server was successfully started.
- Boolean calledStop; // True if the server was explicitly stopped.
+ {
+ ds_ulog( kLogLevelNotice, "Exiting: received signal %d (%s)\n", inSignal, SignalNumberToString( inSignal ) );
+ }
-} DNSServerCmdContext;
+ ForgetDNSServer( &inContext->server );
+}
-typedef void ( *DNSServerEventHandler_f )( DNSServerEventType inType, void *inContext );
+//===========================================================================================================================
+// DNSServerCreate
+//===========================================================================================================================
+
+#define kDDotTestDomainName (const uint8_t *) "\x01" "d" "\x04" "test"
+
+typedef struct DNSDelayedResponse DNSDelayedResponse;
+struct DNSDelayedResponse
+{
+ DNSDelayedResponse * next;
+ sockaddr_ip destAddr;
+ uint64_t targetTicks;
+ uint8_t * msgPtr;
+ size_t msgLen;
+};
+
+struct DNSServerPrivate
+{
+ CFRuntimeBase base; // CF object base.
+ uint8_t * domain; // Parent domain of server's resource records.
+ dispatch_queue_t queue; // Queue for DNS server's events.
+ dispatch_source_t readSourceUDPv4; // Read source for IPv4 UDP socket.
+ dispatch_source_t readSourceUDPv6; // Read source for IPv6 UDP socket.
+ dispatch_source_t readSourceTCPv4; // Read source for IPv4 TCP socket.
+ dispatch_source_t readSourceTCPv6; // Read source for IPv6 TCP socket.
+ SocketRef sockUDPv4;
+ SocketRef sockUDPv6;
+ DNSServerEventHandler_f eventHandler;
+ void * eventContext;
+ DNSDelayedResponse * responseList;
+ dispatch_source_t responseTimer;
+ unsigned int responseDelayMs;
+ uint32_t defaultTTL;
+ uint32_t serial; // Serial number for SOA record.
+ int port; // Port to use for receiving and sending DNS messages.
+ OSStatus stopError;
+ Boolean stopped;
+ Boolean loopbackOnly;
+ Boolean badUDPMode; // True if the server runs in Bad UDP mode.
+};
+
+static void _DNSServerUDPReadHandler( void *inContext );
+static void _DNSServerTCPReadHandler( void *inContext );
+static void _DNSDelayedResponseFree( DNSDelayedResponse *inResponse );
+static void _DNSDelayedResponseFreeList( DNSDelayedResponse *inList );
+
+CF_CLASS_DEFINE( DNSServer );
-CFTypeID DNSServerGetTypeID( void );
static OSStatus
DNSServerCreate(
dispatch_queue_t inQueue,
DNSServerEventHandler_f inEventHandler,
void * inEventContext,
- int inResponseDelayMs,
+ unsigned int inResponseDelayMs,
+ uint32_t inDefaultTTL,
+ int inPort,
Boolean inLoopbackOnly,
- DNSServerRef * outServer );
-static void DNSServerStart( DNSServerRef inServer );
-static void DNSServerStop( DNSServerRef inServer );
-
-static void DNSServerCmdContextFree( DNSServerCmdContext *inContext );
-static void DNSServerCmdEventHandler( DNSServerEventType inType, void *inContext );
-static void DNSServerCmdSigIntHandler( void *inContext );
-static void DNSServerCmdSigTermHandler( void *inContext );
-#if( TARGET_OS_DARWIN )
-static void DNSServerCmdFollowedProcessHandler( void *inContext );
-#endif
-
-ulog_define_ex( "com.apple.dnssdutil", DNSServer, kLogLevelInfo, kLogFlags_None, "DNSServer", NULL );
-#define ds_ulog( LEVEL, ... ) ulog( &log_category_from_name( DNSServer ), (LEVEL), __VA_ARGS__ )
-
-static void DNSServerCmd( void )
+ const char * inDomain,
+ Boolean inBadUDPMode,
+ DNSServerRef * outServer )
{
- OSStatus err;
- DNSServerCmdContext * context;
-
- context = (DNSServerCmdContext *) calloc( 1, sizeof( *context ) );
- require_action( context, exit, err = kNoMemoryErr );
-
- context->loopbackOnly = gDNSServer_LoopbackOnly ? true : false;
+ OSStatus err;
+ DNSServerRef obj = NULL;
-#if( TARGET_OS_DARWIN )
- if( gDNSServer_FollowPID )
- {
- long long value;
-
- err = StringToLongLong( gDNSServer_FollowPID, &value );
- if( !err && ( value < 0 ) ) err = kValueErr;
- if( err )
- {
- FPrintF( stderr, "Invalid followPID argument \"%s\".\n", gDNSServer_FollowPID );
- goto exit;
- }
- context->followPID = (pid_t) value;
-
- err = DispatchProcessMonitorCreate( context->followPID, DISPATCH_PROC_EXIT, dispatch_get_main_queue(),
- DNSServerCmdFollowedProcessHandler, NULL, context, &context->processMonitor );
- require_noerr( err, exit );
- dispatch_resume( context->processMonitor );
- }
- else
- {
- context->followPID = -1;
- }
-#endif
+ require_action_quiet( inDefaultTTL <= INT32_MAX, exit, err = kRangeErr );
- signal( SIGINT, SIG_IGN );
- err = DispatchSignalSourceCreate( SIGINT, DNSServerCmdSigIntHandler, context, &context->sigIntSource );
- require_noerr( err, exit );
- dispatch_resume( context->sigIntSource );
+ CF_OBJECT_CREATE( DNSServer, obj, err, exit );
- signal( SIGTERM, SIG_IGN );
- err = DispatchSignalSourceCreate( SIGTERM, DNSServerCmdSigTermHandler, context, &context->sigTermSource );
- require_noerr( err, exit );
- dispatch_resume( context->sigTermSource );
+ ReplaceDispatchQueue( &obj->queue, inQueue );
+ obj->eventHandler = inEventHandler;
+ obj->eventContext = inEventContext;
+ obj->responseDelayMs = inResponseDelayMs;
+ obj->defaultTTL = inDefaultTTL;
+ obj->port = inPort;
+ obj->loopbackOnly = inLoopbackOnly;
+ obj->badUDPMode = inBadUDPMode;
- if( gDNSServer_Foreground )
+ if( inDomain )
{
- LogControl( "DNSServer:output=file;stdout,DNSServer:flags=time;prefix" );
+ err = StringToDomainName( inDomain, &obj->domain, NULL );
+ require_noerr_quiet( err, exit );
}
-
- if( ( gDNSServer_DefaultTTL < 0 ) || ( gDNSServer_DefaultTTL > INT32_MAX ) )
+ else
{
- ds_ulog( kLogLevelError, "The default TTL %d provided by user is out-of-range. Will use %d instead.\n",
- gDNSServer_DefaultTTL, kDNSServerDefaultTTL );
- gDNSServer_DefaultTTL = kDNSServerDefaultTTL;
+ err = DomainNameDup( kDDotTestDomainName, &obj->domain, NULL );
+ require_noerr_quiet( err, exit );
}
- err = DNSServerCreate( dispatch_get_main_queue(), DNSServerCmdEventHandler, context, gDNSServer_ResponseDelayMs,
- context->loopbackOnly, &context->server );
- require_noerr( err, exit );
-
- DNSServerStart( context->server );
- dispatch_main();
+ *outServer = obj;
+ obj = NULL;
+ err = kNoErr;
exit:
- ds_ulog( kLogLevelError, "Failed to start DNS server: %#m\n", err );
- if( context ) DNSServerCmdContextFree( context );
- if( err ) exit( 1 );
+ CFReleaseNullSafe( obj );
+ return( err );
}
//===========================================================================================================================
-// DNSServerCmdContextFree
+// _DNSServerFinalize
//===========================================================================================================================
-static void DNSServerCmdContextFree( DNSServerCmdContext *inContext )
+static void _DNSServerFinalize( CFTypeRef inObj )
{
- ForgetCF( &inContext->server );
- dispatch_source_forget( &inContext->sigIntSource );
- dispatch_source_forget( &inContext->sigTermSource );
- dispatch_source_forget( &inContext->processMonitor );
- free( inContext );
+ DNSServerRef const me = (DNSServerRef) inObj;
+
+ check( !me->readSourceUDPv4 );
+ check( !me->readSourceUDPv6 );
+ check( !me->readSourceTCPv4 );
+ check( !me->readSourceTCPv6 );
+ check( !me->responseTimer );
+ ForgetMem( &me->domain );
+ dispatch_forget( &me->queue );
}
//===========================================================================================================================
-// DNSServerCmdEventHandler
+// DNSServerStart
//===========================================================================================================================
-#if( TARGET_OS_DARWIN )
-static OSStatus _DNSServerCmdRegisterResolver( void );
-static OSStatus _DNSServerCmdUnregisterResolver( void );
-#endif
+static void _DNSServerStart( void *inContext );
+static void _DNSServerStop( void *inContext, OSStatus inError );
-static void DNSServerCmdEventHandler( DNSServerEventType inType, void *inContext )
-{
- DNSServerCmdContext * const context = (DNSServerCmdContext *) inContext;
-#if( TARGET_OS_DARWIN )
- OSStatus err;
-#endif
-
- if( inType == kDNSServerEvent_Started )
- {
- context->serverStarted = true;
- #if( TARGET_OS_DARWIN )
- err = _DNSServerCmdRegisterResolver();
- if( err )
- {
- ds_ulog( kLogLevelError, "Failed to add resolver to DNS configuration for \"d.test.\" domain: %#m\n", err );
- if( context->loopbackOnly ) exit( 1 );
- }
- else
- {
- context->resolverRegistered = true;
- }
- #endif
- }
- else if( inType == kDNSServerEvent_Stopped )
- {
- #if( TARGET_OS_DARWIN )
- if( context->resolverRegistered )
- {
- err = _DNSServerCmdUnregisterResolver();
- if( err )
- {
- ds_ulog( kLogLevelError, "Failed to remove resolver from DNS configuration: %#m\n", err );
- }
- else
- {
- context->resolverRegistered = false;
- }
- }
-
- if( !context->calledStop )
- {
- ds_ulog( kLogLevelError, "The server stopped unexpectedly.\n" );
- exit( 1 );
- }
- #endif
- DNSServerCmdContextFree( context );
- }
-}
-
-#if( TARGET_OS_DARWIN )
-//===========================================================================================================================
-// _DNSServerCmdRegisterResolver
-//===========================================================================================================================
-
-static OSStatus _DNSServerCmdRegisterResolver( void )
-{
- OSStatus err;
- SCDynamicStoreRef store;
- CFPropertyListRef plist = NULL;
- CFStringRef key = NULL;
- const uint32_t loopbackV4 = htonl( INADDR_LOOPBACK );
- Boolean success;
-
- store = SCDynamicStoreCreate( NULL, CFSTR( "com.apple.dnssdutil" ), NULL, NULL );
- err = map_scerror( store );
- require_noerr( err, exit );
-
- err = CFPropertyListCreateFormatted( kCFAllocatorDefault, &plist,
- "{"
- "%kO="
- "["
- "%s"
- "]"
- "%kO="
- "["
- "%.4a"
- "%.16a"
- "]"
- "}",
- kSCPropNetDNSSupplementalMatchDomains, "d.test.",
- kSCPropNetDNSServerAddresses, &loopbackV4, in6addr_loopback.s6_addr );
- require_noerr( err, exit );
-
- key = SCDynamicStoreKeyCreateNetworkServiceEntity( NULL, kSCDynamicStoreDomainState,
- CFSTR( "com.apple.dnssdutil.server" ), kSCEntNetDNS );
- require_action( key, exit, err = kUnknownErr );
-
- success = SCDynamicStoreSetValue( store, key, plist );
- require_action( success, exit, err = kUnknownErr );
-
-exit:
- CFReleaseNullSafe( store );
- CFReleaseNullSafe( plist );
- CFReleaseNullSafe( key );
- return( err );
-}
-
-//===========================================================================================================================
-// _DNSServerCmdUnregisterResolver
-//===========================================================================================================================
-
-static OSStatus _DNSServerCmdUnregisterResolver( void )
-{
- OSStatus err;
- SCDynamicStoreRef store;
- CFStringRef key = NULL;
- Boolean success;
-
- store = SCDynamicStoreCreate( NULL, CFSTR( "com.apple.dnssdutil" ), NULL, NULL );
- err = map_scerror( store );
- require_noerr( err, exit );
-
- key = SCDynamicStoreKeyCreateNetworkServiceEntity( NULL, kSCDynamicStoreDomainState,
- CFSTR( "com.apple.dnssdutil.server" ), kSCEntNetDNS );
- require_action( key, exit, err = kUnknownErr );
-
- success = SCDynamicStoreRemoveValue( store, key );
- require_action( success, exit, err = kUnknownErr );
-
-exit:
- CFReleaseNullSafe( store );
- CFReleaseNullSafe( key );
- return( err );
-}
-#endif
-
-//===========================================================================================================================
-// DNSServerCmdSigIntHandler
-//===========================================================================================================================
-
-static void _DNSServerCmdExternalExit( DNSServerCmdContext *inContext, int inSignal );
-
-static void DNSServerCmdSigIntHandler( void *inContext )
-{
- _DNSServerCmdExternalExit( (DNSServerCmdContext *) inContext, SIGINT );
-}
-
-//===========================================================================================================================
-// DNSServerCmdSigTermHandler
-//===========================================================================================================================
-
-static void DNSServerCmdSigTermHandler( void *inContext )
-{
- _DNSServerCmdExternalExit( (DNSServerCmdContext *) inContext, SIGTERM );
-}
-
-#if( TARGET_OS_DARWIN )
-//===========================================================================================================================
-// DNSServerCmdFollowedProcessHandler
-//===========================================================================================================================
-
-static void DNSServerCmdFollowedProcessHandler( void *inContext )
-{
- DNSServerCmdContext * const context = (DNSServerCmdContext *) inContext;
-
- if( dispatch_source_get_data( context->processMonitor ) & DISPATCH_PROC_EXIT )
- {
- _DNSServerCmdExternalExit( context, 0 );
- }
-}
-#endif
-
-//===========================================================================================================================
-// _DNSServerCmdExternalExit
-//===========================================================================================================================
-
-#define SignalNumberToString( X ) ( \
- ( (X) == SIGINT ) ? "SIGINT" : \
- ( (X) == SIGTERM ) ? "SIGTERM" : \
- "???" )
-
-static void _DNSServerCmdExternalExit( DNSServerCmdContext *inContext, int inSignal )
-{
- OSStatus err;
-
-#if( TARGET_OS_DARWIN )
- if( inSignal == 0 )
- {
- ds_ulog( kLogLevelNotice, "Exiting: followed process (%lld) exited\n", (int64_t) inContext->followPID );
- }
- else
-#endif
- {
- ds_ulog( kLogLevelNotice, "Exiting: received signal %d (%s)\n", inSignal, SignalNumberToString( inSignal ) );
- }
-
-#if( TARGET_OS_DARWIN )
- if( inContext->resolverRegistered )
- {
- err = _DNSServerCmdUnregisterResolver();
- if( err )
- {
- ds_ulog( kLogLevelError, "Failed to remove resolver from DNS configuration: %#m\n", err );
- goto exit;
- }
- inContext->resolverRegistered = false;
- }
-#endif
- if( inContext->serverStarted )
- {
- DNSServerStop( inContext->server );
- inContext->calledStop = true;
- }
- err = kNoErr;
-
-exit:
- exit( err ? 1 : 0 );
-}
-
-//===========================================================================================================================
-// DNSServerCreate
-//===========================================================================================================================
-
-typedef struct DNSDelayedResponse DNSDelayedResponse;
-struct DNSDelayedResponse
-{
- DNSDelayedResponse * next;
- sockaddr_ip clientAddr;
- uint64_t targetTicks;
- uint8_t * msgPtr;
- size_t msgLen;
-};
-
-#define DNSScheduledResponseFree( X ) do { ForgetMem( &(X)->msgPtr ) ; free( X ); } while( 0 )
-
-struct DNSServerPrivate
-{
- CFRuntimeBase base; // CF object base.
- dispatch_queue_t queue; // Queue for DNS server's events.
- dispatch_source_t readSourceUDPv4; // Read source for IPv4 UDP socket.
- dispatch_source_t readSourceUDPv6; // Read source for IPv6 UDP socket.
- dispatch_source_t readSourceTCPv4; // Read source for IPv4 TCP socket.
- dispatch_source_t readSourceTCPv6; // Read source for IPv6 TCP socket.
- DNSServerEventHandler_f eventHandler;
- void * eventContext;
- DNSDelayedResponse * responseList;
- int responseDelayMs;
- dispatch_source_t responseTimer;
- Boolean loopbackOnly;
- Boolean stopped;
-};
-
-CF_CLASS_DEFINE( DNSServer );
-
-static OSStatus
- DNSServerCreate(
- dispatch_queue_t inQueue,
- DNSServerEventHandler_f inEventHandler,
- void * inEventContext,
- int inResponseDelayMs,
- Boolean inLoopbackOnly,
- DNSServerRef * outServer )
-{
- OSStatus err;
- DNSServerRef obj = NULL;
-
- CF_OBJECT_CREATE( DNSServer, obj, err, exit );
-
- ReplaceDispatchQueue( &obj->queue, inQueue );
- obj->eventHandler = inEventHandler;
- obj->eventContext = inEventContext;
- obj->responseDelayMs = inResponseDelayMs;
- if( inLoopbackOnly ) obj->loopbackOnly = true;
-
- *outServer = obj;
- obj = NULL;
- err = kNoErr;
-
-exit:
- CFReleaseNullSafe( obj );
- return( err );
-}
-
-//===========================================================================================================================
-// _DNSServerFinalize
-//===========================================================================================================================
-
-static void _DNSServerFinalize( CFTypeRef inObj )
-{
- DNSServerRef const me = (DNSServerRef) inObj;
-
- check( !me->readSourceUDPv4 );
- check( !me->readSourceUDPv6 );
- check( !me->readSourceTCPv4 );
- check( !me->readSourceTCPv6 );
- check( !me->responseTimer );
- dispatch_forget( &me->queue );
-}
-
-//===========================================================================================================================
-// DNSServerStart
-//===========================================================================================================================
-
-static void _DNSServerStart( void *inContext );
-static void _DNSServerUDPReadHandler( void *inContext );
-static void _DNSServerTCPReadHandler( void *inContext );
-
-static void DNSServerStart( DNSServerRef me )
+static void DNSServerStart( DNSServerRef me )
{
CFRetain( me );
dispatch_async_f( me->queue, me, _DNSServerStart );
@@ -7255,16 +7320,23 @@ static void DNSServerStart( DNSServerRef me )
static void _DNSServerStart( void *inContext )
{
OSStatus err;
+ struct timeval now;
DNSServerRef const me = (DNSServerRef) inContext;
SocketRef sock = kInvalidSocketRef;
SocketContext * sockCtx = NULL;
const uint32_t loopbackV4 = htonl( INADDR_LOOPBACK );
+ int year, month, day;
// Create IPv4 UDP socket.
+ // Initially, me->port is the port requested by the user. If it's 0, then the user wants any available ephemeral port.
+ // If it's negative, then the user would like a port number equal to its absolute value, but will settle for any
+ // available ephemeral port, if it's not available. The actual port number that was used will be stored in me->port and
+ // used for the remaining sockets.
err = _ServerSocketOpenEx2( AF_INET, SOCK_DGRAM, IPPROTO_UDP, me->loopbackOnly ? &loopbackV4 : NULL,
- kDNSPort, NULL, kSocketBufferSize_DontSet, me->loopbackOnly ? true : false, &sock );
+ me->port, &me->port, kSocketBufferSize_DontSet, me->loopbackOnly ? true : false, &sock );
require_noerr( err, exit );
+ check( me->port > 0 );
// Create read source for IPv4 UDP socket.
@@ -7276,12 +7348,13 @@ static void _DNSServerStart( void *inContext )
&me->readSourceUDPv4 );
require_noerr( err, exit );
dispatch_resume( me->readSourceUDPv4 );
+ me->sockUDPv4 = sockCtx->sock;
sockCtx = NULL;
// Create IPv6 UDP socket.
err = _ServerSocketOpenEx2( AF_INET6, SOCK_DGRAM, IPPROTO_UDP, me->loopbackOnly ? &in6addr_loopback : NULL,
- kDNSPort, NULL, kSocketBufferSize_DontSet, me->loopbackOnly ? true : false, &sock );
+ me->port, NULL, kSocketBufferSize_DontSet, me->loopbackOnly ? true : false, &sock );
require_noerr( err, exit );
// Create read source for IPv6 UDP socket.
@@ -7294,12 +7367,13 @@ static void _DNSServerStart( void *inContext )
&me->readSourceUDPv6 );
require_noerr( err, exit );
dispatch_resume( me->readSourceUDPv6 );
+ me->sockUDPv6 = sockCtx->sock;
sockCtx = NULL;
// Create IPv4 TCP socket.
err = _ServerSocketOpenEx2( AF_INET, SOCK_STREAM, IPPROTO_TCP, me->loopbackOnly ? &loopbackV4 : NULL,
- kDNSPort, NULL, kSocketBufferSize_DontSet, false, &sock );
+ me->port, NULL, kSocketBufferSize_DontSet, false, &sock );
require_noerr( err, exit );
// Create read source for IPv4 TCP socket.
@@ -7317,7 +7391,7 @@ static void _DNSServerStart( void *inContext )
// Create IPv6 TCP socket.
err = _ServerSocketOpenEx2( AF_INET6, SOCK_STREAM, IPPROTO_TCP, me->loopbackOnly ? &in6addr_loopback : NULL,
- kDNSPort, NULL, kSocketBufferSize_DontSet, false, &sock );
+ me->port, NULL, kSocketBufferSize_DontSet, false, &sock );
require_noerr( err, exit );
// Create read source for IPv6 TCP socket.
@@ -7332,46 +7406,62 @@ static void _DNSServerStart( void *inContext )
dispatch_resume( me->readSourceTCPv6 );
sockCtx = NULL;
- CFRetain( me );
- if( me->eventHandler ) me->eventHandler( kDNSServerEvent_Started, me->eventContext );
+ ds_ulog( kLogLevelInfo, "Server is using port %d.\n", me->port );
+ if( me->eventHandler ) me->eventHandler( kDNSServerEvent_Started, (uintptr_t) me->port, me->eventContext );
+
+ // Create the serial number for the server's SOA record in the YYYMMDDnn convention recommended by
+ // <https://tools.ietf.org/html/rfc1912#section-2.2> using the current time.
+
+ gettimeofday( &now, NULL );
+ SecondsToYMD_HMS( ( INT64_C_safe( kDaysToUnixEpoch ) * kSecondsPerDay ) + now.tv_sec, &year, &month, &day,
+ NULL, NULL, NULL );
+ me->serial = (uint32_t)( ( year * 1000000 ) + ( month * 10000 ) + ( day * 100 ) + 1 );
exit:
ForgetSocket( &sock );
if( sockCtx ) SocketContextRelease( sockCtx );
- if( err ) DNSServerStop( me );
- CFRelease( me );
+ if( err ) _DNSServerStop( me, err );
}
//===========================================================================================================================
// DNSServerStop
//===========================================================================================================================
-static void _DNSServerStop( void *inContext );
+static void _DNSServerUserStop( void *inContext );
static void _DNSServerStop2( void *inContext );
static void DNSServerStop( DNSServerRef me )
{
CFRetain( me );
- dispatch_async_f( me->queue, me, _DNSServerStop );
+ dispatch_async_f( me->queue, me, _DNSServerUserStop );
}
-static void _DNSServerStop( void *inContext )
+static void _DNSServerUserStop( void *inContext )
{
- DNSServerRef const me = (DNSServerRef) inContext;
- DNSDelayedResponse * resp;
+ DNSServerRef const me = (DNSServerRef) inContext;
- dispatch_source_forget( &me->readSourceUDPv4 );
+ _DNSServerStop( me, kNoErr );
+ CFRelease( me );
+}
+
+static void _DNSServerStop( void *inContext, OSStatus inError )
+{
+ DNSServerRef const me = (DNSServerRef) inContext;
+
+ me->stopError = inError;
+ dispatch_source_forget( &me->readSourceUDPv4 );
dispatch_source_forget( &me->readSourceUDPv6 );
dispatch_source_forget( &me->readSourceTCPv4 );
dispatch_source_forget( &me->readSourceTCPv6 );
dispatch_source_forget( &me->responseTimer );
+ me->sockUDPv4 = kInvalidSocketRef;
+ me->sockUDPv6 = kInvalidSocketRef;
- while( ( resp = me->responseList ) != NULL )
+ if( me->responseList )
{
- me->responseList = resp->next;
- DNSScheduledResponseFree( resp );
+ _DNSDelayedResponseFreeList( me->responseList );
+ me->responseList = NULL;
}
-
dispatch_async_f( me->queue, me, _DNSServerStop2 );
}
@@ -7382,30 +7472,62 @@ static void _DNSServerStop2( void *inContext )
if( !me->stopped )
{
me->stopped = true;
- if( me->eventHandler ) me->eventHandler( kDNSServerEvent_Stopped, me->eventContext );
+ if( me->eventHandler ) me->eventHandler( kDNSServerEvent_Stopped, (uintptr_t) me->stopError, me->eventContext );
CFRelease( me );
}
CFRelease( me );
}
//===========================================================================================================================
+// _DNSDelayedResponseFree
+//===========================================================================================================================
+
+static void _DNSDelayedResponseFree( DNSDelayedResponse *inResponse )
+{
+ ForgetMem( &inResponse->msgPtr );
+ free( inResponse );
+}
+
+//===========================================================================================================================
+// _DNSDelayedResponseFreeList
+//===========================================================================================================================
+
+static void _DNSDelayedResponseFreeList( DNSDelayedResponse *inList )
+{
+ DNSDelayedResponse * response;
+
+ while( ( response = inList ) != NULL )
+ {
+ inList = response->next;
+ _DNSDelayedResponseFree( response );
+ }
+}
+
+//===========================================================================================================================
// _DNSServerUDPReadHandler
//===========================================================================================================================
static OSStatus
_DNSServerAnswerQuery(
+ DNSServerRef inServer,
const uint8_t * inQueryPtr,
size_t inQueryLen,
Boolean inForTCP,
uint8_t ** outResponsePtr,
size_t * outResponseLen );
-#define _DNSServerAnswerQueryForUDP( IN_QUERY_PTR, IN_QUERY_LEN, IN_RESPONSE_PTR, IN_RESPONSE_LEN ) \
- _DNSServerAnswerQuery( IN_QUERY_PTR, IN_QUERY_LEN, false, IN_RESPONSE_PTR, IN_RESPONSE_LEN )
+#define _DNSServerAnswerQueryForUDP( IN_SERVER, IN_QUERY_PTR, IN_QUERY_LEN, IN_RESPONSE_PTR, IN_RESPONSE_LEN ) \
+ _DNSServerAnswerQuery( IN_SERVER, IN_QUERY_PTR, IN_QUERY_LEN, false, IN_RESPONSE_PTR, IN_RESPONSE_LEN )
-#define _DNSServerAnswerQueryForTCP( IN_QUERY_PTR, IN_QUERY_LEN, IN_RESPONSE_PTR, IN_RESPONSE_LEN ) \
- _DNSServerAnswerQuery( IN_QUERY_PTR, IN_QUERY_LEN, true, IN_RESPONSE_PTR, IN_RESPONSE_LEN )
+#define _DNSServerAnswerQueryForTCP( IN_SERVER, IN_QUERY_PTR, IN_QUERY_LEN, IN_RESPONSE_PTR, IN_RESPONSE_LEN ) \
+ _DNSServerAnswerQuery( IN_SERVER, IN_QUERY_PTR, IN_QUERY_LEN, true, IN_RESPONSE_PTR, IN_RESPONSE_LEN )
+static OSStatus
+ _DNSServerScheduleDelayedResponse(
+ DNSServerRef inServer,
+ const struct sockaddr * inDestAddr,
+ uint8_t * inMsgPtr,
+ size_t inMsgLen );
static void _DNSServerUDPDelayedSend( void *inContext );
static void _DNSServerUDPReadHandler( void *inContext )
@@ -7442,44 +7564,16 @@ static void _DNSServerUDPReadHandler( void *inContext )
// Create response.
- err = _DNSServerAnswerQueryForUDP( msg, (size_t) n, &responsePtr, &responseLen );
+ err = _DNSServerAnswerQueryForUDP( me, msg, (size_t) n, &responsePtr, &responseLen );
require_noerr_quiet( err, exit );
// Schedule response.
if( me->responseDelayMs > 0 )
{
- DNSDelayedResponse * resp;
- DNSDelayedResponse ** ptr;
- DNSDelayedResponse * newResp;
-
- newResp = (DNSDelayedResponse *) calloc( 1, sizeof( *newResp ) );
- require_action( newResp, exit, err = kNoMemoryErr );
-
- SockAddrCopy( &clientAddr, &newResp->clientAddr );
- newResp->targetTicks = UpTicks() + MillisecondsToUpTicks( (uint64_t) me->responseDelayMs );
- newResp->msgLen = responseLen;
- newResp->msgPtr = responsePtr;
+ err = _DNSServerScheduleDelayedResponse( me, &clientAddr.sa, responsePtr, responseLen );
+ require_noerr( err, exit );
responsePtr = NULL;
-
- for( ptr = &me->responseList; ( resp = *ptr ) != NULL; ptr = &resp->next )
- {
- if( newResp->targetTicks < resp->targetTicks ) break;
- }
-
- newResp->next = resp;
- *ptr = newResp;
-
- if( me->responseList == newResp )
- {
- dispatch_source_forget( &me->responseTimer );
-
- err = DispatchTimerCreate( dispatch_time_milliseconds( me->responseDelayMs ), DISPATCH_TIME_FOREVER,
- ( (uint64_t) me->responseDelayMs ) * kNanosecondsPerMillisecond / 10, me->queue,
- _DNSServerUDPDelayedSend, NULL, sockCtx, &me->responseTimer );
- require_noerr( err, exit );
- dispatch_resume( me->responseTimer );
- }
}
else
{
@@ -7495,56 +7589,100 @@ exit:
return;
}
+static OSStatus
+ _DNSServerScheduleDelayedResponse(
+ DNSServerRef me,
+ const struct sockaddr * inDestAddr,
+ uint8_t * inMsgPtr,
+ size_t inMsgLen )
+{
+ OSStatus err;
+ DNSDelayedResponse * response;
+ DNSDelayedResponse ** responsePtr;
+ DNSDelayedResponse * newResponse;
+ uint64_t targetTicks;
+
+ targetTicks = UpTicks() + MillisecondsToUpTicks( me->responseDelayMs );
+
+ newResponse = (DNSDelayedResponse *) calloc( 1, sizeof( *newResponse ) );
+ require_action( newResponse, exit, err = kNoMemoryErr );
+
+ if( !me->responseList || ( targetTicks < me->responseList->targetTicks ) )
+ {
+ dispatch_source_forget( &me->responseTimer );
+
+ err = DispatchTimerCreate( dispatch_time_milliseconds( me->responseDelayMs ), DISPATCH_TIME_FOREVER,
+ ( (uint64_t) me->responseDelayMs ) * kNanosecondsPerMillisecond / 10, me->queue, _DNSServerUDPDelayedSend,
+ NULL, me, &me->responseTimer );
+ require_noerr( err, exit );
+ dispatch_resume( me->responseTimer );
+ }
+
+ SockAddrCopy( inDestAddr, &newResponse->destAddr );
+ newResponse->targetTicks = targetTicks;
+ newResponse->msgPtr = inMsgPtr;
+ newResponse->msgLen = inMsgLen;
+
+ for( responsePtr = &me->responseList; ( response = *responsePtr ) != NULL; responsePtr = &response->next )
+ {
+ if( newResponse->targetTicks < response->targetTicks ) break;
+ }
+ newResponse->next = response;
+ *responsePtr = newResponse;
+ newResponse = NULL;
+ err = kNoErr;
+
+exit:
+ if( newResponse ) _DNSDelayedResponseFree( newResponse );
+ return( err );
+}
+
static void _DNSServerUDPDelayedSend( void *inContext )
{
OSStatus err;
- SocketContext * const sockCtx = (SocketContext *) inContext;
- DNSServerRef const me = (DNSServerRef) sockCtx->userContext;
- DNSDelayedResponse * resp;
+ DNSServerRef const me = (DNSServerRef) inContext;
+ DNSDelayedResponse * response;
+ SocketRef sock;
ssize_t n;
uint64_t nowTicks;
+ uint64_t remainingNs;
DNSDelayedResponse * freeList = NULL;
dispatch_source_forget( &me->responseTimer );
nowTicks = UpTicks();
- while( ( resp = me->responseList ) != NULL )
+ while( ( ( response = me->responseList ) != NULL ) && ( response->targetTicks <= nowTicks ) )
{
- if( resp->targetTicks > nowTicks ) break;
- me->responseList = resp->next;
+ me->responseList = response->next;
ds_ulog( kLogLevelInfo, "UDP sending %zu byte response (delayed):\n\n%1{du:dnsmsg}",
- resp->msgLen, resp->msgPtr, resp->msgLen );
+ response->msgLen, response->msgPtr, response->msgLen );
- n = sendto( sockCtx->sock, (char *) resp->msgPtr, resp->msgLen, 0, &resp->clientAddr.sa,
- SockAddrGetSize( &resp->clientAddr ) );
- err = map_socket_value_errno( sockCtx->sock, n == (ssize_t) resp->msgLen, n );
+ sock = ( response->destAddr.sa.sa_family == AF_INET ) ? me->sockUDPv4 : me->sockUDPv6;
+ n = sendto( sock, (char *) response->msgPtr, response->msgLen, 0, &response->destAddr.sa,
+ SockAddrGetSize( &response->destAddr ) );
+ err = map_socket_value_errno( sock, n == (ssize_t) response->msgLen, n );
check_noerr( err );
- resp->next = freeList;
- freeList = resp;
+ response->next = freeList;
+ freeList = response;
nowTicks = UpTicks();
}
- if( ( resp = me->responseList ) != NULL )
+ if( response )
{
- uint64_t remainingNs;
-
- remainingNs = UpTicksToNanoseconds( resp->targetTicks - nowTicks );
+ check( response->targetTicks > nowTicks );
+ remainingNs = UpTicksToNanoseconds( response->targetTicks - nowTicks );
if( remainingNs > INT64_MAX ) remainingNs = INT64_MAX;
err = DispatchTimerCreate( dispatch_time( DISPATCH_TIME_NOW, (int64_t) remainingNs ), DISPATCH_TIME_FOREVER, 0,
- me->queue, _DNSServerUDPDelayedSend, NULL, sockCtx, &me->responseTimer );
+ me->queue, _DNSServerUDPDelayedSend, NULL, me, &me->responseTimer );
require_noerr( err, exit );
dispatch_resume( me->responseTimer );
}
exit:
- while( ( resp = freeList ) != NULL )
- {
- freeList = resp->next;
- DNSScheduledResponseFree( resp );
- }
+ if( freeList ) _DNSDelayedResponseFreeList( freeList );
}
//===========================================================================================================================
@@ -7553,12 +7691,25 @@ exit:
#define kLabelPrefix_Alias "alias"
#define kLabelPrefix_AliasTTL "alias-ttl"
-#define kLabelPrefix_Count "count"
-#define kLabelPrefix_TTL "ttl"
+#define kLabelPrefix_Count "count-"
+#define kLabelPrefix_Tag "tag-"
+#define kLabelPrefix_TTL "ttl-"
#define kLabel_IPv4 "ipv4"
#define kLabel_IPv6 "ipv6"
+#define kLabelPrefix_SRV "srv-"
#define kMaxAliasTTLCount ( ( kDomainLabelLengthMax - sizeof_string( kLabelPrefix_AliasTTL ) ) / 2 )
+#define kMaxParsedSRVCount ( kDomainNameLengthMax / ( 1 + sizeof_string( kLabelPrefix_SRV ) + 5 ) )
+
+typedef struct
+{
+ uint16_t priority; // Priority from SRV label.
+ uint16_t weight; // Weight from SRV label.
+ uint16_t port; // Port number from SRV label.
+ uint16_t targetLen; // Total length of the target hostname labels that follow an SRV label.
+ const uint8_t * targetPtr; // Pointer to the target hostname embedded in a domain name.
+
+} ParsedSRV;
static OSStatus
_DNSServerInitializeResponseMessage(
@@ -7570,14 +7721,37 @@ static OSStatus
unsigned int inQClass );
static OSStatus
_DNSServerAnswerQueryDynamically(
+ DNSServerRef inServer,
const uint8_t * inQName,
unsigned int inQType,
unsigned int inQClass,
Boolean inForTCP,
DataBuffer * inDB );
+static Boolean
+ _DNSServerNameIsSRVName(
+ DNSServerRef inServer,
+ const uint8_t * inName,
+ const uint8_t ** outDomainPtr,
+ size_t * outDomainLen,
+ ParsedSRV inSRVArray[ kMaxParsedSRVCount ],
+ size_t * outSRVCount );
+static Boolean
+ _DNSServerNameIsHostname(
+ DNSServerRef inServer,
+ const uint8_t * inName,
+ uint32_t * outAliasCount,
+ uint32_t inAliasTTLs[ kMaxAliasTTLCount ],
+ size_t * outAliasTTLCount,
+ unsigned int * outCount,
+ unsigned int * outRandCount,
+ uint32_t * outTTL,
+ Boolean * outHasA,
+ Boolean * outHasAAAA,
+ Boolean * outHasSOA );
static OSStatus
_DNSServerAnswerQuery(
+ DNSServerRef me,
const uint8_t * const inQueryPtr,
const size_t inQueryLen,
Boolean inForTCP,
@@ -7629,10 +7803,11 @@ static OSStatus
if( qflags & kDNSHeaderFlag_RecursionDesired ) rflags |= kDNSHeaderFlag_RecursionDesired;
DNSFlagsSetOpCode( rflags, kDNSOpCode_Query );
+ if( me->badUDPMode && !inForTCP ) msgID = (uint16_t)( msgID + 1 );
err = _DNSServerInitializeResponseMessage( &dataBuf, msgID, rflags, qname, qtype, qclass );
require_noerr( err, exit );
- err = _DNSServerAnswerQueryDynamically( qname, qtype, qclass, inForTCP, &dataBuf );
+ err = _DNSServerAnswerQueryDynamically( me, qname, qtype, qclass, inForTCP, &dataBuf );
if( err )
{
DNSFlagsSetRCode( rflags, kDNSRCode_ServerFailure );
@@ -7657,9 +7832,8 @@ static OSStatus
unsigned int inQType,
unsigned int inQClass )
{
- OSStatus err;
- DNSHeader header;
- DNSQuestionFixedFields fields;
+ OSStatus err;
+ DNSHeader header;
DataBuffer_Reset( inDB );
@@ -7671,11 +7845,8 @@ static OSStatus
err = DataBuffer_Append( inDB, &header, sizeof( header ) );
require_noerr( err, exit );
- err = DataBuffer_Append( inDB, inQName, DomainNameLength( inQName ) );
- require_noerr( err, exit );
-
- DNSQuestionFixedFieldsInit( &fields, inQType, inQClass );
- err = DataBuffer_Append( inDB, &fields, sizeof( fields ) );
+ err = _DataBuffer_AppendDNSQuestion( inDB, inQName, DomainNameLength( inQName ), (uint16_t) inQType,
+ (uint16_t) inQClass );
require_noerr( err, exit );
exit:
@@ -7684,184 +7855,72 @@ exit:
static OSStatus
_DNSServerAnswerQueryDynamically(
+ DNSServerRef me,
const uint8_t * const inQName,
const unsigned int inQType,
const unsigned int inQClass,
const Boolean inForTCP,
DataBuffer * const inDB )
{
- OSStatus err; // General-purpose error variable.
- const uint8_t * labelPtr; // QNAME label pointer.
- size_t labelLen; // QNAME label length.
- DNSHeader * hdr; // Response header pointer.
- unsigned int flags; // Response header flags.
- unsigned int rcode; // Response header response code.
- unsigned int answerCount = 0; // Number of answers contained in response.
- int32_t aliasCount = -1; // Arg from "alias" label. Valid values are in [2 .. 2^31 - 1].
- int count = -1; // First arg from "count" label. Valid values are in [1 .. 255].
- int randCount = -1; // Second arg from "count" label. Valid values are in [1 .. 255].
- int32_t ttl = -1; // Arg from "ttl" label. Valid values are in [0 .. 2^31 - 1].
- uint32_t aliasTTLs[ kMaxAliasTTLCount ]; // Args from "alias-ttl" label. Valid values are in [0 .. 2^31 - 1].
- int i; // General-purpose array index.
- Boolean useAliasTTLs = false; // True if QNAME contained a valid "alias-ttl" label.
- Boolean nameExists = false; // True if name specified by QNAME exists.
- Boolean nameHasA = false; // True if name specified by QNAME has an A record.
- Boolean nameHasAAAA = false; // True if name specified by QNAME has a AAAA record.
- Boolean notImplemented = false; // True if the kind of the query is not supported.
- Boolean truncated = false; // True if the response message is truncated.
- uint8_t namePtr[ 2 ]; // Name compression pointer.
-
- if( inQClass != kDNSServiceClass_IN )
- {
- notImplemented = true;
- goto done;
- }
-
- for( labelPtr = inQName; ( labelLen = *labelPtr ) != 0; labelPtr += ( 1 + labelLen ) )
- {
- const char * const labelStr = (const char *) &labelPtr[ 1 ];
- const char * next;
- long long arg;
- int n;
-
- require_action( labelLen <= kDomainNameLengthMax, exit, err = kUnexpectedErr );
-
- // Check if the first label is a valid alias TTL sequence label.
-
- if( ( labelPtr == inQName ) && ( strnicmp_prefix( labelStr, labelLen, kLabelPrefix_AliasTTL ) == 0 ) )
- {
- const char * src = &labelStr[ sizeof_string( kLabelPrefix_AliasTTL ) ];
- const char * const end = &labelStr[ labelLen ];
- int argCount = 0;
-
- while( src < end )
- {
- n = SNScanF( src, (size_t)( end - src ), "-%10lld%#n", &arg, &next );
- if( n != 1 ) break;
- if( ( arg < 0 ) || ( arg > INT32_MAX ) ) break; // TTL must be >= 0 and <= (2^31 - 1).
- aliasTTLs[ argCount++ ] = (uint32_t) arg;
- src = next;
- }
- if( ( argCount > 0 ) && ( src == end ) )
- {
- aliasCount = argCount;
- useAliasTTLs = true;
- continue;
- }
- }
-
- // Check if the first label is a valid alias label.
-
- if( ( labelPtr == inQName ) && ( strnicmp_prefix( labelStr, labelLen, kLabelPrefix_Alias ) == 0 ) )
- {
- const char * src = &labelStr[ sizeof_string( kLabelPrefix_Alias ) ];
- const char * const end = &labelStr[ labelLen ];
-
- if( src == end )
- {
- aliasCount = 1;
- continue;
- }
-
- n = SNScanF( src, (size_t)( end - src ), "-%10lld%#n", &arg, &next );
- if( ( n == 1 ) && ( next == end ) )
- {
- if( ( arg < 2 ) || ( arg > INT32_MAX ) ) break; // Alias count must be >= 2 and <= (2^31 - 1).
- aliasCount = (int32_t) arg;
- continue;
- }
- }
-
- // Check if the label is a valid count label.
-
- if( strnicmp_prefix( labelStr, labelLen, kLabelPrefix_Count ) == 0 )
- {
- const char * src = &labelStr[ sizeof_string( kLabelPrefix_Count ) ];
- const char * const end = &labelStr[ labelLen ];
-
- n = SNScanF( src, (size_t)( end - src ), "-%3lld%#n", &arg, &next );
- if( n == 1 )
- {
- if( count > 0 ) break; // Count cannot be specified more than once.
- if( ( arg < 1 ) || ( arg > 255 ) ) break; // Count must be >= 1 and <= 255.
- count = (int) arg;
-
- src = next;
- if( src < end )
- {
- n = SNScanF( src, (size_t)( end - src ), "-%3lld%#n", &arg, &next );
- if( ( n != 1 ) || ( next != end ) ) break;
- if( ( arg < count ) || ( arg > 255 ) ) break; // Rand count must be >= count and <= 255.
- randCount = (int) arg;
- }
- continue;
- }
- }
-
- // Check if the label is a valid tag label.
-
- if( strnicmp_prefix( labelStr, labelLen, "tag-" ) == 0 ) continue;
-
- // Check if the label is a valid TTL label.
-
- if( strnicmp_prefix( labelStr, labelLen, kLabelPrefix_TTL ) == 0 )
- {
- const char * src = &labelStr[ sizeof_string( kLabelPrefix_TTL ) ];
- const char * const end = &labelStr[ labelLen ];
-
- n = SNScanF( src, (size_t)( end - src ), "-%10lld%#n", &arg, &next );
- if( ( n == 1 ) && ( next == end ) )
- {
- if( ttl >= 0 ) break; // TTL cannot be specified more than once.
- if( ( arg < 0 ) || ( arg > INT32_MAX ) ) break; // TTL must be >= 0 and <= (2^31 - 1).
- ttl = (int32_t) arg;
- continue;
- }
- }
-
- // Check if the label is a valid IPv4 or IPv6 label.
+ OSStatus err;
+ DNSHeader * hdr;
+ unsigned int flags, rcode;
+ uint32_t aliasCount, i;
+ uint32_t aliasTTLs[ kMaxAliasTTLCount ];
+ size_t aliasTTLCount;
+ unsigned int addrCount, randCount;
+ uint32_t ttl;
+ ParsedSRV srvArray[ kMaxParsedSRVCount ];
+ size_t srvCount;
+ const uint8_t * srvDomainPtr;
+ size_t srvDomainLen;
+ unsigned int answerCount;
+ Boolean notImplemented, truncated;
+ Boolean useAliasTTLs, nameExists, nameHasA, nameHasAAAA, nameHasSRV, nameHasSOA;
+ uint8_t namePtr[ 2 ];
+ DNSRecordFixedFields fields;
+
+ answerCount = 0;
+ truncated = false;
+ nameExists = false;
+ require_action_quiet( inQClass == kDNSServiceClass_IN, done, notImplemented = true );
+
+ notImplemented = false;
+ aliasCount = 0;
+ nameHasA = false;
+ nameHasAAAA = false;
+ nameHasSOA = false;
+ useAliasTTLs = false;
+ nameHasSRV = false;
+ srvDomainLen = 0;
+ srvCount = 0;
+
+ if( _DNSServerNameIsHostname( me, inQName, &aliasCount, aliasTTLs, &aliasTTLCount, &addrCount, &randCount, &ttl,
+ &nameHasA, &nameHasAAAA, &nameHasSOA ) )
+ {
+ check( !( ( aliasCount > 0 ) && ( aliasTTLCount > 0 ) ) );
+ check( ( addrCount >= 1 ) && ( addrCount <= 255 ) );
+ check( ( randCount == 0 ) || ( ( randCount >= addrCount ) && ( randCount <= 255 ) ) );
+ check( nameHasA || nameHasAAAA );
- if( MemIEqual( labelStr, labelLen, kLabel_IPv4, sizeof_string( kLabel_IPv4 ) ) )
- {
- if( nameHasA || nameHasAAAA ) break; // Valid names have at most one IPv4 or IPv6 label.
- nameHasA = true;
- continue;
- }
- if( MemIEqual( labelStr, labelLen, kLabel_IPv6, sizeof_string( kLabel_IPv6 ) ) )
+ if( aliasTTLCount > 0 )
{
- if( nameHasA || nameHasAAAA ) break; // Valid names have at most one IPv4 or IPv6 label.
- nameHasAAAA = true;
- continue;
+ aliasCount = (uint32_t) aliasTTLCount;
+ useAliasTTLs = true;
}
-
- // If the remaining labels are equal to "d.test.", the name exists.
-
- if( DomainNameEqual( labelPtr, (const uint8_t *) "\x01" "d" "\x04" "test" ) ) nameExists = true;
- break;
+ nameExists = true;
}
- require_quiet( nameExists, done );
-
- // Set default values for count and TTL, if those labels were present.
-
- if( count <= 0 ) count = 1;
- check( ( gDNSServer_DefaultTTL >= 0 ) && ( gDNSServer_DefaultTTL <= INT32_MAX ) );
- if( ttl < 0 ) ttl = gDNSServer_DefaultTTL;
-
- // Names that don't specify v4 or v6 have both A and AAAA records.
-
- if( !nameHasA && !nameHasAAAA )
+ else if( _DNSServerNameIsSRVName( me, inQName, &srvDomainPtr, &srvDomainLen, srvArray, &srvCount ) )
{
- nameHasA = true;
- nameHasAAAA = true;
+ nameHasSRV = true;
+ nameExists = true;
}
-
- check( ( count >= 1 ) && ( count <= 255 ) );
- check( ( randCount <= 0 ) || ( ( randCount >= count ) && ( randCount <= 255 ) ) );
+ require_quiet( nameExists, done );
if( aliasCount > 0 )
{
size_t nameOffset;
- uint8_t rdataLabel[ 1 + kDomainLabelLengthMax + 1 ];
+ uint8_t rdataLabel[ 1 + kDomainLabelLengthMax + 1 ];
// If aliasCount is non-zero, then the first label of QNAME is either "alias" or "alias-<N>". superPtr is a name
// compression pointer to the second label of QNAME, i.e., the immediate superdomain name of QNAME. It's used for
@@ -7876,18 +7935,15 @@ static OSStatus
for( i = aliasCount; i >= 1; --i )
{
- size_t nameLen;
- size_t rdataLen;
- int j;
- uint32_t aliasTTL;
- uint8_t nameLabel[ 1 + kDomainLabelLengthMax + 1 ];
- DNSRecordFixedFields fields;
+ size_t nameLen;
+ size_t rdataLen;
+ uint32_t j;
+ uint32_t aliasTTL;
+ uint8_t nameLabel[ 1 + kDomainLabelLengthMax ];
if( nameOffset <= kDNSCompressionOffsetMax )
{
- namePtr[ 0 ] = (uint8_t)( ( ( nameOffset >> 8 ) & 0x3F ) | 0xC0 );
- namePtr[ 1 ] = (uint8_t)( nameOffset & 0xFF );
-
+ WriteDNSCompressionPtr( namePtr, nameOffset );
nameLen = sizeof( namePtr );
}
else
@@ -7899,22 +7955,22 @@ static OSStatus
if( i >= 2 )
{
char * dst = (char *) &rdataLabel[ 1 ];
- char * const end = (char *) &rdataLabel[ countof( rdataLabel ) ];
+ char * const lim = (char *) &rdataLabel[ countof( rdataLabel ) ];
if( useAliasTTLs )
{
- err = SNPrintF_Add( &dst, end, kLabelPrefix_AliasTTL );
+ err = SNPrintF_Add( &dst, lim, kLabelPrefix_AliasTTL );
require_noerr( err, exit );
for( j = aliasCount - ( i - 1 ); j < aliasCount; ++j )
{
- err = SNPrintF_Add( &dst, end, "-%u", aliasTTLs[ j ] );
+ err = SNPrintF_Add( &dst, lim, "-%u", aliasTTLs[ j ] );
require_noerr( err, exit );
}
}
else
{
- err = SNPrintF_Add( &dst, end, kLabelPrefix_Alias "%?{end}-%u", i == 2, i - 1 );
+ err = SNPrintF_Add( &dst, lim, kLabelPrefix_Alias "%?{end}-%u", i == 2, i - 1 );
require_noerr( err, exit );
}
rdataLabel[ 0 ] = (uint8_t)( dst - (char *) &rdataLabel[ 1 ] );
@@ -7955,8 +8011,8 @@ static OSStatus
// Set CNAME record's TYPE, CLASS, TTL, and RDLENGTH.
- aliasTTL = useAliasTTLs ? aliasTTLs[ aliasCount - i ] : ( (uint32_t) gDNSServer_DefaultTTL );
- DNSRecordFixedFieldsInit( &fields, kDNSServiceType_CNAME, kDNSServiceClass_IN, aliasTTL, rdataLen );
+ aliasTTL = useAliasTTLs ? aliasTTLs[ aliasCount - i ] : me->defaultTTL;
+ DNSRecordFixedFieldsSet( &fields, kDNSServiceType_CNAME, kDNSServiceClass_IN, aliasTTL, (uint16_t) rdataLen );
err = DataBuffer_Append( inDB, &fields, sizeof( fields ) );
require_noerr( err, exit );
@@ -7982,30 +8038,36 @@ static OSStatus
{
// There are no aliases, so initialize the name compression pointer to point to QNAME.
- namePtr[ 0 ] = 0xC0;
- namePtr[ 1 ] = kDNSHeaderLength;
+ WriteDNSCompressionPtr( namePtr, kDNSHeaderLength );
}
- if( ( ( inQType == kDNSServiceType_A ) && nameHasA ) ||
- ( ( inQType == kDNSServiceType_AAAA ) && nameHasAAAA ) )
+ if( ( inQType == kDNSServiceType_A ) || ( inQType == kDNSServiceType_AAAA ) )
{
- uint8_t * lsb; // Pointer to the least significant byte of record data.
- size_t recordLen; // Length of the entire record.
- size_t rdataLen; // Length of record's RDATA.
- uint8_t rdata[ 16 ]; // A buffer that's big enough for either A or AAAA RDATA.
- uint8_t randItegers[ 255 ]; // Array for random integers in [1 .. 255].
- DNSRecordFixedFields fields;
+ uint8_t * lsb; // Pointer to the least significant byte of record data.
+ size_t recordLen; // Length of the entire record.
+ size_t rdataLen; // Length of record's RDATA.
+ uint8_t rdata[ 16 ]; // A buffer that's big enough for either A or AAAA RDATA.
+ uint8_t randIntegers[ 255 ]; // Array for random integers in [1, 255].
+ const int useBadAddrs = ( me->badUDPMode && !inForTCP ) ? true : false;
if( inQType == kDNSServiceType_A )
{
+ const uint32_t baseAddrV4 = useBadAddrs ? kDNSServerBadBaseAddrV4 : kDNSServerBaseAddrV4;
+
+ require_quiet( nameHasA, done );
+
rdataLen = 4;
- WriteBig32( rdata, kTestDNSServerBaseAddrV4 );
+ WriteBig32( rdata, baseAddrV4 );
lsb = &rdata[ 3 ];
}
else
{
+ const uint8_t * const baseAddrV6 = useBadAddrs ? kDNSServerBadBaseAddrV6 : kDNSServerBaseAddrV6;
+
+ require_quiet( nameHasAAAA, done );
+
rdataLen = 16;
- memcpy( rdata, kTestDNSServerBaseAddrV6, 16 );
+ memcpy( rdata, baseAddrV6, 16 );
lsb = &rdata[ 15 ];
}
@@ -8013,37 +8075,44 @@ static OSStatus
{
// Populate the array with all integers between 1 and <randCount>, inclusive.
- for( i = 0; i < randCount; ++i ) randItegers[ i ] = (uint8_t)( i + 1 );
+ for( i = 0; i < randCount; ++i ) randIntegers[ i ] = (uint8_t)( i + 1 );
+
+ // Prevent dubious static analyzer warning.
+ // Note: _DNSServerNameIsHostname() already enforces randCount >= addrCount. Also, this require_fatal() check
+ // needs to be placed right before the next for-loop. Any earlier, and the static analyzer warning will persist
+ // for some reason.
+
+ require_fatal( addrCount <= randCount, "Invalid Count label values: addrCount %u > randCount %u",
+ addrCount, randCount );
- // Create a contiguous subarray starting at index 0 that contains <count> randomly chosen integers between
+ // Create a contiguous subarray starting at index 0 that contains <addrCount> randomly chosen integers between
// 1 and <randCount>, inclusive.
- // Loop invariant 1: Array elements with indexes in [0 .. i - 1] have been randomly chosen.
- // Loop invariant 2: Array elements with indexes in [i .. randCount - 1] are candidates for being chosen.
+ // Loop invariant 1: Array elements with indexes in [0, i - 1] have been randomly chosen.
+ // Loop invariant 2: Array elements with indexes in [i, randCount - 1] are candidates for being chosen.
- for( i = 0; i < count; ++i )
+ for( i = 0; i < addrCount; ++i )
{
- uint8_t tmp;
- int j;
+ uint8_t tmp;
+ uint32_t j;
- j = (int) RandomRange( i, randCount - 1 );
+ j = RandomRange( i, randCount - 1 );
if( i != j )
{
- tmp = randItegers[ i ];
- randItegers[ i ] = randItegers[ j ];
- randItegers[ j ] = tmp;
+ tmp = randIntegers[ i ];
+ randIntegers[ i ] = randIntegers[ j ];
+ randIntegers[ j ] = tmp;
}
}
}
recordLen = sizeof( namePtr ) + sizeof( fields ) + rdataLen;
- for( i = 0; i < count; ++i )
+ for( i = 0; i < addrCount; ++i )
{
if( !inForTCP && ( ( DataBuffer_GetLen( inDB ) + recordLen ) > kDNSMaxUDPMessageSize ) )
{
truncated = true;
goto done;
}
- ++answerCount;
// Set record NAME.
@@ -8052,23 +8121,108 @@ static OSStatus
// Set record TYPE, CLASS, TTL, and RDLENGTH.
- DNSRecordFixedFieldsInit( &fields, inQType, kDNSServiceClass_IN, ttl, rdataLen );
+ DNSRecordFixedFieldsSet( &fields, (uint16_t) inQType, kDNSServiceClass_IN, ttl, (uint16_t) rdataLen );
err = DataBuffer_Append( inDB, &fields, sizeof( fields ) );
require_noerr( err, exit );
// Set record RDATA.
- *lsb = ( randCount > 0 ) ? randItegers[ i ] : ( *lsb + 1 );
+ *lsb = ( randCount > 0 ) ? randIntegers[ i ] : ( *lsb + 1 );
err = DataBuffer_Append( inDB, rdata, rdataLen );
require_noerr( err, exit );
+
+ ++answerCount;
+ }
+ }
+ else if( inQType == kDNSServiceType_SRV )
+ {
+ require_quiet( nameHasSRV, done );
+
+ DNSRecordFixedFieldsSet( &fields, kDNSServiceType_SRV, kDNSServiceClass_IN, me->defaultTTL, 0 );
+
+ for( i = 0; i < srvCount; ++i )
+ {
+ SRVRecordDataFixedFields fieldsSRV;
+ size_t rdataLen;
+ size_t recordLen;
+ const ParsedSRV * const srv = &srvArray[ i ];
+
+ rdataLen = sizeof( fieldsSRV ) + srvDomainLen + srv->targetLen + 1;
+ recordLen = sizeof( namePtr ) + sizeof( fields ) + rdataLen;
+
+ if( !inForTCP && ( ( DataBuffer_GetLen( inDB ) + recordLen ) > kDNSMaxUDPMessageSize ) )
+ {
+ truncated = true;
+ goto done;
+ }
+
+ // Append record NAME.
+
+ err = DataBuffer_Append( inDB, namePtr, sizeof( namePtr ) );
+ require_noerr( err, exit );
+
+ // Append record TYPE, CLASS, TTL, and RDLENGTH.
+
+ WriteBig16( fields.rdlength, rdataLen );
+ err = DataBuffer_Append( inDB, &fields, sizeof( fields ) );
+ require_noerr( err, exit );
+
+ // Append SRV RDATA.
+
+ SRVRecordDataFixedFieldsSet( &fieldsSRV, srv->priority, srv->weight, srv->port );
+
+ err = DataBuffer_Append( inDB, &fieldsSRV, sizeof( fieldsSRV ) );
+ require_noerr( err, exit );
+
+ if( srv->targetLen > 0 )
+ {
+ err = DataBuffer_Append( inDB, srv->targetPtr, srv->targetLen );
+ require_noerr( err, exit );
+ }
+
+ if( srvDomainLen > 0 )
+ {
+ err = DataBuffer_Append( inDB, srvDomainPtr, srvDomainLen );
+ require_noerr( err, exit );
+ }
+
+ err = DataBuffer_Append( inDB, "", 1 ); // Append root label.
+ require_noerr( err, exit );
+
+ ++answerCount;
+ }
+ }
+ else if( inQType == kDNSServiceType_SOA )
+ {
+ size_t nameLen, recordLen;
+
+ require_quiet( nameHasSOA, done );
+
+ nameLen = DomainNameLength( me->domain );
+ if( !inForTCP )
+ {
+ err = AppendSOARecord( NULL, me->domain, nameLen, 0, 0, 0, kRootLabel, kRootLabel, 0, 0, 0, 0, 0, &recordLen );
+ require_noerr( err, exit );
+
+ if( ( DataBuffer_GetLen( inDB ) + recordLen ) > kDNSMaxUDPMessageSize )
+ {
+ truncated = true;
+ goto done;
+ }
}
+
+ err = AppendSOARecord( inDB, me->domain, nameLen, kDNSServiceType_SOA, kDNSServiceClass_IN, me->defaultTTL,
+ kRootLabel, kRootLabel, me->serial, 1 * kSecondsPerDay, 2 * kSecondsPerHour, 1000 * kSecondsPerHour,
+ me->defaultTTL, NULL );
+ require_noerr( err, exit );
+
+ ++answerCount;
}
done:
hdr = (DNSHeader *) DataBuffer_GetPtr( inDB );
flags = DNSHeaderGetFlags( hdr );
- if( truncated ) flags |= kDNSHeaderFlag_Truncation;
if( notImplemented )
{
rcode = kDNSRCode_NotImplemented;
@@ -8076,6 +8230,7 @@ done:
else
{
flags |= kDNSHeaderFlag_AuthAnswer;
+ if( truncated ) flags |= kDNSHeaderFlag_Truncation;
rcode = nameExists ? kDNSRCode_NoError : kDNSRCode_NXDomain;
}
DNSFlagsSetRCode( flags, rcode );
@@ -8087,65 +8242,336 @@ exit:
return( err );
}
-//===========================================================================================================================
-// _DNSServerTCPReadHandler
-//===========================================================================================================================
-
-typedef struct
-{
- sockaddr_ip clientAddr; // Client's address.
- dispatch_source_t readSource; // Dispatch read source for client socket.
- dispatch_source_t writeSource; // Dispatch write source for client socket.
- size_t offset; // Offset into receive buffer.
- void * msgPtr; // Pointer to dynamically allocated message buffer.
- size_t msgLen; // Length of message buffer.
- Boolean readSuspended; // True if the read source is currently suspended.
- Boolean writeSuspended; // True if the write source is currently suspended.
- Boolean receivedLength; // True if receiving DNS message as opposed to the message length.
- uint8_t lenBuf[ 2 ]; // Buffer for two-octet message length field.
- iovec_t iov[ 2 ]; // IO vector for writing response message.
- iovec_t * iovPtr; // Vector pointer for SocketWriteData().
- int iovCount; // Vector count for SocketWriteData().
-
-} TCPConnectionContext;
-
-static void TCPConnectionStop( TCPConnectionContext *inContext );
-static void TCPConnectionContextFree( TCPConnectionContext *inContext );
-static void TCPConnectionReadHandler( void *inContext );
-static void TCPConnectionWriteHandler( void *inContext );
-
-#define TCPConnectionForget( X ) ForgetCustomEx( X, TCPConnectionStop, TCPConnectionContextFree )
-
-static void _DNSServerTCPReadHandler( void *inContext )
+static Boolean
+ _DNSServerNameIsHostname(
+ DNSServerRef me,
+ const uint8_t * inName,
+ uint32_t * outAliasCount,
+ uint32_t inAliasTTLs[ kMaxAliasTTLCount ],
+ size_t * outAliasTTLCount,
+ unsigned int * outCount,
+ unsigned int * outRandCount,
+ uint32_t * outTTL,
+ Boolean * outHasA,
+ Boolean * outHasAAAA,
+ Boolean * outHasSOA )
{
- OSStatus err;
- SocketContext * const sockCtx = (SocketContext *) inContext;
- TCPConnectionContext * connection;
- socklen_t clientAddrLen;
- SocketRef newSock = kInvalidSocketRef;
- SocketContext * newSockCtx = NULL;
-
- connection = (TCPConnectionContext *) calloc( 1, sizeof( *connection ) );
- require_action( connection, exit, err = kNoMemoryErr );
-
- clientAddrLen = (socklen_t) sizeof( connection->clientAddr );
- newSock = accept( sockCtx->sock, &connection->clientAddr.sa, &clientAddrLen );
- err = map_socket_creation_errno( newSock );
- require_noerr( err, exit );
-
- err = SocketContextCreate( newSock, connection, &newSockCtx );
- require_noerr( err, exit );
- newSock = kInvalidSocketRef;
-
- err = DispatchReadSourceCreate( newSockCtx->sock, NULL, TCPConnectionReadHandler, SocketContextCancelHandler,
- newSockCtx, &connection->readSource );
- require_noerr( err, exit );
- SocketContextRetain( newSockCtx );
- dispatch_resume( connection->readSource );
-
- err = DispatchWriteSourceCreate( newSockCtx->sock, NULL, TCPConnectionWriteHandler, SocketContextCancelHandler,
- newSockCtx, &connection->writeSource );
- require_noerr( err, exit );
+ OSStatus err;
+ const uint8_t * label;
+ const uint8_t * nextLabel;
+ uint32_t aliasCount = 0; // Arg from Alias label. Valid values are in [2, 2^31 - 1].
+ unsigned int count = 0; // First arg from Count label. Valid values are in [1, 255].
+ unsigned int randCount = 0; // Second arg from Count label. Valid values are in [count, 255].
+ int32_t ttl = -1; // Arg from TTL label. Valid values are in [0, 2^31 - 1].
+ size_t aliasTTLCount = 0; // Count of TTL args from Alias-TTL label.
+ int hasTagLabel = false;
+ int hasIPv4Label = false;
+ int hasIPv6Label = false;
+ int isNameValid = false;
+
+ for( label = inName; label[ 0 ]; label = nextLabel )
+ {
+ uint32_t arg;
+
+ nextLabel = &label[ 1 + label[ 0 ] ];
+
+ // Check if the first label is a valid alias TTL sequence label.
+
+ if( ( label == inName ) && ( strnicmp_prefix( &label[ 1 ], label[ 0 ], kLabelPrefix_AliasTTL ) == 0 ) )
+ {
+ const char * ptr = (const char *) &label[ 1 + sizeof_string( kLabelPrefix_AliasTTL ) ];
+ const char * const end = (const char *) nextLabel;
+ const char * next;
+
+ check( label[ 0 ] <= kDomainLabelLengthMax );
+
+ while( ptr < end )
+ {
+ if( *ptr != '-' ) break;
+ ++ptr;
+ err = DecimalTextToUInt32( ptr, end, &arg, &next );
+ if( err || ( arg > INT32_MAX ) ) break; // TTL must be in [0, 2^31 - 1].
+ inAliasTTLs[ aliasTTLCount++ ] = arg;
+ ptr = next;
+ }
+ if( ( aliasTTLCount == 0 ) || ( ptr != end ) ) break;
+ }
+
+ // Check if the first label is a valid alias label.
+
+ else if( ( label == inName ) && ( strnicmp_prefix( &label[ 1 ], label[ 0 ], kLabelPrefix_Alias ) == 0 ) )
+ {
+ const char * ptr = (const char *) &label[ 1 + sizeof_string( kLabelPrefix_Alias ) ];
+ const char * const end = (const char *) nextLabel;
+
+ if( ptr < end )
+ {
+ if( *ptr++ != '-' ) break;
+ err = DecimalTextToUInt32( ptr, end, &arg, &ptr );
+ if( err || ( arg < 2 ) || ( arg > INT32_MAX ) ) break; // Alias count must be in [2, 2^31 - 1].
+ aliasCount = arg;
+ if( ptr != end ) break;
+ }
+ else
+ {
+ aliasCount = 1;
+ }
+ }
+
+ // Check if this label is a valid count label.
+
+ else if( strnicmp_prefix( &label[ 1 ], label[ 0 ], kLabelPrefix_Count ) == 0 )
+ {
+ const char * ptr = (const char *) &label[ 1 + sizeof_string( kLabelPrefix_Count ) ];
+ const char * const end = (const char *) nextLabel;
+
+ if( count > 0 ) break; // Count cannot be specified more than once.
+
+ err = DecimalTextToUInt32( ptr, end, &arg, &ptr );
+ if( err || ( arg < 1 ) || ( arg > 255 ) ) break; // Count must be in [1, 255].
+ count = (unsigned int) arg;
+
+ if( ptr < end )
+ {
+ if( *ptr++ != '-' ) break;
+ err = DecimalTextToUInt32( ptr, end, &arg, &ptr );
+ if( err || ( arg < (uint32_t) count ) || ( arg > 255 ) ) break; // Rand count must be in [count, 255].
+ randCount = (unsigned int) arg;
+ if( ptr != end ) break;
+ }
+ }
+
+ // Check if this label is a valid TTL label.
+
+ else if( strnicmp_prefix( &label[ 1 ], label[ 0 ], kLabelPrefix_TTL ) == 0 )
+ {
+ const char * ptr = (const char *) &label[ 1 + sizeof_string( kLabelPrefix_TTL ) ];
+ const char * const end = (const char *) nextLabel;
+
+ if( ttl >= 0 ) break; // TTL cannot be specified more than once.
+
+ err = DecimalTextToUInt32( ptr, end, &arg, &ptr );
+ if( err || ( arg > INT32_MAX ) ) break; // TTL must be in [0, 2^31 - 1].
+ ttl = (int32_t) arg;
+ if( ptr != end ) break;
+ }
+
+ // Check if this label is a valid IPv4 label.
+
+ else if( strnicmpx( &label[ 1 ], label[ 0 ], kLabel_IPv4 ) == 0 )
+ {
+ if( hasIPv4Label || hasIPv6Label ) break; // Valid names have at most one IPv4 or IPv6 label.
+ hasIPv4Label = true;
+ }
+
+ // Check if this label is a valid IPv6 label.
+
+ else if( strnicmpx( &label[ 1 ], label[ 0 ], kLabel_IPv6 ) == 0 )
+ {
+ if( hasIPv4Label || hasIPv6Label ) break; // Valid names have at most one IPv4 or IPv6 label.
+ hasIPv6Label = true;
+ }
+
+ // Check if this label is a valid tag label.
+
+ else if( strnicmp_prefix( &label[ 1 ], label[ 0 ], kLabelPrefix_Tag ) == 0 )
+ {
+ hasTagLabel = true;
+ }
+
+ // If this and the remaining labels are equal to "d.test.", then the name exists. Otherwise, this label is invalid.
+ // In both cases, there are no more labels to check.
+
+ else
+ {
+ if( DomainNameEqual( label, me->domain ) ) isNameValid = true;
+ break;
+ }
+ }
+ require_quiet( isNameValid, exit );
+
+ if( outAliasCount ) *outAliasCount = aliasCount;
+ if( outAliasTTLCount ) *outAliasTTLCount = aliasTTLCount;
+ if( outCount ) *outCount = ( count > 0 ) ? count : 1;
+ if( outRandCount ) *outRandCount = randCount;
+ if( outTTL ) *outTTL = ( ttl >= 0 ) ? ( (uint32_t) ttl ) : me->defaultTTL;
+ if( outHasA ) *outHasA = ( hasIPv4Label || !hasIPv6Label ) ? true : false;
+ if( outHasAAAA ) *outHasAAAA = ( hasIPv6Label || !hasIPv4Label ) ? true : false;
+ if( outHasSOA )
+ {
+ *outHasSOA = ( !count && ( ttl < 0 ) && !hasIPv4Label && !hasIPv6Label && !hasTagLabel ) ? true : false;
+ }
+
+exit:
+ return( isNameValid ? true : false );
+}
+
+static Boolean
+ _DNSServerNameIsSRVName(
+ DNSServerRef me,
+ const uint8_t * inName,
+ const uint8_t ** outDomainPtr,
+ size_t * outDomainLen,
+ ParsedSRV inSRVArray[ kMaxParsedSRVCount ],
+ size_t * outSRVCount )
+{
+ OSStatus err;
+ const uint8_t * label;
+ const uint8_t * domainPtr;
+ size_t domainLen;
+ size_t srvCount;
+ uint32_t arg;
+ int isNameValid = false;
+
+ label = inName;
+
+ // Ensure that first label, i.e, the service label, begins with a '_' character.
+
+ require_quiet( ( label[ 0 ] > 0 ) && ( label[ 1 ] == '_' ), exit );
+ label = NextLabel( label );
+
+ // Ensure that the second label, i.e., the proto label, begins with a '_' character (usually _tcp or _udp).
+
+ require_quiet( ( label[ 0 ] > 0 ) && ( label[ 1 ] == '_' ), exit );
+ label = NextLabel( label );
+
+ // Parse the domain name, if any.
+
+ domainPtr = label;
+ while( *label )
+ {
+ if( DomainNameEqual( label, me->domain ) ||
+ ( strnicmp_prefix( &label[ 1 ], label[ 0 ], kLabelPrefix_SRV ) == 0 ) ) break;
+ label = NextLabel( label );
+ }
+ require_quiet( *label, exit );
+
+ domainLen = (size_t)( label - domainPtr );
+
+ // Parse SRV labels, if any.
+
+ srvCount = 0;
+ while( strnicmp_prefix( &label[ 1 ], label[ 0 ], kLabelPrefix_SRV ) == 0 )
+ {
+ const uint8_t * const nextLabel = NextLabel( label );
+ const char * ptr = (const char *) &label[ 1 + sizeof_string( kLabelPrefix_SRV ) ];
+ const char * const end = (const char *) nextLabel;
+ const uint8_t * target;
+ unsigned int priority, weight, port;
+
+ err = DecimalTextToUInt32( ptr, end, &arg, &ptr );
+ require_quiet( !err && ( arg <= UINT16_MAX ), exit );
+ priority = (unsigned int) arg;
+
+ require_quiet( ( ptr < end ) && ( *ptr == '-' ), exit );
+ ++ptr;
+
+ err = DecimalTextToUInt32( ptr, end, &arg, &ptr );
+ require_quiet( !err && ( arg <= UINT16_MAX ), exit );
+ weight = (unsigned int) arg;
+
+ require_quiet( ( ptr < end ) && ( *ptr == '-' ), exit );
+ ++ptr;
+
+ err = DecimalTextToUInt32( ptr, end, &arg, &ptr );
+ require_quiet( !err && ( arg <= UINT16_MAX ), exit );
+ port = (unsigned int) arg;
+
+ require_quiet( ptr == end, exit );
+
+ target = nextLabel;
+ for( label = nextLabel; *label; label = NextLabel( label ) )
+ {
+ if( DomainNameEqual( label, me->domain ) ||
+ ( strnicmp_prefix( &label[ 1 ], label[ 0 ], kLabelPrefix_SRV ) == 0 ) ) break;
+ }
+ require_quiet( *label, exit );
+
+ if( inSRVArray )
+ {
+ inSRVArray[ srvCount ].priority = (uint16_t) priority;
+ inSRVArray[ srvCount ].weight = (uint16_t) weight;
+ inSRVArray[ srvCount ].port = (uint16_t) port;
+ inSRVArray[ srvCount ].targetPtr = target;
+ inSRVArray[ srvCount ].targetLen = (uint16_t)( label - target );
+ }
+ ++srvCount;
+ }
+ require_quiet( DomainNameEqual( label, me->domain ), exit );
+ isNameValid = true;
+
+ if( outDomainPtr ) *outDomainPtr = domainPtr;
+ if( outDomainLen ) *outDomainLen = domainLen;
+ if( outSRVCount ) *outSRVCount = srvCount;
+
+exit:
+ return( isNameValid ? true : false );
+}
+
+//===========================================================================================================================
+// _DNSServerTCPReadHandler
+//===========================================================================================================================
+
+typedef struct
+{
+ DNSServerRef server; // Reference to DNS server object.
+ sockaddr_ip clientAddr; // Client's address.
+ dispatch_source_t readSource; // Dispatch read source for client socket.
+ dispatch_source_t writeSource; // Dispatch write source for client socket.
+ size_t offset; // Offset into receive buffer.
+ void * msgPtr; // Pointer to dynamically allocated message buffer.
+ size_t msgLen; // Length of message buffer.
+ Boolean readSuspended; // True if the read source is currently suspended.
+ Boolean writeSuspended; // True if the write source is currently suspended.
+ Boolean receivedLength; // True if receiving DNS message as opposed to the message length.
+ uint8_t lenBuf[ 2 ]; // Buffer for two-octet message length field.
+ iovec_t iov[ 2 ]; // IO vector for writing response message.
+ iovec_t * iovPtr; // Vector pointer for SocketWriteData().
+ int iovCount; // Vector count for SocketWriteData().
+
+} TCPConnectionContext;
+
+static void TCPConnectionStop( TCPConnectionContext *inContext );
+static void TCPConnectionContextFree( TCPConnectionContext *inContext );
+static void TCPConnectionReadHandler( void *inContext );
+static void TCPConnectionWriteHandler( void *inContext );
+
+#define TCPConnectionForget( X ) ForgetCustomEx( X, TCPConnectionStop, TCPConnectionContextFree )
+
+static void _DNSServerTCPReadHandler( void *inContext )
+{
+ OSStatus err;
+ SocketContext * const sockCtx = (SocketContext *) inContext;
+ DNSServerRef const me = (DNSServerRef) sockCtx->userContext;
+ TCPConnectionContext * connection;
+ socklen_t clientAddrLen;
+ SocketRef newSock = kInvalidSocketRef;
+ SocketContext * newSockCtx = NULL;
+
+ connection = (TCPConnectionContext *) calloc( 1, sizeof( *connection ) );
+ require_action( connection, exit, err = kNoMemoryErr );
+
+ CFRetain( me );
+ connection->server = me;
+
+ clientAddrLen = (socklen_t) sizeof( connection->clientAddr );
+ newSock = accept( sockCtx->sock, &connection->clientAddr.sa, &clientAddrLen );
+ err = map_socket_creation_errno( newSock );
+ require_noerr( err, exit );
+
+ err = SocketContextCreate( newSock, connection, &newSockCtx );
+ require_noerr( err, exit );
+ newSock = kInvalidSocketRef;
+
+ err = DispatchReadSourceCreate( newSockCtx->sock, me->queue, TCPConnectionReadHandler, SocketContextCancelHandler,
+ newSockCtx, &connection->readSource );
+ require_noerr( err, exit );
+ SocketContextRetain( newSockCtx );
+ dispatch_resume( connection->readSource );
+
+ err = DispatchWriteSourceCreate( newSockCtx->sock, me->queue, TCPConnectionWriteHandler, SocketContextCancelHandler,
+ newSockCtx, &connection->writeSource );
+ require_noerr( err, exit );
SocketContextRetain( newSockCtx );
connection->writeSuspended = true;
connection = NULL;
@@ -8174,6 +8600,7 @@ static void TCPConnectionContextFree( TCPConnectionContext *inContext )
{
check( !inContext->readSource );
check( !inContext->writeSource );
+ ForgetCF( &inContext->server );
ForgetMem( &inContext->msgPtr );
free( inContext );
}
@@ -8229,7 +8656,8 @@ static void TCPConnectionReadHandler( void *inContext )
// Create response.
- err = _DNSServerAnswerQueryForTCP( connection->msgPtr, connection->msgLen, &responsePtr, &responseLen );
+ err = _DNSServerAnswerQueryForTCP( connection->server, connection->msgPtr, connection->msgLen, &responsePtr,
+ &responseLen );
require_noerr_quiet( err, exit );
// Send response.
@@ -8281,3095 +8709,9340 @@ exit:
}
//===========================================================================================================================
-// GAIPerfCmd
+// MDNSReplierCmd
//===========================================================================================================================
-#define kGAIPerfStandardTTL ( 1 * kSecondsPerHour )
-
-typedef struct GAITesterPrivate * GAITesterRef;
-typedef struct GAITestCase GAITestCase;
-
-typedef uint32_t GAITesterEventType;
-#define kGAITesterEvent_Started 1
-#define kGAITesterEvent_Stopped 2
-
typedef struct
{
- const char * name; // Domain name that was resolved.
- int64_t connectionTimeUs; // Time in microseconds that it took to create a DNS-SD connection.
- int64_t firstTimeUs; // Time in microseconds that it took to get the first address result.
- int64_t timeUs; // Time in microseconds that it took to get all expected address results.
+ uint8_t * hostname; // Used as the base name for hostnames and service names.
+ uint8_t * serviceLabel; // Label containing the base service name.
+ unsigned int maxInstanceCount; // Maximum number of service instances and hostnames.
+ uint64_t * bitmaps; // Array of 64-bit bitmaps for keeping track of needed responses.
+ size_t bitmapCount; // Number of 64-bit bitmaps.
+ dispatch_source_t readSourceV4; // Read dispatch source for IPv4 socket.
+ dispatch_source_t readSourceV6; // Read dispatch source for IPv6 socket.
+ uint32_t ifIndex; // Index of the interface to run on.
+ unsigned int recordCountA; // Number of A records per hostname.
+ unsigned int recordCountAAAA; // Number of AAAA records per hostname.
+ unsigned int maxDropCount; // If > 0, the drop rates apply to only the first <maxDropCount> responses.
+ double ucastDropRate; // Probability of dropping a unicast response.
+ double mcastDropRate; // Probability of dropping a multicast query or response.
+ uint8_t * dropCounters; // If maxDropCount > 0, array of <maxInstanceCount> response drop counters.
+ Boolean noAdditionals; // True if responses are to not include additional records.
+ Boolean useIPv4; // True if the replier is to use IPv4.
+ Boolean useIPv6; // True if the replier is to use IPv6.
+ uint8_t msgBuf[ kMDNSMessageSizeMax ]; // Buffer for received mDNS message.
+#if( TARGET_OS_DARWIN )
+ dispatch_source_t processMonitor; // Process monitor source for process being followed, if any.
+ pid_t followPID; // PID of process being followed, if any. (If it exits, we exit).
+#endif
-} GAITestItemResult;
-
-typedef void ( *GAITesterEventHandler_f )( GAITesterEventType inType, void *inContext );
-typedef void
- ( *GAITesterResultsHandler_f )(
- const char * inCaseTitle,
- MicroTime64 inCaseStartTime,
- MicroTime64 inCaseEndTime,
- const GAITestItemResult * inResults,
- size_t inResultCount,
- size_t inItemCount,
- void * inContext );
-
-typedef unsigned int GAITestAddrType;
-#define kGAITestAddrType_None 0
-#define kGAITestAddrType_IPv4 ( 1U << 0 )
-#define kGAITestAddrType_IPv6 ( 1U << 1 )
-#define kGAITestAddrType_Both ( kGAITestAddrType_IPv4 | kGAITestAddrType_IPv6 )
-
-#define GAITestAddrTypeIsValid( X ) \
- ( ( (X) & kGAITestAddrType_Both ) && ( ( (X) & ~kGAITestAddrType_Both ) == 0 ) )
+} MDNSReplierContext;
-typedef enum
+typedef struct MRResourceRecord MRResourceRecord;
+struct MRResourceRecord
{
- kGAIPerfOutputFormat_JSON = 1,
- kGAIPerfOutputFormat_XML = 2,
- kGAIPerfOutputFormat_Binary = 3
-
-} GAIPerfOutputFormatType;
+ MRResourceRecord * next; // Next item in list.
+ uint8_t * name; // Resource record name.
+ uint16_t type; // Resource record type.
+ uint16_t class; // Resource record class.
+ uint32_t ttl; // Resource record TTL.
+ uint16_t rdlength; // Resource record data length.
+ uint8_t * rdata; // Resource record data.
+ const uint8_t * target; // For SRV records, pointer to target in RDATA.
+};
-typedef struct
+typedef struct MRNameOffsetItem MRNameOffsetItem;
+struct MRNameOffsetItem
{
- GAITesterRef tester; // GAI tester object.
- CFMutableArrayRef caseResults; // Array of test case results.
- char * outputFilePath; // File to write test results to. If NULL, then write to stdout.
- GAIPerfOutputFormatType outputFormat; // Format of test results output.
- unsigned int callDelayMs; // Amount of time to wait before calling DNSServiceGetAddrInfo().
- unsigned int serverDelayMs; // Amount of additional time to have server delay its responses.
- unsigned int defaultIterCount; // Default test case iteration count.
- dispatch_source_t sigIntSource; // Dispatch source for SIGINT.
- dispatch_source_t sigTermSource; // Dispatch source for SIGTERM.
- Boolean gotSignal; // True if SIGINT or SIGTERM was caught.
- Boolean testerStarted; // True if the GAI tester was started.
- Boolean appendNewLine; // True if a newline character should be appended to JSON output.
-
-} GAIPerfContext;
-
-static void GAIPerfContextFree( GAIPerfContext *inContext );
-static OSStatus GAIPerfAddAdvancedTestCases( GAIPerfContext *inContext );
-static OSStatus GAIPerfAddBasicTestCases( GAIPerfContext *inContext );
-static void GAIPerfEventHandler( GAITesterEventType inType, void *inContext );
-static void
- GAIPerfResultsHandler(
- const char * inCaseTitle,
- MicroTime64 inCaseStartTime,
- MicroTime64 inCaseEndTime,
- const GAITestItemResult * inResults,
- size_t inResultCount,
- size_t inItemCount,
- void * inContext );
-static void GAIPerfSignalHandler( void *inContext );
+ MRNameOffsetItem * next; // Next item in list.
+ uint16_t offset; // Offset of domain name in response message.
+ uint8_t name[ 1 ]; // Variable-length array for domain name.
+};
-CFTypeID GAITesterGetTypeID( void );
+#if( TARGET_OS_DARWIN )
+static void _MDNSReplierFollowedProcessHandler( void *inContext );
+#endif
+static void _MDNSReplierReadHandler( void *inContext );
static OSStatus
- GAITesterCreate(
- dispatch_queue_t inQueue,
- int inCallDelayMs,
- int inServerDelayMs,
- int inServerDefaultTTL,
- GAITesterRef * outTester );
-static void GAITesterStart( GAITesterRef inTester );
-static void GAITesterStop( GAITesterRef inTester );
-static void GAITesterAddCase( GAITesterRef inTester, GAITestCase *inCase );
-static void
- GAITesterSetEventHandler(
- GAITesterRef inTester,
- GAITesterEventHandler_f inEventHandler,
- void * inEventContext );
+ _MDNSReplierAnswerQuery(
+ MDNSReplierContext * inContext,
+ const uint8_t * inQueryPtr,
+ size_t inQueryLen,
+ sockaddr_ip * inSender,
+ SocketRef inSock,
+ unsigned int inIndex );
+static OSStatus
+ _MDNSReplierAnswerListAdd(
+ MDNSReplierContext * inContext,
+ MRResourceRecord ** inAnswerList,
+ unsigned int inIndex,
+ const uint8_t * inName,
+ unsigned int inType,
+ unsigned int inClass );
static void
- GAITesterSetResultsHandler(
- GAITesterRef inTester,
- GAITesterResultsHandler_f inResultsHandler,
- void * inResultsContext );
-
-static OSStatus GAITestCaseCreate( const char *inTitle, unsigned int inTimeLimitMs, GAITestCase **outSet );
-static void GAITestCaseFree( GAITestCase *inCase );
+ _MDNSReplierAnswerListRemovePTR(
+ MRResourceRecord ** inAnswerListPtr,
+ const uint8_t * inName,
+ const uint8_t * inRData );
static OSStatus
- GAITestCaseAddItem(
- GAITestCase * inCase,
- unsigned int inAliasCount,
- unsigned int inAddressCount,
- int inTTL,
- GAITestAddrType inHasAddrs,
- GAITestAddrType inWantAddrs,
- unsigned int inItemCount );
-static OSStatus GAITestCaseAddLocalHostItem( GAITestCase *inCase, GAITestAddrType inWantAddrs, unsigned int inItemCount );
+ _MDNSReplierSendOrDropResponse(
+ MDNSReplierContext * inContext,
+ MRResourceRecord * inAnswerList,
+ sockaddr_ip * inQuerier,
+ SocketRef inSock,
+ unsigned int inIndex,
+ Boolean inUnicast );
+static OSStatus
+ _MDNSReplierCreateResponse(
+ MDNSReplierContext * inContext,
+ MRResourceRecord * inAnswerList,
+ unsigned int inIndex,
+ uint8_t ** outResponsePtr,
+ size_t * outResponseLen );
+static OSStatus
+ _MDNSReplierAppendNameToResponse(
+ DataBuffer * inResponse,
+ const uint8_t * inName,
+ MRNameOffsetItem ** inNameOffsetListPtr );
+static Boolean
+ _MDNSReplierServiceTypeMatch(
+ const MDNSReplierContext * inContext,
+ const uint8_t * inName,
+ unsigned int * outTXTSize,
+ unsigned int * outCount );
+static Boolean
+ _MDNSReplierServiceInstanceNameMatch(
+ const MDNSReplierContext * inContext,
+ const uint8_t * inName,
+ unsigned int * outIndex,
+ unsigned int * outTXTSize,
+ unsigned int * outCount );
+static Boolean _MDNSReplierAboutRecordNameMatch( const MDNSReplierContext *inContext, const uint8_t *inName );
+static Boolean
+ _MDNSReplierHostnameMatch(
+ const MDNSReplierContext * inContext,
+ const uint8_t * inName,
+ unsigned int * outIndex );
+static OSStatus _MDNSReplierCreateTXTRecord( const uint8_t *inRecordName, size_t inSize, uint8_t **outTXT );
+static OSStatus
+ _MRResourceRecordCreate(
+ uint8_t * inName,
+ uint16_t inType,
+ uint16_t inClass,
+ uint32_t inTTL,
+ uint16_t inRDLength,
+ uint8_t * inRData,
+ MRResourceRecord ** outRecord );
+static void _MRResourceRecordFree( MRResourceRecord *inRecord );
+static void _MRResourceRecordFreeList( MRResourceRecord *inList );
+static OSStatus _MRNameOffsetItemCreate( const uint8_t *inName, uint16_t inOffset, MRNameOffsetItem **outItem );
+static void _MRNameOffsetItemFree( MRNameOffsetItem *inItem );
+static void _MRNameOffsetItemFreeList( MRNameOffsetItem *inList );
-#define kGAIPerfTestSuite_Basic 1
-#define kGAIPerfTestSuite_Advanced 2
+ulog_define_ex( "com.apple.dnssdutil", MDNSReplier, kLogLevelInfo, kLogFlags_None, "MDNSReplier", NULL );
+#define mr_ulog( LEVEL, ... ) ulog( &log_category_from_name( MDNSReplier ), (LEVEL), __VA_ARGS__ )
-static void GAIPerfCmd( void )
+static void MDNSReplierCmd( void )
{
- OSStatus err;
- GAIPerfContext * context;
- int suiteValue;
+ OSStatus err;
+ MDNSReplierContext * context;
+ SocketRef sockV4 = kInvalidSocketRef;
+ SocketRef sockV6 = kInvalidSocketRef;
+ const char * ifname;
+ size_t len;
+ uint8_t name[ 1 + kDomainLabelLengthMax + 1 ];
+ char ifnameBuf[ IF_NAMESIZE + 1 ];
- context = (GAIPerfContext *) calloc( 1, sizeof( *context ) );
- require_action( context, exit, err = kNoMemoryErr );
+ err = CheckIntegerArgument( gMDNSReplier_MaxInstanceCount, "max instance count", 1, UINT16_MAX );
+ require_noerr_quiet( err, exit );
- context->caseResults = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks );
- require_action( context->caseResults, exit, err = kNoMemoryErr );
+ err = CheckIntegerArgument( gMDNSReplier_RecordCountA, "A record count", 0, 255 );
+ require_noerr_quiet( err, exit );
- context->outputFormat = (GAIPerfOutputFormatType) CLIArgToValue( "format", gGAIPerf_OutputFormat, &err,
- "json", kGAIPerfOutputFormat_JSON,
- "xml", kGAIPerfOutputFormat_XML,
- "binary", kGAIPerfOutputFormat_Binary,
- NULL );
+ err = CheckIntegerArgument( gMDNSReplier_RecordCountAAAA, "AAAA record count", 0, 255 );
require_noerr_quiet( err, exit );
- context->callDelayMs = ( gGAIPerf_CallDelayMs >= 0 ) ? (unsigned int) gGAIPerf_CallDelayMs : 0;
- context->serverDelayMs = ( gGAIPerf_ServerDelayMs >= 0 ) ? (unsigned int) gGAIPerf_ServerDelayMs : 0;
- context->defaultIterCount = ( gGAIPerf_DefaultIterCount >= 0 ) ? (unsigned int) gGAIPerf_DefaultIterCount : 0;
- context->appendNewLine = gGAIPerf_OutputAppendNewLine ? true : false;
+ err = CheckDoubleArgument( gMDNSReplier_UnicastDropRate, "unicast drop rate", 0.0, 1.0 );
+ require_noerr_quiet( err, exit );
- if( gGAIPerf_OutputFilePath )
+ err = CheckDoubleArgument( gMDNSReplier_MulticastDropRate, "multicast drop rate", 0.0, 1.0 );
+ require_noerr_quiet( err, exit );
+
+ err = CheckIntegerArgument( gMDNSReplier_MaxDropCount, "drop count", 0, 255 );
+ require_noerr_quiet( err, exit );
+
+ if( gMDNSReplier_Foreground )
{
- context->outputFilePath = strdup( gGAIPerf_OutputFilePath );
- require_action( context->outputFilePath, exit, err = kNoMemoryErr );
+ LogControl( "MDNSReplier:output=file;stdout,MDNSReplier:flags=time;prefix" );
}
- err = GAITesterCreate( dispatch_get_main_queue(), (int) context->callDelayMs, (int) context->serverDelayMs,
- kGAIPerfStandardTTL, &context->tester );
- require_noerr( err, exit );
+ context = (MDNSReplierContext *) calloc( 1, sizeof( *context ) );
+ require_action( context, exit, err = kNoMemoryErr );
- check( gGAIPerf_TestSuite );
- suiteValue = CLIArgToValue( "suite", gGAIPerf_TestSuite, &err,
- "basic", kGAIPerfTestSuite_Basic,
- "advanced", kGAIPerfTestSuite_Advanced,
- NULL );
- require_noerr_quiet( err, exit );
+ context->maxInstanceCount = (unsigned int) gMDNSReplier_MaxInstanceCount;
+ context->recordCountA = (unsigned int) gMDNSReplier_RecordCountA;
+ context->recordCountAAAA = (unsigned int) gMDNSReplier_RecordCountAAAA;
+ context->maxDropCount = (unsigned int) gMDNSReplier_MaxDropCount;
+ context->ucastDropRate = gMDNSReplier_UnicastDropRate;
+ context->mcastDropRate = gMDNSReplier_MulticastDropRate;
+ context->noAdditionals = gMDNSReplier_NoAdditionals ? true : false;
+ context->useIPv4 = ( gMDNSReplier_UseIPv4 || !gMDNSReplier_UseIPv6 ) ? true : false;
+ context->useIPv6 = ( gMDNSReplier_UseIPv6 || !gMDNSReplier_UseIPv4 ) ? true : false;
+ context->bitmapCount = ( context->maxInstanceCount + 63 ) / 64;
- switch( suiteValue )
+#if( TARGET_OS_DARWIN )
+ if( gMDNSReplier_FollowPID )
{
- case kGAIPerfTestSuite_Basic:
- err = GAIPerfAddBasicTestCases( context );
- require_noerr( err, exit );
- break;
+ err = StringToPID( gMDNSReplier_FollowPID, &context->followPID );
+ if( err || ( context->followPID < 0 ) )
+ {
+ FPrintF( stderr, "error: Invalid follow PID: %s\n", gMDNSReplier_FollowPID );
+ goto exit;
+ }
- case kGAIPerfTestSuite_Advanced:
- err = GAIPerfAddAdvancedTestCases( context );
- require_noerr( err, exit );
- break;
+ err = DispatchProcessMonitorCreate( context->followPID, DISPATCH_PROC_EXIT, dispatch_get_main_queue(),
+ _MDNSReplierFollowedProcessHandler, NULL, context, &context->processMonitor );
+ require_noerr( err, exit );
+ dispatch_resume( context->processMonitor );
+ }
+ else
+ {
+ context->followPID = -1;
+ }
+#endif
+
+ if( context->maxDropCount > 0 )
+ {
+ context->dropCounters = (uint8_t *) calloc( context->maxInstanceCount, sizeof( *context->dropCounters ) );
+ require_action( context->dropCounters, exit, err = kNoMemoryErr );
+ }
+
+ context->bitmaps = (uint64_t *) calloc( context->bitmapCount, sizeof( *context->bitmaps ) );
+ require_action( context->bitmaps, exit, err = kNoMemoryErr );
+
+ // Create the base hostname label.
+
+ len = strlen( gMDNSReplier_Hostname );
+ if( context->maxInstanceCount > 1 )
+ {
+ unsigned int maxInstanceCount, digitCount;
- default:
- err = kValueErr;
- break;
+ // When there's more than one instance, extra bytes are needed to append " (<instance index>)" or
+ // "-<instance index>" to the base hostname.
+
+ maxInstanceCount = context->maxInstanceCount;
+ for( digitCount = 0; maxInstanceCount > 0; ++digitCount ) maxInstanceCount /= 10;
+ len += ( 3 + digitCount );
}
- GAITesterSetEventHandler( context->tester, GAIPerfEventHandler, context );
- GAITesterSetResultsHandler( context->tester, GAIPerfResultsHandler, context );
+ if( len <= kDomainLabelLengthMax )
+ {
+ uint8_t * dst = &name[ 1 ];
+ uint8_t * lim = &name[ countof( name ) ];
+
+ SNPrintF_Add( (char **) &dst, (char *) lim, "%s", gMDNSReplier_Hostname );
+ name[ 0 ] = (uint8_t)( dst - &name[ 1 ] );
+
+ err = DomainNameDupLower( name, &context->hostname, NULL );
+ require_noerr( err, exit );
+ }
+ else
+ {
+ FPrintF( stderr, "error: Base name \"%s\" is too long for max instance count of %u.\n",
+ gMDNSReplier_Hostname, context->maxInstanceCount );
+ goto exit;
+ }
- signal( SIGINT, SIG_IGN );
- err = DispatchSignalSourceCreate( SIGINT, GAIPerfSignalHandler, context, &context->sigIntSource );
- require_noerr( err, exit );
- dispatch_resume( context->sigIntSource );
+ // Create the service label.
- signal( SIGTERM, SIG_IGN );
- err = DispatchSignalSourceCreate( SIGTERM, GAIPerfSignalHandler, context, &context->sigTermSource );
- require_noerr( err, exit );
- dispatch_resume( context->sigTermSource );
+ len = strlen( gMDNSReplier_ServiceTypeTag ) + 3; // We need three extra bytes for the service type prefix "_t-".
+ if( len <= kDomainLabelLengthMax )
+ {
+ uint8_t * dst = &name[ 1 ];
+ uint8_t * lim = &name[ countof( name ) ];
+
+ SNPrintF_Add( (char **) &dst, (char *) lim, "_t-%s", gMDNSReplier_ServiceTypeTag );
+ name[ 0 ] = (uint8_t)( dst - &name[ 1 ] );
+
+ err = DomainNameDupLower( name, &context->serviceLabel, NULL );
+ require_noerr( err, exit );
+ }
+ else
+ {
+ FPrintF( stderr, "error: Service type tag is too long.\n" );
+ goto exit;
+ }
+
+ err = InterfaceIndexFromArgString( gInterface, &context->ifIndex );
+ require_noerr_quiet( err, exit );
+
+ ifname = if_indextoname( context->ifIndex, ifnameBuf );
+ require_action( ifname, exit, err = kNameErr );
+
+ // Set up IPv4 socket.
+
+ if( context->useIPv4 )
+ {
+ err = CreateMulticastSocket( GetMDNSMulticastAddrV4(), kMDNSPort, ifname, context->ifIndex, true, NULL, &sockV4 );
+ require_noerr( err, exit );
+ }
+
+ // Set up IPv6 socket.
+
+ if( context->useIPv6 )
+ {
+ err = CreateMulticastSocket( GetMDNSMulticastAddrV6(), kMDNSPort, ifname, context->ifIndex, true, NULL, &sockV6 );
+ require_noerr( err, exit );
+ }
+
+ // Create dispatch read sources for socket(s).
+
+ if( IsValidSocket( sockV4 ) )
+ {
+ SocketContext * sockCtx;
+
+ err = SocketContextCreate( sockV4, context, &sockCtx );
+ require_noerr( err, exit );
+ sockV4 = kInvalidSocketRef;
+
+ err = DispatchReadSourceCreate( sockCtx->sock, NULL, _MDNSReplierReadHandler, SocketContextCancelHandler, sockCtx,
+ &context->readSourceV4 );
+ if( err ) ForgetSocketContext( &sockCtx );
+ require_noerr( err, exit );
+
+ dispatch_resume( context->readSourceV4 );
+ }
+
+ if( IsValidSocket( sockV6 ) )
+ {
+ SocketContext * sockCtx;
+
+ err = SocketContextCreate( sockV6, context, &sockCtx );
+ require_noerr( err, exit );
+ sockV6 = kInvalidSocketRef;
+
+ err = DispatchReadSourceCreate( sockCtx->sock, NULL, _MDNSReplierReadHandler, SocketContextCancelHandler, sockCtx,
+ &context->readSourceV6 );
+ if( err ) ForgetSocketContext( &sockCtx );
+ require_noerr( err, exit );
+
+ dispatch_resume( context->readSourceV6 );
+ }
- GAITesterStart( context->tester );
dispatch_main();
exit:
- if( context ) GAIPerfContextFree( context );
- if( err ) exit( 1 );
+ ForgetSocket( &sockV4 );
+ ForgetSocket( &sockV6 );
+ exit( 1 );
}
+#if( TARGET_OS_DARWIN )
//===========================================================================================================================
-// GAIPerfContextFree
+// _MDNSReplierFollowedProcessHandler
//===========================================================================================================================
-static void GAIPerfContextFree( GAIPerfContext *inContext )
+static void _MDNSReplierFollowedProcessHandler( void *inContext )
{
- ForgetCF( &inContext->tester );
- ForgetCF( &inContext->caseResults );
- ForgetMem( &inContext->outputFilePath );
- dispatch_source_forget( &inContext->sigIntSource );
- dispatch_source_forget( &inContext->sigTermSource );
- free( inContext );
+ MDNSReplierContext * const context = (MDNSReplierContext *) inContext;
+
+ if( dispatch_source_get_data( context->processMonitor ) & DISPATCH_PROC_EXIT )
+ {
+ mr_ulog( kLogLevelNotice, "Exiting: followed process (%lld) exited.\n", (int64_t) context->followPID );
+ exit( 0 );
+ }
}
+#endif
//===========================================================================================================================
-// GAIPerfAddAdvancedTestCases
+// _MDNSReplierReadHandler
//===========================================================================================================================
-#define kTestCaseTitleBufferSize 128
-
-static void
- _GAIPerfWriteTestCaseTitle(
- char inBuffer[ kTestCaseTitleBufferSize ],
- unsigned int inCNAMERecordCount,
- unsigned int inARecordCount,
- unsigned int inAAAARecordCount,
- GAITestAddrType inRequested,
- unsigned int inIterationCount,
- Boolean inIterationsAreUnique );
-static void
- _GAIPerfWriteLocalHostTestCaseTitle(
- char inBuffer[ kTestCaseTitleBufferSize ],
- GAITestAddrType inRequested,
- unsigned int inIterationCount );
-static unsigned int
- _GAIPerfTimeLimitMs(
- unsigned int inCallDelayMs,
- unsigned int inServerDelayMs,
- unsigned int inIterationCount );
-
-#define kGAIPerfAdvancedTestSuite_MaxAliasCount 4
-#define kGAIPerfAdvancedTestSuite_MaxAddrCount 8
+#define ShouldDrop( P ) ( ( (P) > 0.0 ) && ( ( (P) >= 1.0 ) || RandomlyTrue( P ) ) )
-static OSStatus GAIPerfAddAdvancedTestCases( GAIPerfContext *inContext )
+static void _MDNSReplierReadHandler( void *inContext )
{
- OSStatus err;
- unsigned int aliasCount, addressCount, timeLimitMs, i;
- GAITestCase * testCase = NULL;
- char title[ kTestCaseTitleBufferSize ];
+ OSStatus err;
+ SocketContext * const sockCtx = (SocketContext *) inContext;
+ MDNSReplierContext * const context = (MDNSReplierContext *) sockCtx->userContext;
+ size_t msgLen;
+ sockaddr_ip sender;
+ const DNSHeader * hdr;
+ unsigned int flags, questionCount, i, j;
+ const uint8_t * ptr;
+ int drop, isMetaQuery;
- aliasCount = 0;
- while( aliasCount <= kGAIPerfAdvancedTestSuite_MaxAliasCount )
+ err = SocketRecvFrom( sockCtx->sock, context->msgBuf, sizeof( context->msgBuf ), &msgLen, &sender, sizeof( sender ),
+ NULL, NULL, NULL, NULL );
+ require_noerr( err, exit );
+
+ if( msgLen < kDNSHeaderLength )
{
- for( addressCount = 1; addressCount <= kGAIPerfAdvancedTestSuite_MaxAddrCount; addressCount *= 2 )
+ mr_ulog( kLogLevelInfo, "Message is too small (%zu < %d).\n", msgLen, kDNSHeaderLength );
+ goto exit;
+ }
+
+ // Perform header field checks.
+ // The message ID and most flag bits are silently ignored (see <https://tools.ietf.org/html/rfc6762#section-18>).
+
+ hdr = (DNSHeader *) context->msgBuf;
+ flags = DNSHeaderGetFlags( hdr );
+ require_quiet( ( flags & kDNSHeaderFlag_Response ) == 0, exit ); // Reject responses.
+ require_quiet( DNSFlagsGetOpCode( flags ) == kDNSOpCode_Query, exit ); // Reject opcodes other than standard query.
+ require_quiet( DNSFlagsGetRCode( flags ) == kDNSRCode_NoError, exit ); // Reject non-zero rcodes.
+
+ drop = ( !context->maxDropCount && ShouldDrop( context->mcastDropRate ) ) ? true : false;
+
+ mr_ulog( kLogLevelInfo, "Received %zu byte message from %##a%?s:\n\n%#1{du:dnsmsg}",
+ msgLen, &sender, drop, " (dropping)", context->msgBuf, msgLen );
+
+ // Based on the QNAMEs in the query message, determine from which sets of records we may possibly need answers.
+
+ questionCount = DNSHeaderGetQuestionCount( hdr );
+ require_quiet( questionCount > 0, exit );
+
+ memset( context->bitmaps, 0, context->bitmapCount * sizeof_element( context->bitmaps ) );
+
+ isMetaQuery = false;
+ ptr = (const uint8_t *) &hdr[ 1 ];
+ for( i = 0; i < questionCount; ++i )
+ {
+ unsigned int count, index;
+ uint16_t qtype, qclass;
+ uint8_t qname[ kDomainNameLengthMax ];
+
+ err = DNSMessageExtractQuestion( context->msgBuf, msgLen, ptr, qname, &qtype, &qclass, &ptr );
+ require_noerr_quiet( err, exit );
+
+ if( ( qclass & ~kQClassUnicastResponseBit ) != kDNSServiceClass_IN ) continue;
+
+ if( _MDNSReplierHostnameMatch( context, qname, &index ) ||
+ _MDNSReplierServiceInstanceNameMatch( context, qname, &index, NULL, NULL ) )
{
- // Add a test case to resolve a domain name with
- //
- // <aliasCount> CNAME records, <addressCount> A records, and <addressCount> AAAA records
- //
- // to its IPv4 and IPv6 addresses. Each iteration resolves a unique instance of such a domain name, which
- // requires server queries.
-
- _GAIPerfWriteTestCaseTitle( title, aliasCount, addressCount, addressCount, kGAITestAddrType_Both,
- inContext->defaultIterCount, true );
-
- timeLimitMs = _GAIPerfTimeLimitMs( inContext->callDelayMs, inContext->serverDelayMs,
- inContext->defaultIterCount );
- err = GAITestCaseCreate( title, timeLimitMs, &testCase );
- require_noerr( err, exit );
-
- for( i = 0; i < inContext->defaultIterCount; ++i )
+ if( ( index >= 1 ) && ( index <= context->maxInstanceCount ) )
{
- err = GAITestCaseAddItem( testCase, aliasCount, addressCount, kGAIPerfStandardTTL,
- kGAITestAddrType_Both, kGAITestAddrType_Both, 1 );
- require_noerr( err, exit );
+ context->bitmaps[ ( index - 1 ) / 64 ] |= ( UINT64_C( 1 ) << ( ( index - 1 ) % 64 ) );
}
-
- GAITesterAddCase( inContext->tester, testCase );
- testCase = NULL;
-
- // Add a test case to resolve a domain name with
- //
- // <aliasCount> CNAME records, <addressCount> A records, and <addressCount> AAAA records
- //
- // to its IPv4 and IPv6 addresses. A preliminary iteration resolves a unique domain name, which requires a server
- // query. The subsequent iterations resolve the same domain name as the preliminary iteration, which should
- // ideally require no server queries, i.e., the results should come from the cache.
-
- _GAIPerfWriteTestCaseTitle( title, aliasCount, addressCount, addressCount, kGAITestAddrType_Both,
- inContext->defaultIterCount, false );
-
- timeLimitMs = _GAIPerfTimeLimitMs( inContext->callDelayMs, inContext->serverDelayMs, 1 ) +
- _GAIPerfTimeLimitMs( inContext->callDelayMs, 0, inContext->defaultIterCount );
- err = GAITestCaseCreate( title, timeLimitMs, &testCase );
- require_noerr( err, exit );
-
- err = GAITestCaseAddItem( testCase, aliasCount, addressCount, kGAIPerfStandardTTL,
- kGAITestAddrType_Both, kGAITestAddrType_Both, inContext->defaultIterCount + 1 );
- require_noerr( err, exit );
-
- GAITesterAddCase( inContext->tester, testCase );
- testCase = NULL;
}
-
- if( aliasCount == 0 ) aliasCount = 1;
- else aliasCount *= 2;
+ else if( _MDNSReplierServiceTypeMatch( context, qname, NULL, &count ) )
+ {
+ if( ( count >= 1 ) && ( count <= context->maxInstanceCount ) )
+ {
+ for( j = 0; j < (unsigned int) context->bitmapCount; ++j )
+ {
+ if( count < 64 )
+ {
+ context->bitmaps[ j ] |= ( ( UINT64_C( 1 ) << count ) - 1 );
+ break;
+ }
+ else
+ {
+ context->bitmaps[ j ] = ~UINT64_C( 0 );
+ count -= 64;
+ }
+ }
+ }
+ }
+ else if( _MDNSReplierAboutRecordNameMatch( context, qname ) )
+ {
+ isMetaQuery = true;
+ }
}
- // Finally, add a test case to resolve localhost to its IPv4 and IPv6 addresses.
-
- _GAIPerfWriteLocalHostTestCaseTitle( title, kGAITestAddrType_Both, inContext->defaultIterCount );
-
- timeLimitMs = _GAIPerfTimeLimitMs( inContext->callDelayMs, 0, inContext->defaultIterCount );
- err = GAITestCaseCreate( title, timeLimitMs, &testCase );
- require_noerr( err, exit );
+ // Attempt to answer the query message using selected record sets.
- err = GAITestCaseAddLocalHostItem( testCase, kGAITestAddrType_Both, inContext->defaultIterCount );
- require_noerr( err, exit );
+ if( isMetaQuery )
+ {
+ err = _MDNSReplierAnswerQuery( context, context->msgBuf, msgLen, &sender, sockCtx->sock, 0 );
+ check_noerr( err );
+ }
+ if( drop ) goto exit;
- GAITesterAddCase( inContext->tester, testCase );
- testCase = NULL;
+ for( i = 0; i < context->bitmapCount; ++i )
+ {
+ for( j = 0; ( context->bitmaps[ i ] != 0 ) && ( j < 64 ); ++j )
+ {
+ const uint64_t bitmask = UINT64_C( 1 ) << j;
+
+ if( context->bitmaps[ i ] & bitmask )
+ {
+ context->bitmaps[ i ] &= ~bitmask;
+
+ err = _MDNSReplierAnswerQuery( context, context->msgBuf, msgLen, &sender, sockCtx->sock,
+ ( i * 64 ) + j + 1 );
+ check_noerr( err );
+ }
+ }
+ }
exit:
- if( testCase ) GAITestCaseFree( testCase );
- return( err );
+ return;
}
//===========================================================================================================================
-// _GAIPerfWriteTestCaseTitle
+// _MDNSReplierAnswerQuery
//===========================================================================================================================
-#define GAITestAddrTypeToRequestKeyValue( X ) ( \
- ( (X) == kGAITestAddrType_Both ) ? "ipv4\\,ipv6" : \
- ( (X) == kGAITestAddrType_IPv4 ) ? "ipv4" : \
- ( (X) == kGAITestAddrType_IPv6 ) ? "ipv6" : \
- "" )
-
-static void
- _GAIPerfWriteTestCaseTitle(
- char inBuffer[ kTestCaseTitleBufferSize ],
- unsigned int inCNAMERecordCount,
- unsigned int inARecordCount,
- unsigned int inAAAARecordCount,
- GAITestAddrType inRequested,
- unsigned int inIterationCount,
- Boolean inIterationsAreUnique )
-{
- SNPrintF( inBuffer, kTestCaseTitleBufferSize, "name=dynamic,cname=%u,a=%u,aaaa=%u,req=%s,iterations=%u%?s",
- inCNAMERecordCount, inARecordCount, inAAAARecordCount, GAITestAddrTypeToRequestKeyValue( inRequested ),
- inIterationCount, inIterationsAreUnique, ",unique" );
-}
-
-//===========================================================================================================================
-// _GAIPerfWriteLocalHostTestCaseTitle
-//===========================================================================================================================
-
-static void
- _GAIPerfWriteLocalHostTestCaseTitle(
- char inBuffer[ kTestCaseTitleBufferSize ],
- GAITestAddrType inRequested,
- unsigned int inIterationCount )
-{
- SNPrintF( inBuffer, kTestCaseTitleBufferSize, "name=localhost,req=%s,iterations=%u",
- GAITestAddrTypeToRequestKeyValue( inRequested ), inIterationCount );
-}
-
-//===========================================================================================================================
-// _GAIPerfTimeLimitMs
-//===========================================================================================================================
-
-static unsigned int
- _GAIPerfTimeLimitMs(
- unsigned int inCallDelayMs,
- unsigned int inServerDelayMs,
- unsigned int inIterationCount )
-{
- // Allow each iteration 20 ms to complete (in addition to the call and server delay times).
-
- return( ( inCallDelayMs + inServerDelayMs + 20 ) * inIterationCount );
-}
-
-//===========================================================================================================================
-// GAIPerfAddBasicTestCases
-//===========================================================================================================================
-
-#define kGAIPerfBasicTestSuite_AliasCount 2
-#define kGAIPerfBasicTestSuite_AddrCount 4
-
-static OSStatus GAIPerfAddBasicTestCases( GAIPerfContext *inContext )
+static OSStatus
+ _MDNSReplierAnswerQuery(
+ MDNSReplierContext * inContext,
+ const uint8_t * inQueryPtr,
+ size_t inQueryLen,
+ sockaddr_ip * inSender,
+ SocketRef inSock,
+ unsigned int inIndex )
{
- OSStatus err;
- GAITestCase * testCase = NULL;
- char title[ kTestCaseTitleBufferSize ];
- unsigned int timeLimitMs, i;
+ OSStatus err;
+ const DNSHeader * hdr;
+ const uint8_t * ptr;
+ unsigned int questionCount, answerCount, i;
+ MRResourceRecord * ucastAnswerList = NULL;
+ MRResourceRecord * mcastAnswerList = NULL;
- // Test Case #1:
- // Resolve a domain name with
- //
- // 2 CNAME records, 4 A records, and 4 AAAA records
- //
- // to its IPv4 and IPv6 addresses. Each of the iterations resolves a unique domain name, which requires server
- // queries.
+ require_action( inIndex <= inContext->maxInstanceCount, exit, err = kRangeErr );
- _GAIPerfWriteTestCaseTitle( title, kGAIPerfBasicTestSuite_AliasCount,
- kGAIPerfBasicTestSuite_AddrCount, kGAIPerfBasicTestSuite_AddrCount, kGAITestAddrType_Both,
- inContext->defaultIterCount, true );
+ // Get answers for questions.
- timeLimitMs = _GAIPerfTimeLimitMs( inContext->callDelayMs, inContext->serverDelayMs, inContext->defaultIterCount );
- err = GAITestCaseCreate( title, timeLimitMs, &testCase );
- require_noerr( err, exit );
+ check( inQueryLen >= kDNSHeaderLength );
+ hdr = (const DNSHeader *) inQueryPtr;
+ questionCount = DNSHeaderGetQuestionCount( hdr );
- for( i = 0; i < inContext->defaultIterCount; ++i )
+ ptr = (const uint8_t *) &hdr[ 1 ];
+ for( i = 0; i < questionCount; ++i )
{
- err = GAITestCaseAddItem( testCase, kGAIPerfBasicTestSuite_AliasCount, kGAIPerfBasicTestSuite_AddrCount,
- kGAIPerfStandardTTL, kGAITestAddrType_Both, kGAITestAddrType_Both, 1 );
+ MRResourceRecord ** answerListPtr;
+ uint16_t qtype, qclass;
+ uint8_t qname[ kDomainNameLengthMax ];
+
+ err = DNSMessageExtractQuestion( inQueryPtr, inQueryLen, ptr, qname, &qtype, &qclass, &ptr );
+ require_noerr_quiet( err, exit );
+
+ if( qclass & kQClassUnicastResponseBit )
+ {
+ qclass &= ~kQClassUnicastResponseBit;
+ answerListPtr = &ucastAnswerList;
+ }
+ else
+ {
+ answerListPtr = &mcastAnswerList;
+ }
+
+ err = _MDNSReplierAnswerListAdd( inContext, answerListPtr, inIndex, qname, qtype, qclass );
require_noerr( err, exit );
}
+ require_action_quiet( mcastAnswerList || ucastAnswerList, exit, err = kNoErr );
- GAITesterAddCase( inContext->tester, testCase );
- testCase = NULL;
-
- // Test Case #2:
- // Resolve a domain name with
- //
- // 2 CNAME records, 4 A records, and 4 AAAA records
- //
- // to its IPv4 and IPv6 addresses. A preliminary iteration resolves a unique instance of such a domain name, which
- // requires server queries. Each of the subsequent iterations resolves the same domain name as the preliminary
- // iteration, which should ideally require no additional server queries, i.e., the results should come from the cache.
-
- _GAIPerfWriteTestCaseTitle( title, kGAIPerfBasicTestSuite_AliasCount,
- kGAIPerfBasicTestSuite_AddrCount, kGAIPerfBasicTestSuite_AddrCount, kGAITestAddrType_Both,
- inContext->defaultIterCount, false );
-
- timeLimitMs = _GAIPerfTimeLimitMs( inContext->callDelayMs, inContext->serverDelayMs, 1 ) +
- _GAIPerfTimeLimitMs( inContext->callDelayMs, 0, inContext->defaultIterCount );
- err = GAITestCaseCreate( title, timeLimitMs, &testCase );
- require_noerr( err, exit );
-
- err = GAITestCaseAddItem( testCase, kGAIPerfBasicTestSuite_AliasCount, kGAIPerfBasicTestSuite_AddrCount,
- kGAIPerfStandardTTL, kGAITestAddrType_Both, kGAITestAddrType_Both, inContext->defaultIterCount + 1 );
- require_noerr( err, exit );
-
- GAITesterAddCase( inContext->tester, testCase );
- testCase = NULL;
-
- // Test Case #3:
- // Each iteration resolves localhost to its IPv4 and IPv6 addresses.
-
- _GAIPerfWriteLocalHostTestCaseTitle( title, kGAITestAddrType_Both, inContext->defaultIterCount );
-
- timeLimitMs = _GAIPerfTimeLimitMs( inContext->callDelayMs, 0, inContext->defaultIterCount );
- err = GAITestCaseCreate( title, timeLimitMs, &testCase );
- require_noerr( err, exit );
-
- err = GAITestCaseAddLocalHostItem( testCase, kGAITestAddrType_Both, inContext->defaultIterCount );
- require_noerr( err, exit );
-
- GAITesterAddCase( inContext->tester, testCase );
- testCase = NULL;
-
-exit:
- if( testCase ) GAITestCaseFree( testCase );
- return( err );
-}
-
-//===========================================================================================================================
-// GAIPerfEventHandler
-//===========================================================================================================================
-
-static void _GAIPerfOutputResultsAndExit( GAIPerfContext *inContext ) ATTRIBUTE_NORETURN;
-
-static void GAIPerfEventHandler( GAITesterEventType inType, void *inContext )
-{
- GAIPerfContext * const context = (GAIPerfContext *) inContext;
-
- if( inType == kGAITesterEvent_Started )
- {
- context->testerStarted = true;
- }
- else if( inType == kGAITesterEvent_Stopped )
- {
- if( context->gotSignal ) exit( 1 );
- _GAIPerfOutputResultsAndExit( context );
- }
-}
-
-//===========================================================================================================================
-// _GAIPerfOutputResultsAndExit
-//===========================================================================================================================
-
-#define kGAIPerfResultsKey_TestCases CFSTR( "testCases" )
-#define kGAIPerfResultsKey_Info CFSTR( "info" )
-
-#define kGAIPerfInfoKey_CallDelay CFSTR( "callDelayMs" )
-#define kGAIPerfInfoKey_ServerDelay CFSTR( "serverDelayMs" )
-
-static void _GAIPerfOutputResultsAndExit( GAIPerfContext *inContext )
-{
- OSStatus err;
- CFPropertyListRef plist = NULL;
- CFDataRef results = NULL;
- FILE * file = NULL;
-
- err = CFPropertyListCreateFormatted( kCFAllocatorDefault, &plist,
- "{"
- "%kO=%O"
- "%kO="
- "{"
- "%kO=%lli"
- "%kO=%lli"
- "}"
- "}",
- kGAIPerfResultsKey_TestCases, inContext->caseResults,
- kGAIPerfResultsKey_Info,
- kGAIPerfInfoKey_CallDelay, (int64_t) inContext->callDelayMs,
- kGAIPerfInfoKey_ServerDelay, (int64_t) inContext->serverDelayMs );
- require_noerr( err, exit );
-
- // Convert results to a specific format.
+ // Suppress known answers.
+ // Records in the Answer section of the query message are known answers, so remove them from the answer lists.
+ // See <https://tools.ietf.org/html/rfc6762#section-7.1>.
- switch( inContext->outputFormat )
+ answerCount = DNSHeaderGetAnswerCount( hdr );
+ for( i = 0; i < answerCount; ++i )
{
- case kGAIPerfOutputFormat_JSON:
- results = CFCreateJSONData( plist, kJSONFlags_None, NULL );
- require_action( results, exit, err = kUnknownErr );
- break;
+ const uint8_t * rdataPtr;
+ const uint8_t * recordPtr;
+ uint16_t type, class;
+ uint8_t name[ kDomainNameLengthMax ];
+ uint8_t instance[ kDomainNameLengthMax ];
- case kGAIPerfOutputFormat_XML:
- results = CFPropertyListCreateData( NULL, plist, kCFPropertyListXMLFormat_v1_0, 0, NULL );
- require_action( results, exit, err = kUnknownErr );
- break;
+ recordPtr = ptr;
+ err = DNSMessageExtractRecord( inQueryPtr, inQueryLen, ptr, NULL, &type, &class, NULL, NULL, NULL, &ptr );
+ require_noerr_quiet( err, exit );
- case kGAIPerfOutputFormat_Binary:
- results = CFPropertyListCreateData( NULL, plist, kCFPropertyListBinaryFormat_v1_0, 0, NULL );
- require_action( results, exit, err = kUnknownErr );
- break;
+ if( ( type != kDNSServiceType_PTR ) || ( class != kDNSServiceClass_IN ) ) continue;
- default:
- err = kValueErr;
- goto exit;
+ err = DNSMessageExtractRecord( inQueryPtr, inQueryLen, recordPtr, name, NULL, NULL, NULL, &rdataPtr, NULL, NULL );
+ require_noerr( err, exit );
+
+ err = DNSMessageExtractDomainName( inQueryPtr, inQueryLen, rdataPtr, instance, NULL );
+ require_noerr_quiet( err, exit );
+
+ if( ucastAnswerList ) _MDNSReplierAnswerListRemovePTR( &ucastAnswerList, name, instance );
+ if( mcastAnswerList ) _MDNSReplierAnswerListRemovePTR( &mcastAnswerList, name, instance );
}
+ require_action_quiet( mcastAnswerList || ucastAnswerList, exit, err = kNoErr );
- // Write formatted results to file or stdout.
+ // Send or drop responses.
- if( inContext->outputFilePath )
+ if( ucastAnswerList )
{
- file = fopen( inContext->outputFilePath, "wb" );
- err = map_global_value_errno( file, file );
+ err = _MDNSReplierSendOrDropResponse( inContext, ucastAnswerList, inSender, inSock, inIndex, true );
require_noerr( err, exit );
}
- else
- {
- file = stdout;
- }
-
- err = WriteANSIFile( file, CFDataGetBytePtr( results ), (size_t) CFDataGetLength( results ) );
- require_noerr( err, exit );
-
- // Write a trailing newline for JSON-formatted results if requested.
- if( ( inContext->outputFormat == kGAIPerfOutputFormat_JSON ) && inContext->appendNewLine )
+ if( mcastAnswerList )
{
- err = WriteANSIFile( file, "\n", 1 );
+ err = _MDNSReplierSendOrDropResponse( inContext, mcastAnswerList, inSender, inSock, inIndex, false );
require_noerr( err, exit );
}
+ err = kNoErr;
exit:
- CFReleaseNullSafe( plist );
- CFReleaseNullSafe( results );
- if( file && ( file != stdout ) ) fclose( file );
- GAIPerfContextFree( inContext );
- exit( err ? 1 : 0 );
+ _MRResourceRecordFreeList( ucastAnswerList );
+ _MRResourceRecordFreeList( mcastAnswerList );
+ return( err );
}
//===========================================================================================================================
-// GAIPerfResultsHandler
+// _MDNSReplierAnswerListAdd
//===========================================================================================================================
-// Keys for test case dictionary
-
-#define kGAIPerfTestCaseKey_Title CFSTR( "title" )
-#define kGAIPerfTestCaseKey_StartTime CFSTR( "startTimeUs" )
-#define kGAIPerfTestCaseKey_EndTime CFSTR( "endTimeUs" )
-#define kGAIPerfTestCaseKey_Results CFSTR( "results" )
-#define kGAIPerfTestCaseKey_FirstStats CFSTR( "firstStats" )
-#define kGAIPerfTestCaseKey_ConnectionStats CFSTR( "connectionStats" )
-#define kGAIPerfTestCaseKey_Stats CFSTR( "stats" )
-#define kGAIPerfTestCaseKey_TimedOut CFSTR( "timedOut" )
-
-// Keys for test case results array entry dictionaries
-
-#define kGAIPerfTestCaseResultKey_Name CFSTR( "name" )
-#define kGAIPerfTestCaseResultKey_ConnectionTime CFSTR( "connectionTimeUs" )
-#define kGAIPerfTestCaseResultKey_FirstTime CFSTR( "firstTimeUs" )
-#define kGAIPerfTestCaseResultKey_Time CFSTR( "timeUs" )
-
-// Keys for test case stats dictionaries
-
-#define kGAIPerfTestCaseStatsKey_Count CFSTR( "count" )
-#define kGAIPerfTestCaseStatsKey_Min CFSTR( "min" )
-#define kGAIPerfTestCaseStatsKey_Max CFSTR( "max" )
-#define kGAIPerfTestCaseStatsKey_Mean CFSTR( "mean" )
-#define kGAIPerfTestCaseStatsKey_StdDev CFSTR( "sd" )
-
-typedef struct
-{
- double min;
- double max;
- double mean;
- double stdDev;
-
-} GAIPerfStats;
-
-#define GAIPerfStatsInit( X ) \
- do { (X)->min = DBL_MAX; (X)->max = DBL_MIN; (X)->mean = 0.0; (X)->stdDev = 0.0; } while( 0 )
-
-static void
- GAIPerfResultsHandler(
- const char * inCaseTitle,
- MicroTime64 inCaseStartTime,
- MicroTime64 inCaseEndTime,
- const GAITestItemResult * inResults,
- size_t inResultCount,
- size_t inItemCount,
- void * inContext )
+static OSStatus
+ _MDNSReplierAnswerListAdd(
+ MDNSReplierContext * inContext,
+ MRResourceRecord ** inAnswerList,
+ unsigned int inIndex,
+ const uint8_t * inName,
+ unsigned int inType,
+ unsigned int inClass )
{
OSStatus err;
- GAIPerfContext * const context = (GAIPerfContext *) inContext;
- int namesAreDynamic, namesAreUnique;
- const char * ptr;
- size_t count, startIndex;
- CFMutableArrayRef results = NULL;
- GAIPerfStats stats, firstStats, connStats;
- double sum, firstSum, connSum, value, diff;
- size_t keyValueLen, i;
- char keyValue[ 16 ]; // Size must be at least strlen( "name=dynamic" ) + 1 bytes.
-
- // If this test case resolves the same "d.test." name in each iteration (title contains the "name=dynamic" key-value
- // pair, but not the "unique" key), then don't count the first iteration, whose purpose is to populate the cache with the
- // domain name's CNAME, A, and AAAA records.
-
- namesAreDynamic = false;
- namesAreUnique = false;
- ptr = inCaseTitle;
- while( ParseQuotedEscapedString( ptr, NULL, ",", keyValue, sizeof( keyValue ), &keyValueLen, NULL, &ptr ) )
- {
- if( strnicmpx( keyValue, keyValueLen, "name=dynamic" ) == 0 )
- {
- namesAreDynamic = true;
- }
- else if( strnicmpx( keyValue, keyValueLen, "unique" ) == 0 )
+ uint8_t * recordName = NULL;
+ uint8_t * rdataPtr = NULL;
+ size_t rdataLen;
+ MRResourceRecord * answer;
+ MRResourceRecord ** answerPtr;
+ const uint8_t * const hostname = inContext->hostname;
+ unsigned int i;
+ uint32_t index;
+ unsigned int count, txtSize;
+
+ require_action( inIndex <= inContext->maxInstanceCount, exit, err = kRangeErr );
+ require_action_quiet( inClass == kDNSServiceClass_IN, exit, err = kNoErr );
+
+ for( answerPtr = inAnswerList; ( answer = *answerPtr ) != NULL; answerPtr = &answer->next )
+ {
+ if( ( answer->type == inType ) && DomainNameEqual( answer->name, inName ) )
{
- namesAreUnique = true;
+ err = kNoErr;
+ goto exit;
}
- if( namesAreDynamic && namesAreUnique ) break;
- }
-
- if( namesAreDynamic && !namesAreUnique && ( inItemCount > 0 ) )
- {
- count = ( inResultCount > 0 ) ? ( inResultCount - 1 ) : 0;
- startIndex = 1;
- }
- else
- {
- count = inResultCount;
- startIndex = 0;
}
- results = CFArrayCreateMutable( NULL, (CFIndex) count, &kCFTypeArrayCallBacks );
- require_action( results, exit, err = kNoMemoryErr );
+ // Index 0 is reserved for answering queries about the mdnsreplier, while all other index values up to the maximum
+ // instance count are for answering queries about service instances.
- GAIPerfStatsInit( &stats );
- GAIPerfStatsInit( &firstStats );
- GAIPerfStatsInit( &connStats );
-
- sum = 0.0;
- firstSum = 0.0;
- connSum = 0.0;
- for( i = startIndex; i < count; ++i )
+ if( inIndex == 0 )
+ {
+ if( _MDNSReplierAboutRecordNameMatch( inContext, inName ) )
+ {
+ int listHasTXT = false;
+
+ if( inType == kDNSServiceType_ANY )
+ {
+ for( answer = *inAnswerList; answer; answer = answer->next )
+ {
+ if( ( answer->type == kDNSServiceType_TXT ) && DomainNameEqual( answer->name, inName ) )
+ {
+ listHasTXT = true;
+ break;
+ }
+ }
+ }
+
+ if( ( inType == kDNSServiceType_TXT ) || ( ( inType == kDNSServiceType_ANY ) && !listHasTXT ) )
+ {
+ err = DomainNameDupLower( inName, &recordName, NULL );
+ require_noerr( err, exit );
+
+ err = CreateTXTRecordDataFromString( "ready=yes", ',', &rdataPtr, &rdataLen );
+ require_noerr( err, exit );
+
+ err = _MRResourceRecordCreate( recordName, kDNSServiceType_TXT, kDNSServiceClass_IN, kMDNSRecordTTL_Other,
+ (uint16_t) rdataLen, rdataPtr, &answer );
+ require_noerr( err, exit );
+ recordName = NULL;
+ rdataPtr = NULL;
+
+ *answerPtr = answer;
+ }
+ else if( inType == kDNSServiceType_NSEC )
+ {
+ err = DomainNameDupLower( inName, &recordName, NULL );
+ require_noerr( err, exit );
+
+ err = CreateNSECRecordData( recordName, &rdataPtr, &rdataLen, 1, kDNSServiceType_TXT );
+ require_noerr( err, exit );
+
+ err = _MRResourceRecordCreate( recordName, kDNSServiceType_NSEC, kDNSServiceClass_IN, kMDNSRecordTTL_Host,
+ (uint16_t) rdataLen, rdataPtr, &answer );
+ require_noerr( err, exit );
+ recordName = NULL;
+ rdataPtr = NULL;
+
+ *answerPtr = answer;
+ }
+ }
+ }
+ else if( _MDNSReplierHostnameMatch( inContext, inName, &index ) && ( index == inIndex ) )
{
- value = (double) inResults[ i ].timeUs;
- if( value < stats.min ) stats.min = value;
- if( value > stats.max ) stats.max = value;
- sum += value;
+ int listHasA = false;
+ int listHasAAAA = false;
- value = (double) inResults[ i ].firstTimeUs;
- if( value < firstStats.min ) firstStats.min = value;
- if( value > firstStats.max ) firstStats.max = value;
- firstSum += value;
+ if( inType == kDNSServiceType_ANY )
+ {
+ for( answer = *inAnswerList; answer; answer = answer->next )
+ {
+ if( answer->type == kDNSServiceType_A )
+ {
+ if( !listHasA && DomainNameEqual( answer->name, inName ) ) listHasA = true;
+ }
+ else if( answer->type == kDNSServiceType_AAAA )
+ {
+ if( !listHasAAAA && DomainNameEqual( answer->name, inName ) ) listHasAAAA = true;
+ }
+ if( listHasA && listHasAAAA ) break;
+ }
+ }
- value = (double) inResults[ i ].connectionTimeUs;
- if( value < connStats.min ) connStats.min = value;
- if( value > connStats.max ) connStats.max = value;
- connSum += value;
+ if( ( inType == kDNSServiceType_A ) || ( ( inType == kDNSServiceType_ANY ) && !listHasA ) )
+ {
+ for( i = 1; i <= inContext->recordCountA; ++i )
+ {
+ err = DomainNameDupLower( inName, &recordName, NULL );
+ require_noerr( err, exit );
+
+ rdataLen = 4;
+ rdataPtr = (uint8_t *) malloc( rdataLen );
+ require_action( rdataPtr, exit, err = kNoMemoryErr );
+
+ rdataPtr[ 0 ] = 0;
+ WriteBig16( &rdataPtr[ 1 ], inIndex );
+ rdataPtr[ 3 ] = (uint8_t) i;
+
+ err = _MRResourceRecordCreate( recordName, kDNSServiceType_A, kDNSServiceClass_IN, kMDNSRecordTTL_Host,
+ (uint16_t) rdataLen, rdataPtr, &answer );
+ require_noerr( err, exit );
+ recordName = NULL;
+ rdataPtr = NULL;
+
+ *answerPtr = answer;
+ answerPtr = &answer->next;
+ }
+ }
- err = CFPropertyListAppendFormatted( kCFAllocatorDefault, results,
- "{"
- "%kO=%s"
- "%kO=%lli"
- "%kO=%lli"
- "%kO=%lli"
- "}",
- kGAIPerfTestCaseResultKey_Name, inResults[ i ].name,
- kGAIPerfTestCaseResultKey_ConnectionTime, inResults[ i ].connectionTimeUs,
- kGAIPerfTestCaseResultKey_FirstTime, inResults[ i ].firstTimeUs,
- kGAIPerfTestCaseResultKey_Time, inResults[ i ].timeUs );
- require_noerr( err, exit );
+ if( ( inType == kDNSServiceType_AAAA ) || ( ( inType == kDNSServiceType_ANY ) && !listHasAAAA ) )
+ {
+ for( i = 1; i <= inContext->recordCountAAAA; ++i )
+ {
+ err = DomainNameDupLower( inName, &recordName, NULL );
+ require_noerr( err, exit );
+
+ rdataLen = 16;
+ rdataPtr = (uint8_t *) memdup( kMDNSReplierBaseAddrV6, rdataLen );
+ require_action( rdataPtr, exit, err = kNoMemoryErr );
+
+ WriteBig16( &rdataPtr[ 12 ], inIndex );
+ rdataPtr[ 15 ] = (uint8_t) i;
+
+ err = _MRResourceRecordCreate( recordName, kDNSServiceType_AAAA, kDNSServiceClass_IN, kMDNSRecordTTL_Host,
+ (uint16_t) rdataLen, rdataPtr, &answer );
+ require_noerr( err, exit );
+ recordName = NULL;
+ rdataPtr = NULL;
+
+ *answerPtr = answer;
+ answerPtr = &answer->next;
+ }
+ }
+ else if( inType == kDNSServiceType_NSEC )
+ {
+ err = DomainNameDupLower( inName, &recordName, NULL );
+ require_noerr( err, exit );
+
+ if( ( inContext->recordCountA > 0 ) && ( inContext->recordCountAAAA > 0 ) )
+ {
+ err = CreateNSECRecordData( recordName, &rdataPtr, &rdataLen, 2, kDNSServiceType_A, kDNSServiceType_AAAA );
+ require_noerr( err, exit );
+ }
+ else if( inContext->recordCountA > 0 )
+ {
+ err = CreateNSECRecordData( recordName, &rdataPtr, &rdataLen, 1, kDNSServiceType_A );
+ require_noerr( err, exit );
+ }
+ else if( inContext->recordCountAAAA > 0 )
+ {
+ err = CreateNSECRecordData( recordName, &rdataPtr, &rdataLen, 1, kDNSServiceType_AAAA );
+ require_noerr( err, exit );
+ }
+ else
+ {
+ err = CreateNSECRecordData( recordName, &rdataPtr, &rdataLen, 0 );
+ require_noerr( err, exit );
+ }
+
+ err = _MRResourceRecordCreate( recordName, kDNSServiceType_NSEC, kDNSServiceClass_IN, kMDNSRecordTTL_Host,
+ (uint16_t) rdataLen, rdataPtr, &answer );
+ require_noerr( err, exit );
+ recordName = NULL;
+ rdataPtr = NULL;
+
+ *answerPtr = answer;
+ }
}
-
- if( count > 0 )
+ else if( _MDNSReplierServiceTypeMatch( inContext, inName, NULL, &count ) && ( count >= inIndex ) )
{
- stats.mean = sum / count;
- firstStats.mean = firstSum / count;
- connStats.mean = connSum / count;
+ int listHasPTR = false;
- sum = 0.0;
- firstSum = 0.0;
- connSum = 0.0;
- for( i = startIndex; i < count; ++i )
+ if( inType == kDNSServiceType_ANY )
{
- diff = stats.mean - (double) inResults[ i ].timeUs;
- sum += ( diff * diff );
+ for( answer = *inAnswerList; answer; answer = answer->next )
+ {
+ if( ( answer->type == kDNSServiceType_PTR ) && DomainNameEqual( answer->name, inName ) )
+ {
+ listHasPTR = true;
+ break;
+ }
+ }
+ }
+
+ if( ( inType == kDNSServiceType_PTR ) || ( ( inType == kDNSServiceType_ANY ) && !listHasPTR ) )
+ {
+ size_t recordNameLen;
+ uint8_t * ptr;
+ uint8_t * lim;
- diff = firstStats.mean - (double) inResults[ i ].firstTimeUs;
- firstSum += ( diff * diff );
+ err = DomainNameDupLower( inName, &recordName, &recordNameLen );
+ require_noerr( err, exit );
- diff = connStats.mean - (double) inResults[ i ].connectionTimeUs;
- connSum += ( diff * diff );
+ rdataLen = 1 + hostname[ 0 ] + 10 + recordNameLen;
+ rdataPtr = (uint8_t *) malloc( rdataLen );
+ require_action( rdataPtr, exit, err = kNoMemoryErr );
+
+ lim = &rdataPtr[ rdataLen ];
+
+ ptr = &rdataPtr[ 1 ];
+ memcpy( ptr, &hostname[ 1 ], hostname[ 0 ] );
+ ptr += hostname[ 0 ];
+ if( inIndex != 1 ) SNPrintF_Add( (char **) &ptr, (char *) lim, " (%u)", inIndex );
+ rdataPtr[ 0 ] = (uint8_t)( ptr - &rdataPtr[ 1 ] );
+
+ check( (size_t)( lim - ptr ) >= recordNameLen );
+ memcpy( ptr, recordName, recordNameLen );
+ ptr += recordNameLen;
+
+ rdataLen = (size_t)( ptr - rdataPtr );
+
+ err = _MRResourceRecordCreate( recordName, kDNSServiceType_PTR, kDNSServiceClass_IN, kMDNSRecordTTL_Other,
+ (uint16_t) rdataLen, rdataPtr, &answer );
+ require_noerr( err, exit );
+ recordName = NULL;
+ rdataPtr = NULL;
+
+ *answerPtr = answer;
}
- stats.stdDev = sqrt( sum / count );
- firstStats.stdDev = sqrt( firstSum / count );
- connStats.stdDev = sqrt( connSum / count );
}
-
- err = CFPropertyListAppendFormatted( kCFAllocatorDefault, context->caseResults,
- "{"
- "%kO=%s"
- "%kO=%lli"
- "%kO=%lli"
- "%kO=%O"
- "%kO="
- "{"
- "%kO=%lli"
- "%kO=%f"
- "%kO=%f"
- "%kO=%f"
- "%kO=%f"
- "}"
- "%kO="
- "{"
- "%kO=%lli"
- "%kO=%f"
- "%kO=%f"
- "%kO=%f"
- "%kO=%f"
- "}"
- "%kO="
- "{"
- "%kO=%lli"
- "%kO=%f"
- "%kO=%f"
- "%kO=%f"
- "%kO=%f"
- "}"
- "%kO=%b"
- "}",
- kGAIPerfTestCaseKey_Title, inCaseTitle,
- kGAIPerfTestCaseKey_StartTime, (int64_t) inCaseStartTime,
- kGAIPerfTestCaseKey_EndTime, (int64_t) inCaseEndTime,
- kGAIPerfTestCaseKey_Results, results,
- kGAIPerfTestCaseKey_Stats,
- kGAIPerfTestCaseStatsKey_Count, (int64_t) count,
- kGAIPerfTestCaseStatsKey_Min, stats.min,
- kGAIPerfTestCaseStatsKey_Max, stats.max,
- kGAIPerfTestCaseStatsKey_Mean, stats.mean,
- kGAIPerfTestCaseStatsKey_StdDev, stats.stdDev,
- kGAIPerfTestCaseKey_FirstStats,
- kGAIPerfTestCaseStatsKey_Count, (int64_t) count,
- kGAIPerfTestCaseStatsKey_Min, firstStats.min,
- kGAIPerfTestCaseStatsKey_Max, firstStats.max,
- kGAIPerfTestCaseStatsKey_Mean, firstStats.mean,
- kGAIPerfTestCaseStatsKey_StdDev, firstStats.stdDev,
- kGAIPerfTestCaseKey_ConnectionStats,
- kGAIPerfTestCaseStatsKey_Count, (int64_t) count,
- kGAIPerfTestCaseStatsKey_Min, connStats.min,
- kGAIPerfTestCaseStatsKey_Max, connStats.max,
- kGAIPerfTestCaseStatsKey_Mean, connStats.mean,
- kGAIPerfTestCaseStatsKey_StdDev, connStats.stdDev,
- kGAIPerfTestCaseKey_TimedOut, ( inResultCount < inItemCount ) ? true : false );
- require_noerr( err, exit );
+ else if( _MDNSReplierServiceInstanceNameMatch( inContext, inName, &index, &txtSize, &count ) &&
+ ( index == inIndex ) && ( count >= inIndex ) )
+ {
+ int listHasSRV = false;
+ int listHasTXT = false;
+
+ if( inType == kDNSServiceType_ANY )
+ {
+ for( answer = *inAnswerList; answer; answer = answer->next )
+ {
+ if( answer->type == kDNSServiceType_SRV )
+ {
+ if( !listHasSRV && DomainNameEqual( answer->name, inName ) ) listHasSRV = true;
+ }
+ else if( answer->type == kDNSServiceType_TXT )
+ {
+ if( !listHasTXT && DomainNameEqual( answer->name, inName ) ) listHasTXT = true;
+ }
+ if( listHasSRV && listHasTXT ) break;
+ }
+ }
+
+ if( ( inType == kDNSServiceType_SRV ) || ( ( inType == kDNSServiceType_ANY ) && !listHasSRV ) )
+ {
+ SRVRecordDataFixedFields * fields;
+ uint8_t * ptr;
+ uint8_t * lim;
+ uint8_t * targetPtr;
+
+ err = DomainNameDupLower( inName, &recordName, NULL );
+ require_noerr( err, exit );
+
+ rdataLen = sizeof( SRVRecordDataFixedFields ) + 1 + hostname[ 0 ] + 10 + kLocalNameLen;
+ rdataPtr = (uint8_t *) malloc( rdataLen );
+ require_action( rdataPtr, exit, err = kNoMemoryErr );
+
+ lim = &rdataPtr[ rdataLen ];
+
+ fields = (SRVRecordDataFixedFields *) rdataPtr;
+ SRVRecordDataFixedFieldsSet( fields, 0, 0, (uint16_t)( kMDNSReplierPortBase + txtSize ) );
+
+ targetPtr = (uint8_t *) &fields[ 1 ];
+
+ ptr = &targetPtr[ 1 ];
+ memcpy( ptr, &hostname[ 1 ], hostname[ 0 ] );
+ ptr += hostname[ 0 ];
+ if( inIndex != 1 ) SNPrintF_Add( (char **) &ptr, (char *) lim, "-%u", inIndex );
+ targetPtr[ 0 ] = (uint8_t)( ptr - &targetPtr[ 1 ] );
+
+ check( (size_t)( lim - ptr ) >= kLocalNameLen );
+ memcpy( ptr, kLocalName, kLocalNameLen );
+ ptr += kLocalNameLen;
+
+ rdataLen = (size_t)( ptr - rdataPtr );
+
+ err = _MRResourceRecordCreate( recordName, kDNSServiceType_SRV, kDNSServiceClass_IN, kMDNSRecordTTL_Host,
+ (uint16_t) rdataLen, rdataPtr, &answer );
+ require_noerr( err, exit );
+ recordName = NULL;
+ rdataPtr = NULL;
+
+ *answerPtr = answer;
+ answerPtr = &answer->next;
+ }
+
+ if( ( inType == kDNSServiceType_TXT ) || ( ( inType == kDNSServiceType_ANY ) && !listHasTXT ) )
+ {
+ err = DomainNameDupLower( inName, &recordName, NULL );
+ require_noerr( err, exit );
+
+ rdataLen = txtSize;
+ err = _MDNSReplierCreateTXTRecord( inName, rdataLen, &rdataPtr );
+ require_noerr( err, exit );
+
+ err = _MRResourceRecordCreate( recordName, kDNSServiceType_TXT, kDNSServiceClass_IN, kMDNSRecordTTL_Other,
+ (uint16_t) rdataLen, rdataPtr, &answer );
+ require_noerr( err, exit );
+ recordName = NULL;
+ rdataPtr = NULL;
+
+ *answerPtr = answer;
+ }
+ else if( inType == kDNSServiceType_NSEC )
+ {
+ err = DomainNameDupLower( inName, &recordName, NULL );
+ require_noerr( err, exit );
+
+ err = CreateNSECRecordData( recordName, &rdataPtr, &rdataLen, 2, kDNSServiceType_TXT, kDNSServiceType_SRV );
+ require_noerr( err, exit );
+
+ err = _MRResourceRecordCreate( recordName, kDNSServiceType_NSEC, kDNSServiceClass_IN, kMDNSRecordTTL_Host,
+ (uint16_t) rdataLen, rdataPtr, &answer );
+ require_noerr( err, exit );
+ recordName = NULL;
+ rdataPtr = NULL;
+
+ *answerPtr = answer;
+ }
+ }
+ err = kNoErr;
exit:
- CFReleaseNullSafe( results );
+ FreeNullSafe( recordName );
+ FreeNullSafe( rdataPtr );
+ return( err );
}
//===========================================================================================================================
-// GAIPerfSignalHandler
+// _MDNSReplierAnswerListRemovePTR
//===========================================================================================================================
-static void GAIPerfSignalHandler( void *inContext )
+static void
+ _MDNSReplierAnswerListRemovePTR(
+ MRResourceRecord ** inAnswerListPtr,
+ const uint8_t * inName,
+ const uint8_t * inRData )
{
- GAIPerfContext * const context = (GAIPerfContext *) inContext;
+ MRResourceRecord * answer;
+ MRResourceRecord ** answerPtr;
- context->gotSignal = true;
- if( context->tester && context->testerStarted )
+ for( answerPtr = inAnswerListPtr; ( answer = *answerPtr ) != NULL; answerPtr = &answer->next )
{
- GAITesterStop( context->tester );
+ if( ( answer->type == kDNSServiceType_PTR ) && ( answer->class == kDNSServiceClass_IN ) &&
+ DomainNameEqual( answer->name, inName ) && DomainNameEqual( answer->rdata, inRData ) ) break;
}
- else
+ if( answer )
{
- exit( 1 );
+ *answerPtr = answer->next;
+ _MRResourceRecordFree( answer );
}
}
//===========================================================================================================================
-// GAITesterCreate
+// _MDNSReplierSendOrDropResponse
//===========================================================================================================================
-typedef enum
+static OSStatus
+ _MDNSReplierSendOrDropResponse(
+ MDNSReplierContext * inContext,
+ MRResourceRecord * inAnswerList,
+ sockaddr_ip * inQuerier,
+ SocketRef inSock,
+ unsigned int inIndex,
+ Boolean inUnicast )
{
- kGAITestConnType_UseMainConnection = 1,
- kGAITestConnType_OwnSharedConnection = 2
+ OSStatus err;
+ uint8_t * responsePtr = NULL;
+ size_t responseLen;
+ const struct sockaddr * destAddr;
+ ssize_t n;
+ const double dropRate = inUnicast ? inContext->ucastDropRate : inContext->mcastDropRate;
+ int drop;
-} GAITestConnType;
-
-typedef struct GAITestItem GAITestItem;
-struct GAITestItem
-{
- GAITestItem * next; // Next test item in list.
- char * name; // Domain name to resolve.
- int64_t connectionTimeUs; // Time in microseconds that it took to create a DNS-SD connection.
- int64_t firstTimeUs; // Time in microseconds that it took to get the first address result.
- int64_t timeUs; // Time in microseconds that it took to get all expected address results.
- unsigned int addressCount; // Address count of the domain name, i.e., the Count label argument.
- Boolean hasV4; // True if the domain name has one or more IPv4 addresses.
- Boolean hasV6; // True if the domain name has one or more IPv6 addresses.
- Boolean wantV4; // True if DNSServiceGetAddrInfo() should be called to get IPv4 addresses.
- Boolean wantV6; // True if DNSServiceGetAddrInfo() should be called to get IPv6 addresses.
-};
-
-struct GAITestCase
-{
- GAITestCase * next; // Next test case in list.
- GAITestItem * itemList; // List of test items.
- char * title; // Title of the test case.
- unsigned int timeLimitMs; // Time limit in milliseconds for the test case's completion.
-};
-
-struct GAITesterPrivate
-{
- CFRuntimeBase base; // CF object base.
- dispatch_queue_t queue; // Serial work queue.
- DNSServiceRef mainRef; // Reference to the main shared DNS-SD connection.
- DNSServiceRef opRef; // Reference to the current DNSServiceGetAddrInfo operation.
- GAITestCase * caseList; // List of test cases.
- GAITestCase * currentCase; // Pointer to the current test case.
- GAITestItem * currentItem; // Pointer to the current test item.
- MicroTime64 caseStartTime; // Start time of current test case in Unix time as microseconds.
- MicroTime64 caseEndTime; // End time of current test case in Unix time as microseconds.
- Boolean started; // True if the tester has been successfully started.
- Boolean stopped; // True if the tester has been stopped.
- int callDelayMs; // Amount of time to wait before calling DNSServiceGetAddrInfo().
- dispatch_source_t caseTimer; // Timer for enforcing a test case time limits.
- pcap_t * pcap; // Captures traffic between mDNSResponder and test DNS server.
- pid_t serverPID; // PID of the test DNS server.
- int serverDelayMs; // Additional time to have the server delay its responses by.
- int serverDefaultTTL; // Default TTL for the server's records.
- GAITesterEventHandler_f eventHandler; // User's event handler.
- void * eventContext; // User's event handler context.
- GAITesterResultsHandler_f resultsHandler; // User's results handler.
- void * resultsContext; // User's results handler context.
+ check( inIndex <= inContext->maxInstanceCount );
- // Variables for current test item.
+ // If maxDropCount > 0, then the drop rates apply only to the first maxDropCount responses. Otherwise, all messages are
+ // subject to their respective drop rate. Also, responses to queries about mDNS replier itself (indicated by index 0),
+ // as opposed to those for service instance records, are never dropped.
- uint64_t bitmapV4; // Bitmap of IPv4 results that have yet to be received.
- uint64_t bitmapV6; // Bitmap of IPv6 results that have yet to be received.
- uint64_t startTicks; // Start ticks of DNSServiceGetAddrInfo().
- uint64_t connTicks; // Ticks when the connection was created.
- uint64_t firstTicks; // Ticks when the first DNSServiceGetAddrInfo result was received.
- uint64_t endTicks; // Ticks when the last DNSServiceGetAddrInfo result was received.
- Boolean gotFirstResult; // True if the first result has been received.
-};
-
-CF_CLASS_DEFINE( GAITester );
-
-static void _GAITesterRun( void *inContext );
-static OSStatus _GAITesterCreatePacketCapture( pcap_t **outPCap );
-static void _GAITesterTimeout( void *inContext );
-static void _GAITesterAdvanceCurrentItem( GAITesterRef inTester );
-static void _GAITesterAdvanceCurrentSet( GAITesterRef inTester );
-static void _GAITesterInitializeCurrentTest( GAITesterRef inTester );
-static void DNSSD_API
- _GAITesterGetAddrInfoCallback(
- DNSServiceRef inSDRef,
- DNSServiceFlags inFlags,
- uint32_t inInterfaceIndex,
- DNSServiceErrorType inError,
- const char * inHostname,
- const struct sockaddr * inSockAddr,
- uint32_t inTTL,
- void * inContext );
-static void _GAITesterCompleteCurrentTest( GAITesterRef inTester, Boolean inTimedOut );
-
-#define ForgetPacketCapture( X ) ForgetCustom( X, pcap_close )
-
-static OSStatus
- GAITestItemCreate(
- const char * inName,
- unsigned int inAddressCount,
- GAITestAddrType inHasAddrs,
- GAITestAddrType inWantAddrs,
- GAITestItem ** outItem );
-static OSStatus GAITestItemDuplicate( const GAITestItem *inItem, GAITestItem **outItem );
-static void GAITestItemFree( GAITestItem *inItem );
-
-static OSStatus
- GAITesterCreate(
- dispatch_queue_t inQueue,
- int inCallDelayMs,
- int inServerDelayMs,
- int inServerDefaultTTL,
- GAITesterRef * outTester )
-{
- OSStatus err;
- GAITesterRef obj = NULL;
+ drop = false;
+ if( inIndex > 0 )
+ {
+ if( inContext->maxDropCount > 0 )
+ {
+ uint8_t * const dropCount = &inContext->dropCounters[ inIndex - 1 ];
+
+ if( *dropCount < inContext->maxDropCount )
+ {
+ if( ShouldDrop( dropRate ) ) drop = true;
+ *dropCount += 1;
+ }
+ }
+ else if( ShouldDrop( dropRate ) )
+ {
+ drop = true;
+ }
+ }
- CF_OBJECT_CREATE( GAITester, obj, err, exit );
+ err = _MDNSReplierCreateResponse( inContext, inAnswerList, inIndex, &responsePtr, &responseLen );
+ require_noerr( err, exit );
- ReplaceDispatchQueue( &obj->queue, inQueue );
- obj->callDelayMs = inCallDelayMs;
- obj->serverPID = -1;
- obj->serverDelayMs = inServerDelayMs;
- obj->serverDefaultTTL = inServerDefaultTTL;
+ if( inUnicast )
+ {
+ destAddr = &inQuerier->sa;
+ }
+ else
+ {
+ destAddr = ( inQuerier->sa.sa_family == AF_INET ) ? GetMDNSMulticastAddrV4() : GetMDNSMulticastAddrV6();
+ }
- *outTester = obj;
- obj = NULL;
- err = kNoErr;
+ mr_ulog( kLogLevelInfo, "%s %zu byte response to %##a:\n\n%#1{du:dnsmsg}",
+ drop ? "Dropping" : "Sending", responseLen, destAddr, responsePtr, responseLen );
+
+ if( !drop )
+ {
+ n = sendto( inSock, (char *) responsePtr, responseLen, 0, destAddr, SockAddrGetSize( destAddr ) );
+ err = map_socket_value_errno( inSock, n == (ssize_t) responseLen, n );
+ require_noerr( err, exit );
+ }
exit:
- CFReleaseNullSafe( obj );
+ FreeNullSafe( responsePtr );
return( err );
}
//===========================================================================================================================
-// _GAITesterFinalize
+// _MDNSReplierCreateResponse
//===========================================================================================================================
-static void _GAITesterFinalize( CFTypeRef inObj )
+static OSStatus
+ _MDNSReplierCreateResponse(
+ MDNSReplierContext * inContext,
+ MRResourceRecord * inAnswerList,
+ unsigned int inIndex,
+ uint8_t ** outResponsePtr,
+ size_t * outResponseLen )
{
- GAITesterRef const me = (GAITesterRef) inObj;
- GAITestCase * testCase;
+ OSStatus err;
+ DataBuffer responseDB;
+ DNSHeader hdr;
+ MRResourceRecord * answer;
+ uint8_t * responsePtr;
+ size_t responseLen, len;
+ unsigned int answerCount, recordCount;
+ MRNameOffsetItem * nameOffsetList = NULL;
- check( !me->opRef );
- check( !me->mainRef );
- check( !me->caseTimer );
- dispatch_forget( &me->queue );
- while( ( testCase = me->caseList ) != NULL )
+ DataBuffer_Init( &responseDB, NULL, 0, SIZE_MAX );
+
+ // The current answers in the answer list will make up the response's Answer Record Section.
+
+ answerCount = 0;
+ for( answer = inAnswerList; answer; answer = answer->next ) { ++answerCount; }
+
+ // Unless configured not to, add any additional answers to the answer list for the Additional Record Section.
+
+ if( !inContext->noAdditionals )
{
- me->caseList = testCase->next;
- GAITestCaseFree( testCase );
+ for( answer = inAnswerList; answer; answer = answer->next )
+ {
+ switch( answer->type )
+ {
+ case kDNSServiceType_PTR:
+ err = _MDNSReplierAnswerListAdd( inContext, &inAnswerList, inIndex, answer->rdata, kDNSServiceType_SRV,
+ answer->class );
+ require_noerr( err, exit );
+
+ err = _MDNSReplierAnswerListAdd( inContext, &inAnswerList, inIndex, answer->rdata, kDNSServiceType_TXT,
+ answer->class );
+ require_noerr( err, exit );
+ break;
+
+ case kDNSServiceType_SRV:
+ err = _MDNSReplierAnswerListAdd( inContext, &inAnswerList, inIndex, answer->target, kDNSServiceType_A,
+ answer->class );
+ require_noerr( err, exit );
+
+ err = _MDNSReplierAnswerListAdd( inContext, &inAnswerList, inIndex, answer->target, kDNSServiceType_AAAA,
+ answer->class );
+ require_noerr( err, exit );
+
+ err = _MDNSReplierAnswerListAdd( inContext, &inAnswerList, inIndex, answer->name, kDNSServiceType_NSEC,
+ answer->class );
+ require_noerr( err, exit );
+ break;
+
+ case kDNSServiceType_TXT:
+ err = _MDNSReplierAnswerListAdd( inContext, &inAnswerList, inIndex, answer->name, kDNSServiceType_NSEC,
+ answer->class );
+ require_noerr( err, exit );
+ break;
+
+ case kDNSServiceType_A:
+ err = _MDNSReplierAnswerListAdd( inContext, &inAnswerList, inIndex, answer->name, kDNSServiceType_AAAA,
+ answer->class );
+ require_noerr( err, exit );
+
+ err = _MDNSReplierAnswerListAdd( inContext, &inAnswerList, inIndex, answer->name, kDNSServiceType_NSEC,
+ answer->class );
+ require_noerr( err, exit );
+ break;
+
+ case kDNSServiceType_AAAA:
+ err = _MDNSReplierAnswerListAdd( inContext, &inAnswerList, inIndex, answer->name, kDNSServiceType_A,
+ answer->class );
+ require_noerr( err, exit );
+
+ err = _MDNSReplierAnswerListAdd( inContext, &inAnswerList, inIndex, answer->name, kDNSServiceType_NSEC,
+ answer->class );
+ require_noerr( err, exit );
+ break;
+
+ default:
+ break;
+ }
+ }
}
-}
-
-//===========================================================================================================================
-// GAITesterStart
-//===========================================================================================================================
-
-static void _GAITesterStart( void *inContext );
-static void _GAITesterStop( GAITesterRef me );
-
-static void GAITesterStart( GAITesterRef me )
-{
- CFRetain( me );
- dispatch_async_f( me->queue, me, _GAITesterStart );
-}
-
-extern char ** environ;
-
-static void _GAITesterStart( void *inContext )
-{
- OSStatus err;
- GAITesterRef const me = (GAITesterRef) inContext;
- char * argv[ 4 ];
- char * ptr;
- char * end;
- char command[ 128 ];
- ptr = &command[ 0 ];
- end = &command[ countof( command ) ];
- SNPrintF_Add( &ptr, end, "dnssdutil server --loopback --followPID %lld", (int64_t) getpid() );
- if( me->serverDefaultTTL >= 0 ) SNPrintF_Add( &ptr, end, " --defaultTTL %d", me->serverDefaultTTL );
- if( me->serverDelayMs >= 0 ) SNPrintF_Add( &ptr, end, " --responseDelay %d", me->serverDelayMs );
+ // Append a provisional header to the response message.
- argv[ 0 ] = "/bin/sh";
- argv[ 1 ] = "-c";
- argv[ 2 ] = command;
- argv[ 3 ] = NULL;
- err = posix_spawn( &me->serverPID, argv[ 0 ], NULL, NULL, argv, environ );
+ memset( &hdr, 0, sizeof( hdr ) );
+ DNSHeaderSetFlags( &hdr, kDNSHeaderFlag_Response | kDNSHeaderFlag_AuthAnswer );
+
+ err = DataBuffer_Append( &responseDB, &hdr, sizeof( hdr ) );
require_noerr( err, exit );
- me->currentCase = me->caseList;
- me->currentItem = me->currentCase ? me->currentCase->itemList : NULL;
- _GAITesterInitializeCurrentTest( me );
+ // Append answers to response message.
+
+ responseLen = DataBuffer_GetLen( &responseDB );
+ recordCount = 0;
+ for( answer = inAnswerList; answer; answer = answer->next )
+ {
+ DNSRecordFixedFields fields;
+ unsigned int class;
+
+ // Append record NAME.
+
+ err = _MDNSReplierAppendNameToResponse( &responseDB, answer->name, &nameOffsetList );
+ require_noerr( err, exit );
+
+ // Append record TYPE, CLASS, TTL, and provisional RDLENGTH.
+
+ class = answer->class;
+ if( ( answer->type == kDNSServiceType_SRV ) || ( answer->type == kDNSServiceType_TXT ) ||
+ ( answer->type == kDNSServiceType_A ) || ( answer->type == kDNSServiceType_AAAA ) ||
+ ( answer->type == kDNSServiceType_NSEC ) )
+ {
+ class |= kRRClassCacheFlushBit;
+ }
+
+ DNSRecordFixedFieldsSet( &fields, answer->type, (uint16_t) class, answer->ttl, (uint16_t) answer->rdlength );
+ err = DataBuffer_Append( &responseDB, &fields, sizeof( fields ) );
+ require_noerr( err, exit );
+
+ // Append record RDATA.
+ // The RDATA of PTR, SRV, and NSEC records contain domain names, which are subject to name compression.
+
+ if( ( answer->type == kDNSServiceType_PTR ) || ( answer->type == kDNSServiceType_SRV ) ||
+ ( answer->type == kDNSServiceType_NSEC ) )
+ {
+ size_t rdlength;
+ uint8_t * rdLengthPtr;
+ const size_t rdLengthOffset = DataBuffer_GetLen( &responseDB ) - 2;
+ const size_t rdataOffset = DataBuffer_GetLen( &responseDB );
+
+ if( answer->type == kDNSServiceType_PTR )
+ {
+ err = _MDNSReplierAppendNameToResponse( &responseDB, answer->rdata, &nameOffsetList );
+ require_noerr( err, exit );
+ }
+ else if( answer->type == kDNSServiceType_SRV )
+ {
+ require_fatal( answer->target == &answer->rdata[ 6 ], "Bad SRV record target pointer." );
+
+ err = DataBuffer_Append( &responseDB, answer->rdata, (size_t)( answer->target - answer->rdata ) );
+ require_noerr( err, exit );
+
+ err = _MDNSReplierAppendNameToResponse( &responseDB, answer->target, &nameOffsetList );
+ require_noerr( err, exit );
+ }
+ else
+ {
+ const size_t nameLen = DomainNameLength( answer->rdata );
+
+ err = _MDNSReplierAppendNameToResponse( &responseDB, answer->rdata, &nameOffsetList );
+ require_noerr( err, exit );
+
+ require_fatal( answer->rdlength > nameLen, "Bad NSEC record data length." );
+
+ err = DataBuffer_Append( &responseDB, &answer->rdata[ nameLen ], answer->rdlength - nameLen );
+ require_noerr( err, exit );
+ }
+
+ // Set the actual RDLENGTH, which may be less than the original due to name compression.
+
+ rdlength = DataBuffer_GetLen( &responseDB ) - rdataOffset;
+ check( rdlength <= UINT16_MAX );
+
+ rdLengthPtr = DataBuffer_GetPtr( &responseDB ) + rdLengthOffset;
+ WriteBig16( rdLengthPtr, rdlength );
+ }
+ else
+ {
+ err = DataBuffer_Append( &responseDB, answer->rdata, answer->rdlength );
+ require_noerr( err, exit );
+ }
+
+ if( DataBuffer_GetLen( &responseDB ) > kMDNSMessageSizeMax ) break;
+ responseLen = DataBuffer_GetLen( &responseDB );
+ ++recordCount;
+ }
- // Hack: The first tester run is delayed for three seconds to allow the test DNS server to start up.
- // A better way to handle this is to issue an asynchronous query for something in the d.test. domain. As soon as an
- // expected response is received, the server can be considered to be up and running.
+ // Set the response header's Answer and Additional record counts.
+ // Note: recordCount may be less than answerCount if including all answerCount records would cause the size of the
+ // response message to exceed the maximum mDNS message size.
- CFRetain( me );
- dispatch_after_f( dispatch_time_seconds( 3 ), me->queue, me, _GAITesterRun );
+ if( recordCount <= answerCount )
+ {
+ DNSHeaderSetAnswerCount( (DNSHeader *) DataBuffer_GetPtr( &responseDB ), recordCount );
+ }
+ else
+ {
+ DNSHeaderSetAnswerCount( (DNSHeader *) DataBuffer_GetPtr( &responseDB ), answerCount );
+ DNSHeaderSetAdditionalCount( (DNSHeader *) DataBuffer_GetPtr( &responseDB ), recordCount - answerCount );
+ }
- CFRetain( me );
- me->started = true;
- if( me->eventHandler ) me->eventHandler( kGAITesterEvent_Started, me->eventContext );
+ err = DataBuffer_Detach( &responseDB, &responsePtr, &len );
+ require_noerr( err, exit );
+
+ if( outResponsePtr ) *outResponsePtr = responsePtr;
+ if( outResponseLen ) *outResponseLen = responseLen;
exit:
- if( err ) _GAITesterStop( me );
- CFRelease( me );
+ _MRNameOffsetItemFreeList( nameOffsetList );
+ DataBuffer_Free( &responseDB );
+ return( err );
}
//===========================================================================================================================
-// GAITesterStop
+// _MDNSReplierAppendNameToResponse
//===========================================================================================================================
-static void _GAITesterUserStop( void *inContext );
-
-static void GAITesterStop( GAITesterRef me )
-{
- CFRetain( me );
- dispatch_async_f( me->queue, me, _GAITesterUserStop );
-}
-
-static void _GAITesterUserStop( void *inContext )
+static OSStatus
+ _MDNSReplierAppendNameToResponse(
+ DataBuffer * inResponse,
+ const uint8_t * inName,
+ MRNameOffsetItem ** inNameOffsetListPtr )
{
- GAITesterRef const me = (GAITesterRef) inContext;
+ OSStatus err;
+ const uint8_t * subname;
+ const uint8_t * limit;
+ size_t nameOffset;
+ MRNameOffsetItem * item;
+ uint8_t compressionPtr[ 2 ];
- _GAITesterStop( me );
- CFRelease( me );
-}
-
-static void _GAITesterStop( GAITesterRef me )
-{
- OSStatus err;
+ nameOffset = DataBuffer_GetLen( inResponse );
- DNSServiceForget( &me->opRef );
- DNSServiceForget( &me->mainRef );
- ForgetPacketCapture( &me->pcap );
- dispatch_source_forget( &me->caseTimer );
- if( me->serverPID != -1 )
+ // Find the name's longest subname (more accurately, its longest sub-FQDN) in the name compression list.
+
+ for( subname = inName; subname[ 0 ] != 0; subname += ( 1 + subname[ 0 ] ) )
{
- err = kill( me->serverPID, SIGTERM );
- err = map_global_noerr_errno( err );
- check_noerr( err );
+ for( item = *inNameOffsetListPtr; item; item = item->next )
+ {
+ if( DomainNameEqual( item->name, subname ) ) break;
+ }
+
+ // If an item was found for this subname, then append a name compression pointer and we're done. Otherwise, append
+ // the subname's first label.
+
+ if( item )
+ {
+ WriteDNSCompressionPtr( compressionPtr, item->offset );
+
+ err = DataBuffer_Append( inResponse, compressionPtr, sizeof( compressionPtr ) );
+ require_noerr( err, exit );
+ break;
+ }
+ else
+ {
+ err = DataBuffer_Append( inResponse, subname, 1 + subname[ 0 ] );
+ require_noerr( err, exit );
+ }
}
+
+ // If we made it to the root label, then no subname was able to be compressed. All of the name's labels up to the root
+ // label were appended to the response message, so a root label is needed to terminate the complete name.
- if( !me->stopped )
+ if( subname[ 0 ] == 0 )
{
- me->stopped = true;
- if( me->eventHandler ) me->eventHandler( kGAITesterEvent_Stopped, me->eventContext );
- if( me->started ) CFRelease( me );
+ err = DataBuffer_Append( inResponse, "", 1 );
+ require_noerr( err, exit );
}
-}
-
-//===========================================================================================================================
-// GAITesterAddCase
-//===========================================================================================================================
-
-static void GAITesterAddCase( GAITesterRef me, GAITestCase *inCase )
-{
- GAITestCase ** ptr;
- for( ptr = &me->caseList; *ptr != NULL; ptr = &( *ptr )->next ) {}
- *ptr = inCase;
+ // Add subnames that weren't able to be compressed and their offsets to the name compression list.
+
+ limit = subname;
+ for( subname = inName; subname < limit; subname += ( 1 + subname[ 0 ] ) )
+ {
+ const size_t subnameOffset = nameOffset + (size_t)( subname - inName );
+
+ if( subnameOffset > kDNSCompressionOffsetMax ) break;
+
+ err = _MRNameOffsetItemCreate( subname, (uint16_t) subnameOffset, &item );
+ require_noerr( err, exit );
+
+ item->next = *inNameOffsetListPtr;
+ *inNameOffsetListPtr = item;
+ }
+ err = kNoErr;
+
+exit:
+ return( err );
}
//===========================================================================================================================
-// GAITesterSetEventHandler
+// _MDNSReplierServiceTypeMatch
//===========================================================================================================================
-static void GAITesterSetEventHandler( GAITesterRef me, GAITesterEventHandler_f inEventHandler, void *inEventContext )
+static Boolean
+ _MDNSReplierServiceTypeMatch(
+ const MDNSReplierContext * inContext,
+ const uint8_t * inName,
+ unsigned int * outTXTSize,
+ unsigned int * outCount )
{
- me->eventHandler = inEventHandler;
- me->eventContext = inEventContext;
+ OSStatus err;
+ const char * ptr;
+ const char * end;
+ uint32_t txtSize, count;
+ const uint8_t * const serviceLabel = inContext->serviceLabel;
+ int nameMatches = false;
+
+ require_quiet( inName[ 0 ] >= serviceLabel[ 0 ], exit );
+ if( memicmp( &inName[ 1 ], &serviceLabel[ 1 ], serviceLabel[ 0 ] ) != 0 ) goto exit;
+
+ ptr = (const char *) &inName[ 1 + serviceLabel[ 0 ] ];
+ end = (const char *) &inName[ 1 + inName[ 0 ] ];
+
+ require_quiet( ( ptr < end ) && ( *ptr == '-' ), exit );
+ ++ptr;
+
+ err = DecimalTextToUInt32( ptr, end, &txtSize, &ptr );
+ require_noerr_quiet( err, exit );
+ require_quiet( txtSize <= UINT16_MAX, exit );
+
+ require_quiet( ( ptr < end ) && ( *ptr == '-' ), exit );
+ ++ptr;
+
+ err = DecimalTextToUInt32( ptr, end, &count, &ptr );
+ require_noerr_quiet( err, exit );
+ require_quiet( count <= UINT16_MAX, exit );
+ require_quiet( ptr == end, exit );
+
+ if( !DomainNameEqual( (const uint8_t *) ptr, (const uint8_t *) "\x04" "_tcp" "\x05" "local" ) ) goto exit;
+ nameMatches = true;
+
+ if( outTXTSize ) *outTXTSize = txtSize;
+ if( outCount ) *outCount = count;
+
+exit:
+ return( nameMatches ? true : false );
}
//===========================================================================================================================
-// GAITesterSetResultsHandler
+// _MDNSReplierServiceInstanceNameMatch
//===========================================================================================================================
-static void GAITesterSetResultsHandler( GAITesterRef me, GAITesterResultsHandler_f inResultsHandler, void *inResultsContext )
+static Boolean
+ _MDNSReplierServiceInstanceNameMatch(
+ const MDNSReplierContext * inContext,
+ const uint8_t * inName,
+ unsigned int * outIndex,
+ unsigned int * outTXTSize,
+ unsigned int * outCount )
{
- me->resultsHandler = inResultsHandler;
- me->resultsContext = inResultsContext;
+ OSStatus err;
+ const uint8_t * ptr;
+ const uint8_t * end;
+ uint32_t index;
+ unsigned int txtSize, count;
+ const uint8_t * const hostname = inContext->hostname;
+ int nameMatches = false;
+
+ require_quiet( inName[ 0 ] >= hostname[ 0 ], exit );
+ if( memicmp( &inName[ 1 ], &hostname[ 1 ], hostname[ 0 ] ) != 0 ) goto exit;
+
+ ptr = &inName[ 1 + hostname[ 0 ] ];
+ end = &inName[ 1 + inName[ 0 ] ];
+ if( ptr < end )
+ {
+ require_quiet( ( end - ptr ) >= 2, exit );
+ require_quiet( ( ptr[ 0 ] == ' ' ) && ( ptr[ 1 ] == '(' ), exit );
+ ptr += 2;
+
+ err = DecimalTextToUInt32( (const char *) ptr, (const char *) end, &index, (const char **) &ptr );
+ require_noerr_quiet( err, exit );
+ require_quiet( ( index >= 2 ) && ( index <= UINT16_MAX ), exit );
+
+ require_quiet( ( ( end - ptr ) == 1 ) && ( *ptr == ')' ), exit );
+ ++ptr;
+ }
+ else
+ {
+ index = 1;
+ }
+
+ if( !_MDNSReplierServiceTypeMatch( inContext, ptr, &txtSize, &count ) ) goto exit;
+ nameMatches = true;
+
+ if( outIndex ) *outIndex = index;
+ if( outTXTSize ) *outTXTSize = txtSize;
+ if( outCount ) *outCount = count;
+
+exit:
+ return( nameMatches ? true : false );
}
//===========================================================================================================================
-// _GAITesterRun
+// _MDNSReplierAboutRecordNameMatch
//===========================================================================================================================
-static void _GAITesterRun( void *inContext )
+static Boolean _MDNSReplierAboutRecordNameMatch( const MDNSReplierContext *inContext, const uint8_t *inName )
{
- OSStatus err;
- GAITesterRef const me = (GAITesterRef) inContext;
- GAITestItem * item;
- GAITestItemResult * results = NULL;
+ const uint8_t * subname;
+ const uint8_t * const hostname = inContext->hostname;
+ int nameMatches = false;
- require_action_quiet( !me->stopped, exit, err = kNoErr );
+ if( strnicmpx( &inName[ 1 ], inName[ 0 ], "about" ) != 0 ) goto exit;
+ subname = NextLabel( inName );
- for( ;; )
- {
- item = me->currentItem;
- if( item )
- {
- DNSServiceProtocol protocols;
-
- check( !me->opRef );
- check( ( me->bitmapV4 != 0 ) || ( me->bitmapV6 != 0 ) );
-
- // Perform preliminary tasks if this is the start of a new test case.
-
- if( item == me->currentCase->itemList )
- {
- // Flush mDNSResponder's cache.
-
- err = systemf( NULL, "killall -HUP mDNSResponder" );
- require_noerr( err, exit );
- usleep( kMicrosecondsPerSecond );
-
- // Start a packet capture.
-
- check( !me->pcap );
- err = _GAITesterCreatePacketCapture( &me->pcap );
- require_noerr( err, exit );
-
- // Start the test case time limit timer.
-
- check( !me->caseTimer );
- if( me->currentCase->timeLimitMs > 0 )
- {
- const int64_t timeLimitSecs = ( me->currentCase->timeLimitMs + 999 ) / 1000;
-
- err = DispatchTimerCreate( dispatch_time_seconds( timeLimitSecs ), DISPATCH_TIME_FOREVER,
- ( (uint64_t) timeLimitSecs ) * kNanosecondsPerSecond / 10,
- me->queue, _GAITesterTimeout, NULL, me, &me->caseTimer );
- require_noerr( err, exit );
- dispatch_resume( me->caseTimer );
- }
-
- me->caseStartTime = GetCurrentMicroTime();
- }
-
- // Call DNSServiceGetAddrInfo().
-
- if( me->callDelayMs > 0 ) usleep( ( (useconds_t) me->callDelayMs ) * kMicrosecondsPerMillisecond );
-
- protocols = 0;
- if( item->wantV4 ) protocols |= kDNSServiceProtocol_IPv4;
- if( item->wantV6 ) protocols |= kDNSServiceProtocol_IPv6;
-
- check( !me->mainRef );
- me->startTicks = UpTicks();
-
- err = DNSServiceCreateConnection( &me->mainRef );
- require_noerr( err, exit );
-
- err = DNSServiceSetDispatchQueue( me->mainRef, me->queue );
- require_noerr( err, exit );
-
- me->connTicks = UpTicks();
-
- me->opRef = me->mainRef;
- err = DNSServiceGetAddrInfo( &me->opRef, kDNSServiceFlagsShareConnection | kDNSServiceFlagsReturnIntermediates,
- kDNSServiceInterfaceIndexAny, protocols, item->name, _GAITesterGetAddrInfoCallback, me );
- require_noerr( err, exit );
- break;
- }
- else
- {
- // No more test items means that this test case has completed (or timed out).
-
- me->caseEndTime = GetCurrentMicroTime();
- dispatch_source_forget( &me->caseTimer );
- ForgetPacketCapture( &me->pcap );
-
- if( me->resultsHandler )
- {
- size_t resultCount, itemCount, i;
- int timedOut;
-
- itemCount = 0;
- resultCount = 0;
- timedOut = false;
- for( item = me->currentCase->itemList; item; item = item->next )
- {
- if( !timedOut )
- {
- if( item->timeUs < 0 )
- {
- timedOut = true;
- }
- else
- {
- ++resultCount;
- }
- }
- ++itemCount;
- }
- if( resultCount > 0 )
- {
- results = (GAITestItemResult *) calloc( resultCount, sizeof( *results ) );
- require_action( results, exit, err = kNoMemoryErr );
-
- item = me->currentCase->itemList;
- for( i = 0; i < resultCount; ++i )
- {
- results[ i ].name = item->name;
- results[ i ].connectionTimeUs = item->connectionTimeUs;
- results[ i ].firstTimeUs = item->firstTimeUs;
- results[ i ].timeUs = item->timeUs;
- item = item->next;
- }
- }
- me->resultsHandler( me->currentCase->title, me->caseStartTime, me->caseEndTime, results, resultCount,
- itemCount, me->resultsContext );
- ForgetMem( &results );
- }
-
- _GAITesterAdvanceCurrentSet( me );
- require_action_quiet( me->currentCase, exit, err = kEndingErr );
- }
- }
+ if( !MemIEqual( &subname[ 1 ], subname[ 0 ], &hostname[ 1 ], hostname[ 0 ] ) ) goto exit;
+ subname = NextLabel( subname );
+
+ if( !DomainNameEqual( subname, kLocalName ) ) goto exit;
+ nameMatches = true;
exit:
- FreeNullSafe( results );
- if( err ) _GAITesterStop( me );
- CFRelease( me );
+ return( nameMatches ? true : false );
}
//===========================================================================================================================
-// _GAITesterCreatePacketCapture
+// _MDNSReplierHostnameMatch
//===========================================================================================================================
-static OSStatus _GAITesterCreatePacketCapture( pcap_t **outPCap )
+static Boolean
+ _MDNSReplierHostnameMatch(
+ const MDNSReplierContext * inContext,
+ const uint8_t * inName,
+ unsigned int * outIndex )
{
- OSStatus err;
- pcap_t * pcap;
- struct bpf_program program;
- char errBuf[ PCAP_ERRBUF_SIZE ];
+ OSStatus err;
+ const uint8_t * ptr;
+ const uint8_t * end;
+ uint32_t index;
+ const uint8_t * const hostname = inContext->hostname;
+ int nameMatches = false;
- pcap = pcap_create( "lo0", errBuf );
- require_action_string( pcap, exit, err = kUnknownErr, errBuf );
+ require_quiet( inName[ 0 ] >= hostname[ 0 ], exit );
+ if( memicmp( &inName[ 1 ], &hostname[ 1 ], hostname[ 0 ] ) != 0 ) goto exit;
- err = pcap_set_buffer_size( pcap, 512 * kBytesPerKiloByte );
- require_noerr_action( err, exit, err = kUnknownErr );
+ ptr = &inName[ 1 + hostname[ 0 ] ];
+ end = &inName[ 1 + inName[ 0 ] ];
+ if( ptr < end )
+ {
+ require_quiet( *ptr == '-', exit );
+ ++ptr;
+
+ err = DecimalTextToUInt32( (const char *) ptr, (const char *) end, &index, (const char **) &ptr );
+ require_noerr_quiet( err, exit );
+ require_quiet( ( index >= 2 ) && ( index <= UINT16_MAX ), exit );
+ require_quiet( ptr == end, exit );
+ }
+ else
+ {
+ index = 1;
+ }
- err = pcap_set_snaplen( pcap, 512 );
- require_noerr_action( err, exit, err = kUnknownErr );
+ if( !DomainNameEqual( ptr, kLocalName ) ) goto exit;
+ nameMatches = true;
- err = pcap_set_immediate_mode( pcap, 0 );
- require_noerr_action_string( err, exit, err = kUnknownErr, pcap_geterr( pcap ) );
+ if( outIndex ) *outIndex = index;
- err = pcap_activate( pcap );
- require_noerr_action_string( err, exit, err = kUnknownErr, pcap_geterr( pcap ) );
+exit:
+ return( nameMatches ? true : false );
+}
+
+//===========================================================================================================================
+// _MDNSReplierCreateTXTRecord
+//===========================================================================================================================
+
+static OSStatus _MDNSReplierCreateTXTRecord( const uint8_t *inRecordName, size_t inSize, uint8_t **outTXT )
+{
+ OSStatus err;
+ uint8_t * txt;
+ uint8_t * ptr;
+ size_t i, wholeCount, remCount;
+ uint32_t hash;
+ int n;
+ uint8_t txtStr[ 16 ];
- err = pcap_setdirection( pcap, PCAP_D_INOUT );
- require_noerr_action_string( err, exit, err = kUnknownErr, pcap_geterr( pcap ) );
+ require_action_quiet( inSize > 0, exit, err = kSizeErr );
- err = pcap_setnonblock( pcap, 1, errBuf );
- require_noerr_action_string( err, exit, err = kUnknownErr, pcap_geterr( pcap ) );
+ txt = (uint8_t *) malloc( inSize );
+ require_action( txt, exit, err = kNoMemoryErr );
- err = pcap_compile( pcap, &program, "udp port 53", 1, PCAP_NETMASK_UNKNOWN );
- require_noerr_action_string( err, exit, err = kUnknownErr, pcap_geterr( pcap ) );
+ hash = FNV1( inRecordName, DomainNameLength( inRecordName ) );
- err = pcap_setfilter( pcap, &program );
- pcap_freecode( &program );
- require_noerr_action_string( err, exit, err = kUnknownErr, pcap_geterr( pcap ) );
+ txtStr[ 0 ] = 15;
+ n = MemPrintF( &txtStr[ 1 ], 15, "hash=0x%08X", hash );
+ check( n == 15 );
- *outPCap = pcap;
- pcap = NULL;
+ ptr = txt;
+ wholeCount = inSize / 16;
+ for( i = 0; i < wholeCount; ++i )
+ {
+ memcpy( ptr, txtStr, 16 );
+ ptr += 16;
+ }
+
+ remCount = inSize % 16;
+ if( remCount > 0 )
+ {
+ txtStr[ 0 ] = (uint8_t)( remCount - 1 );
+ memcpy( ptr, txtStr, remCount );
+ ptr += remCount;
+ }
+ check( ptr == &txt[ inSize ] );
+
+ *outTXT = txt;
+ err = kNoErr;
exit:
- if( pcap ) pcap_close( pcap );
return( err );
}
//===========================================================================================================================
-// _GAITesterTimeout
+// _MRResourceRecordCreate
//===========================================================================================================================
-static void _GAITesterTimeout( void *inContext )
+static OSStatus
+ _MRResourceRecordCreate(
+ uint8_t * inName,
+ uint16_t inType,
+ uint16_t inClass,
+ uint32_t inTTL,
+ uint16_t inRDLength,
+ uint8_t * inRData,
+ MRResourceRecord ** outRecord )
{
- GAITesterRef const me = (GAITesterRef) inContext;
+ OSStatus err;
+ MRResourceRecord * obj;
+
+ obj = (MRResourceRecord *) calloc( 1, sizeof( *obj ) );
+ require_action( obj, exit, err = kNoMemoryErr );
+
+ obj->name = inName;
+ obj->type = inType;
+ obj->class = inClass;
+ obj->ttl = inTTL;
+ obj->rdlength = inRDLength;
+ obj->rdata = inRData;
+
+ if( inType == kDNSServiceType_SRV )
+ {
+ require_action_quiet( obj->rdlength > sizeof( SRVRecordDataFixedFields ), exit, err = kMalformedErr );
+ obj->target = obj->rdata + sizeof( SRVRecordDataFixedFields );
+ }
- dispatch_source_forget( &me->caseTimer );
+ *outRecord = obj;
+ obj = NULL;
+ err = kNoErr;
- _GAITesterCompleteCurrentTest( me, true );
+exit:
+ FreeNullSafe( obj );
+ return( err );
}
//===========================================================================================================================
-// _GAITesterAdvanceCurrentItem
+// _MRResourceRecordFree
//===========================================================================================================================
-static void _GAITesterAdvanceCurrentItem( GAITesterRef me )
+static void _MRResourceRecordFree( MRResourceRecord *inRecord )
{
- if( me->currentItem )
- {
- me->currentItem = me->currentItem->next;
- _GAITesterInitializeCurrentTest( me );
- }
+ ForgetMem( &inRecord->name );
+ ForgetMem( &inRecord->rdata );
+ free( inRecord );
}
//===========================================================================================================================
-// _GAITesterAdvanceCurrentSet
+// _MRResourceRecordFreeList
//===========================================================================================================================
-static void _GAITesterAdvanceCurrentSet( GAITesterRef me )
+static void _MRResourceRecordFreeList( MRResourceRecord *inList )
{
- if( me->currentCase )
+ MRResourceRecord * record;
+
+ while( ( record = inList ) != NULL )
{
- me->caseStartTime = 0;
- me->caseEndTime = 0;
- me->currentCase = me->currentCase->next;
- if( me->currentCase )
- {
- me->currentItem = me->currentCase->itemList;
- _GAITesterInitializeCurrentTest( me );
- }
+ inList = record->next;
+ _MRResourceRecordFree( record );
}
}
//===========================================================================================================================
-// _GAITesterInitializeCurrentTest
+// _MRNameOffsetItemCreate
+//===========================================================================================================================
+
+static OSStatus _MRNameOffsetItemCreate( const uint8_t *inName, uint16_t inOffset, MRNameOffsetItem **outItem )
+{
+ OSStatus err;
+ MRNameOffsetItem * obj;
+ size_t nameLen;
+
+ require_action_quiet( inOffset <= kDNSCompressionOffsetMax, exit, err = kSizeErr );
+
+ nameLen = DomainNameLength( inName );
+ obj = (MRNameOffsetItem *) calloc( 1, offsetof( MRNameOffsetItem, name ) + nameLen );
+ require_action( obj, exit, err = kNoMemoryErr );
+
+ obj->offset = inOffset;
+ memcpy( obj->name, inName, nameLen );
+
+ *outItem = obj;
+ err = kNoErr;
+
+exit:
+ return( err );
+}
+
+//===========================================================================================================================
+// _MRNameOffsetItemFree
+//===========================================================================================================================
+
+static void _MRNameOffsetItemFree( MRNameOffsetItem *inItem )
+{
+ free( inItem );
+}
+
+//===========================================================================================================================
+// _MRNameOffsetItemFreeList
//===========================================================================================================================
-static void _GAITesterInitializeCurrentTest( GAITesterRef me )
+static void _MRNameOffsetItemFreeList( MRNameOffsetItem *inList )
{
- GAITestItem * const item = me->currentItem;
+ MRNameOffsetItem * item;
- if( item )
+ while( ( item = inList ) != NULL )
{
- check( item->addressCount > 0 );
- if( item->wantV4 )
- {
- me->bitmapV4 = item->hasV4 ? ( ( UINT64_C( 1 ) << item->addressCount ) - 1 ) : 1;
- }
- else
- {
- me->bitmapV4 = 0;
- }
-
- if( item->wantV6 )
- {
- me->bitmapV6 = item->hasV6 ? ( ( UINT64_C( 1 ) << item->addressCount ) - 1 ) : 1;
- }
- else
- {
- me->bitmapV6 = 0;
- }
- me->gotFirstResult = false;
+ inList = item->next;
+ _MRNameOffsetItemFree( item );
}
}
//===========================================================================================================================
-// _GAITesterGetAddrInfoCallback
+// GAIPerfCmd
//===========================================================================================================================
-static void DNSSD_API
- _GAITesterGetAddrInfoCallback(
- DNSServiceRef inSDRef,
- DNSServiceFlags inFlags,
- uint32_t inInterfaceIndex,
- DNSServiceErrorType inError,
- const char * inHostname,
- const struct sockaddr * inSockAddr,
- uint32_t inTTL,
- void * inContext )
+#define kGAIPerfGAITimeLimitMs 500 // Allow at most 500 ms for a DNSServiceGetAddrInfo() operation to complete.
+#define kGAIPerfStandardTTL ( 1 * kSecondsPerHour )
+
+typedef struct GAITesterPrivate * GAITesterRef;
+typedef struct GAITestCase GAITestCase;
+
+typedef struct
{
- GAITesterRef const me = (GAITesterRef) inContext;
- GAITestItem * const item = me->currentItem;
- const sockaddr_ip * const sip = (const sockaddr_ip *) inSockAddr;
- uint64_t nowTicks;
- uint64_t * bitmapPtr;
- uint64_t bitmask;
- unsigned int addrOffset;
-
- Unused( inSDRef );
- Unused( inInterfaceIndex );
- Unused( inHostname );
- Unused( inTTL );
-
- nowTicks = UpTicks();
+ const char * name; // Domain name that was resolved.
+ uint64_t connectionTimeUs; // Time in microseconds that it took to create a DNS-SD connection.
+ uint64_t firstTimeUs; // Time in microseconds that it took to get the first address result.
+ uint64_t timeUs; // Time in microseconds that it took to get all expected address results.
+ OSStatus error;
- require_quiet( inFlags & kDNSServiceFlagsAdd, exit );
- require_quiet( !inError || ( inError == kDNSServiceErr_NoSuchRecord ), exit );
+} GAITestItemResult;
+
+typedef void ( *GAITesterStopHandler_f )( void *inContext, OSStatus inError );
+typedef void
+ ( *GAITesterResultsHandler_f )(
+ const char * inCaseTitle,
+ NanoTime64 inCaseStartTime,
+ NanoTime64 inCaseEndTime,
+ const GAITestItemResult * inResultArray,
+ size_t inResultCount,
+ void * inContext );
+
+typedef unsigned int GAITestAddrType;
+#define kGAITestAddrType_None 0
+#define kGAITestAddrType_IPv4 ( 1U << 0 )
+#define kGAITestAddrType_IPv6 ( 1U << 1 )
+#define kGAITestAddrType_Both ( kGAITestAddrType_IPv4 | kGAITestAddrType_IPv6 )
+
+#define GAITestAddrTypeIsValid( X ) \
+ ( ( (X) & kGAITestAddrType_Both ) && ( ( (X) & ~kGAITestAddrType_Both ) == 0 ) )
+
+typedef struct
+{
+ GAITesterRef tester; // GAI tester object.
+ CFMutableArrayRef testCaseResults; // Array of test case results.
+ unsigned int callDelayMs; // Amount of time to wait before calling DNSServiceGetAddrInfo().
+ unsigned int serverDelayMs; // Amount of additional time to have server delay its responses.
+ unsigned int defaultIterCount; // Default test case iteration count.
+ dispatch_source_t sigIntSource; // Dispatch source for SIGINT.
+ dispatch_source_t sigTermSource; // Dispatch source for SIGTERM.
+ char * outputFilePath; // File to write test results to. If NULL, then write to stdout.
+ OutputFormatType outputFormat; // Format of test results output.
+ Boolean appendNewline; // True if a newline character should be appended to JSON output.
+ Boolean skipPathEval; // True if DNSServiceGetAddrInfo() path evaluation is to be skipped.
+ Boolean badUDPMode; // True if the test DNS server is to run in Bad UDP mode.
+ Boolean testFailed; // True if at least one test case iteration failed.
- bitmapPtr = NULL;
- bitmask = 0;
- if( ( sip->sa.sa_family == AF_INET ) && item->wantV4 )
- {
- if( item->hasV4 )
- {
- if( !inError )
- {
- const uint32_t addrV4 = ntohl( sip->v4.sin_addr.s_addr );
-
- if( strcasecmp( item->name, "localhost." ) == 0 )
- {
- if( addrV4 == INADDR_LOOPBACK )
- {
- bitmask = 1;
- bitmapPtr = &me->bitmapV4;
- }
- }
- else
- {
- addrOffset = addrV4 - kTestDNSServerBaseAddrV4;
- if( ( addrOffset >= 1 ) && ( addrOffset <= item->addressCount ) )
- {
- bitmask = UINT64_C( 1 ) << ( addrOffset - 1 );
- bitmapPtr = &me->bitmapV4;
- }
- }
- }
- }
- else if( inError == kDNSServiceErr_NoSuchRecord )
- {
- bitmask = 1;
- bitmapPtr = &me->bitmapV4;
- }
- }
- else if( ( sip->sa.sa_family == AF_INET6 ) && item->wantV6 )
- {
- if( item->hasV6 )
- {
- if( !inError )
- {
- const uint8_t * const addrV6 = sip->v6.sin6_addr.s6_addr;
-
- if( strcasecmp( item->name, "localhost." ) == 0 )
- {
- if( memcmp( addrV6, in6addr_loopback.s6_addr, 16 ) == 0 )
- {
- bitmask = 1;
- bitmapPtr = &me->bitmapV6;
- }
- }
- else if( memcmp( addrV6, kTestDNSServerBaseAddrV6, 15 ) == 0 )
- {
- addrOffset = addrV6[ 15 ];
- if( ( addrOffset >= 1 ) && ( addrOffset <= item->addressCount ) )
- {
- bitmask = UINT64_C( 1 ) << ( addrOffset - 1 );
- bitmapPtr = &me->bitmapV6;
- }
- }
- }
- }
- else if( inError == kDNSServiceErr_NoSuchRecord )
- {
- bitmask = 1;
- bitmapPtr = &me->bitmapV6;
- }
- }
-
- if( bitmapPtr && ( *bitmapPtr & bitmask ) )
- {
- *bitmapPtr &= ~bitmask;
- if( !me->gotFirstResult )
- {
- me->firstTicks = nowTicks;
- me->gotFirstResult = true;
- }
-
- if( ( me->bitmapV4 == 0 ) && ( me->bitmapV6 == 0 ) )
- {
- me->endTicks = nowTicks;
- _GAITesterCompleteCurrentTest( me, false );
- }
- }
-
-exit:
- return;
-}
+} GAIPerfContext;
-//===========================================================================================================================
-// _GAITesterCompleteCurrentTest
-//===========================================================================================================================
+static void GAIPerfContextFree( GAIPerfContext *inContext );
+static OSStatus GAIPerfAddAdvancedTestCases( GAIPerfContext *inContext );
+static OSStatus GAIPerfAddBasicTestCases( GAIPerfContext *inContext );
+static void GAIPerfTesterStopHandler( void *inContext, OSStatus inError );
+static void
+ GAIPerfResultsHandler(
+ const char * inCaseTitle,
+ NanoTime64 inCaseStartTime,
+ NanoTime64 inCaseEndTime,
+ const GAITestItemResult * inResultArray,
+ size_t inResultCount,
+ void * inContext );
+static void GAIPerfSignalHandler( void *inContext );
+
+CFTypeID GAITesterGetTypeID( void );
+static OSStatus
+ GAITesterCreate(
+ dispatch_queue_t inQueue,
+ int inCallDelayMs,
+ int inServerDelayMs,
+ int inServerDefaultTTL,
+ Boolean inSkipPathEvaluation,
+ Boolean inBadUDPMode,
+ GAITesterRef * outTester );
+static void GAITesterStart( GAITesterRef inTester );
+static void GAITesterStop( GAITesterRef inTester );
+static OSStatus GAITesterAddTestCase( GAITesterRef inTester, GAITestCase *inCase );
+static void
+ GAITesterSetStopHandler(
+ GAITesterRef inTester,
+ GAITesterStopHandler_f inEventHandler,
+ void * inEventContext );
+static void
+ GAITesterSetResultsHandler(
+ GAITesterRef inTester,
+ GAITesterResultsHandler_f inResultsHandler,
+ void * inResultsContext );
+static OSStatus GAITestCaseCreate( const char *inTitle, GAITestCase **outCase );
+static void GAITestCaseFree( GAITestCase *inCase );
static OSStatus
- _GAITesterGetDNSMessageFromPacket(
- const uint8_t * inPacketPtr,
- size_t inPacketLen,
- const uint8_t ** outMsgPtr,
- size_t * outMsgLen );
+ GAITestCaseAddItem(
+ GAITestCase * inCase,
+ unsigned int inAliasCount,
+ unsigned int inAddressCount,
+ int inTTL,
+ GAITestAddrType inHasAddrs,
+ GAITestAddrType inWantAddrs,
+ unsigned int inTimeLimitMs,
+ unsigned int inItemCount );
+static OSStatus
+ GAITestCaseAddLocalHostItem(
+ GAITestCase * inCase,
+ GAITestAddrType inWantAddrs,
+ unsigned int inTimeLimitMs,
+ unsigned int inItemCount );
-static void _GAITesterCompleteCurrentTest( GAITesterRef me, Boolean inTimedOut )
+static void GAIPerfCmd( void )
{
OSStatus err;
- GAITestItem * item;
- struct timeval * tsQA = NULL;
- struct timeval * tsQAAAA = NULL;
- struct timeval * tsRA = NULL;
- struct timeval * tsRAAAA = NULL;
- struct timeval timeStamps[ 4 ];
- struct timeval * tsPtr = &timeStamps[ 0 ];
- struct timeval * tsQ;
- struct timeval * tsR;
- int64_t idleTimeUs;
- uint8_t name[ kDomainNameLengthMax ];
+ GAIPerfContext * context = NULL;
- DNSServiceForget( &me->opRef );
- DNSServiceForget( &me->mainRef );
+ err = CheckRootUser();
+ require_noerr_quiet( err, exit );
- if( inTimedOut )
- {
- for( item = me->currentItem; item; item = item->next )
- {
- item->firstTimeUs = -1;
- item->timeUs = -1;
- }
- me->currentItem = NULL;
-
- CFRetain( me );
- dispatch_async_f( me->queue, me, _GAITesterRun );
- return;
- }
+ err = CheckIntegerArgument( gGAIPerf_CallDelayMs, "call delay (ms)", 0, INT_MAX );
+ require_noerr_quiet( err, exit );
- item = me->currentItem;
- err = DomainNameFromString( name, item->name, NULL );
- require_noerr( err, exit );
+ err = CheckIntegerArgument( gGAIPerf_ServerDelayMs, "server delay (ms)", 0, INT_MAX );
+ require_noerr_quiet( err, exit );
- for( ;; )
- {
- int status;
- struct pcap_pkthdr * pktHdr;
- const uint8_t * packet;
- const uint8_t * msgPtr;
- size_t msgLen;
- const DNSHeader * hdr;
- unsigned int flags;
- const uint8_t * ptr;
- const DNSQuestionFixedFields * qfields;
- unsigned int qtype;
- uint8_t qname[ kDomainNameLengthMax ];
-
- status = pcap_next_ex( me->pcap, &pktHdr, &packet );
- if( status != 1 ) break;
- if( _GAITesterGetDNSMessageFromPacket( packet, pktHdr->caplen, &msgPtr, &msgLen ) != kNoErr ) continue;
- if( msgLen < kDNSHeaderLength ) continue;
-
- hdr = (const DNSHeader *) msgPtr;
- flags = DNSHeaderGetFlags( hdr );
- if( DNSFlagsGetOpCode( flags ) != kDNSOpCode_Query ) continue;
- if( DNSHeaderGetQuestionCount( hdr ) < 1 ) continue;
-
- ptr = (const uint8_t *) &hdr[ 1 ];
- if( DNSMessageExtractDomainName( msgPtr, msgLen, ptr, qname, &ptr ) != kNoErr ) continue;
- if( !DomainNameEqual( qname, name ) ) continue;
-
- qfields = (const DNSQuestionFixedFields *) ptr;
- if( DNSQuestionFixedFieldsGetClass( qfields ) != kDNSServiceClass_IN ) continue;
-
- qtype = DNSQuestionFixedFieldsGetType( qfields );
- if( item->wantV4 && ( qtype == kDNSServiceType_A ) )
- {
- if( flags & kDNSHeaderFlag_Response )
- {
- if( tsQA && !tsRA )
- {
- tsRA = tsPtr++;
- *tsRA = pktHdr->ts;
- }
- }
- else if( !tsQA )
- {
- tsQA = tsPtr++;
- *tsQA = pktHdr->ts;
- }
- }
- else if( item->wantV6 && ( qtype == kDNSServiceType_AAAA ) )
- {
- if( flags & kDNSHeaderFlag_Response )
- {
- if( tsQAAAA && !tsRAAAA )
- {
- tsRAAAA = tsPtr++;
- *tsRAAAA = pktHdr->ts;
- }
- }
- else if( !tsQAAAA )
- {
- tsQAAAA = tsPtr++;
- *tsQAAAA = pktHdr->ts;
- }
- }
- }
+ err = CheckIntegerArgument( gGAIPerf_IterationCount, "iteration count", 1, INT_MAX );
+ require_noerr_quiet( err, exit );
- if( tsQA && tsQAAAA ) tsQ = TIMEVAL_GT( *tsQA, *tsQAAAA ) ? tsQA : tsQAAAA;
- else tsQ = tsQA ? tsQA : tsQAAAA;
+ context = (GAIPerfContext *) calloc( 1, sizeof( *context ) );
+ require_action( context, exit, err = kNoMemoryErr );
- if( tsRA && tsRAAAA ) tsR = TIMEVAL_LT( *tsRA, *tsRAAAA ) ? tsRA : tsRAAAA;
- else tsR = tsQA ? tsQA : tsQAAAA;
+ context->testCaseResults = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks );
+ require_action( context->testCaseResults, exit, err = kNoMemoryErr );
- if( tsQ && tsR )
- {
- idleTimeUs = TIMEVAL_USEC64_DIFF( *tsR, *tsQ );
- if( idleTimeUs < 0 ) idleTimeUs = 0;
- }
- else
+ context->callDelayMs = (unsigned int) gGAIPerf_CallDelayMs;
+ context->serverDelayMs = (unsigned int) gGAIPerf_ServerDelayMs;
+ context->defaultIterCount = (unsigned int) gGAIPerf_IterationCount;
+ context->appendNewline = gGAIPerf_OutputAppendNewline ? true : false;
+ context->skipPathEval = gGAIPerf_SkipPathEvalulation ? true : false;
+ context->badUDPMode = gGAIPerf_BadUDPMode ? true : false;
+
+ if( gGAIPerf_OutputFilePath )
{
- idleTimeUs = 0;
+ context->outputFilePath = strdup( gGAIPerf_OutputFilePath );
+ require_action( context->outputFilePath, exit, err = kNoMemoryErr );
}
- item->connectionTimeUs = (int64_t) UpTicksToMicroseconds( me->connTicks - me->startTicks );
- item->firstTimeUs = (int64_t)( UpTicksToMicroseconds( me->firstTicks - me->connTicks ) - (uint64_t) idleTimeUs );
- item->timeUs = (int64_t)( UpTicksToMicroseconds( me->endTicks - me->connTicks ) - (uint64_t) idleTimeUs );
-
- _GAITesterAdvanceCurrentItem( me );
- CFRetain( me );
- dispatch_async_f( me->queue, me, _GAITesterRun );
-
-exit:
- if( err ) _GAITesterStop( me );
-}
-
-//===========================================================================================================================
-// _GAITesterGetDNSMessageFromPacket
-//===========================================================================================================================
-
-#define kHeaderSizeNullLink 4
-#define kHeaderSizeIPv4Min 20
-#define kHeaderSizeIPv6 40
-#define kHeaderSizeUDP 8
-
-#define kIPProtocolUDP 0x11
-
-static OSStatus
- _GAITesterGetDNSMessageFromPacket(
- const uint8_t * inPacketPtr,
- size_t inPacketLen,
- const uint8_t ** outMsgPtr,
- size_t * outMsgLen )
-{
- OSStatus err;
- const uint8_t * nullLink;
- uint32_t addressFamily;
- const uint8_t * ip;
- int ipHeaderLen;
- int protocol;
- const uint8_t * msg;
- const uint8_t * const end = &inPacketPtr[ inPacketLen ];
+ context->outputFormat = (OutputFormatType) CLIArgToValue( "format", gGAIPerf_OutputFormat, &err,
+ kOutputFormatStr_JSON, kOutputFormatType_JSON,
+ kOutputFormatStr_XML, kOutputFormatType_XML,
+ kOutputFormatStr_Binary, kOutputFormatType_Binary,
+ NULL );
+ require_noerr_quiet( err, exit );
- nullLink = &inPacketPtr[ 0 ];
- require_action_quiet( ( end - nullLink ) >= kHeaderSizeNullLink, exit, err = kUnderrunErr );
- addressFamily = ReadHost32( &nullLink[ 0 ] );
+ err = GAITesterCreate( dispatch_get_main_queue(), (int) context->callDelayMs, (int) context->serverDelayMs,
+ kGAIPerfStandardTTL, context->skipPathEval, context->badUDPMode, &context->tester );
+ require_noerr( err, exit );
- ip = &nullLink[ kHeaderSizeNullLink ];
- if( addressFamily == AF_INET )
+ check( gGAIPerf_TestSuite );
+ if( strcasecmp( gGAIPerf_TestSuite, kGAIPerfTestSuiteName_Basic ) == 0 )
{
- require_action_quiet( ( end - ip ) >= kHeaderSizeIPv4Min, exit, err = kUnderrunErr );
- ipHeaderLen = ( ip[ 0 ] & 0x0F ) * 4;
- protocol = ip[ 9 ];
+ err = GAIPerfAddBasicTestCases( context );
+ require_noerr( err, exit );
}
- else if( addressFamily == AF_INET6 )
+ else if( strcasecmp( gGAIPerf_TestSuite, kGAIPerfTestSuiteName_Advanced ) == 0 )
{
- require_action_quiet( ( end - ip ) >= kHeaderSizeIPv6, exit, err = kUnderrunErr );
- ipHeaderLen = kHeaderSizeIPv6;
- protocol = ip[ 6 ];
+ err = GAIPerfAddAdvancedTestCases( context );
+ require_noerr( err, exit );
}
else
{
- err = kTypeErr;
+ FPrintF( stderr, "error: Invalid test suite name: %s.\n", gGAIPerf_TestSuite );
+ err = kParamErr;
goto exit;
}
- require_action_quiet( protocol == kIPProtocolUDP, exit, err = kTypeErr );
- require_action_quiet( ( end - ip ) >= ( ipHeaderLen + kHeaderSizeUDP ), exit, err = kUnderrunErr );
- msg = &ip[ ipHeaderLen + kHeaderSizeUDP ];
+ GAITesterSetStopHandler( context->tester, GAIPerfTesterStopHandler, context );
+ GAITesterSetResultsHandler( context->tester, GAIPerfResultsHandler, context );
- *outMsgPtr = msg;
- *outMsgLen = (size_t)( end - msg );
- err = kNoErr;
+ signal( SIGINT, SIG_IGN );
+ err = DispatchSignalSourceCreate( SIGINT, GAIPerfSignalHandler, context, &context->sigIntSource );
+ require_noerr( err, exit );
+ dispatch_resume( context->sigIntSource );
+
+ signal( SIGTERM, SIG_IGN );
+ err = DispatchSignalSourceCreate( SIGTERM, GAIPerfSignalHandler, context, &context->sigTermSource );
+ require_noerr( err, exit );
+ dispatch_resume( context->sigTermSource );
+
+ GAITesterStart( context->tester );
+ dispatch_main();
exit:
- return( err );
+ if( context ) GAIPerfContextFree( context );
+ exit( 1 );
}
//===========================================================================================================================
-// GAITestCaseCreate
+// GAIPerfContextFree
+//===========================================================================================================================
+
+static void GAIPerfContextFree( GAIPerfContext *inContext )
+{
+ ForgetCF( &inContext->tester );
+ ForgetCF( &inContext->testCaseResults );
+ ForgetMem( &inContext->outputFilePath );
+ dispatch_source_forget( &inContext->sigIntSource );
+ dispatch_source_forget( &inContext->sigTermSource );
+ free( inContext );
+}
+
//===========================================================================================================================
+// GAIPerfAddAdvancedTestCases
+//===========================================================================================================================
+
+#define kTestCaseTitleBufferSize 128
+
+static void
+ _GAIPerfWriteTestCaseTitle(
+ char inBuffer[ kTestCaseTitleBufferSize ],
+ unsigned int inCNAMERecordCount,
+ unsigned int inARecordCount,
+ unsigned int inAAAARecordCount,
+ GAITestAddrType inRequested,
+ unsigned int inIterationCount,
+ Boolean inIterationsAreUnique );
+static void
+ _GAIPerfWriteLocalHostTestCaseTitle(
+ char inBuffer[ kTestCaseTitleBufferSize ],
+ GAITestAddrType inRequested,
+ unsigned int inIterationCount );
+
+#define kGAIPerfAdvancedTestSuite_MaxAliasCount 4
+#define kGAIPerfAdvancedTestSuite_MaxAddrCount 8
-static OSStatus GAITestCaseCreate( const char *inTitle, unsigned int inTimeLimitMs, GAITestCase **outSet )
+static OSStatus GAIPerfAddAdvancedTestCases( GAIPerfContext *inContext )
{
OSStatus err;
- GAITestCase * obj;
+ unsigned int aliasCount, addressCount, i;
+ GAITestCase * testCase = NULL;
+ char title[ kTestCaseTitleBufferSize ];
- obj = (GAITestCase *) calloc( 1, sizeof( *obj ) );
- require_action( obj, exit, err = kNoMemoryErr );
+ aliasCount = 0;
+ while( aliasCount <= kGAIPerfAdvancedTestSuite_MaxAliasCount )
+ {
+ for( addressCount = 1; addressCount <= kGAIPerfAdvancedTestSuite_MaxAddrCount; addressCount *= 2 )
+ {
+ // Add a test case to resolve a domain name with
+ //
+ // <aliasCount> CNAME records, <addressCount> A records, and <addressCount> AAAA records
+ //
+ // to its IPv4 and IPv6 addresses. Each iteration resolves a unique instance of such a domain name, which
+ // requires server queries.
+
+ _GAIPerfWriteTestCaseTitle( title, aliasCount, addressCount, addressCount, kGAITestAddrType_Both,
+ inContext->defaultIterCount, true );
+
+ err = GAITestCaseCreate( title, &testCase );
+ require_noerr( err, exit );
+
+ for( i = 0; i < inContext->defaultIterCount; ++i )
+ {
+ err = GAITestCaseAddItem( testCase, aliasCount, addressCount, kGAIPerfStandardTTL,
+ kGAITestAddrType_Both, kGAITestAddrType_Both, kGAIPerfGAITimeLimitMs, 1 );
+ require_noerr( err, exit );
+ }
+
+ err = GAITesterAddTestCase( inContext->tester, testCase );
+ require_noerr( err, exit );
+ testCase = NULL;
+
+ // Add a test case to resolve a domain name with
+ //
+ // <aliasCount> CNAME records, <addressCount> A records, and <addressCount> AAAA records
+ //
+ // to its IPv4 and IPv6 addresses. A preliminary iteration resolves a unique domain name, which requires a server
+ // query. The subsequent iterations resolve the same domain name as the preliminary iteration, which should
+ // ideally require no server queries, i.e., the results should come from the cache.
+
+ _GAIPerfWriteTestCaseTitle( title, aliasCount, addressCount, addressCount, kGAITestAddrType_Both,
+ inContext->defaultIterCount, false );
+
+ err = GAITestCaseCreate( title, &testCase );
+ require_noerr( err, exit );
+
+ err = GAITestCaseAddItem( testCase, aliasCount, addressCount, kGAIPerfStandardTTL,
+ kGAITestAddrType_Both, kGAITestAddrType_Both, kGAIPerfGAITimeLimitMs, inContext->defaultIterCount + 1 );
+ require_noerr( err, exit );
+
+ err = GAITesterAddTestCase( inContext->tester, testCase );
+ require_noerr( err, exit );
+ testCase = NULL;
+ }
+
+ aliasCount = ( aliasCount == 0 ) ? 1 : ( 2 * aliasCount );
+ }
- obj->title = strdup( inTitle );
- require_action( obj->title, exit, err = kNoMemoryErr );
+ // Finally, add a test case to resolve localhost to its IPv4 and IPv6 addresses.
- obj->timeLimitMs = inTimeLimitMs;
+ _GAIPerfWriteLocalHostTestCaseTitle( title, kGAITestAddrType_Both, inContext->defaultIterCount );
- *outSet = obj;
- obj = NULL;
- err = kNoErr;
+ err = GAITestCaseCreate( title, &testCase );
+ require_noerr( err, exit );
+
+ err = GAITestCaseAddLocalHostItem( testCase, kGAITestAddrType_Both, kGAIPerfGAITimeLimitMs,
+ inContext->defaultIterCount );
+ require_noerr( err, exit );
+
+ err = GAITesterAddTestCase( inContext->tester, testCase );
+ require_noerr( err, exit );
+ testCase = NULL;
exit:
- if( obj ) GAITestCaseFree( obj );
+ if( testCase ) GAITestCaseFree( testCase );
return( err );
}
//===========================================================================================================================
-// GAITestCaseFree
+// _GAIPerfWriteTestCaseTitle
//===========================================================================================================================
-static void GAITestCaseFree( GAITestCase *inCase )
+#define GAITestAddrTypeToRequestKeyValue( X ) ( \
+ ( (X) == kGAITestAddrType_Both ) ? "ipv4\\,ipv6" : \
+ ( (X) == kGAITestAddrType_IPv4 ) ? "ipv4" : \
+ ( (X) == kGAITestAddrType_IPv6 ) ? "ipv6" : \
+ "" )
+
+static void
+ _GAIPerfWriteTestCaseTitle(
+ char inBuffer[ kTestCaseTitleBufferSize ],
+ unsigned int inCNAMERecordCount,
+ unsigned int inARecordCount,
+ unsigned int inAAAARecordCount,
+ GAITestAddrType inRequested,
+ unsigned int inIterationCount,
+ Boolean inIterationsAreUnique )
{
- GAITestItem * item;
-
- while( ( item = inCase->itemList ) != NULL )
- {
- inCase->itemList = item->next;
- GAITestItemFree( item );
- }
- ForgetMem( &inCase->title );
- free( inCase );
+ SNPrintF( inBuffer, kTestCaseTitleBufferSize, "name=dynamic,cname=%u,a=%u,aaaa=%u,req=%s,iterations=%u%?s",
+ inCNAMERecordCount, inARecordCount, inAAAARecordCount, GAITestAddrTypeToRequestKeyValue( inRequested ),
+ inIterationCount, inIterationsAreUnique, ",unique" );
}
//===========================================================================================================================
-// GAITestCaseAddItem
+// _GAIPerfWriteLocalHostTestCaseTitle
//===========================================================================================================================
-// A character set of lower-case alphabet characters and digits and a string length of six allows for 36^6 = 2,176,782,336
-// possible strings to use in the Tag label.
+static void
+ _GAIPerfWriteLocalHostTestCaseTitle(
+ char inBuffer[ kTestCaseTitleBufferSize ],
+ GAITestAddrType inRequested,
+ unsigned int inIterationCount )
+{
+ SNPrintF( inBuffer, kTestCaseTitleBufferSize, "name=localhost,req=%s,iterations=%u",
+ GAITestAddrTypeToRequestKeyValue( inRequested ), inIterationCount );
+}
-#define kUniqueStringCharSet "abcdefghijklmnopqrstuvwxyz0123456789"
-#define kUniqueStringCharSetLen sizeof_string( kUniqueStringCharSet )
-#define kUniqueStringLen 6
+//===========================================================================================================================
+// GAIPerfAddBasicTestCases
+//===========================================================================================================================
-static OSStatus
- GAITestCaseAddItem(
- GAITestCase * inCase,
- unsigned int inAliasCount,
- unsigned int inAddressCount,
- int inTTL,
- GAITestAddrType inHasAddrs,
- GAITestAddrType inWantAddrs,
- unsigned int inItemCount )
+#define kGAIPerfBasicTestSuite_AliasCount 2
+#define kGAIPerfBasicTestSuite_AddrCount 4
+
+static OSStatus GAIPerfAddBasicTestCases( GAIPerfContext *inContext )
{
OSStatus err;
- GAITestItem * item;
- GAITestItem * item2;
- GAITestItem * newItemList = NULL;
- GAITestItem ** itemPtr;
- char * ptr;
- char * end;
+ GAITestCase * testCase = NULL;
+ char title[ kTestCaseTitleBufferSize ];
unsigned int i;
- char name[ 64 ];
- char uniqueStr[ kUniqueStringLen + 1 ];
- require_action_quiet( inItemCount > 0, exit, err = kNoErr );
+ // Test Case #1:
+ // Resolve a domain name with
+ //
+ // 2 CNAME records, 4 A records, and 4 AAAA records
+ //
+ // to its IPv4 and IPv6 addresses. Each of the iterations resolves a unique domain name, which requires server
+ // queries.
- // Limit address count to 64 because we use 64-bit bitmaps for keeping track of addresses.
+ _GAIPerfWriteTestCaseTitle( title, kGAIPerfBasicTestSuite_AliasCount,
+ kGAIPerfBasicTestSuite_AddrCount, kGAIPerfBasicTestSuite_AddrCount, kGAITestAddrType_Both,
+ inContext->defaultIterCount, true );
- require_action_quiet( ( inAddressCount >= 1 ) && ( inAddressCount <= 64 ), exit, err = kCountErr );
- require_action_quiet( ( inAliasCount >= 0 ) && ( inAliasCount <= INT32_MAX ), exit, err = kCountErr );
- require_action_quiet( GAITestAddrTypeIsValid( inHasAddrs ), exit, err = kValueErr );
+ err = GAITestCaseCreate( title, &testCase );
+ require_noerr( err, exit );
- ptr = &name[ 0 ];
- end = &name[ countof( name ) ];
+ for( i = 0; i < inContext->defaultIterCount; ++i )
+ {
+ err = GAITestCaseAddItem( testCase, kGAIPerfBasicTestSuite_AliasCount, kGAIPerfBasicTestSuite_AddrCount,
+ kGAIPerfStandardTTL, kGAITestAddrType_Both, kGAITestAddrType_Both, kGAIPerfGAITimeLimitMs, 1 );
+ require_noerr( err, exit );
+ }
- // Add Alias label.
+ err = GAITesterAddTestCase( inContext->tester, testCase );
+ require_noerr( err, exit );
+ testCase = NULL;
- if( inAliasCount == 1 ) SNPrintF_Add( &ptr, end, "alias." );
- else if( inAliasCount >= 2 ) SNPrintF_Add( &ptr, end, "alias-%u.", inAliasCount );
+ // Test Case #2:
+ // Resolve a domain name with
+ //
+ // 2 CNAME records, 4 A records, and 4 AAAA records
+ //
+ // to its IPv4 and IPv6 addresses. A preliminary iteration resolves a unique instance of such a domain name, which
+ // requires server queries. Each of the subsequent iterations resolves the same domain name as the preliminary
+ // iteration, which should ideally require no additional server queries, i.e., the results should come from the cache.
- // Add Count label.
+ _GAIPerfWriteTestCaseTitle( title, kGAIPerfBasicTestSuite_AliasCount,
+ kGAIPerfBasicTestSuite_AddrCount, kGAIPerfBasicTestSuite_AddrCount, kGAITestAddrType_Both,
+ inContext->defaultIterCount, false );
- SNPrintF_Add( &ptr, end, "count-%u.", inAddressCount );
+ err = GAITestCaseCreate( title, &testCase );
+ require_noerr( err, exit );
- // Add TTL label.
+ err = GAITestCaseAddItem( testCase, kGAIPerfBasicTestSuite_AliasCount, kGAIPerfBasicTestSuite_AddrCount,
+ kGAIPerfStandardTTL, kGAITestAddrType_Both, kGAITestAddrType_Both, kGAIPerfGAITimeLimitMs,
+ inContext->defaultIterCount + 1 );
+ require_noerr( err, exit );
- if( inTTL >= 0 ) SNPrintF_Add( &ptr, end, "ttl-%d.", inTTL );
+ err = GAITesterAddTestCase( inContext->tester, testCase );
+ require_noerr( err, exit );
+ testCase = NULL;
- // Add Tag label.
+ // Test Case #3:
+ // Each iteration resolves localhost to its IPv4 and IPv6 addresses.
- RandomString( kUniqueStringCharSet, kUniqueStringCharSetLen, kUniqueStringLen, kUniqueStringLen, uniqueStr );
- SNPrintF_Add( &ptr, end, "tag-%s.", uniqueStr );
+ _GAIPerfWriteLocalHostTestCaseTitle( title, kGAITestAddrType_Both, inContext->defaultIterCount );
- // Add IPv4 or IPv6 label if necessary.
+ err = GAITestCaseCreate( title, &testCase );
+ require_noerr( err, exit );
- switch( inHasAddrs )
- {
- case kGAITestAddrType_IPv4:
- SNPrintF_Add( &ptr, end, "ipv4." );
- break;
-
- case kGAITestAddrType_IPv6:
- SNPrintF_Add( &ptr, end, "ipv6." );
- break;
- }
+ err = GAITestCaseAddLocalHostItem( testCase, kGAITestAddrType_Both, kGAIPerfGAITimeLimitMs,
+ inContext->defaultIterCount );
+ require_noerr( err, exit );
- // Add d.test. labels.
+ err = GAITesterAddTestCase( inContext->tester, testCase );
+ require_noerr( err, exit );
+ testCase = NULL;
- SNPrintF_Add( &ptr, end, "d.test." );
+exit:
+ if( testCase ) GAITestCaseFree( testCase );
+ return( err );
+}
+
+//===========================================================================================================================
+// GAIPerfTesterStopHandler
+//===========================================================================================================================
+
+#define kGAIPerfResultsKey_Info CFSTR( "info" )
+#define kGAIPerfResultsKey_TestCases CFSTR( "testCases" )
+#define kGAIPerfResultsKey_Success CFSTR( "success" )
+
+#define kGAIPerfInfoKey_CallDelay CFSTR( "callDelayMs" )
+#define kGAIPerfInfoKey_ServerDelay CFSTR( "serverDelayMs" )
+#define kGAIPerfInfoKey_SkippedPathEval CFSTR( "skippedPathEval" )
+#define kGAIPerfInfoKey_UsedBadUDPMode CFSTR( "usedBadUPDMode" )
+
+static void GAIPerfTesterStopHandler( void *inContext, OSStatus inError )
+{
+ OSStatus err;
+ GAIPerfContext * const context = (GAIPerfContext *) inContext;
+ CFPropertyListRef plist;
+ int exitCode;
- // Create item.
+ err = inError;
+ require_noerr_quiet( err, exit );
- err = GAITestItemCreate( name, inAddressCount, inHasAddrs, inWantAddrs, &item );
+ err = CFPropertyListCreateFormatted( kCFAllocatorDefault, &plist,
+ "{"
+ "%kO=" // info
+ "{"
+ "%kO=%lli" // callDelayMs
+ "%kO=%lli" // serverDelayMs
+ "%kO=%b" // skippedPathEval
+ "%kO=%b" // usedBadUPDMode
+ "}"
+ "%kO=%O" // testCases
+ "%kO=%b" // success
+ "}",
+ kGAIPerfResultsKey_Info,
+ kGAIPerfInfoKey_CallDelay, (int64_t) context->callDelayMs,
+ kGAIPerfInfoKey_ServerDelay, (int64_t) context->serverDelayMs,
+ kGAIPerfInfoKey_SkippedPathEval, context->skipPathEval,
+ kGAIPerfInfoKey_UsedBadUDPMode, context->badUDPMode,
+ kGAIPerfResultsKey_TestCases, context->testCaseResults,
+ kGAIPerfResultsKey_Success, !context->testFailed );
require_noerr( err, exit );
- newItemList = item;
- itemPtr = &item->next;
-
- // Create repeat items.
-
- for( i = 1; i < inItemCount; ++i )
- {
- err = GAITestItemDuplicate( item, &item2 );
- require_noerr( err, exit );
-
- *itemPtr = item2;
- itemPtr = &item2->next;
- }
-
- // Append to test case's item list.
-
- for( itemPtr = &inCase->itemList; *itemPtr; itemPtr = &( *itemPtr )->next ) {}
- *itemPtr = newItemList;
- newItemList = NULL;
-
-exit:
- while( ( item = newItemList ) != NULL )
- {
- newItemList = item->next;
- GAITestItemFree( item );
- }
- return( err );
-}
-
-//===========================================================================================================================
-// GAITestCaseAddLocalHostItem
-//===========================================================================================================================
-
-static OSStatus GAITestCaseAddLocalHostItem( GAITestCase *inCase, GAITestAddrType inWantAddrs, unsigned int inItemCount )
-{
- OSStatus err;
- GAITestItem * item;
- GAITestItem * item2;
- GAITestItem * newItemList = NULL;
- GAITestItem ** itemPtr;
- unsigned int i;
-
- require_action_quiet( inItemCount > 1, exit, err = kNoErr );
-
- err = GAITestItemCreate( "localhost.", 1, kGAITestAddrType_Both, inWantAddrs, &item );
+ err = OutputPropertyList( plist, context->outputFormat, context->appendNewline, context->outputFilePath );
+ CFRelease( plist );
require_noerr( err, exit );
- newItemList = item;
- itemPtr = &item->next;
-
- // Create repeat items.
-
- for( i = 1; i < inItemCount; ++i )
- {
- err = GAITestItemDuplicate( item, &item2 );
- require_noerr( err, exit );
-
- *itemPtr = item2;
- itemPtr = &item2->next;
- }
-
- for( itemPtr = &inCase->itemList; *itemPtr; itemPtr = &( *itemPtr )->next ) {}
- *itemPtr = newItemList;
- newItemList = NULL;
-
exit:
- while( ( item = newItemList ) != NULL )
- {
- newItemList = item->next;
- GAITestItemFree( item );
- }
- return( err );
+ exitCode = err ? 1 : ( context->testFailed ? 2 : 0 );
+ GAIPerfContextFree( context );
+ exit( exitCode );
}
//===========================================================================================================================
-// GAITestItemCreate
+// GAIPerfResultsHandler
//===========================================================================================================================
-static OSStatus
- GAITestItemCreate(
- const char * inName,
- unsigned int inAddressCount,
- GAITestAddrType inHasAddrs,
- GAITestAddrType inWantAddrs,
- GAITestItem ** outItem )
-{
- OSStatus err;
- GAITestItem * obj = NULL;
-
- require_action_quiet( inAddressCount >= 1, exit, err = kCountErr );
- require_action_quiet( GAITestAddrTypeIsValid( inHasAddrs ), exit, err = kValueErr );
- require_action_quiet( GAITestAddrTypeIsValid( inWantAddrs ), exit, err = kValueErr );
-
- obj = (GAITestItem *) calloc( 1, sizeof( *obj ) );
- require_action( obj, exit, err = kNoMemoryErr );
-
- obj->name = strdup( inName );
- require_action( obj->name, exit, err = kNoMemoryErr );
-
- obj->addressCount = inAddressCount;
- obj->hasV4 = ( inHasAddrs & kGAITestAddrType_IPv4 ) ? true : false;
- obj->hasV6 = ( inHasAddrs & kGAITestAddrType_IPv6 ) ? true : false;
- obj->wantV4 = ( inWantAddrs & kGAITestAddrType_IPv4 ) ? true : false;
- obj->wantV6 = ( inWantAddrs & kGAITestAddrType_IPv6 ) ? true : false;
-
- *outItem = obj;
- obj = NULL;
- err = kNoErr;
-
-exit:
- if( obj ) GAITestItemFree( obj );
- return( err );
-}
-
-//===========================================================================================================================
-// GAITestItemDuplicate
-//===========================================================================================================================
+// Keys for test case dictionary
-static OSStatus GAITestItemDuplicate( const GAITestItem *inItem, GAITestItem **outItem )
-{
- OSStatus err;
- GAITestItem * obj;
-
- obj = (GAITestItem *) calloc( 1, sizeof( *obj ) );
- require_action( obj, exit, err = kNoMemoryErr );
-
- *obj = *inItem;
- obj->next = NULL;
- if( inItem->name )
- {
- obj->name = strdup( inItem->name );
- require_action( obj->name, exit, err = kNoMemoryErr );
- }
-
- *outItem = obj;
- obj = NULL;
- err = kNoErr;
-
-exit:
- if( obj ) GAITestItemFree( obj );
- return( err );
-}
+#define kGAIPerfTestCaseKey_Title CFSTR( "title" )
+#define kGAIPerfTestCaseKey_StartTime CFSTR( "startTime" )
+#define kGAIPerfTestCaseKey_EndTime CFSTR( "endTime" )
+#define kGAIPerfTestCaseKey_Results CFSTR( "results" )
+#define kGAIPerfTestCaseKey_FirstStats CFSTR( "firstStats" )
+#define kGAIPerfTestCaseKey_ConnectionStats CFSTR( "connectionStats" )
+#define kGAIPerfTestCaseKey_Stats CFSTR( "stats" )
-//===========================================================================================================================
-// GAITestItemFree
-//===========================================================================================================================
+// Keys for test case results array entry dictionaries
-static void GAITestItemFree( GAITestItem *inItem )
-{
- ForgetMem( &inItem->name );
- free( inItem );
-}
+#define kGAIPerfTestCaseResultKey_Name CFSTR( "name" )
+#define kGAIPerfTestCaseResultKey_ConnectionTime CFSTR( "connectionTimeUs" )
+#define kGAIPerfTestCaseResultKey_FirstTime CFSTR( "firstTimeUs" )
+#define kGAIPerfTestCaseResultKey_Time CFSTR( "timeUs" )
-//===========================================================================================================================
-// SSDPDiscoverCmd
-//===========================================================================================================================
+// Keys for test case stats dictionaries
-#define kSSDPPort 1900
+#define kGAIPerfTestCaseStatsKey_Count CFSTR( "count" )
+#define kGAIPerfTestCaseStatsKey_Min CFSTR( "min" )
+#define kGAIPerfTestCaseStatsKey_Max CFSTR( "max" )
+#define kGAIPerfTestCaseStatsKey_Mean CFSTR( "mean" )
+#define kGAIPerfTestCaseStatsKey_StdDev CFSTR( "sd" )
typedef struct
{
- HTTPHeader header; // HTTP header object for sending and receiving.
- dispatch_source_t readSourceV4; // Read dispatch source for IPv4 socket.
- dispatch_source_t readSourceV6; // Read dispatch source for IPv6 socket.
- int receiveSecs; // After send, the amount of time to spend receiving.
- uint32_t ifindex; // Index of the interface over which to send the query.
- Boolean useIPv4; // True if the query should be sent via IPv4 multicast.
- Boolean useIPv6; // True if the query should be sent via IPv6 multicast.
+ double min;
+ double max;
+ double mean;
+ double stdDev;
-} SSDPDiscoverContext;
+} GAIPerfStats;
-static void SSDPDiscoverPrintPrologue( const SSDPDiscoverContext *inContext );
-static void SSDPDiscoverReadHandler( void *inContext );
-static int SocketToPortNumber( SocketRef inSock );
-static OSStatus WriteSSDPSearchRequest( HTTPHeader *inHeader, const void *inHostSA, int inMX, const char *inST );
+#define GAIPerfStatsInit( X ) \
+ do { (X)->min = DBL_MAX; (X)->max = DBL_MIN; (X)->mean = 0.0; (X)->stdDev = 0.0; } while( 0 )
-static void SSDPDiscoverCmd( void )
+static void
+ GAIPerfResultsHandler(
+ const char * inCaseTitle,
+ NanoTime64 inCaseStartTime,
+ NanoTime64 inCaseEndTime,
+ const GAITestItemResult * inResultArray,
+ size_t inResultCount,
+ void * inContext )
{
- OSStatus err;
- struct timeval now;
- SSDPDiscoverContext * context;
- dispatch_source_t signalSource = NULL;
- SocketRef sockV4 = kInvalidSocketRef;
- SocketRef sockV6 = kInvalidSocketRef;
- ssize_t n;
- int sendCount;
-
- // Set up SIGINT handler.
-
- signal( SIGINT, SIG_IGN );
- err = DispatchSignalSourceCreate( SIGINT, Exit, kExitReason_SIGINT, &signalSource );
- require_noerr( err, exit );
- dispatch_resume( signalSource );
+ OSStatus err;
+ GAIPerfContext * const context = (GAIPerfContext *) inContext;
+ int namesAreDynamic, namesAreUnique;
+ const char * ptr;
+ size_t count, startIndex;
+ CFMutableArrayRef results = NULL;
+ GAIPerfStats stats, firstStats, connStats;
+ double sum, firstSum, connSum;
+ size_t keyValueLen, i;
+ char keyValue[ 16 ]; // Size must be at least strlen( "name=dynamic" ) + 1 bytes.
+ char startTimeStr[ 32 ];
+ char endTimeStr[ 32 ];
+ const GAITestItemResult * result;
- // Check command parameters.
+ // If this test case resolves the same "d.test." name in each iteration (title contains the "name=dynamic" key-value
+ // pair, but not the "unique" key), then don't count the first iteration, whose purpose is to populate the cache with
+ // the domain name's CNAME, A, and AAAA records.
- if( gSSDPDiscover_ReceiveSecs < -1 )
+ namesAreDynamic = false;
+ namesAreUnique = false;
+ ptr = inCaseTitle;
+ while( ParseQuotedEscapedString( ptr, NULL, ",", keyValue, sizeof( keyValue ), &keyValueLen, NULL, &ptr ) )
{
- FPrintF( stdout, "Invalid receive time: %d seconds.\n", gSSDPDiscover_ReceiveSecs );
- err = kParamErr;
- goto exit;
+ if( strnicmpx( keyValue, keyValueLen, "name=dynamic" ) == 0 )
+ {
+ namesAreDynamic = true;
+ }
+ else if( strnicmpx( keyValue, keyValueLen, "unique" ) == 0 )
+ {
+ namesAreUnique = true;
+ }
+ if( namesAreDynamic && namesAreUnique ) break;
}
- // Create context.
-
- context = (SSDPDiscoverContext *) calloc( 1, sizeof( *context ) );
- require_action( context, exit, err = kNoMemoryErr );
-
- context->receiveSecs = gSSDPDiscover_ReceiveSecs;
- context->useIPv4 = ( gSSDPDiscover_UseIPv4 || !gSSDPDiscover_UseIPv6 ) ? true : false;
- context->useIPv6 = ( gSSDPDiscover_UseIPv6 || !gSSDPDiscover_UseIPv4 ) ? true : false;
-
- err = InterfaceIndexFromArgString( gInterface, &context->ifindex );
- require_noerr_quiet( err, exit );
+ startIndex = ( ( inResultCount > 0 ) && namesAreDynamic && !namesAreUnique ) ? 1 : 0;
+ results = CFArrayCreateMutable( NULL, (CFIndex)( inResultCount - startIndex ), &kCFTypeArrayCallBacks );
+ require_action( results, exit, err = kNoMemoryErr );
- // Set up IPv4 socket.
+ GAIPerfStatsInit( &stats );
+ GAIPerfStatsInit( &firstStats );
+ GAIPerfStatsInit( &connStats );
- if( context->useIPv4 )
+ sum = 0.0;
+ firstSum = 0.0;
+ connSum = 0.0;
+ count = 0;
+ for( i = startIndex; i < inResultCount; ++i )
{
- int port;
- err = UDPClientSocketOpen( AF_INET, NULL, 0, -1, &port, &sockV4 );
- require_noerr( err, exit );
+ double value;
- err = SocketSetMulticastInterface( sockV4, NULL, context->ifindex );
- require_noerr( err, exit );
+ result = &inResultArray[ i ];
- err = setsockopt( sockV4, IPPROTO_IP, IP_MULTICAST_LOOP, (char *) &(uint8_t){ 1 }, (socklen_t) sizeof( uint8_t ) );
- err = map_socket_noerr_errno( sockV4, err );
+ err = CFPropertyListAppendFormatted( kCFAllocatorDefault, results,
+ "{"
+ "%kO=%s" // name
+ "%kO=%lli" // connectionTimeUs
+ "%kO=%lli" // firstTimeUs
+ "%kO=%lli" // timeUs
+ "%kO=%lli" // error
+ "}",
+ kGAIPerfTestCaseResultKey_Name, result->name,
+ kGAIPerfTestCaseResultKey_ConnectionTime, (int64_t) result->connectionTimeUs,
+ kGAIPerfTestCaseResultKey_FirstTime, (int64_t) result->firstTimeUs,
+ kGAIPerfTestCaseResultKey_Time, (int64_t) result->timeUs,
+ CFSTR( "error" ), (int64_t) result->error );
+ require_noerr( err, exit );
+
+ if( !result->error )
+ {
+ value = (double) result->timeUs;
+ if( value < stats.min ) stats.min = value;
+ if( value > stats.max ) stats.max = value;
+ sum += value;
+
+ value = (double) result->firstTimeUs;
+ if( value < firstStats.min ) firstStats.min = value;
+ if( value > firstStats.max ) firstStats.max = value;
+ firstSum += value;
+
+ value = (double) result->connectionTimeUs;
+ if( value < connStats.min ) connStats.min = value;
+ if( value > connStats.max ) connStats.max = value;
+ connSum += value;
+
+ ++count;
+ }
+ else
+ {
+ context->testFailed = true;
+ }
+ }
+
+ if( count > 0 )
+ {
+ stats.mean = sum / count;
+ firstStats.mean = firstSum / count;
+ connStats.mean = connSum / count;
+
+ sum = 0.0;
+ firstSum = 0.0;
+ connSum = 0.0;
+ for( i = startIndex; i < inResultCount; ++i )
+ {
+ double diff;
+
+ result = &inResultArray[ i ];
+ if( result->error ) continue;
+
+ diff = stats.mean - (double) result->timeUs;
+ sum += ( diff * diff );
+
+ diff = firstStats.mean - (double) result->firstTimeUs;
+ firstSum += ( diff * diff );
+
+ diff = connStats.mean - (double) result->connectionTimeUs;
+ connSum += ( diff * diff );
+ }
+ stats.stdDev = sqrt( sum / count );
+ firstStats.stdDev = sqrt( firstSum / count );
+ connStats.stdDev = sqrt( connSum / count );
+ }
+
+ err = CFPropertyListAppendFormatted( kCFAllocatorDefault, context->testCaseResults,
+ "{"
+ "%kO=%s"
+ "%kO=%s"
+ "%kO=%s"
+ "%kO=%O"
+ "%kO="
+ "{"
+ "%kO=%lli"
+ "%kO=%f"
+ "%kO=%f"
+ "%kO=%f"
+ "%kO=%f"
+ "}"
+ "%kO="
+ "{"
+ "%kO=%lli"
+ "%kO=%f"
+ "%kO=%f"
+ "%kO=%f"
+ "%kO=%f"
+ "}"
+ "%kO="
+ "{"
+ "%kO=%lli"
+ "%kO=%f"
+ "%kO=%f"
+ "%kO=%f"
+ "%kO=%f"
+ "}"
+ "}",
+ kGAIPerfTestCaseKey_Title, inCaseTitle,
+ kGAIPerfTestCaseKey_StartTime, _NanoTime64ToDateString( inCaseStartTime, startTimeStr, sizeof( startTimeStr ) ),
+ kGAIPerfTestCaseKey_EndTime, _NanoTime64ToDateString( inCaseEndTime, endTimeStr, sizeof( endTimeStr ) ),
+ kGAIPerfTestCaseKey_Results, results,
+ kGAIPerfTestCaseKey_Stats,
+ kGAIPerfTestCaseStatsKey_Count, (int64_t) count,
+ kGAIPerfTestCaseStatsKey_Min, stats.min,
+ kGAIPerfTestCaseStatsKey_Max, stats.max,
+ kGAIPerfTestCaseStatsKey_Mean, stats.mean,
+ kGAIPerfTestCaseStatsKey_StdDev, stats.stdDev,
+ kGAIPerfTestCaseKey_FirstStats,
+ kGAIPerfTestCaseStatsKey_Count, (int64_t) count,
+ kGAIPerfTestCaseStatsKey_Min, firstStats.min,
+ kGAIPerfTestCaseStatsKey_Max, firstStats.max,
+ kGAIPerfTestCaseStatsKey_Mean, firstStats.mean,
+ kGAIPerfTestCaseStatsKey_StdDev, firstStats.stdDev,
+ kGAIPerfTestCaseKey_ConnectionStats,
+ kGAIPerfTestCaseStatsKey_Count, (int64_t) count,
+ kGAIPerfTestCaseStatsKey_Min, connStats.min,
+ kGAIPerfTestCaseStatsKey_Max, connStats.max,
+ kGAIPerfTestCaseStatsKey_Mean, connStats.mean,
+ kGAIPerfTestCaseStatsKey_StdDev, connStats.stdDev );
+ require_noerr( err, exit );
+
+exit:
+ CFReleaseNullSafe( results );
+ if( err ) exit( 1 );
+}
+
+//===========================================================================================================================
+// GAIPerfSignalHandler
+//===========================================================================================================================
+
+static void GAIPerfSignalHandler( void *inContext )
+{
+ GAIPerfContext * const context = (GAIPerfContext *) inContext;
+
+ if( !context->tester ) exit( 1 );
+ GAITesterStop( context->tester );
+ context->tester = NULL;
+}
+
+//===========================================================================================================================
+// GAITesterCreate
+//===========================================================================================================================
+
+// A character set of lower-case alphabet characters and digits and a string length of six allows for 36^6 = 2,176,782,336
+// possible strings to use in the Tag label.
+
+#define kGAITesterTagStringLen 6
+
+typedef struct GAITestItem GAITestItem;
+struct GAITestItem
+{
+ GAITestItem * next; // Next test item in list.
+ char * name; // Domain name to resolve.
+ uint64_t connectionTimeUs; // Time in microseconds that it took to create a DNS-SD connection.
+ uint64_t firstTimeUs; // Time in microseconds that it took to get the first address result.
+ uint64_t timeUs; // Time in microseconds that it took to get all expected address results.
+ unsigned int addressCount; // Address count of the domain name, i.e., the Count label argument.
+ OSStatus error; // Current status/error.
+ unsigned int timeLimitMs; // Time limit in milliseconds for the test item's completion.
+ Boolean hasV4; // True if the domain name has one or more IPv4 addresses.
+ Boolean hasV6; // True if the domain name has one or more IPv6 addresses.
+ Boolean wantV4; // True if DNSServiceGetAddrInfo() should be called to get IPv4 addresses.
+ Boolean wantV6; // True if DNSServiceGetAddrInfo() should be called to get IPv6 addresses.
+};
+
+struct GAITestCase
+{
+ GAITestCase * next; // Next test case in list.
+ GAITestItem * itemList; // List of test items.
+ char * title; // Title of the test case.
+};
+
+struct GAITesterPrivate
+{
+ CFRuntimeBase base; // CF object base.
+ dispatch_queue_t queue; // Serial work queue.
+ DNSServiceRef connection; // Reference to the shared DNS-SD connection.
+ DNSServiceRef getAddrInfo; // Reference to the current DNSServiceGetAddrInfo operation.
+ GAITestCase * caseList; // List of test cases.
+ GAITestCase * currentCase; // Pointer to the current test case.
+ GAITestItem * currentItem; // Pointer to the current test item.
+ NanoTime64 caseStartTime; // Start time of current test case in Unix time as nanoseconds.
+ NanoTime64 caseEndTime; // End time of current test case in Unix time as nanoseconds.
+ int callDelayMs; // Amount of time to wait before calling DNSServiceGetAddrInfo().
+ Boolean skipPathEval; // True if DNSServiceGetAddrInfo() path evaluation is to be skipped.
+ Boolean stopped; // True if the tester has been stopped.
+ Boolean badUDPMode; // True if the test DNS server is to run in Bad UDP mode.
+ dispatch_source_t timer; // Timer for enforcing a test item's time limit.
+ pcap_t * pcap; // Captures traffic between mDNSResponder and test DNS server.
+ pid_t serverPID; // PID of the test DNS server.
+ int serverDelayMs; // Additional time to have the server delay its responses by.
+ int serverDefaultTTL; // Default TTL for the server's records.
+ GAITesterStopHandler_f stopHandler; // User's stop handler.
+ void * stopContext; // User's event handler context.
+ GAITesterResultsHandler_f resultsHandler; // User's results handler.
+ void * resultsContext; // User's results handler context.
+
+ // Variables for current test item.
+
+ uint64_t bitmapV4; // Bitmap of IPv4 results that have yet to be received.
+ uint64_t bitmapV6; // Bitmap of IPv6 results that have yet to be received.
+ uint64_t startTicks; // Start ticks of DNSServiceGetAddrInfo().
+ uint64_t connTicks; // Ticks when the connection was created.
+ uint64_t firstTicks; // Ticks when the first DNSServiceGetAddrInfo result was received.
+ uint64_t endTicks; // Ticks when the last DNSServiceGetAddrInfo result was received.
+ Boolean gotFirstResult; // True if the first result has been received.
+};
+
+CF_CLASS_DEFINE( GAITester );
+
+static void _GAITesterStartNextTest( GAITesterRef inTester );
+static OSStatus _GAITesterCreatePacketCapture( pcap_t **outPCap );
+static void _GAITesterFirstGAITimeout( void *inContext );
+static void _GAITesterTimeout( void *inContext );
+static void DNSSD_API
+ _GAITesterFirstGAICallback(
+ DNSServiceRef inSDRef,
+ DNSServiceFlags inFlags,
+ uint32_t inInterfaceIndex,
+ DNSServiceErrorType inError,
+ const char * inHostname,
+ const struct sockaddr * inSockAddr,
+ uint32_t inTTL,
+ void * inContext );
+static void DNSSD_API
+ _GAITesterGetAddrInfoCallback(
+ DNSServiceRef inSDRef,
+ DNSServiceFlags inFlags,
+ uint32_t inInterfaceIndex,
+ DNSServiceErrorType inError,
+ const char * inHostname,
+ const struct sockaddr * inSockAddr,
+ uint32_t inTTL,
+ void * inContext );
+static void _GAITesterCompleteCurrentTest( GAITesterRef inTester, OSStatus inError );
+
+#define ForgetPacketCapture( X ) ForgetCustom( X, pcap_close )
+
+static OSStatus
+ GAITestItemCreate(
+ const char * inName,
+ unsigned int inAddressCount,
+ GAITestAddrType inHasAddrs,
+ GAITestAddrType inWantAddrs,
+ unsigned int inTimeLimitMs,
+ GAITestItem ** outItem );
+static OSStatus GAITestItemDup( const GAITestItem *inItem, GAITestItem **outItem );
+static void GAITestItemFree( GAITestItem *inItem );
+
+static OSStatus
+ GAITesterCreate(
+ dispatch_queue_t inQueue,
+ int inCallDelayMs,
+ int inServerDelayMs,
+ int inServerDefaultTTL,
+ Boolean inSkipPathEvaluation,
+ Boolean inBadUDPMode,
+ GAITesterRef * outTester )
+{
+ OSStatus err;
+ GAITesterRef obj = NULL;
+
+ CF_OBJECT_CREATE( GAITester, obj, err, exit );
+
+ ReplaceDispatchQueue( &obj->queue, inQueue );
+ obj->callDelayMs = inCallDelayMs;
+ obj->serverPID = -1;
+ obj->serverDelayMs = inServerDelayMs;
+ obj->serverDefaultTTL = inServerDefaultTTL;
+ obj->skipPathEval = inSkipPathEvaluation;
+ obj->badUDPMode = inBadUDPMode;
+
+ *outTester = obj;
+ obj = NULL;
+ err = kNoErr;
+
+exit:
+ CFReleaseNullSafe( obj );
+ return( err );
+}
+
+//===========================================================================================================================
+// _GAITesterFinalize
+//===========================================================================================================================
+
+static void _GAITesterFinalize( CFTypeRef inObj )
+{
+ GAITesterRef const me = (GAITesterRef) inObj;
+ GAITestCase * testCase;
+
+ check( !me->getAddrInfo );
+ check( !me->connection );
+ check( !me->timer );
+ dispatch_forget( &me->queue );
+ while( ( testCase = me->caseList ) != NULL )
+ {
+ me->caseList = testCase->next;
+ GAITestCaseFree( testCase );
+ }
+}
+
+//===========================================================================================================================
+// GAITesterStart
+//===========================================================================================================================
+
+static void _GAITesterStart( void *inContext );
+static void _GAITesterStop( GAITesterRef me, OSStatus inError );
+
+static void GAITesterStart( GAITesterRef me )
+{
+ CFRetain( me );
+ dispatch_async_f( me->queue, me, _GAITesterStart );
+}
+
+#define kGAITesterFirstGAITimeoutSecs 4
+
+static void _GAITesterStart( void *inContext )
+{
+ OSStatus err;
+ GAITesterRef const me = (GAITesterRef) inContext;
+ DNSServiceFlags flags;
+ char name[ 64 ];
+ char tag[ kGAITesterTagStringLen + 1 ];
+
+ err = SpawnCommand( &me->serverPID, "dnssdutil server --loopback --follow %lld%?s%?d%?s%?d%?s",
+ (int64_t) getpid(),
+ me->serverDefaultTTL >= 0, " --defaultTTL ",
+ me->serverDefaultTTL >= 0, me->serverDefaultTTL,
+ me->serverDelayMs >= 0, " --responseDelay ",
+ me->serverDelayMs >= 0, me->serverDelayMs,
+ me->badUDPMode, " --badUDPMode" );
+ require_noerr_quiet( err, exit );
+
+ SNPrintF( name, sizeof( name ), "tag-gaitester-probe-%s.ipv4.d.test",
+ _RandomStringExact( kLowerAlphaNumericCharSet, kLowerAlphaNumericCharSetSize, sizeof( tag ) - 1, tag ) );
+
+ flags = 0;
+ if( me->skipPathEval ) flags |= kDNSServiceFlagsPathEvaluationDone;
+
+ err = DNSServiceGetAddrInfo( &me->getAddrInfo, flags, kDNSServiceInterfaceIndexAny, kDNSServiceProtocol_IPv4, name,
+ _GAITesterFirstGAICallback, me );
+ require_noerr( err, exit );
+
+ err = DNSServiceSetDispatchQueue( me->getAddrInfo, me->queue );
+ require_noerr( err, exit );
+
+ err = DispatchTimerOneShotCreate( dispatch_time_seconds( kGAITesterFirstGAITimeoutSecs ),
+ UINT64_C_safe( kGAITesterFirstGAITimeoutSecs ) * kNanosecondsPerSecond / 10, me->queue,
+ _GAITesterFirstGAITimeout, me, &me->timer );
+ require_noerr( err, exit );
+ dispatch_resume( me->timer );
+
+exit:
+ if( err ) _GAITesterStop( me, err );
+}
+
+//===========================================================================================================================
+// GAITesterStop
+//===========================================================================================================================
+
+static void _GAITesterUserStop( void *inContext );
+
+static void GAITesterStop( GAITesterRef me )
+{
+ CFRetain( me );
+ dispatch_async_f( me->queue, me, _GAITesterUserStop );
+}
+
+static void _GAITesterUserStop( void *inContext )
+{
+ GAITesterRef const me = (GAITesterRef) inContext;
+
+ _GAITesterStop( me, kCanceledErr );
+ CFRelease( me );
+}
+
+static void _GAITesterStop( GAITesterRef me, OSStatus inError )
+{
+ OSStatus err;
+
+ ForgetPacketCapture( &me->pcap );
+ dispatch_source_forget( &me->timer );
+ DNSServiceForget( &me->getAddrInfo );
+ DNSServiceForget( &me->connection );
+ if( me->serverPID != -1 )
+ {
+ err = kill( me->serverPID, SIGTERM );
+ err = map_global_noerr_errno( err );
+ check_noerr( err );
+ me->serverPID = -1;
+ }
+
+ if( !me->stopped )
+ {
+ me->stopped = true;
+ if( me->stopHandler ) me->stopHandler( me->stopContext, inError );
+ CFRelease( me );
+ }
+}
+
+//===========================================================================================================================
+// GAITesterAddTestCase
+//===========================================================================================================================
+
+static OSStatus GAITesterAddTestCase( GAITesterRef me, GAITestCase *inCase )
+{
+ OSStatus err;
+ GAITestCase ** ptr;
+
+ require_action_quiet( inCase->itemList, exit, err = kCountErr );
+
+ for( ptr = &me->caseList; *ptr; ptr = &( *ptr )->next ) {}
+ *ptr = inCase;
+ err = kNoErr;
+
+exit:
+ return( err );
+}
+
+//===========================================================================================================================
+// GAITesterSetStopHandler
+//===========================================================================================================================
+
+static void GAITesterSetStopHandler( GAITesterRef me, GAITesterStopHandler_f inStopHandler, void *inStopContext )
+{
+ me->stopHandler = inStopHandler;
+ me->stopContext = inStopContext;
+}
+
+//===========================================================================================================================
+// GAITesterSetResultsHandler
+//===========================================================================================================================
+
+static void GAITesterSetResultsHandler( GAITesterRef me, GAITesterResultsHandler_f inResultsHandler, void *inResultsContext )
+{
+ me->resultsHandler = inResultsHandler;
+ me->resultsContext = inResultsContext;
+}
+
+//===========================================================================================================================
+// _GAITesterStartNextTest
+//===========================================================================================================================
+
+static void _GAITesterStartNextTest( GAITesterRef me )
+{
+ OSStatus err;
+ GAITestItem * item;
+ DNSServiceFlags flags;
+ DNSServiceProtocol protocols;
+ int done = false;
+
+ if( me->currentItem ) me->currentItem = me->currentItem->next;
+
+ if( !me->currentItem )
+ {
+ if( me->currentCase )
+ {
+ // No more test items means that the current test case has completed.
+
+ me->caseEndTime = NanoTimeGetCurrent();
+
+ if( me->resultsHandler )
+ {
+ size_t resultCount, i;
+ GAITestItemResult * resultArray;
+
+ resultCount = 0;
+ for( item = me->currentCase->itemList; item; item = item->next ) ++resultCount;
+ check( resultCount > 0 );
+
+ resultArray = (GAITestItemResult *) calloc( resultCount, sizeof( *resultArray ) );
+ require_action( resultArray, exit, err = kNoMemoryErr );
+
+ item = me->currentCase->itemList;
+ for( i = 0; i < resultCount; ++i )
+ {
+ resultArray[ i ].name = item->name;
+ resultArray[ i ].connectionTimeUs = item->connectionTimeUs;
+ resultArray[ i ].firstTimeUs = item->firstTimeUs;
+ resultArray[ i ].timeUs = item->timeUs;
+ resultArray[ i ].error = item->error;
+ item = item->next;
+ }
+ me->resultsHandler( me->currentCase->title, me->caseStartTime, me->caseEndTime, resultArray, resultCount,
+ me->resultsContext );
+ ForgetMem( &resultArray );
+ }
+
+ me->currentCase = me->currentCase->next;
+ if( !me->currentCase )
+ {
+ done = true;
+ err = kNoErr;
+ goto exit;
+ }
+ }
+ else
+ {
+ me->currentCase = me->caseList;
+ }
+ require_action_quiet( me->currentCase->itemList, exit, err = kInternalErr );
+ me->currentItem = me->currentCase->itemList;
+ }
+
+ item = me->currentItem;
+ check( ( item->addressCount >= 1 ) && ( item->addressCount <= 64 ) );
+
+ if( !item->wantV4 ) me->bitmapV4 = 0;
+ else if( !item->hasV4 ) me->bitmapV4 = 1;
+ else if( item->addressCount < 64 ) me->bitmapV4 = ( UINT64_C( 1 ) << item->addressCount ) - 1;
+ else me->bitmapV4 = ~UINT64_C( 0 );
+
+ if( !item->wantV6 ) me->bitmapV6 = 0;
+ else if( !item->hasV6 ) me->bitmapV6 = 1;
+ else if( item->addressCount < 64 ) me->bitmapV6 = ( UINT64_C( 1 ) << item->addressCount ) - 1;
+ else me->bitmapV6 = ~UINT64_C( 0 );
+ check( ( me->bitmapV4 != 0 ) || ( me->bitmapV6 != 0 ) );
+ me->gotFirstResult = false;
+
+ // Perform preliminary tasks if this is the start of a new test case.
+
+ if( item == me->currentCase->itemList )
+ {
+ // Flush mDNSResponder's cache.
+
+ err = systemf( NULL, "killall -HUP mDNSResponder" );
+ require_noerr( err, exit );
+ sleep( 1 );
+
+ me->caseStartTime = NanoTimeGetCurrent();
+ me->caseEndTime = kNanoTime_Invalid;
+ }
+
+ // Start a packet capture.
+
+ check( !me->pcap );
+ err = _GAITesterCreatePacketCapture( &me->pcap );
+ require_noerr( err, exit );
+
+ // Start timer for test item's time limit.
+
+ check( !me->timer );
+ if( item->timeLimitMs > 0 )
+ {
+ unsigned int timeLimitMs;
+
+ timeLimitMs = item->timeLimitMs;
+ if( me->callDelayMs > 0 ) timeLimitMs += (unsigned int) me->callDelayMs;
+ if( me->serverDelayMs > 0 ) timeLimitMs += (unsigned int) me->serverDelayMs;
+
+ err = DispatchTimerCreate( dispatch_time_milliseconds( timeLimitMs ), DISPATCH_TIME_FOREVER,
+ ( (uint64_t) timeLimitMs ) * kNanosecondsPerMillisecond / 10,
+ me->queue, _GAITesterTimeout, NULL, me, &me->timer );
+ require_noerr( err, exit );
+ dispatch_resume( me->timer );
+ }
+
+ // Call DNSServiceGetAddrInfo().
+
+ if( me->callDelayMs > 0 ) usleep( ( (useconds_t) me->callDelayMs ) * kMicrosecondsPerMillisecond );
+
+ flags = kDNSServiceFlagsShareConnection | kDNSServiceFlagsReturnIntermediates;
+ if( me->skipPathEval ) flags |= kDNSServiceFlagsPathEvaluationDone;
+
+ protocols = 0;
+ if( item->wantV4 ) protocols |= kDNSServiceProtocol_IPv4;
+ if( item->wantV6 ) protocols |= kDNSServiceProtocol_IPv6;
+
+ me->startTicks = UpTicks();
+
+ check( !me->connection );
+ err = DNSServiceCreateConnection( &me->connection );
+ require_noerr( err, exit );
+
+ err = DNSServiceSetDispatchQueue( me->connection, me->queue );
+ require_noerr( err, exit );
+
+ me->connTicks = UpTicks();
+
+ check( !me->getAddrInfo );
+ me->getAddrInfo = me->connection;
+ err = DNSServiceGetAddrInfo( &me->getAddrInfo, flags, kDNSServiceInterfaceIndexAny, protocols, item->name,
+ _GAITesterGetAddrInfoCallback, me );
+ require_noerr( err, exit );
+
+exit:
+ if( err || done ) _GAITesterStop( me, err );
+}
+
+//===========================================================================================================================
+// _GAITesterCreatePacketCapture
+//===========================================================================================================================
+
+static OSStatus _GAITesterCreatePacketCapture( pcap_t **outPCap )
+{
+ OSStatus err;
+ pcap_t * pcap;
+ struct bpf_program program;
+ char errBuf[ PCAP_ERRBUF_SIZE ];
+
+ pcap = pcap_create( "lo0", errBuf );
+ require_action_string( pcap, exit, err = kUnknownErr, errBuf );
+
+ err = pcap_set_buffer_size( pcap, 512 * kBytesPerKiloByte );
+ require_noerr_action( err, exit, err = kUnknownErr );
+
+ err = pcap_set_snaplen( pcap, 512 );
+ require_noerr_action( err, exit, err = kUnknownErr );
+
+ err = pcap_set_immediate_mode( pcap, 0 );
+ require_noerr_action_string( err, exit, err = kUnknownErr, pcap_geterr( pcap ) );
+
+ err = pcap_activate( pcap );
+ require_noerr_action_string( err, exit, err = kUnknownErr, pcap_geterr( pcap ) );
+
+ err = pcap_setdirection( pcap, PCAP_D_INOUT );
+ require_noerr_action_string( err, exit, err = kUnknownErr, pcap_geterr( pcap ) );
+
+ err = pcap_setnonblock( pcap, 1, errBuf );
+ require_noerr_action_string( err, exit, err = kUnknownErr, errBuf );
+
+ err = pcap_compile( pcap, &program, "udp port 53", 1, PCAP_NETMASK_UNKNOWN );
+ require_noerr_action_string( err, exit, err = kUnknownErr, pcap_geterr( pcap ) );
+
+ err = pcap_setfilter( pcap, &program );
+ pcap_freecode( &program );
+ require_noerr_action_string( err, exit, err = kUnknownErr, pcap_geterr( pcap ) );
+
+ *outPCap = pcap;
+ pcap = NULL;
+
+exit:
+ if( pcap ) pcap_close( pcap );
+ return( err );
+}
+
+//===========================================================================================================================
+// _GAITesterFirstGAITimeout
+//===========================================================================================================================
+
+static void _GAITesterFirstGAITimeout( void *inContext )
+{
+ GAITesterRef const me = (GAITesterRef) inContext;
+
+ _GAITesterStop( me, kNoResourcesErr );
+}
+
+//===========================================================================================================================
+// _GAITesterTimeout
+//===========================================================================================================================
+
+static void _GAITesterTimeout( void *inContext )
+{
+ GAITesterRef const me = (GAITesterRef) inContext;
+
+ _GAITesterCompleteCurrentTest( me, kTimeoutErr );
+}
+
+//===========================================================================================================================
+// _GAITesterFirstGAICallback
+//===========================================================================================================================
+
+static void DNSSD_API
+ _GAITesterFirstGAICallback(
+ DNSServiceRef inSDRef,
+ DNSServiceFlags inFlags,
+ uint32_t inInterfaceIndex,
+ DNSServiceErrorType inError,
+ const char * inHostname,
+ const struct sockaddr * inSockAddr,
+ uint32_t inTTL,
+ void * inContext )
+{
+ GAITesterRef const me = (GAITesterRef) inContext;
+
+ Unused( inSDRef );
+ Unused( inInterfaceIndex );
+ Unused( inHostname );
+ Unused( inSockAddr );
+ Unused( inTTL );
+
+ if( ( inFlags & kDNSServiceFlagsAdd ) && !inError )
+ {
+ dispatch_source_forget( &me->timer );
+ DNSServiceForget( &me->getAddrInfo );
+
+ _GAITesterStartNextTest( me );
+ }
+}
+
+//===========================================================================================================================
+// _GAITesterGetAddrInfoCallback
+//===========================================================================================================================
+
+static void DNSSD_API
+ _GAITesterGetAddrInfoCallback(
+ DNSServiceRef inSDRef,
+ DNSServiceFlags inFlags,
+ uint32_t inInterfaceIndex,
+ DNSServiceErrorType inError,
+ const char * inHostname,
+ const struct sockaddr * inSockAddr,
+ uint32_t inTTL,
+ void * inContext )
+{
+ OSStatus err;
+ GAITesterRef const me = (GAITesterRef) inContext;
+ GAITestItem * const item = me->currentItem;
+ const sockaddr_ip * const sip = (const sockaddr_ip *) inSockAddr;
+ uint64_t nowTicks;
+ uint64_t * bitmapPtr;
+ uint64_t bitmask;
+ int hasAddr;
+
+ Unused( inSDRef );
+ Unused( inInterfaceIndex );
+ Unused( inHostname );
+ Unused( inTTL );
+
+ nowTicks = UpTicks();
+
+ require_action_quiet( inFlags & kDNSServiceFlagsAdd, exit, err = kFlagErr );
+
+ // Check if we were expecting an IP address result of this type.
+
+ if( sip->sa.sa_family == AF_INET )
+ {
+ bitmapPtr = &me->bitmapV4;
+ hasAddr = item->hasV4;
+ }
+ else if( sip->sa.sa_family == AF_INET6 )
+ {
+ bitmapPtr = &me->bitmapV6;
+ hasAddr = item->hasV6;
+ }
+ else
+ {
+ err = kTypeErr;
+ goto exit;
+ }
+
+ bitmask = 0;
+ if( hasAddr )
+ {
+ uint32_t addrOffset;
+
+ require_noerr_action_quiet( inError, exit, err = inError );
+
+ if( sip->sa.sa_family == AF_INET )
+ {
+ const uint32_t addrV4 = ntohl( sip->v4.sin_addr.s_addr );
+
+ if( strcasecmp( item->name, "localhost." ) == 0 )
+ {
+ if( addrV4 == INADDR_LOOPBACK ) bitmask = 1;
+ }
+ else
+ {
+ addrOffset = addrV4 - kDNSServerBaseAddrV4;
+ if( ( addrOffset >= 1 ) && ( addrOffset <= item->addressCount ) )
+ {
+ bitmask = UINT64_C( 1 ) << ( addrOffset - 1 );
+ }
+ }
+ }
+ else
+ {
+ const uint8_t * const addrV6 = sip->v6.sin6_addr.s6_addr;
+
+ if( strcasecmp( item->name, "localhost." ) == 0 )
+ {
+ if( memcmp( addrV6, in6addr_loopback.s6_addr, 16 ) == 0 ) bitmask = 1;
+ }
+ else if( memcmp( addrV6, kDNSServerBaseAddrV6, 15 ) == 0 )
+ {
+ addrOffset = addrV6[ 15 ];
+ if( ( addrOffset >= 1 ) && ( addrOffset <= item->addressCount ) )
+ {
+ bitmask = UINT64_C( 1 ) << ( addrOffset - 1 );
+ }
+ }
+ }
+ }
+ else
+ {
+ require_action_quiet( inError == kDNSServiceErr_NoSuchRecord, exit, err = inError ? inError : kUnexpectedErr );
+ bitmask = 1;
+ }
+ require_action_quiet( bitmask != 0, exit, err = kValueErr );
+ require_action_quiet( *bitmapPtr & bitmask, exit, err = kDuplicateErr );
+
+ *bitmapPtr &= ~bitmask;
+ if( !me->gotFirstResult )
+ {
+ me->firstTicks = nowTicks;
+ me->gotFirstResult = true;
+ }
+ err = kNoErr;
+
+exit:
+ if( err || ( ( me->bitmapV4 == 0 ) && ( me->bitmapV6 == 0 ) ) )
+ {
+ me->endTicks = nowTicks;
+ _GAITesterCompleteCurrentTest( me, err );
+ }
+}
+
+//===========================================================================================================================
+// _GAITesterCompleteCurrentTest
+//===========================================================================================================================
+
+static OSStatus
+ _GAITesterGetDNSMessageFromPacket(
+ const uint8_t * inPacketPtr,
+ size_t inPacketLen,
+ const uint8_t ** outMsgPtr,
+ size_t * outMsgLen );
+
+static void _GAITesterCompleteCurrentTest( GAITesterRef me, OSStatus inError )
+{
+ OSStatus err;
+ GAITestItem * const item = me->currentItem;
+ struct timeval timeStamps[ 4 ];
+ struct timeval * tsPtr;
+ struct timeval * tsQA = NULL;
+ struct timeval * tsQAAAA = NULL;
+ struct timeval * tsRA = NULL;
+ struct timeval * tsRAAAA = NULL;
+ struct timeval * t1;
+ struct timeval * t2;
+ int64_t idleTimeUs;
+ uint8_t name[ kDomainNameLengthMax ];
+
+ dispatch_source_forget( &me->timer );
+ DNSServiceForget( &me->getAddrInfo );
+ DNSServiceForget( &me->connection );
+
+ item->error = inError;
+ if( item->error )
+ {
+ err = kNoErr;
+ goto exit;
+ }
+
+ err = DomainNameFromString( name, item->name, NULL );
+ require_noerr( err, exit );
+
+ tsPtr = &timeStamps[ 0 ];
+ for( ;; )
+ {
+ int status;
+ struct pcap_pkthdr * pktHdr;
+ const uint8_t * packet;
+ const uint8_t * msgPtr;
+ size_t msgLen;
+ const DNSHeader * hdr;
+ unsigned int flags;
+ const uint8_t * ptr;
+ uint16_t qtype, qclass;
+ uint8_t qname[ kDomainNameLengthMax ];
+
+ status = pcap_next_ex( me->pcap, &pktHdr, &packet );
+ if( status != 1 ) break;
+ if( _GAITesterGetDNSMessageFromPacket( packet, pktHdr->caplen, &msgPtr, &msgLen ) != kNoErr ) continue;
+ if( msgLen < kDNSHeaderLength ) continue;
+
+ hdr = (const DNSHeader *) msgPtr;
+ flags = DNSHeaderGetFlags( hdr );
+ if( DNSFlagsGetOpCode( flags ) != kDNSOpCode_Query ) continue;
+ if( DNSHeaderGetQuestionCount( hdr ) < 1 ) continue;
+
+ ptr = (const uint8_t *) &hdr[ 1 ];
+ if( DNSMessageExtractQuestion( msgPtr, msgLen, ptr, qname, &qtype, &qclass, NULL ) != kNoErr ) continue;
+ if( qclass != kDNSServiceClass_IN ) continue;
+ if( !DomainNameEqual( qname, name ) ) continue;
+
+ if( item->wantV4 && ( qtype == kDNSServiceType_A ) )
+ {
+ if( flags & kDNSHeaderFlag_Response )
+ {
+ if( tsQA && !tsRA )
+ {
+ tsRA = tsPtr++;
+ *tsRA = pktHdr->ts;
+ }
+ }
+ else if( !tsQA )
+ {
+ tsQA = tsPtr++;
+ *tsQA = pktHdr->ts;
+ }
+ }
+ else if( item->wantV6 && ( qtype == kDNSServiceType_AAAA ) )
+ {
+ if( flags & kDNSHeaderFlag_Response )
+ {
+ if( tsQAAAA && !tsRAAAA )
+ {
+ tsRAAAA = tsPtr++;
+ *tsRAAAA = pktHdr->ts;
+ }
+ }
+ else if( !tsQAAAA )
+ {
+ tsQAAAA = tsPtr++;
+ *tsQAAAA = pktHdr->ts;
+ }
+ }
+ }
+
+ // t1 is the time when the last query was sent.
+
+ if( tsQA && tsQAAAA ) t1 = TIMEVAL_GT( *tsQA, *tsQAAAA ) ? tsQA : tsQAAAA;
+ else t1 = tsQA ? tsQA : tsQAAAA;
+
+ // t2 is when the first response was received.
+
+ if( tsRA && tsRAAAA ) t2 = TIMEVAL_LT( *tsRA, *tsRAAAA ) ? tsRA : tsRAAAA;
+ else t2 = tsRA ? tsRA : tsRAAAA;
+
+ if( t1 && t2 )
+ {
+ idleTimeUs = TIMEVAL_USEC64_DIFF( *t2, *t1 );
+ if( idleTimeUs < 0 ) idleTimeUs = 0;
+ }
+ else
+ {
+ idleTimeUs = 0;
+ }
+
+ item->connectionTimeUs = UpTicksToMicroseconds( me->connTicks - me->startTicks );
+ item->firstTimeUs = UpTicksToMicroseconds( me->firstTicks - me->connTicks ) - (uint64_t) idleTimeUs;
+ item->timeUs = UpTicksToMicroseconds( me->endTicks - me->connTicks ) - (uint64_t) idleTimeUs;
+
+exit:
+ ForgetPacketCapture( &me->pcap );
+ if( err ) _GAITesterStop( me, err );
+ else _GAITesterStartNextTest( me );
+}
+
+//===========================================================================================================================
+// _GAITesterGetDNSMessageFromPacket
+//===========================================================================================================================
+
+#define kHeaderSizeNullLink 4
+#define kHeaderSizeIPv4Min 20
+#define kHeaderSizeIPv6 40
+#define kHeaderSizeUDP 8
+
+#define kIPProtocolUDP 0x11
+
+static OSStatus
+ _GAITesterGetDNSMessageFromPacket(
+ const uint8_t * inPacketPtr,
+ size_t inPacketLen,
+ const uint8_t ** outMsgPtr,
+ size_t * outMsgLen )
+{
+ OSStatus err;
+ const uint8_t * nullLink;
+ uint32_t addressFamily;
+ const uint8_t * ip;
+ int ipHeaderLen;
+ int protocol;
+ const uint8_t * msg;
+ const uint8_t * const end = &inPacketPtr[ inPacketLen ];
+
+ nullLink = &inPacketPtr[ 0 ];
+ require_action_quiet( ( end - nullLink ) >= kHeaderSizeNullLink, exit, err = kUnderrunErr );
+ addressFamily = ReadHost32( &nullLink[ 0 ] );
+
+ ip = &nullLink[ kHeaderSizeNullLink ];
+ if( addressFamily == AF_INET )
+ {
+ require_action_quiet( ( end - ip ) >= kHeaderSizeIPv4Min, exit, err = kUnderrunErr );
+ ipHeaderLen = ( ip[ 0 ] & 0x0F ) * 4;
+ protocol = ip[ 9 ];
+ }
+ else if( addressFamily == AF_INET6 )
+ {
+ require_action_quiet( ( end - ip ) >= kHeaderSizeIPv6, exit, err = kUnderrunErr );
+ ipHeaderLen = kHeaderSizeIPv6;
+ protocol = ip[ 6 ];
+ }
+ else
+ {
+ err = kTypeErr;
+ goto exit;
+ }
+ require_action_quiet( protocol == kIPProtocolUDP, exit, err = kTypeErr );
+ require_action_quiet( ( end - ip ) >= ( ipHeaderLen + kHeaderSizeUDP ), exit, err = kUnderrunErr );
+
+ msg = &ip[ ipHeaderLen + kHeaderSizeUDP ];
+
+ *outMsgPtr = msg;
+ *outMsgLen = (size_t)( end - msg );
+ err = kNoErr;
+
+exit:
+ return( err );
+}
+
+//===========================================================================================================================
+// GAITestCaseCreate
+//===========================================================================================================================
+
+static OSStatus GAITestCaseCreate( const char *inTitle, GAITestCase **outCase )
+{
+ OSStatus err;
+ GAITestCase * obj;
+
+ obj = (GAITestCase *) calloc( 1, sizeof( *obj ) );
+ require_action( obj, exit, err = kNoMemoryErr );
+
+ obj->title = strdup( inTitle );
+ require_action( obj->title, exit, err = kNoMemoryErr );
+
+ *outCase = obj;
+ obj = NULL;
+ err = kNoErr;
+
+exit:
+ if( obj ) GAITestCaseFree( obj );
+ return( err );
+}
+
+//===========================================================================================================================
+// GAITestCaseFree
+//===========================================================================================================================
+
+static void GAITestCaseFree( GAITestCase *inCase )
+{
+ GAITestItem * item;
+
+ while( ( item = inCase->itemList ) != NULL )
+ {
+ inCase->itemList = item->next;
+ GAITestItemFree( item );
+ }
+ ForgetMem( &inCase->title );
+ free( inCase );
+}
+
+//===========================================================================================================================
+// GAITestCaseAddItem
+//===========================================================================================================================
+
+static OSStatus
+ GAITestCaseAddItem(
+ GAITestCase * inCase,
+ unsigned int inAliasCount,
+ unsigned int inAddressCount,
+ int inTTL,
+ GAITestAddrType inHasAddrs,
+ GAITestAddrType inWantAddrs,
+ unsigned int inTimeLimitMs,
+ unsigned int inItemCount )
+{
+ OSStatus err;
+ GAITestItem * item;
+ GAITestItem * item2;
+ GAITestItem * newItemList = NULL;
+ GAITestItem ** itemPtr;
+ char * ptr;
+ char * end;
+ unsigned int i;
+ char name[ 64 ];
+ char tag[ kGAITesterTagStringLen + 1 ];
+
+ require_action_quiet( inItemCount > 0, exit, err = kNoErr );
+
+ // Limit address count to 64 because we use 64-bit bitmaps for keeping track of addresses.
+
+ require_action_quiet( ( inAddressCount >= 1 ) && ( inAddressCount <= 64 ), exit, err = kCountErr );
+ require_action_quiet( ( inAliasCount >= 0 ) && ( inAliasCount <= INT32_MAX ), exit, err = kCountErr );
+ require_action_quiet( GAITestAddrTypeIsValid( inHasAddrs ), exit, err = kValueErr );
+
+ ptr = &name[ 0 ];
+ end = &name[ countof( name ) ];
+
+ // Add Alias label.
+
+ if( inAliasCount == 1 ) SNPrintF_Add( &ptr, end, "alias." );
+ else if( inAliasCount >= 2 ) SNPrintF_Add( &ptr, end, "alias-%u.", inAliasCount );
+
+ // Add Count label.
+
+ SNPrintF_Add( &ptr, end, "count-%u.", inAddressCount );
+
+ // Add TTL label.
+
+ if( inTTL >= 0 ) SNPrintF_Add( &ptr, end, "ttl-%d.", inTTL );
+
+ // Add Tag label.
+
+ SNPrintF_Add( &ptr, end, "tag-%s.",
+ _RandomStringExact( kLowerAlphaNumericCharSet, kLowerAlphaNumericCharSetSize, sizeof( tag ) - 1, tag ) );
+
+ // Add IPv4 or IPv6 label if necessary.
+
+ if( inHasAddrs == kGAITestAddrType_IPv4 ) SNPrintF_Add( &ptr, end, "ipv4." );
+ else if( inHasAddrs == kGAITestAddrType_IPv6 ) SNPrintF_Add( &ptr, end, "ipv6." );
+
+ // Finally, add the d.test. labels.
+
+ SNPrintF_Add( &ptr, end, "d.test." );
+
+ // Create item.
+
+ err = GAITestItemCreate( name, inAddressCount, inHasAddrs, inWantAddrs, inTimeLimitMs, &item );
+ require_noerr( err, exit );
+
+ newItemList = item;
+ itemPtr = &item->next;
+
+ // Create repeat items.
+
+ for( i = 1; i < inItemCount; ++i )
+ {
+ err = GAITestItemDup( item, &item2 );
+ require_noerr( err, exit );
+
+ *itemPtr = item2;
+ itemPtr = &item2->next;
+ }
+
+ // Append to test case's item list.
+
+ for( itemPtr = &inCase->itemList; *itemPtr; itemPtr = &( *itemPtr )->next ) {}
+ *itemPtr = newItemList;
+ newItemList = NULL;
+
+exit:
+ while( ( item = newItemList ) != NULL )
+ {
+ newItemList = item->next;
+ GAITestItemFree( item );
+ }
+ return( err );
+}
+
+//===========================================================================================================================
+// GAITestCaseAddLocalHostItem
+//===========================================================================================================================
+
+static OSStatus
+ GAITestCaseAddLocalHostItem(
+ GAITestCase * inCase,
+ GAITestAddrType inWantAddrs,
+ unsigned int inTimeLimitMs,
+ unsigned int inItemCount )
+{
+ OSStatus err;
+ GAITestItem * item;
+ GAITestItem * item2;
+ GAITestItem * newItemList = NULL;
+ GAITestItem ** itemPtr;
+ unsigned int i;
+
+ require_action_quiet( inItemCount > 1, exit, err = kNoErr );
+
+ err = GAITestItemCreate( "localhost.", 1, kGAITestAddrType_Both, inWantAddrs, inTimeLimitMs, &item );
+ require_noerr( err, exit );
+
+ newItemList = item;
+ itemPtr = &item->next;
+
+ // Create repeat items.
+
+ for( i = 1; i < inItemCount; ++i )
+ {
+ err = GAITestItemDup( item, &item2 );
+ require_noerr( err, exit );
+
+ *itemPtr = item2;
+ itemPtr = &item2->next;
+ }
+
+ for( itemPtr = &inCase->itemList; *itemPtr; itemPtr = &( *itemPtr )->next ) {}
+ *itemPtr = newItemList;
+ newItemList = NULL;
+
+exit:
+ while( ( item = newItemList ) != NULL )
+ {
+ newItemList = item->next;
+ GAITestItemFree( item );
+ }
+ return( err );
+}
+
+//===========================================================================================================================
+// GAITestItemCreate
+//===========================================================================================================================
+
+static OSStatus
+ GAITestItemCreate(
+ const char * inName,
+ unsigned int inAddressCount,
+ GAITestAddrType inHasAddrs,
+ GAITestAddrType inWantAddrs,
+ unsigned int inTimeLimitMs,
+ GAITestItem ** outItem )
+{
+ OSStatus err;
+ GAITestItem * obj = NULL;
+
+ require_action_quiet( inAddressCount >= 1, exit, err = kCountErr );
+ require_action_quiet( GAITestAddrTypeIsValid( inHasAddrs ), exit, err = kValueErr );
+ require_action_quiet( GAITestAddrTypeIsValid( inWantAddrs ), exit, err = kValueErr );
+
+ obj = (GAITestItem *) calloc( 1, sizeof( *obj ) );
+ require_action( obj, exit, err = kNoMemoryErr );
+
+ obj->name = strdup( inName );
+ require_action( obj->name, exit, err = kNoMemoryErr );
+
+ obj->addressCount = inAddressCount;
+ obj->hasV4 = ( inHasAddrs & kGAITestAddrType_IPv4 ) ? true : false;
+ obj->hasV6 = ( inHasAddrs & kGAITestAddrType_IPv6 ) ? true : false;
+ obj->wantV4 = ( inWantAddrs & kGAITestAddrType_IPv4 ) ? true : false;
+ obj->wantV6 = ( inWantAddrs & kGAITestAddrType_IPv6 ) ? true : false;
+ obj->error = kInProgressErr;
+ obj->timeLimitMs = inTimeLimitMs;
+
+ *outItem = obj;
+ obj = NULL;
+ err = kNoErr;
+
+exit:
+ if( obj ) GAITestItemFree( obj );
+ return( err );
+}
+
+//===========================================================================================================================
+// GAITestItemDup
+//===========================================================================================================================
+
+static OSStatus GAITestItemDup( const GAITestItem *inItem, GAITestItem **outItem )
+{
+ OSStatus err;
+ GAITestItem * obj;
+
+ obj = (GAITestItem *) calloc( 1, sizeof( *obj ) );
+ require_action( obj, exit, err = kNoMemoryErr );
+
+ *obj = *inItem;
+ obj->next = NULL;
+ if( inItem->name )
+ {
+ obj->name = strdup( inItem->name );
+ require_action( obj->name, exit, err = kNoMemoryErr );
+ }
+
+ *outItem = obj;
+ obj = NULL;
+ err = kNoErr;
+
+exit:
+ if( obj ) GAITestItemFree( obj );
+ return( err );
+}
+
+//===========================================================================================================================
+// GAITestItemFree
+//===========================================================================================================================
+
+static void GAITestItemFree( GAITestItem *inItem )
+{
+ ForgetMem( &inItem->name );
+ free( inItem );
+}
+
+//===========================================================================================================================
+// MDNSDiscoveryTestCmd
+//===========================================================================================================================
+
+#define kMDNSDiscoveryTestFirstQueryTimeoutSecs 4
+
+typedef struct
+{
+ DNSServiceRef query; // Reference to DNSServiceQueryRecord for replier's "about" TXT record.
+ dispatch_source_t queryTimer; // Used to time out the "about" TXT record query.
+ NanoTime64 startTime; // When the test started.
+ NanoTime64 endTime; // When the test ended.
+ pid_t replierPID; // PID of mDNS replier.
+ uint32_t ifIndex; // Index of interface to run the replier on.
+ unsigned int instanceCount; // Desired number of service instances.
+ unsigned int txtSize; // Desired size of each service instance's TXT record data.
+ unsigned int recordCountA; // Desired number of A records per replier hostname.
+ unsigned int recordCountAAAA; // Desired number of AAAA records per replier hostname.
+ unsigned int maxDropCount; // Replier's --maxDropCount option argument.
+ double ucastDropRate; // Replier's probability of dropping a unicast response.
+ double mcastDropRate; // Replier's probability of dropping a multicast query or response.
+ Boolean noAdditionals; // True if the replier is to not include additional records in responses.
+ Boolean useIPv4; // True if the replier is to use IPv4.
+ Boolean useIPv6; // True if the replier is to use IPv6.
+ Boolean flushedCache; // True if mDNSResponder's record cache was flushed before testing.
+ char * replierCommand; // Command used to run the replier.
+ char * serviceType; // Type of services to browse for.
+ ServiceBrowserRef browser; // Service browser.
+ unsigned int browseTimeSecs; // Amount of time to spend browsing in seconds.
+ const char * outputFilePath; // File to write test results to. If NULL, then write to stdout.
+ OutputFormatType outputFormat; // Format of test results output.
+ Boolean outputAppendNewline; // True if a newline character should be appended to JSON output.
+ char hostname[ 32 + 1 ]; // Base hostname that the replier is to use for instance and host names.
+ char tag[ 4 + 1 ]; // Tag that the replier is to use in its service types.
+
+} MDNSDiscoveryTestContext;
+
+static OSStatus GetAnyMDNSInterface( char inNameBuf[ IF_NAMESIZE + 1 ], uint32_t *outIndex );
+static void _MDNSDiscoveryTestFirstQueryTimeout( void *inContext );
+static void DNSSD_API
+ _MDNSDiscoveryTestAboutQueryCallback(
+ 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
+ _MDNSDiscoveryTestServiceBrowserCallback(
+ ServiceBrowserResults * inResults,
+ OSStatus inError,
+ void * inContext );
+static Boolean _MDNSDiscoveryTestTXTRecordIsValid( const uint8_t *inRecordName, const uint8_t *inTXTPtr, size_t inTXTLen );
+
+static void MDNSDiscoveryTestCmd( void )
+{
+ OSStatus err;
+ MDNSDiscoveryTestContext * context;
+ char queryName[ sizeof_field( MDNSDiscoveryTestContext, hostname ) + 15 ];
+
+ context = (MDNSDiscoveryTestContext *) calloc( 1, sizeof( *context ) );
+ require_action( context, exit, err = kNoMemoryErr );
+
+ err = CheckIntegerArgument( gMDNSDiscoveryTest_InstanceCount, "instance count", 1, UINT16_MAX );
+ require_noerr_quiet( err, exit );
+
+ err = CheckIntegerArgument( gMDNSDiscoveryTest_TXTSize, "TXT size", 1, UINT16_MAX );
+ require_noerr_quiet( err, exit );
+
+ err = CheckIntegerArgument( gMDNSDiscoveryTest_BrowseTimeSecs, "browse time (seconds)", 1, INT_MAX );
+ require_noerr_quiet( err, exit );
+
+ err = CheckIntegerArgument( gMDNSDiscoveryTest_RecordCountA, "A record count", 0, 64 );
+ require_noerr_quiet( err, exit );
+
+ err = CheckIntegerArgument( gMDNSDiscoveryTest_RecordCountAAAA, "AAAA record count", 0, 64 );
+ require_noerr_quiet( err, exit );
+
+ err = CheckDoubleArgument( gMDNSDiscoveryTest_UnicastDropRate, "unicast drop rate", 0.0, 1.0 );
+ require_noerr_quiet( err, exit );
+
+ err = CheckDoubleArgument( gMDNSDiscoveryTest_MulticastDropRate, "multicast drop rate", 0.0, 1.0 );
+ require_noerr_quiet( err, exit );
+
+ err = CheckIntegerArgument( gMDNSDiscoveryTest_MaxDropCount, "drop count", 0, 255 );
+ require_noerr_quiet( err, exit );
+
+ context->replierPID = -1;
+ context->instanceCount = (unsigned int) gMDNSDiscoveryTest_InstanceCount;
+ context->txtSize = (unsigned int) gMDNSDiscoveryTest_TXTSize;
+ context->browseTimeSecs = (unsigned int) gMDNSDiscoveryTest_BrowseTimeSecs;
+ context->recordCountA = (unsigned int) gMDNSDiscoveryTest_RecordCountA;
+ context->recordCountAAAA = (unsigned int) gMDNSDiscoveryTest_RecordCountAAAA;
+ context->ucastDropRate = gMDNSDiscoveryTest_UnicastDropRate;
+ context->mcastDropRate = gMDNSDiscoveryTest_MulticastDropRate;
+ context->maxDropCount = (unsigned int) gMDNSDiscoveryTest_MaxDropCount;
+ context->outputFilePath = gMDNSDiscoveryTest_OutputFilePath;
+ context->outputAppendNewline = gMDNSDiscoveryTest_OutputAppendNewline ? true : false;
+ context->noAdditionals = gMDNSDiscoveryTest_NoAdditionals ? true : false;
+ context->useIPv4 = ( gMDNSDiscoveryTest_UseIPv4 || !gMDNSDiscoveryTest_UseIPv6 ) ? true : false;
+ context->useIPv6 = ( gMDNSDiscoveryTest_UseIPv6 || !gMDNSDiscoveryTest_UseIPv4 ) ? true : false;
+
+ if( gMDNSDiscoveryTest_Interface )
+ {
+ err = InterfaceIndexFromArgString( gMDNSDiscoveryTest_Interface, &context->ifIndex );
+ require_noerr_quiet( err, exit );
+ }
+ else
+ {
+ err = GetAnyMDNSInterface( NULL, &context->ifIndex );
+ require_noerr_quiet( err, exit );
+ }
+
+ context->outputFormat = (OutputFormatType) CLIArgToValue( "format", gMDNSDiscoveryTest_OutputFormat, &err,
+ kOutputFormatStr_JSON, kOutputFormatType_JSON,
+ kOutputFormatStr_XML, kOutputFormatType_XML,
+ kOutputFormatStr_Binary, kOutputFormatType_Binary,
+ NULL );
+ require_noerr_quiet( err, exit );
+
+ if( gMDNSDiscoveryTest_FlushCache )
+ {
+ err = CheckRootUser();
+ require_noerr_quiet( err, exit );
+
+ err = systemf( NULL, "killall -HUP mDNSResponder" );
+ require_noerr( err, exit );
+ sleep( 1 );
+ context->flushedCache = true;
+ }
+
+ _RandomStringExact( kLowerAlphaNumericCharSet, kLowerAlphaNumericCharSetSize, sizeof( context->hostname ) - 1,
+ context->hostname );
+ _RandomStringExact( kLowerAlphaNumericCharSet, kLowerAlphaNumericCharSetSize, sizeof( context->tag ) - 1, context->tag );
+
+ ASPrintF( &context->serviceType, "_t-%s-%u-%u._tcp", context->tag, context->txtSize, context->instanceCount );
+ require_action( context->serviceType, exit, err = kUnknownErr );
+
+ err = ASPrintF( &context->replierCommand,
+ "dnssdutil mdnsreplier --follow %lld --interface %u --hostname %s --tag %s --maxInstanceCount %u "
+ "--countA %u --countAAAA %u --udrop %.1f --mdrop %.1f --maxDropCount %u %?s%?s%?s",
+ (int64_t) getpid(),
+ context->ifIndex,
+ context->hostname,
+ context->tag,
+ context->instanceCount,
+ context->recordCountA,
+ context->recordCountAAAA,
+ context->ucastDropRate,
+ context->mcastDropRate,
+ context->maxDropCount,
+ context->noAdditionals, " --noAdditionals",
+ context->useIPv4, " --ipv4",
+ context->useIPv6, " --ipv6" );
+ require_action_quiet( context->replierCommand, exit, err = kUnknownErr );
+
+ err = SpawnCommand( &context->replierPID, "%s", context->replierCommand );
+ require_noerr_quiet( err, exit );
+
+ // Query for the replier's about TXT record. A response means it's fully up and running.
+
+ SNPrintF( queryName, sizeof( queryName ), "about.%s.local.", context->hostname );
+ err = DNSServiceQueryRecord( &context->query, kDNSServiceFlagsForceMulticast, context->ifIndex, queryName,
+ kDNSServiceType_TXT, kDNSServiceClass_IN, _MDNSDiscoveryTestAboutQueryCallback, context );
+ require_noerr( err, exit );
+
+ err = DNSServiceSetDispatchQueue( context->query, dispatch_get_main_queue() );
+ require_noerr( err, exit );
+
+ err = DispatchTimerCreate( dispatch_time_seconds( kMDNSDiscoveryTestFirstQueryTimeoutSecs ),
+ DISPATCH_TIME_FOREVER, UINT64_C_safe( kMDNSDiscoveryTestFirstQueryTimeoutSecs ) * kNanosecondsPerSecond / 10, NULL,
+ _MDNSDiscoveryTestFirstQueryTimeout, NULL, context, &context->queryTimer );
+ require_noerr( err, exit );
+ dispatch_resume( context->queryTimer );
+
+ context->startTime = NanoTimeGetCurrent();
+ dispatch_main();
+
+exit:
+ exit( 1 );
+}
+
+//===========================================================================================================================
+// _MDNSDiscoveryTestFirstQueryTimeout
+//===========================================================================================================================
+
+static void _MDNSDiscoveryTestFirstQueryTimeout( void *inContext )
+{
+ MDNSDiscoveryTestContext * const context = (MDNSDiscoveryTestContext *) inContext;
+
+ dispatch_source_forget( &context->queryTimer );
+
+ FPrintF( stderr, "error: Query for mdnsreplier's \"about\" TXT record timed out.\n" );
+ exit( 1 );
+}
+
+//===========================================================================================================================
+// _MDNSDiscoveryTestAboutQueryCallback
+//===========================================================================================================================
+
+static void DNSSD_API
+ _MDNSDiscoveryTestAboutQueryCallback(
+ 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;
+ MDNSDiscoveryTestContext * const context = (MDNSDiscoveryTestContext *) inContext;
+
+ Unused( inSDRef );
+ Unused( inInterfaceIndex );
+ Unused( inFullName );
+ Unused( inType );
+ Unused( inClass );
+ Unused( inRDataLen );
+ Unused( inRDataPtr );
+ Unused( inTTL );
+
+ err = inError;
+ require_noerr( err, exit );
+ require_quiet( inFlags & kDNSServiceFlagsAdd, exit );
+
+ DNSServiceForget( &context->query );
+ dispatch_source_forget( &context->queryTimer );
+
+ err = ServiceBrowserCreate( dispatch_get_main_queue(), 0, "local.", context->browseTimeSecs, false, &context->browser );
+ require_noerr( err, exit );
+
+ err = ServiceBrowserAddServiceType( context->browser, context->serviceType );
+ require_noerr( err, exit );
+
+ ServiceBrowserSetCallback( context->browser, _MDNSDiscoveryTestServiceBrowserCallback, context );
+ ServiceBrowserStart( context->browser );
+
+exit:
+ if( err ) exit( 1 );
+}
+
+//===========================================================================================================================
+// _MDNSDiscoveryTestServiceBrowserCallback
+//===========================================================================================================================
+
+#define kMDNSDiscoveryTestResultsKey_ReplierInfo CFSTR( "replierInfo" )
+#define kMDNSDiscoveryTestResultsKey_StartTime CFSTR( "startTime" )
+#define kMDNSDiscoveryTestResultsKey_EndTime CFSTR( "endTime" )
+#define kMDNSDiscoveryTestResultsKey_BrowseTimeSecs CFSTR( "browseTimeSecs" )
+#define kMDNSDiscoveryTestResultsKey_ServiceType CFSTR( "serviceType" )
+#define kMDNSDiscoveryTestResultsKey_FlushedCache CFSTR( "flushedCache" )
+#define kMDNSDiscoveryTestResultsKey_UnexpectedInstances CFSTR( "unexpectedInstances" )
+#define kMDNSDiscoveryTestResultsKey_MissingInstances CFSTR( "missingInstances" )
+#define kMDNSDiscoveryTestResultsKey_IncorrectInstances CFSTR( "incorrectInstances" )
+#define kMDNSDiscoveryTestResultsKey_Success CFSTR( "success" )
+#define kMDNSDiscoveryTestResultsKey_TotalResolveTime CFSTR( "totalResolveTimeUs" )
+
+#define kMDNSDiscoveryTestReplierInfoKey_Command CFSTR( "command" )
+#define kMDNSDiscoveryTestReplierInfoKey_InstanceCount CFSTR( "instanceCount" )
+#define kMDNSDiscoveryTestReplierInfoKey_TXTSize CFSTR( "txtSize" )
+#define kMDNSDiscoveryTestReplierInfoKey_RecordCountA CFSTR( "recordCountA" )
+#define kMDNSDiscoveryTestReplierInfoKey_RecordCountAAAA CFSTR( "recordCountAAAA" )
+#define kMDNSDiscoveryTestReplierInfoKey_Hostname CFSTR( "hostname" )
+#define kMDNSDiscoveryTestReplierInfoKey_NoAdditionals CFSTR( "noAdditionals" )
+#define kMDNSDiscoveryTestReplierInfoKey_UnicastDropRate CFSTR( "ucastDropRate" )
+#define kMDNSDiscoveryTestReplierInfoKey_MulticastDropRate CFSTR( "mcastDropRate" )
+#define kMDNSDiscoveryTestReplierInfoKey_MaxDropCount CFSTR( "maxDropCount" )
+
+#define kMDNSDiscoveryTestUnexpectedInstanceKey_Name CFSTR( "name" )
+#define kMDNSDiscoveryTestUnexpectedInstanceKey_InterfaceIndex CFSTR( "interfaceIndex" )
+
+#define kMDNSDiscoveryTestIncorrectInstanceKey_Name CFSTR( "name" )
+#define kMDNSDiscoveryTestIncorrectInstanceKey_DidResolve CFSTR( "didResolve" )
+#define kMDNSDiscoveryTestIncorrectInstanceKey_BadHostname CFSTR( "badHostname" )
+#define kMDNSDiscoveryTestIncorrectInstanceKey_BadPort CFSTR( "badPort" )
+#define kMDNSDiscoveryTestIncorrectInstanceKey_BadTXT CFSTR( "badTXT" )
+#define kMDNSDiscoveryTestIncorrectInstanceKey_UnexpectedAddrs CFSTR( "unexpectedAddrs" )
+#define kMDNSDiscoveryTestIncorrectInstanceKey_MissingAddrs CFSTR( "missingAddrs" )
+
+static void _MDNSDiscoveryTestServiceBrowserCallback( ServiceBrowserResults *inResults, OSStatus inError, void *inContext )
+{
+ OSStatus err;
+ MDNSDiscoveryTestContext * const context = (MDNSDiscoveryTestContext *) inContext;
+ const SBRDomain * domain;
+ const SBRServiceType * type;
+ const SBRServiceInstance * instance;
+ const SBRServiceInstance ** instanceArray = NULL;
+ const SBRIPAddress * ipaddr;
+ size_t hostnameLen;
+ const char * ptr;
+ const char * end;
+ unsigned int i;
+ uint32_t u32;
+ CFMutableArrayRef unexpectedInstances;
+ CFMutableArrayRef missingInstances;
+ CFMutableArrayRef incorrectInstances;
+ CFMutableDictionaryRef plist = NULL;
+ CFMutableDictionaryRef badDict = NULL;
+ CFMutableArrayRef unexpectedAddrs = NULL;
+ CFMutableArrayRef missingAddrs = NULL;
+ uint64_t maxResolveTimeUs;
+ int success = false;
+ char startTimeStr[ 32 ];
+ char endTimeStr[ 32 ];
+
+ context->endTime = NanoTimeGetCurrent();
+
+ err = inError;
+ require_noerr( err, exit );
+
+ _NanoTime64ToDateString( context->startTime, startTimeStr, sizeof( startTimeStr ) );
+ _NanoTime64ToDateString( context->endTime, endTimeStr, sizeof( endTimeStr ) );
+ err = CFPropertyListCreateFormatted( kCFAllocatorDefault, &plist,
+ "{"
+ "%kO="
+ "{"
+ "%kO=%s" // replierCommand
+ "%kO=%lli" // txtSize
+ "%kO=%lli" // instanceCount
+ "%kO=%lli" // recordCountA
+ "%kO=%lli" // recordCountAAAA
+ "%kO=%s" // hostname
+ "%kO=%b" // noAdditionals
+ "%kO=%f" // ucastDropRate
+ "%kO=%f" // mcastDropRate
+ "%kO=%i" // maxDropCount
+ "}"
+ "%kO=%s" // startTime
+ "%kO=%s" // endTime
+ "%kO=%lli" // browseTimeSecs
+ "%kO=%s" // serviceType
+ "%kO=%b" // flushedCache
+ "%kO=[%@]" // unexpectedInstances
+ "%kO=[%@]" // missingInstances
+ "%kO=[%@]" // incorrectInstances
+ "}",
+ kMDNSDiscoveryTestResultsKey_ReplierInfo,
+ kMDNSDiscoveryTestReplierInfoKey_Command, context->replierCommand,
+ kMDNSDiscoveryTestReplierInfoKey_InstanceCount, (int64_t) context->instanceCount,
+ kMDNSDiscoveryTestReplierInfoKey_TXTSize, (int64_t) context->txtSize,
+ kMDNSDiscoveryTestReplierInfoKey_RecordCountA, (int64_t) context->recordCountA,
+ kMDNSDiscoveryTestReplierInfoKey_RecordCountAAAA, (int64_t) context->recordCountAAAA,
+ kMDNSDiscoveryTestReplierInfoKey_Hostname, context->hostname,
+ kMDNSDiscoveryTestReplierInfoKey_NoAdditionals, context->noAdditionals,
+ kMDNSDiscoveryTestReplierInfoKey_UnicastDropRate, context->ucastDropRate,
+ kMDNSDiscoveryTestReplierInfoKey_MulticastDropRate, context->mcastDropRate,
+ kMDNSDiscoveryTestReplierInfoKey_MaxDropCount, context->maxDropCount,
+ kMDNSDiscoveryTestResultsKey_StartTime, startTimeStr,
+ kMDNSDiscoveryTestResultsKey_EndTime, endTimeStr,
+ kMDNSDiscoveryTestResultsKey_BrowseTimeSecs, (int64_t) context->browseTimeSecs,
+ kMDNSDiscoveryTestResultsKey_ServiceType, context->serviceType,
+ kMDNSDiscoveryTestResultsKey_FlushedCache, context->flushedCache,
+ kMDNSDiscoveryTestResultsKey_UnexpectedInstances, &unexpectedInstances,
+ kMDNSDiscoveryTestResultsKey_MissingInstances, &missingInstances,
+ kMDNSDiscoveryTestResultsKey_IncorrectInstances, &incorrectInstances );
+ require_noerr( err, exit );
+
+ for( domain = inResults->domainList; domain && ( strcasecmp( domain->name, "local.") != 0 ); domain = domain->next ) {}
+ require_action( domain, exit, err = kInternalErr );
+
+ for( type = domain->typeList; type && ( strcasecmp( type->name, context->serviceType ) != 0 ); type = type->next ) {}
+ require_action( type, exit, err = kInternalErr );
+
+ instanceArray = (const SBRServiceInstance **) calloc( context->instanceCount, sizeof( *instanceArray ) );
+ require_action( instanceArray, exit, err = kNoMemoryErr );
+
+ hostnameLen = strlen( context->hostname );
+ for( instance = type->instanceList; instance; instance = instance->next )
+ {
+ unsigned int instanceNumber = 0;
+
+ if( strcmp_prefix( instance->name, context->hostname ) == 0 )
+ {
+ ptr = &instance->name[ hostnameLen ];
+ if( ( ptr[ 0 ] == ' ' ) && ( ptr[ 1 ] == '(' ) )
+ {
+ ptr += 2;
+ for( end = ptr; isdigit_safe( *end ); ++end ) {}
+ if( DecimalTextToUInt32( ptr, end, &u32, &ptr ) == kNoErr )
+ {
+ if( ( u32 >= 2 ) && ( u32 <= context->instanceCount ) && ( ptr[ 0 ] == ')' ) && ( ptr[ 1 ] == '\0' ) )
+ {
+ instanceNumber = u32;
+ }
+ }
+ }
+ else if( *ptr == '\0' )
+ {
+ instanceNumber = 1;
+ }
+ }
+ if( ( instanceNumber != 0 ) && ( instance->ifIndex == context->ifIndex ) )
+ {
+ check( !instanceArray[ instanceNumber - 1 ] );
+ instanceArray[ instanceNumber - 1 ] = instance;
+ }
+ else
+ {
+ err = CFPropertyListAppendFormatted( kCFAllocatorDefault, unexpectedInstances,
+ "{"
+ "%kO=%s"
+ "%kO=%lli"
+ "}",
+ kMDNSDiscoveryTestUnexpectedInstanceKey_Name, instance->name,
+ kMDNSDiscoveryTestUnexpectedInstanceKey_InterfaceIndex, (int64_t) instance->ifIndex );
+ require_noerr( err, exit );
+ }
+ }
+
+ maxResolveTimeUs = 0;
+ for( i = 1; i <= context->instanceCount; ++i )
+ {
+ int isHostnameValid;
+ int isTXTValid;
+
+ instance = instanceArray[ i - 1 ];
+ if( !instance )
+ {
+ if( i == 1 )
+ {
+ err = CFPropertyListAppendFormatted( kCFAllocatorDefault, missingInstances, "%s", context->hostname );
+ require_noerr( err, exit );
+ }
+ else
+ {
+ char * instanceName = NULL;
+
+ ASPrintF( &instanceName, "%s (%u)", context->hostname, i );
+ require_action( instanceName, exit, err = kUnknownErr );
+
+ err = CFPropertyListAppendFormatted( kCFAllocatorDefault, missingInstances, "%s", instanceName );
+ free( instanceName );
+ require_noerr( err, exit );
+ }
+ continue;
+ }
+
+ if( !instance->hostname )
+ {
+ err = CFPropertyListAppendFormatted( kCFAllocatorDefault, incorrectInstances,
+ "{"
+ "%kO=%s"
+ "%kO=%b"
+ "}",
+ kMDNSDiscoveryTestIncorrectInstanceKey_Name, instance->name,
+ kMDNSDiscoveryTestIncorrectInstanceKey_DidResolve, false );
+ require_noerr( err, exit );
+ continue;
+ }
+
+ badDict = CFDictionaryCreateMutable( NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks );
+ require_action( badDict, exit, err = kNoMemoryErr );
+
+ isHostnameValid = false;
+ if( strcmp_prefix( instance->hostname, context->hostname ) == 0 )
+ {
+ ptr = &instance->hostname[ hostnameLen ];
+ if( i == 1 )
+ {
+ if( strcmp( ptr, ".local." ) == 0 ) isHostnameValid = true;
+ }
+ else if( *ptr == '-' )
+ {
+ ++ptr;
+ for( end = ptr; isdigit_safe( *end ); ++end ) {}
+ if( DecimalTextToUInt32( ptr, end, &u32, &ptr ) == kNoErr )
+ {
+ if( ( u32 == i ) && ( strcmp( ptr, ".local." ) == 0 ) ) isHostnameValid = true;
+ }
+ }
+ }
+ if( !isHostnameValid )
+ {
+ err = CFDictionarySetCString( badDict, kMDNSDiscoveryTestIncorrectInstanceKey_BadHostname, instance->hostname,
+ kSizeCString );
+ require_noerr( err, exit );
+ }
+
+ if( instance->port != (uint16_t)( kMDNSReplierPortBase + context->txtSize ) )
+ {
+ err = CFDictionarySetInt64( badDict, kMDNSDiscoveryTestIncorrectInstanceKey_BadPort, instance->port );
+ require_noerr( err, exit );
+ }
+
+ isTXTValid = false;
+ if( instance->txtLen == context->txtSize )
+ {
+ uint8_t name[ kDomainNameLengthMax ];
+
+ err = DomainNameFromString( name, instance->name, NULL );
+ require_noerr( err, exit );
+
+ err = DomainNameAppendString( name, type->name, NULL );
+ require_noerr( err, exit );
+
+ err = DomainNameAppendString( name, "local", NULL );
+ require_noerr( err, exit );
+
+ if( _MDNSDiscoveryTestTXTRecordIsValid( name, instance->txtPtr, instance->txtLen ) ) isTXTValid = true;
+ }
+ if( !isTXTValid )
+ {
+ char * hexStr = NULL;
+
+ ASPrintF( &hexStr, "%.4H", instance->txtPtr, (int) instance->txtLen, (int) instance->txtLen );
+ require_action( hexStr, exit, err = kUnknownErr );
+
+ err = CFDictionarySetCString( badDict, kMDNSDiscoveryTestIncorrectInstanceKey_BadTXT, hexStr, kSizeCString );
+ free( hexStr );
+ require_noerr( err, exit );
+ }
+
+ if( isHostnameValid )
+ {
+ uint64_t addrV4Bitmap, addrV6Bitmap, bitmask, resolveTimeUs;
+ unsigned int j;
+ uint8_t addrV4[ 4 ];
+ uint8_t addrV6[ 16 ];
+
+ if( context->recordCountA < 64 ) addrV4Bitmap = ( UINT64_C( 1 ) << context->recordCountA ) - 1;
+ else addrV4Bitmap = ~UINT64_C( 0 );
+
+ if( context->recordCountAAAA < 64 ) addrV6Bitmap = ( UINT64_C( 1 ) << context->recordCountAAAA ) - 1;
+ else addrV6Bitmap = ~UINT64_C( 0 );
+
+ addrV4[ 0 ] = 0;
+ WriteBig16( &addrV4[ 1 ], i );
+ addrV4[ 3 ] = 0;
+
+ memcpy( addrV6, kMDNSReplierBaseAddrV6, 16 );
+ WriteBig16( &addrV6[ 12 ], i );
+
+ unexpectedAddrs = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks );
+ require_action( unexpectedAddrs, exit, err = kNoMemoryErr );
+
+ resolveTimeUs = 0;
+ for( ipaddr = instance->ipaddrList; ipaddr; ipaddr = ipaddr->next )
+ {
+ const uint8_t * addrPtr;
+ unsigned int lsb;
+ int isAddrValid = false;
+
+ if( ipaddr->sip.sa.sa_family == AF_INET )
+ {
+ addrPtr = (const uint8_t *) &ipaddr->sip.v4.sin_addr.s_addr;
+ lsb = addrPtr[ 3 ];
+ if( ( memcmp( addrPtr, addrV4, 3 ) == 0 ) && ( lsb >= 1 ) && ( lsb <= context->recordCountA ) )
+ {
+ bitmask = UINT64_C( 1 ) << ( lsb - 1 );
+ addrV4Bitmap &= ~bitmask;
+ isAddrValid = true;
+ }
+ }
+ else if( ipaddr->sip.sa.sa_family == AF_INET6 )
+ {
+ addrPtr = ipaddr->sip.v6.sin6_addr.s6_addr;
+ lsb = addrPtr[ 15 ];
+ if( ( memcmp( addrPtr, addrV6, 15 ) == 0 ) && ( lsb >= 1 ) && ( lsb <= context->recordCountAAAA ) )
+ {
+ bitmask = UINT64_C( 1 ) << ( lsb - 1 );
+ addrV6Bitmap &= ~bitmask;
+ isAddrValid = true;
+ }
+ }
+ if( isAddrValid )
+ {
+ if( ipaddr->resolveTimeUs > resolveTimeUs ) resolveTimeUs = ipaddr->resolveTimeUs;
+ }
+ else
+ {
+ err = CFPropertyListAppendFormatted( kCFAllocatorDefault, unexpectedAddrs, "%##a", &ipaddr->sip );
+ require_noerr( err, exit );
+ }
+ }
+
+ resolveTimeUs += ( instance->discoverTimeUs + instance->resolveTimeUs );
+ if( resolveTimeUs > maxResolveTimeUs ) maxResolveTimeUs = resolveTimeUs;
+
+ if( CFArrayGetCount( unexpectedAddrs ) > 0 )
+ {
+ CFDictionarySetValue( badDict, kMDNSDiscoveryTestIncorrectInstanceKey_UnexpectedAddrs, unexpectedAddrs );
+ }
+ ForgetCF( &unexpectedAddrs );
+
+ missingAddrs = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks );
+ require_action( missingAddrs, exit, err = kNoMemoryErr );
+
+ for( j = 1; addrV4Bitmap != 0; ++j )
+ {
+ bitmask = UINT64_C( 1 ) << ( j - 1 );
+ if( addrV4Bitmap & bitmask )
+ {
+ addrV4Bitmap &= ~bitmask;
+ addrV4[ 3 ] = (uint8_t) j;
+ err = CFPropertyListAppendFormatted( kCFAllocatorDefault, missingAddrs, "%.4a", addrV4 );
+ require_noerr( err, exit );
+ }
+ }
+ for( j = 1; addrV6Bitmap != 0; ++j )
+ {
+ bitmask = UINT64_C( 1 ) << ( j - 1 );
+ if( addrV6Bitmap & bitmask )
+ {
+ addrV6Bitmap &= ~bitmask;
+ addrV6[ 15 ] = (uint8_t) j;
+ err = CFPropertyListAppendFormatted( kCFAllocatorDefault, missingAddrs, "%.16a", addrV6 );
+ require_noerr( err, exit );
+ }
+ }
+
+ if( CFArrayGetCount( missingAddrs ) > 0 )
+ {
+ CFDictionarySetValue( badDict, kMDNSDiscoveryTestIncorrectInstanceKey_MissingAddrs, missingAddrs );
+ }
+ ForgetCF( &missingAddrs );
+ }
+
+ if( CFDictionaryGetCount( badDict ) > 0 )
+ {
+ err = CFDictionarySetCString( badDict, kMDNSDiscoveryTestIncorrectInstanceKey_Name, instance->name,
+ kSizeCString );
+ require_noerr( err, exit );
+
+ CFDictionarySetBoolean( badDict, kMDNSDiscoveryTestIncorrectInstanceKey_DidResolve, true );
+ CFArrayAppendValue( incorrectInstances, badDict );
+ }
+ ForgetCF( &badDict );
+ }
+
+ if( ( CFArrayGetCount( unexpectedInstances ) == 0 ) &&
+ ( CFArrayGetCount( missingInstances ) == 0 ) &&
+ ( CFArrayGetCount( incorrectInstances ) == 0 ) )
+ {
+ err = CFDictionarySetInt64( plist, kMDNSDiscoveryTestResultsKey_TotalResolveTime, (int64_t) maxResolveTimeUs );
+ require_noerr( err, exit );
+ success = true;
+ }
+ else
+ {
+ success = false;
+ }
+ CFDictionarySetBoolean( plist, kMDNSDiscoveryTestResultsKey_Success, success );
+
+ err = OutputPropertyList( plist, context->outputFormat, context->outputAppendNewline, context->outputFilePath );
+ require_noerr_quiet( err, exit );
+
+exit:
+ ForgetCF( &context->browser );
+ if( context->replierPID != -1 )
+ {
+ kill( context->replierPID, SIGTERM );
+ context->replierPID = -1;
+ }
+ FreeNullSafe( instanceArray );
+ CFReleaseNullSafe( plist );
+ CFReleaseNullSafe( badDict );
+ CFReleaseNullSafe( unexpectedAddrs );
+ CFReleaseNullSafe( missingAddrs );
+ exit( err ? 1 : ( success ? 0 : 2 ) );
+}
+
+//===========================================================================================================================
+// _MDNSDiscoveryTestTXTRecordIsValid
+//===========================================================================================================================
+
+static Boolean _MDNSDiscoveryTestTXTRecordIsValid( const uint8_t *inRecordName, const uint8_t *inTXTPtr, size_t inTXTLen )
+{
+ uint32_t hash;
+ int n;
+ const uint8_t * ptr;
+ size_t i, wholeCount, remCount;
+ uint8_t txtStr[ 16 ];
+
+ if( inTXTLen == 0 ) return( false );
+
+ hash = FNV1( inRecordName, DomainNameLength( inRecordName ) );
+
+ txtStr[ 0 ] = 15;
+ n = MemPrintF( &txtStr[ 1 ], 15, "hash=0x%08X", hash );
+ check( n == 15 );
+
+ ptr = inTXTPtr;
+ wholeCount = inTXTLen / 16;
+ for( i = 0; i < wholeCount; ++i )
+ {
+ if( memcmp( ptr, txtStr, 16 ) != 0 ) return( false );
+ ptr += 16;
+ }
+
+ remCount = inTXTLen % 16;
+ if( remCount > 0 )
+ {
+ txtStr[ 0 ] = (uint8_t)( remCount - 1 );
+ if( memcmp( ptr, txtStr, remCount ) != 0 ) return( false );
+ ptr += remCount;
+ }
+ check( ptr == &inTXTPtr[ inTXTLen ] );
+ return( true );
+}
+
+//===========================================================================================================================
+// DotLocalTestCmd
+//===========================================================================================================================
+
+#define kDotLocalTestPreparationTimeLimitSecs 5
+#define kDotLocalTestSubTestDurationSecs 5
+
+// Constants for SRV record query subtest.
+
+#define kDotLocalTestSRV_Priority 1
+#define kDotLocalTestSRV_Weight 0
+#define kDotLocalTestSRV_Port 80
+#define kDotLocalTestSRV_TargetName ( (const uint8_t *) "\x03" "www" "\x07" "example" "\x03" "com" )
+#define kDotLocalTestSRV_TargetStr "www.example.com."
+#define kDotLocalTestSRV_ResultStr "1 0 80 " kDotLocalTestSRV_TargetStr
+
+typedef enum
+{
+ kDotLocalTestState_Unset = 0,
+ kDotLocalTestState_Preparing = 1,
+ kDotLocalTestState_GAIMDNSOnly = 2,
+ kDotLocalTestState_GAIDNSOnly = 3,
+ kDotLocalTestState_GAIBoth = 4,
+ kDotLocalTestState_GAINeither = 5,
+ kDotLocalTestState_GAINoSuchRecord = 6,
+ kDotLocalTestState_QuerySRV = 7,
+ kDotLocalTestState_Done = 8
+
+} DotLocalTestState;
+
+typedef struct
+{
+ const char * testDesc; // Description of the current subtest.
+ char * queryName; // Query name for GetAddrInfo or QueryRecord operation.
+ dispatch_source_t timer; // Timer used for limiting the time for each subtest.
+ NanoTime64 startTime; // Timestamp of when the subtest started.
+ NanoTime64 endTime; // Timestamp of when the subtest ended.
+ CFMutableArrayRef correctResults; // Operation results that were expected.
+ CFMutableArrayRef duplicateResults; // Operation results that were expected, but were already received.
+ CFMutableArrayRef unexpectedResults; // Operation results that were unexpected.
+ OSStatus error; // Subtest's error code.
+ uint32_t addrDNSv4; // If hasDNSv4 is true, the expected DNS IPv4 address for queryName.
+ uint32_t addrMDNSv4; // If hasMDNSv4 is true, the expected MDNS IPv4 address for queryName.
+ uint8_t addrDNSv6[ 16 ]; // If hasDNSv6 is true, the expected DNS IPv6 address for queryName.
+ uint8_t addrMDNSv6[ 16 ]; // If hasMDNSv6 is true, the expected MDNS IPv6 address for queryName.
+ Boolean hasDNSv4; // True if queryName has a DNS IPv4 address.
+ Boolean hasDNSv6; // True if queryName has a DNS IPv6 address.
+ Boolean hasMDNSv4; // True if queryName has an MDNS IPv4 address.
+ Boolean hasMDNSv6; // True if queryName has an MDNS IPv6 address.
+ Boolean needDNSv4; // True if operation is expecting, but hasn't received a DNS IPv4 result.
+ Boolean needDNSv6; // True if operation is expecting, but hasn't received a DNS IPv6 result.
+ Boolean needMDNSv4; // True if operation is expecting, but hasn't received an MDNS IPv4 result.
+ Boolean needMDNSv6; // True if operation is expecting, but hasn't received an MDNS IPv6 result.
+ Boolean needSRV; // True if operation is expecting, but hasn't received an SRV result.
+
+} DotLocalSubtest;
+
+typedef struct
+{
+ dispatch_source_t timer; // Timer used for limiting the time for each state/subtest.
+ DotLocalSubtest * subtest; // Current subtest's state.
+ DNSServiceRef connection; // Shared connection for DNS-SD operations.
+ DNSServiceRef op; // Reference for the current DNS-SD operation.
+ DNSServiceRef op2; // Reference for mdnsreplier probe query used during preparing state.
+ DNSRecordRef localSOARef; // Reference returned by DNSServiceRegisterRecord() for local. SOA record.
+ char * replierCmd; // Command used to invoke the mdnsreplier.
+ char * serverCmd; // Command used to invoke the test DNS server.
+ CFMutableArrayRef reportsGAI; // Reports for subtests that use DNSServiceGetAddrInfo.
+ CFMutableArrayRef reportsQuerySRV; // Reports for subtests that use DNSServiceQueryRecord for SRV records.
+ NanoTime64 startTime; // Timestamp for when the test started.
+ NanoTime64 endTime; // Timestamp for when the test ended.
+ DotLocalTestState state; // The test's current state.
+ pid_t replierPID; // PID of spawned mdnsreplier.
+ pid_t serverPID; // PID of spawned test DNS server.
+ uint32_t ifIndex; // Interface index used for mdnsreplier.
+ char * outputFilePath; // File to write test results to. If NULL, then write to stdout.
+ OutputFormatType outputFormat; // Format of test results output.
+ Boolean appendNewline; // True if a newline character should be appended to JSON output.
+ Boolean registeredSOA; // True if the dummy local. SOA record was successfully registered.
+ Boolean serverIsReady; // True if response was received for test DNS server probe query.
+ Boolean replierIsReady; // True if response was received for mdnsreplier probe query.
+ Boolean testFailed; // True if at least one subtest failed.
+ char labelStr[ 20 + 1 ]; // Unique label string used for for making the query names used by subtests.
+ // The format of this string is "dotlocal-test-<six random chars>".
+} DotLocalTestContext;
+
+static void _DotLocalTestStateMachine( DotLocalTestContext *inContext );
+static void DNSSD_API
+ _DotLocalTestProbeQueryRecordCallback(
+ 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
+ _DotLocalTestRegisterRecordCallback(
+ DNSServiceRef inSDRef,
+ DNSRecordRef inRecordRef,
+ DNSServiceFlags inFlags,
+ DNSServiceErrorType inError,
+ void * inContext );
+static void _DotLocalTestTimerHandler( void *inContext );
+static void DNSSD_API
+ _DotLocalTestGAICallback(
+ DNSServiceRef inSDRef,
+ DNSServiceFlags inFlags,
+ uint32_t inInterfaceIndex,
+ DNSServiceErrorType inError,
+ const char * inHostname,
+ const struct sockaddr * inSockAddr,
+ uint32_t inTTL,
+ void * inContext );
+static void DNSSD_API
+ _DotLocalTestQueryRecordCallback(
+ 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 DotLocalTestCmd( void )
+{
+ OSStatus err;
+ DotLocalTestContext * context;
+ uint8_t * rdataPtr;
+ size_t rdataLen;
+ DNSServiceFlags flags;
+ char queryName[ 64 ];
+ char randBuf[ 6 + 1 ]; // Large enough for four and six character random strings below.
+
+ context = (DotLocalTestContext *) calloc( 1, sizeof( *context ) );
+ require_action( context, exit, err = kNoMemoryErr );
+
+ context->startTime = NanoTimeGetCurrent();
+ context->endTime = kNanoTime_Invalid;
+
+ context->state = kDotLocalTestState_Preparing;
+
+ if( gDotLocalTest_Interface )
+ {
+ err = InterfaceIndexFromArgString( gDotLocalTest_Interface, &context->ifIndex );
+ require_noerr_quiet( err, exit );
+ }
+ else
+ {
+ err = GetAnyMDNSInterface( NULL, &context->ifIndex );
+ require_noerr_quiet( err, exit );
+ }
+
+ if( gDotLocalTest_OutputFilePath )
+ {
+ context->outputFilePath = strdup( gDotLocalTest_OutputFilePath );
+ require_action( context->outputFilePath, exit, err = kNoMemoryErr );
+ }
+
+ context->outputFormat = (OutputFormatType) CLIArgToValue( "format", gDotLocalTest_OutputFormat, &err,
+ kOutputFormatStr_JSON, kOutputFormatType_JSON,
+ kOutputFormatStr_XML, kOutputFormatType_XML,
+ kOutputFormatStr_Binary, kOutputFormatType_Binary,
+ NULL );
+ require_noerr_quiet( err, exit );
+
+ context->appendNewline = gDotLocalTest_OutputAppendNewline ? true : false;
+
+ context->reportsGAI = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks );
+ require_action( context->reportsGAI, exit, err = kNoMemoryErr );
+
+ context->reportsQuerySRV = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks );
+ require_action( context->reportsQuerySRV, exit, err = kNoMemoryErr );
+
+ SNPrintF( context->labelStr, sizeof( context->labelStr ), "dotlocal-test-%s",
+ _RandomStringExact( kLowerAlphaNumericCharSet, kLowerAlphaNumericCharSetSize, 6, randBuf ) );
+
+ // Spawn an mdnsreplier.
+
+ err = ASPrintF( &context->replierCmd,
+ "dnssdutil mdnsreplier --follow %lld --interface %u --hostname %s --tag %s --maxInstanceCount 2 --countA 1"
+ " --countAAAA 1",
+ (int64_t) getpid(), context->ifIndex, context->labelStr,
+ _RandomStringExact( kLowerAlphaNumericCharSet, kLowerAlphaNumericCharSetSize, 4, randBuf ) );
+ require_action_quiet( context->replierCmd, exit, err = kUnknownErr );
+
+ err = SpawnCommand( &context->replierPID, "%s", context->replierCmd );
+ require_noerr( err, exit );
+
+ // Spawn a test DNS server
+
+ err = ASPrintF( &context->serverCmd,
+ "dnssdutil server --loopback --follow %lld --port 0 --defaultTTL 300 --domain %s.local.",
+ (int64_t) getpid(), context->labelStr );
+ require_action_quiet( context->serverCmd, exit, err = kUnknownErr );
+
+ err = SpawnCommand( &context->serverPID, "%s", context->serverCmd );
+ require_noerr( err, exit );
+
+ // Create a shared DNS-SD connection.
+
+ err = DNSServiceCreateConnection( &context->connection );
+ require_noerr( err, exit );
+
+ err = DNSServiceSetDispatchQueue( context->connection, dispatch_get_main_queue() );
+ require_noerr( err, exit );
+
+ // Create probe query for DNS server, i.e., query for any name that has an A record.
+
+ SNPrintF( queryName, sizeof( queryName ), "tag-dotlocal-test-probe.ipv4.%s.local.", context->labelStr );
+
+ flags = kDNSServiceFlagsShareConnection;
+#if( TARGET_OS_WATCH )
+ flags |= kDNSServiceFlagsPathEvaluationDone;
+#endif
+
+ context->op = context->connection;
+ err = DNSServiceQueryRecord( &context->op, flags, kDNSServiceInterfaceIndexAny, queryName, kDNSServiceType_A,
+ kDNSServiceClass_IN, _DotLocalTestProbeQueryRecordCallback, context );
+ require_noerr( err, exit );
+
+ // Create probe query for mdnsreplier's "about" TXT record.
+
+ SNPrintF( queryName, sizeof( queryName ), "about.%s.local.", context->labelStr );
+
+ flags = kDNSServiceFlagsShareConnection | kDNSServiceFlagsForceMulticast;
+#if( TARGET_OS_WATCH )
+ flags |= kDNSServiceFlagsPathEvaluationDone;
+#endif
+
+ context->op2 = context->connection;
+ err = DNSServiceQueryRecord( &context->op2, flags, context->ifIndex, queryName, kDNSServiceType_TXT, kDNSServiceClass_IN,
+ _DotLocalTestProbeQueryRecordCallback, context );
+ require_noerr( err, exit );
+
+ // Register a dummy local. SOA record.
+
+ err = CreateSOARecordData( kRootLabel, kRootLabel, 1976040101, 1 * kSecondsPerDay, 2 * kSecondsPerHour,
+ 1000 * kSecondsPerHour, 2 * kSecondsPerDay, &rdataPtr, &rdataLen );
+ require_noerr( err, exit );
+
+ err = DNSServiceRegisterRecord( context->connection, &context->localSOARef, kDNSServiceFlagsUnique,
+ kDNSServiceInterfaceIndexLocalOnly, "local.", kDNSServiceType_SOA, kDNSServiceClass_IN, 1,
+ rdataPtr, 1 * kSecondsPerHour, _DotLocalTestRegisterRecordCallback, context );
+ require_noerr( err, exit );
+
+ // Start timer for probe responses and SOA record registration.
+
+ err = DispatchTimerOneShotCreate( dispatch_time_seconds( kDotLocalTestPreparationTimeLimitSecs ),
+ INT64_C_safe( kDotLocalTestPreparationTimeLimitSecs ) * kNanosecondsPerSecond / 10, dispatch_get_main_queue(),
+ _DotLocalTestTimerHandler, context, &context->timer );
+ require_noerr( err, exit );
+ dispatch_resume( context->timer );
+
+ dispatch_main();
+
+exit:
+ if( err ) ErrQuit( 1, "error: %#m\n", err );
+}
+
+//===========================================================================================================================
+// _DotLocalTestStateMachine
+//===========================================================================================================================
+
+static OSStatus _DotLocalSubtestCreate( DotLocalSubtest **outSubtest );
+static void _DotLocalSubtestFree( DotLocalSubtest *inSubtest );
+static OSStatus _DotLocalTestStartSubtest( DotLocalTestContext *inContext );
+static OSStatus _DotLocalTestFinalizeSubtest( DotLocalTestContext *inContext );
+static void _DotLocalTestFinalizeAndExit( DotLocalTestContext *inContext ) ATTRIBUTE_NORETURN;
+
+static void _DotLocalTestStateMachine( DotLocalTestContext *inContext )
+{
+ OSStatus err;
+ DotLocalTestState nextState;
+
+ DNSServiceForget( &inContext->op );
+ DNSServiceForget( &inContext->op2 );
+ dispatch_source_forget( &inContext->timer );
+
+ switch( inContext->state )
+ {
+ case kDotLocalTestState_Preparing: nextState = kDotLocalTestState_GAIMDNSOnly; break;
+ case kDotLocalTestState_GAIMDNSOnly: nextState = kDotLocalTestState_GAIDNSOnly; break;
+ case kDotLocalTestState_GAIDNSOnly: nextState = kDotLocalTestState_GAIBoth; break;
+ case kDotLocalTestState_GAIBoth: nextState = kDotLocalTestState_GAINeither; break;
+ case kDotLocalTestState_GAINeither: nextState = kDotLocalTestState_GAINoSuchRecord; break;
+ case kDotLocalTestState_GAINoSuchRecord: nextState = kDotLocalTestState_QuerySRV; break;
+ case kDotLocalTestState_QuerySRV: nextState = kDotLocalTestState_Done; break;
+ default: err = kStateErr; goto exit;
+ }
+
+ if( inContext->state == kDotLocalTestState_Preparing )
+ {
+ if( !inContext->registeredSOA || !inContext->serverIsReady || !inContext->replierIsReady )
+ {
+ FPrintF( stderr, "Preparation timed out: Registered SOA? %s. Server ready? %s. mdnsreplier ready? %s.\n",
+ YesNoStr( inContext->registeredSOA ),
+ YesNoStr( inContext->serverIsReady ),
+ YesNoStr( inContext->replierIsReady ) );
+ err = kNotPreparedErr;
+ goto exit;
+ }
+ }
+ else
+ {
+ err = _DotLocalTestFinalizeSubtest( inContext );
+ require_noerr( err, exit );
+ }
+
+ inContext->state = nextState;
+ if( inContext->state == kDotLocalTestState_Done ) _DotLocalTestFinalizeAndExit( inContext );
+ err = _DotLocalTestStartSubtest( inContext );
+
+exit:
+ if( err ) ErrQuit( 1, "error: %#m\n", err );
+}
+
+//===========================================================================================================================
+// _DotLocalSubtestCreate
+//===========================================================================================================================
+
+static OSStatus _DotLocalSubtestCreate( DotLocalSubtest **outSubtest )
+{
+ OSStatus err;
+ DotLocalSubtest * obj;
+
+ obj = (DotLocalSubtest *) calloc( 1, sizeof( *obj ) );
+ require_action( obj, exit, err = kNoMemoryErr );
+
+ obj->correctResults = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks );
+ require_action( obj->correctResults, exit, err = kNoMemoryErr );
+
+ obj->duplicateResults = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks );
+ require_action( obj->duplicateResults, exit, err = kNoMemoryErr );
+
+ obj->unexpectedResults = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks );
+ require_action( obj->unexpectedResults, exit, err = kNoMemoryErr );
+
+ *outSubtest = obj;
+ obj = NULL;
+ err = kNoErr;
+
+exit:
+ if( obj ) _DotLocalSubtestFree( obj );
+ return( err );
+}
+
+//===========================================================================================================================
+// _DotLocalSubtestFree
+//===========================================================================================================================
+
+static void _DotLocalSubtestFree( DotLocalSubtest *inSubtest )
+{
+ ForgetMem( &inSubtest->queryName );
+ ForgetCF( &inSubtest->correctResults );
+ ForgetCF( &inSubtest->duplicateResults );
+ ForgetCF( &inSubtest->unexpectedResults );
+ free( inSubtest );
+}
+
+//===========================================================================================================================
+// _DotLocalTestStartSubtest
+//===========================================================================================================================
+
+static OSStatus _DotLocalTestStartSubtest( DotLocalTestContext *inContext )
+{
+ OSStatus err;
+ DotLocalSubtest * subtest = NULL;
+ DNSServiceRef op = NULL;
+ DNSServiceFlags flags;
+
+ err = _DotLocalSubtestCreate( &subtest );
+ require_noerr( err, exit );
+
+ if( inContext->state == kDotLocalTestState_GAIMDNSOnly )
+ {
+ ASPrintF( &subtest->queryName, "%s-2.local.", inContext->labelStr );
+ require_action_quiet( subtest->queryName, exit, err = kNoMemoryErr );
+
+ subtest->hasMDNSv4 = subtest->needMDNSv4 = true;
+ subtest->hasMDNSv6 = subtest->needMDNSv6 = true;
+
+ subtest->addrMDNSv4 = htonl( 0x00000201 ); // 0.0.2.1
+ memcpy( subtest->addrMDNSv6, kMDNSReplierBaseAddrV6, 16 ); // 2001:db8:2::2:1
+ subtest->addrMDNSv6[ 13 ] = 2;
+ subtest->addrMDNSv6[ 15 ] = 1;
+
+ subtest->testDesc = kDotLocalTestSubtestDesc_GAIMDNSOnly;
+ }
+
+ else if( inContext->state == kDotLocalTestState_GAIDNSOnly )
+ {
+ ASPrintF( &subtest->queryName, "tag-dns-only.%s.local.", inContext->labelStr );
+ require_action_quiet( subtest->queryName, exit, err = kNoMemoryErr );
+
+ subtest->hasDNSv4 = subtest->needDNSv4 = true;
+ subtest->hasDNSv6 = subtest->needDNSv6 = true;
+
+ subtest->addrDNSv4 = htonl( kDNSServerBaseAddrV4 + 1 ); // 203.0.113.1
+ memcpy( subtest->addrDNSv6, kDNSServerBaseAddrV6, 16 ); // 2001:db8:1::1
+ subtest->addrDNSv6[ 15 ] = 1;
+
+ subtest->testDesc = kDotLocalTestSubtestDesc_GAIDNSOnly;
+ }
+
+ else if( inContext->state == kDotLocalTestState_GAIBoth )
+ {
+ ASPrintF( &subtest->queryName, "%s.local.", inContext->labelStr );
+ require_action_quiet( subtest->queryName, exit, err = kNoMemoryErr );
+
+ subtest->hasDNSv4 = subtest->needDNSv4 = true;
+ subtest->hasDNSv6 = subtest->needDNSv6 = true;
+ subtest->hasMDNSv4 = subtest->needMDNSv4 = true;
+ subtest->hasMDNSv6 = subtest->needMDNSv6 = true;
+
+ subtest->addrDNSv4 = htonl( kDNSServerBaseAddrV4 + 1 ); // 203.0.113.1
+ memcpy( subtest->addrDNSv6, kDNSServerBaseAddrV6, 16 ); // 2001:db8:1::1
+ subtest->addrDNSv6[ 15 ] = 1;
+
+ subtest->addrMDNSv4 = htonl( 0x00000101 ); // 0.0.1.1
+ memcpy( subtest->addrMDNSv6, kMDNSReplierBaseAddrV6, 16 ); // 2001:db8:2::1:1
+ subtest->addrMDNSv6[ 13 ] = 1;
+ subtest->addrMDNSv6[ 15 ] = 1;
+
+ subtest->testDesc = kDotLocalTestSubtestDesc_GAIBoth;
+ }
+
+ else if( inContext->state == kDotLocalTestState_GAINeither )
+ {
+ ASPrintF( &subtest->queryName, "doesnotexit-%s.local.", inContext->labelStr );
+ require_action_quiet( subtest->queryName, exit, err = kNoMemoryErr );
+
+ subtest->testDesc = kDotLocalTestSubtestDesc_GAINeither;
+ }
+
+ else if( inContext->state == kDotLocalTestState_GAINoSuchRecord )
+ {
+ ASPrintF( &subtest->queryName, "doesnotexit-dns.%s.local.", inContext->labelStr );
+ require_action_quiet( subtest->queryName, exit, err = kNoMemoryErr );
+
+ subtest->hasDNSv4 = subtest->needDNSv4 = true;
+ subtest->hasDNSv6 = subtest->needDNSv6 = true;
+ subtest->testDesc = kDotLocalTestSubtestDesc_GAINoSuchRecord;
+ }
+
+ else if( inContext->state == kDotLocalTestState_QuerySRV )
+ {
+ ASPrintF( &subtest->queryName, "_http._tcp.srv-%u-%u-%u.%s%s.local.",
+ kDotLocalTestSRV_Priority, kDotLocalTestSRV_Weight, kDotLocalTestSRV_Port, kDotLocalTestSRV_TargetStr,
+ inContext->labelStr );
+ require_action_quiet( subtest->queryName, exit, err = kNoMemoryErr );
+
+ subtest->needSRV = true;
+ subtest->testDesc = kDotLocalTestSubtestDesc_QuerySRV;
+ }
+
+ else
+ {
+ err = kStateErr;
+ goto exit;
+ }
+
+ // Start new operation.
+
+ flags = kDNSServiceFlagsShareConnection | kDNSServiceFlagsReturnIntermediates;
+#if( TARGET_OS_WATCH )
+ flags |= kDNSServiceFlagsPathEvaluationDone;
+#endif
+
+ subtest->startTime = NanoTimeGetCurrent();
+ subtest->endTime = kNanoTime_Invalid;
+
+ if( inContext->state == kDotLocalTestState_QuerySRV )
+ {
+ op = inContext->connection;
+ err = DNSServiceQueryRecord( &op, flags, kDNSServiceInterfaceIndexAny, subtest->queryName,
+ kDNSServiceType_SRV, kDNSServiceClass_IN, _DotLocalTestQueryRecordCallback, inContext );
+ require_noerr( err, exit );
+ }
+ else
+ {
+ op = inContext->connection;
+ err = DNSServiceGetAddrInfo( &op, flags, kDNSServiceInterfaceIndexAny,
+ kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6, subtest->queryName, _DotLocalTestGAICallback, inContext );
+ require_noerr( err, exit );
+ }
+
+ // Start timer.
+
+ check( !inContext->timer );
+ err = DispatchTimerOneShotCreate( dispatch_time_seconds( kDotLocalTestSubTestDurationSecs ),
+ INT64_C_safe( kDotLocalTestSubTestDurationSecs ) * kNanosecondsPerSecond / 10, dispatch_get_main_queue(),
+ _DotLocalTestTimerHandler, inContext, &inContext->timer );
+ require_noerr( err, exit );
+ dispatch_resume( inContext->timer );
+
+ check( !inContext->op );
+ inContext->op = op;
+ op = NULL;
+
+ check( !inContext->subtest );
+ inContext->subtest = subtest;
+ subtest = NULL;
+
+exit:
+ if( subtest ) _DotLocalSubtestFree( subtest );
+ if( op ) DNSServiceRefDeallocate( op );
+ return( err );
+}
+
+//===========================================================================================================================
+// _DotLocalTestFinalizeSubtest
+//===========================================================================================================================
+
+#define kDotLocalTestReportKey_StartTime CFSTR( "startTime" ) // String.
+#define kDotLocalTestReportKey_EndTime CFSTR( "endTime" ) // String.
+#define kDotLocalTestReportKey_Success CFSTR( "success" ) // Boolean.
+#define kDotLocalTestReportKey_MDNSReplierCmd CFSTR( "replierCmd" ) // String.
+#define kDotLocalTestReportKey_DNSServerCmd CFSTR( "serverCmd" ) // String.
+#define kDotLocalTestReportKey_GetAddrInfoTests CFSTR( "testsGAI" ) // Array of Dictionaries.
+#define kDotLocalTestReportKey_QuerySRVTests CFSTR( "testsQuerySRV" ) // Array of Dictionaries.
+#define kDotLocalTestReportKey_Description CFSTR( "description" ) // String.
+#define kDotLocalTestReportKey_QueryName CFSTR( "queryName" ) // String.
+#define kDotLocalTestReportKey_Error CFSTR( "error" ) // Integer.
+#define kDotLocalTestReportKey_Results CFSTR( "results" ) // Dictionary of Arrays.
+#define kDotLocalTestReportKey_CorrectResults CFSTR( "correct" ) // Array of Strings
+#define kDotLocalTestReportKey_DuplicateResults CFSTR( "duplicates" ) // Array of Strings.
+#define kDotLocalTestReportKey_UnexpectedResults CFSTR( "unexpected" ) // Array of Strings.
+#define kDotLocalTestReportKey_MissingResults CFSTR( "missing" ) // Array of Strings.
+
+static OSStatus _DotLocalTestFinalizeSubtest( DotLocalTestContext *inContext )
+{
+ OSStatus err;
+ DotLocalSubtest * subtest;
+ CFMutableDictionaryRef reportDict;
+ CFMutableDictionaryRef resultsDict;
+ CFMutableArrayRef missingResults, reportArray;
+ char startTimeStr[ 32 ];
+ char endTimeStr[ 32 ];
+
+ subtest = inContext->subtest;
+ inContext->subtest = NULL;
+
+ subtest->endTime = NanoTimeGetCurrent();
+ _NanoTime64ToDateString( subtest->startTime, startTimeStr, sizeof( startTimeStr ) );
+ _NanoTime64ToDateString( subtest->endTime, endTimeStr, sizeof( endTimeStr ) );
+
+ reportDict = NULL;
+ err = CFPropertyListCreateFormatted( kCFAllocatorDefault, &reportDict,
+ "{"
+ "%kO=%s" // startTime
+ "%kO=%s" // endTime
+ "%kO=%s" // queryName
+ "%kO=%s" // description
+ "%kO={%@}" // results
+ "}",
+ kDotLocalTestReportKey_StartTime, startTimeStr,
+ kDotLocalTestReportKey_EndTime, endTimeStr,
+ kDotLocalTestReportKey_QueryName, subtest->queryName,
+ kDotLocalTestReportKey_Description, subtest->testDesc,
+ kDotLocalTestReportKey_Results, &resultsDict );
+ require_noerr( err, exit );
+
+ missingResults = NULL;
+ switch( inContext->state )
+ {
+ case kDotLocalTestState_GAIMDNSOnly:
+ case kDotLocalTestState_GAIDNSOnly:
+ case kDotLocalTestState_GAIBoth:
+ case kDotLocalTestState_GAINeither:
+ if( subtest->needDNSv4 || subtest->needDNSv6 || subtest->needMDNSv4 || subtest->needMDNSv6 )
+ {
+ err = CFPropertyListCreateFormatted( kCFAllocatorDefault, &missingResults,
+ "["
+ "%.4a" // Expected DNS IPv4 address
+ "%.16a" // Expected DNS IPv6 address
+ "%.4a" // Expected MDNS IPv4 address
+ "%.16a" // Expected MDNS IPv6 address
+ "]",
+ subtest->needDNSv4 ? &subtest->addrDNSv4 : NULL,
+ subtest->needDNSv6 ? subtest->addrDNSv6 : NULL,
+ subtest->needMDNSv4 ? &subtest->addrMDNSv4 : NULL,
+ subtest->needMDNSv6 ? subtest->addrMDNSv6 : NULL );
+ require_noerr( err, exit );
+ }
+ break;
+
+ case kDotLocalTestState_QuerySRV:
+ if( subtest->needSRV )
+ {
+ err = CFPropertyListCreateFormatted( kCFAllocatorDefault, &missingResults,
+ "["
+ "%s" // Expected SRV record data as a string.
+ "]",
+ kDotLocalTestSRV_ResultStr );
+ require_noerr( err, exit );
+ }
+ break;
+
+ case kDotLocalTestState_GAINoSuchRecord:
+ if( subtest->needDNSv4 || subtest->needDNSv6 )
+ {
+ err = CFPropertyListCreateFormatted( kCFAllocatorDefault, &missingResults,
+ "["
+ "%s" // No Such Record (A)
+ "%s" // No Such Record (AAAA)
+ "]",
+ subtest->needDNSv4 ? kNoSuchRecordAStr : NULL,
+ subtest->needDNSv6 ? kNoSuchRecordAAAAStr : NULL );
+ require_noerr( err, exit );
+ }
+ break;
+
+ default:
+ err = kStateErr;
+ goto exit;
+ }
+
+ CFDictionarySetValue( resultsDict, kDotLocalTestReportKey_CorrectResults, subtest->correctResults );
+
+ if( missingResults )
+ {
+ CFDictionarySetValue( resultsDict, kDotLocalTestReportKey_MissingResults, missingResults );
+ ForgetCF( &missingResults );
+ if( !subtest->error ) subtest->error = kNotFoundErr;
+ }
+
+ if( CFArrayGetCount( subtest->unexpectedResults ) > 0 )
+ {
+ CFDictionarySetValue( resultsDict, kDotLocalTestReportKey_UnexpectedResults, subtest->unexpectedResults );
+ if( !subtest->error ) subtest->error = kUnexpectedErr;
+ }
+
+ if( CFArrayGetCount( subtest->duplicateResults ) > 0 )
+ {
+ CFDictionarySetValue( resultsDict, kDotLocalTestReportKey_DuplicateResults, subtest->duplicateResults );
+ if( !subtest->error ) subtest->error = kDuplicateErr;
+ }
+
+ if( subtest->error ) inContext->testFailed = true;
+ err = CFDictionarySetInt64( reportDict, kDotLocalTestReportKey_Error, subtest->error );
+ require_noerr( err, exit );
+
+ reportArray = ( inContext->state == kDotLocalTestState_QuerySRV ) ? inContext->reportsQuerySRV : inContext->reportsGAI;
+ CFArrayAppendValue( reportArray, reportDict );
+
+exit:
+ _DotLocalSubtestFree( subtest );
+ CFReleaseNullSafe( reportDict );
+ return( err );
+}
+
+//===========================================================================================================================
+// _DotLocalTestFinalizeAndExit
+//===========================================================================================================================
+
+static void _DotLocalTestFinalizeAndExit( DotLocalTestContext *inContext )
+{
+ OSStatus err;
+ CFPropertyListRef plist;
+ char startTimeStr[ 32 ];
+ char endTimeStr[ 32 ];
+
+ check( !inContext->subtest );
+ inContext->endTime = NanoTimeGetCurrent();
+
+ if( inContext->replierPID != -1 )
+ {
+ kill( inContext->replierPID, SIGTERM );
+ inContext->replierPID = -1;
+ }
+ if( inContext->serverPID != -1 )
+ {
+ kill( inContext->serverPID, SIGTERM );
+ inContext->serverPID = -1;
+ }
+ err = DNSServiceRemoveRecord( inContext->connection, inContext->localSOARef, 0 );
+ require_noerr( err, exit );
+
+ _NanoTime64ToDateString( inContext->startTime, startTimeStr, sizeof( startTimeStr ) );
+ _NanoTime64ToDateString( inContext->endTime, endTimeStr, sizeof( endTimeStr ) );
+
+ err = CFPropertyListCreateFormatted( kCFAllocatorDefault, &plist,
+ "{"
+ "%kO=%s" // startTime
+ "%kO=%s" // endTime
+ "%kO=%O" // testsGAI
+ "%kO=%O" // testsQuerySRV
+ "%kO=%b" // success
+ "%kO=%s" // replierCmd
+ "%kO=%s" // serverCmd
+ "}",
+ kDotLocalTestReportKey_StartTime, startTimeStr,
+ kDotLocalTestReportKey_EndTime, endTimeStr,
+ kDotLocalTestReportKey_GetAddrInfoTests, inContext->reportsGAI,
+ kDotLocalTestReportKey_QuerySRVTests, inContext->reportsQuerySRV,
+ kDotLocalTestReportKey_Success, inContext->testFailed ? false : true,
+ kDotLocalTestReportKey_MDNSReplierCmd, inContext->replierCmd,
+ kDotLocalTestReportKey_DNSServerCmd, inContext->serverCmd );
+ require_noerr( err, exit );
+
+ ForgetCF( &inContext->reportsGAI );
+ ForgetCF( &inContext->reportsQuerySRV );
+
+ err = OutputPropertyList( plist, inContext->outputFormat, inContext->appendNewline, inContext->outputFilePath );
+ CFRelease( plist );
+ require_noerr( err, exit );
+
+ exit( inContext->testFailed ? 2 : 0 );
+
+exit:
+ ErrQuit( 1, "error: %#m\n", err );
+}
+
+//===========================================================================================================================
+// _DotLocalTestProbeQueryRecordCallback
+//===========================================================================================================================
+
+static void DNSSD_API
+ _DotLocalTestProbeQueryRecordCallback(
+ 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 )
+{
+ DotLocalTestContext * const context = (DotLocalTestContext *) inContext;
+
+ Unused( inInterfaceIndex );
+ Unused( inFullName );
+ Unused( inType );
+ Unused( inClass );
+ Unused( inRDataLen );
+ Unused( inRDataPtr );
+ Unused( inTTL );
+
+ check( context->state == kDotLocalTestState_Preparing );
+
+ require_quiet( ( inFlags & kDNSServiceFlagsAdd ) && !inError, exit );
+
+ if( inSDRef == context->op )
+ {
+ DNSServiceForget( &context->op );
+ context->serverIsReady = true;
+ }
+ else if( inSDRef == context->op2 )
+ {
+ DNSServiceForget( &context->op2 );
+ context->replierIsReady = true;
+ }
+
+ if( context->registeredSOA && context->serverIsReady && context->replierIsReady )
+ {
+ _DotLocalTestStateMachine( context );
+ }
+
+exit:
+ return;
+}
+
+//===========================================================================================================================
+// _DotLocalTestRegisterRecordCallback
+//===========================================================================================================================
+
+static void DNSSD_API
+ _DotLocalTestRegisterRecordCallback(
+ DNSServiceRef inSDRef,
+ DNSRecordRef inRecordRef,
+ DNSServiceFlags inFlags,
+ DNSServiceErrorType inError,
+ void * inContext )
+{
+ DotLocalTestContext * const context = (DotLocalTestContext *) inContext;
+
+ Unused( inSDRef );
+ Unused( inRecordRef );
+ Unused( inFlags );
+
+ if( inError ) ErrQuit( 1, "error: local. SOA record registration failed: %#m\n", inError );
+
+ if( !context->registeredSOA )
+ {
+ context->registeredSOA = true;
+ if( context->serverIsReady && context->replierIsReady ) _DotLocalTestStateMachine( context );
+ }
+}
+
+//===========================================================================================================================
+// _DotLocalTestTimerHandler
+//===========================================================================================================================
+
+static void _DotLocalTestTimerHandler( void *inContext )
+{
+ _DotLocalTestStateMachine( (DotLocalTestContext *) inContext );
+}
+
+//===========================================================================================================================
+// _DotLocalTestGAICallback
+//===========================================================================================================================
+
+static void DNSSD_API
+ _DotLocalTestGAICallback(
+ DNSServiceRef inSDRef,
+ DNSServiceFlags inFlags,
+ uint32_t inInterfaceIndex,
+ DNSServiceErrorType inError,
+ const char * inHostname,
+ const struct sockaddr * inSockAddr,
+ uint32_t inTTL,
+ void * inContext )
+{
+ OSStatus err;
+ DotLocalTestContext * const context = (DotLocalTestContext *) inContext;
+ DotLocalSubtest * const subtest = context->subtest;
+ const sockaddr_ip * const sip = (const sockaddr_ip *) inSockAddr;
+
+ Unused( inSDRef );
+ Unused( inInterfaceIndex );
+ Unused( inHostname );
+ Unused( inTTL );
+
+ require_action_quiet( inFlags & kDNSServiceFlagsAdd, exit, err = kFlagErr );
+ require_action_quiet( ( sip->sa.sa_family == AF_INET ) || ( sip->sa.sa_family == AF_INET6 ), exit, err = kTypeErr );
+
+ if( context->state == kDotLocalTestState_GAINoSuchRecord )
+ {
+ if( inError == kDNSServiceErr_NoSuchRecord )
+ {
+ CFMutableArrayRef array = NULL;
+ const char * noSuchRecordStr;
+
+ if( sip->sa.sa_family == AF_INET )
+ {
+ array = subtest->needDNSv4 ? subtest->correctResults : subtest->duplicateResults;
+ subtest->needDNSv4 = false;
+
+ noSuchRecordStr = kNoSuchRecordAStr;
+ }
+ else
+ {
+ array = subtest->needDNSv6 ? subtest->correctResults : subtest->duplicateResults;
+ subtest->needDNSv6 = false;
+
+ noSuchRecordStr = kNoSuchRecordAAAAStr;
+ }
+ err = CFPropertyListAppendFormatted( kCFAllocatorDefault, array, "%s", noSuchRecordStr );
+ require_noerr( err, fatal );
+ }
+ else if( !inError )
+ {
+ err = CFPropertyListAppendFormatted( kCFAllocatorDefault, subtest->unexpectedResults, "%##a", sip );
+ require_noerr( err, fatal );
+ }
+ else
+ {
+ err = inError;
+ goto exit;
+ }
+ }
+ else
+ {
+ if( !inError )
+ {
+ CFMutableArrayRef array = NULL;
+
+ if( sip->sa.sa_family == AF_INET )
+ {
+ const uint32_t addrV4 = sip->v4.sin_addr.s_addr;
+
+ if( subtest->hasDNSv4 && ( addrV4 == subtest->addrDNSv4 ) )
+ {
+ array = subtest->needDNSv4 ? subtest->correctResults : subtest->duplicateResults;
+ subtest->needDNSv4 = false;
+ }
+ else if( subtest->hasMDNSv4 && ( addrV4 == subtest->addrMDNSv4 ) )
+ {
+ array = subtest->needMDNSv4 ? subtest->correctResults : subtest->duplicateResults;
+ subtest->needMDNSv4 = false;
+ }
+ }
+ else
+ {
+ const uint8_t * const addrV6 = sip->v6.sin6_addr.s6_addr;
+
+ if( subtest->hasDNSv6 && ( memcmp( addrV6, subtest->addrDNSv6, 16 ) == 0 ) )
+ {
+ array = subtest->needDNSv6 ? subtest->correctResults : subtest->duplicateResults;
+ subtest->needDNSv6 = false;
+ }
+ else if( subtest->hasMDNSv6 && ( memcmp( addrV6, subtest->addrMDNSv6, 16 ) == 0 ) )
+ {
+ array = subtest->needMDNSv6 ? subtest->correctResults : subtest->duplicateResults;
+ subtest->needMDNSv6 = false;
+ }
+ }
+ if( !array ) array = subtest->unexpectedResults;
+ err = CFPropertyListAppendFormatted( kCFAllocatorDefault, array, "%##a", sip );
+ require_noerr( err, fatal );
+ }
+ else if( inError == kDNSServiceErr_NoSuchRecord )
+ {
+ err = CFPropertyListAppendFormatted( kCFAllocatorDefault, subtest->unexpectedResults, "%s",
+ ( sip->sa.sa_family == AF_INET ) ? kNoSuchRecordAStr : kNoSuchRecordAAAAStr );
+ require_noerr( err, fatal );
+ }
+ else
+ {
+ err = inError;
+ goto exit;
+ }
+ }
+
+exit:
+ if( err )
+ {
+ subtest->error = err;
+ _DotLocalTestStateMachine( context );
+ }
+ return;
+
+fatal:
+ ErrQuit( 1, "error: %#m\n", err );
+}
+
+//===========================================================================================================================
+// _DotLocalTestQueryRecordCallback
+//===========================================================================================================================
+
+static void DNSSD_API
+ _DotLocalTestQueryRecordCallback(
+ 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;
+ DotLocalTestContext * const context = (DotLocalTestContext *) inContext;
+ DotLocalSubtest * const subtest = context->subtest;
+ const SRVRecordDataFixedFields * fields;
+ const uint8_t * target;
+ const uint8_t * ptr;
+ const uint8_t * end;
+ char * rdataStr;
+ unsigned int priority, weight, port;
+ CFMutableArrayRef array;
+
+ Unused( inSDRef );
+ Unused( inInterfaceIndex );
+ Unused( inFullName );
+ Unused( inTTL );
+
+ check( context->state == kDotLocalTestState_QuerySRV );
+
+ err = inError;
+ require_noerr_quiet( err, exit );
+ require_action_quiet( inFlags & kDNSServiceFlagsAdd, exit, err = kFlagErr );
+ require_action_quiet( ( inType == kDNSServiceType_SRV ) && ( inClass == kDNSServiceClass_IN ), exit, err = kTypeErr );
+ require_action_quiet( inRDataLen > sizeof( SRVRecordDataFixedFields ), exit, err = kSizeErr );
+
+ fields = (const SRVRecordDataFixedFields *) inRDataPtr;
+ SRVRecordDataFixedFieldsGet( fields, &priority, &weight, &port );
+ target = (const uint8_t *) &fields[ 1 ];
+ end = ( (const uint8_t *) inRDataPtr ) + inRDataLen;
+ for( ptr = target; ( ptr < end ) && ( *ptr != 0 ); ptr += ( 1 + *ptr ) ) {}
+
+ if( ( priority == kDotLocalTestSRV_Priority ) &&
+ ( weight == kDotLocalTestSRV_Weight ) &&
+ ( port == kDotLocalTestSRV_Port ) &&
+ ( ptr < end ) && DomainNameEqual( target, kDotLocalTestSRV_TargetName ) )
+ {
+ array = subtest->needSRV ? subtest->correctResults : subtest->duplicateResults;
+ subtest->needSRV = false;
+ }
+ else
+ {
+ array = subtest->unexpectedResults;
+ }
+
+ rdataStr = NULL;
+ DNSRecordDataToString( inRDataPtr, inRDataLen, kDNSServiceType_SRV, NULL, 0, &rdataStr );
+ if( !rdataStr )
+ {
+ ASPrintF( &rdataStr, "%#H", inRDataPtr, inRDataLen, inRDataLen );
+ require_action( rdataStr, fatal, err = kNoMemoryErr );
+ }
+
+ err = CFPropertyListAppendFormatted( kCFAllocatorDefault, array, "%s", rdataStr );
+ free( rdataStr );
+ require_noerr( err, fatal );
+
+exit:
+ if( err )
+ {
+ subtest->error = err;
+ _DotLocalTestStateMachine( context );
+ }
+ return;
+
+fatal:
+ ErrQuit( 1, "error: %#m\n", err );
+}
+
+//===========================================================================================================================
+// ProbeConflictTestCmd
+//===========================================================================================================================
+
+#define kProbeConflictTestService_DefaultName "name"
+#define kProbeConflictTestService_Port 60000
+
+#define kProbeConflictTestTXTPtr "\x13" "PROBE-CONFLICT-TEST"
+#define kProbeConflictTestTXTLen sizeof_string( kProbeConflictTestTXTPtr )
+
+typedef struct
+{
+ const char * description;
+ const char * program;
+ Boolean expectsRename;
+
+} ProbeConflictTestCase;
+
+#define kPCTProgPreWait "wait 1000;" // Wait 1 second before sending gratuitous response.
+#define kPCTProgPostWait "wait 8000;" // Wait 8 seconds after sending gratuitous response.
+ // This allows ~2.75 seconds for probing and ~5 seconds for a rename.
+
+static const ProbeConflictTestCase kProbeConflictTestCases[] =
+{
+ // No conflicts
+
+ { "No probe conflicts.", kPCTProgPreWait "probes n-n-n;" "send;" kPCTProgPostWait, false },
+
+ // One multicast probe conflict
+
+ { "One multicast probe conflict (1).", kPCTProgPreWait "probes m;" "send;" kPCTProgPostWait, false },
+ { "One multicast probe conflict (2).", kPCTProgPreWait "probes n-m;" "send;" kPCTProgPostWait, false },
+ { "One multicast probe conflict (3).", kPCTProgPreWait "probes n-n-m;" "send;" kPCTProgPostWait, false },
+
+ // One unicast probe conflict
+
+ { "One unicast probe conflict (1).", kPCTProgPreWait "probes u;" "send;" kPCTProgPostWait, true },
+ { "One unicast probe conflict (2).", kPCTProgPreWait "probes n-u;" "send;" kPCTProgPostWait, true },
+ { "One unicast probe conflict (3).", kPCTProgPreWait "probes n-n-u;" "send;" kPCTProgPostWait, true },
+
+ // One multicast and one unicast probe conflict
+
+ { "Multicast and unicast probe conflict (1).", kPCTProgPreWait "probes m-u;" "send;" kPCTProgPostWait, true },
+ { "Multicast and unicast probe conflict (2).", kPCTProgPreWait "probes m-n-u;" "send;" kPCTProgPostWait, true },
+ { "Multicast and unicast probe conflict (3).", kPCTProgPreWait "probes m-n-n-u;" "send;" kPCTProgPostWait, true },
+ { "Multicast and unicast probe conflict (4).", kPCTProgPreWait "probes n-m-u;" "send;" kPCTProgPostWait, true },
+ { "Multicast and unicast probe conflict (5).", kPCTProgPreWait "probes n-m-n-u;" "send;" kPCTProgPostWait, true },
+ { "Multicast and unicast probe conflict (6).", kPCTProgPreWait "probes n-m-n-n-u;" "send;" kPCTProgPostWait, true },
+ { "Multicast and unicast probe conflict (7).", kPCTProgPreWait "probes n-n-m-u;" "send;" kPCTProgPostWait, true },
+ { "Multicast and unicast probe conflict (8).", kPCTProgPreWait "probes n-n-m-n-u;" "send;" kPCTProgPostWait, true },
+ { "Multicast and unicast probe conflict (9).", kPCTProgPreWait "probes n-n-m-n-n-u;" "send;" kPCTProgPostWait, true },
+
+ // Two multicast probe conflicts
+
+ { "Two multicast probe conflicts (1).", kPCTProgPreWait "probes m-m;" "send;" kPCTProgPostWait, true },
+ { "Two multicast probe conflicts (2).", kPCTProgPreWait "probes m-n-m;" "send;" kPCTProgPostWait, true },
+ { "Two multicast probe conflicts (3).", kPCTProgPreWait "probes m-n-n-m;" "send;" kPCTProgPostWait, true },
+ { "Two multicast probe conflicts (4).", kPCTProgPreWait "probes n-m-m;" "send;" kPCTProgPostWait, true },
+ { "Two multicast probe conflicts (5).", kPCTProgPreWait "probes n-m-n-m-n;" "send;" kPCTProgPostWait, true },
+ { "Two multicast probe conflicts (6).", kPCTProgPreWait "probes n-m-n-n-m;" "send;" kPCTProgPostWait, true },
+ { "Two multicast probe conflicts (7).", kPCTProgPreWait "probes n-n-m-m;" "send;" kPCTProgPostWait, true },
+ { "Two multicast probe conflicts (8).", kPCTProgPreWait "probes n-n-m-n-m;" "send;" kPCTProgPostWait, true },
+ { "Two multicast probe conflicts (9).", kPCTProgPreWait "probes n-n-m-n-n-m;" "send;" kPCTProgPostWait, true },
+};
+
+#define kProbeConflictTestCaseCount countof( kProbeConflictTestCases )
+
+typedef struct
+{
+ DNSServiceRef registration; // Test service registration.
+ NanoTime64 testStartTime; // Test's start time.
+ NanoTime64 startTime; // Current test case's start time.
+ MDNSColliderRef collider; // mDNS collider object.
+ CFMutableArrayRef results; // Array of test case results.
+ char * serviceName; // Test service's instance name as a string. (malloced)
+ char * serviceType; // Test service's service type as a string. (malloced)
+ uint8_t * recordName; // FQDN of collider's record (same as test service's SRV+TXT records). (malloced)
+ unsigned int testCaseIndex; // Index of the current test case.
+ uint32_t ifIndex; // Index of the interface that the collider is to operate on.
+ char * outputFilePath; // File to write test results to. If NULL, then write to stdout. (malloced)
+ OutputFormatType outputFormat; // Format of test report output.
+ Boolean appendNewline; // True if a newline character should be appended to JSON output.
+ Boolean registered; // True if the test service instance is currently registered.
+ Boolean testFailed; // True if at least one test case failed.
+
+} ProbeConflictTestContext;
+
+static void DNSSD_API
+ _ProbeConflictTestRegisterCallback(
+ DNSServiceRef inSDRef,
+ DNSServiceFlags inFlags,
+ DNSServiceErrorType inError,
+ const char * inName,
+ const char * inType,
+ const char * inDomain,
+ void * inContext );
+static void _ProbeConflictTestColliderStopHandler( void *inContext, OSStatus inError );
+static OSStatus _ProbeConflictTestStartNextTest( ProbeConflictTestContext *inContext );
+static OSStatus _ProbeConflictTestStopCurrentTest( ProbeConflictTestContext *inContext, Boolean inRenamed );
+static void _ProbeConflictTestFinalizeAndExit( ProbeConflictTestContext *inContext ) ATTRIBUTE_NORETURN;
+
+static void ProbeConflictTestCmd( void )
+{
+ OSStatus err;
+ ProbeConflictTestContext * context;
+ const char * serviceName;
+ char tag[ 6 + 1 ];
+
+ context = (ProbeConflictTestContext *) calloc( 1, sizeof( *context ) );
+ require_action( context, exit, err = kNoMemoryErr );
+
+ if( gProbeConflictTest_Interface )
+ {
+ err = InterfaceIndexFromArgString( gProbeConflictTest_Interface, &context->ifIndex );
+ require_noerr_quiet( err, exit );
+ }
+ else
+ {
+ err = GetAnyMDNSInterface( NULL, &context->ifIndex );
+ require_noerr_quiet( err, exit );
+ }
+
+ if( gProbeConflictTest_OutputFilePath )
+ {
+ context->outputFilePath = strdup( gProbeConflictTest_OutputFilePath );
+ require_action( context->outputFilePath, exit, err = kNoMemoryErr );
+ }
+
+ context->appendNewline = gProbeConflictTest_OutputAppendNewline ? true : false;
+ context->outputFormat = (OutputFormatType) CLIArgToValue( "format", gProbeConflictTest_OutputFormat, &err,
+ kOutputFormatStr_JSON, kOutputFormatType_JSON,
+ kOutputFormatStr_XML, kOutputFormatType_XML,
+ kOutputFormatStr_Binary, kOutputFormatType_Binary,
+ NULL );
+ require_noerr_quiet( err, exit );
+
+ context->results = CFArrayCreateMutable( NULL, kProbeConflictTestCaseCount, &kCFTypeArrayCallBacks );
+ require_action( context->results, exit, err = kNoMemoryErr );
+
+ serviceName = gProbeConflictTest_UseComputerName ? NULL : kProbeConflictTestService_DefaultName;
+
+ ASPrintF( &context->serviceType, "_pctest-%s._udp",
+ _RandomStringExact( kLowerAlphaNumericCharSet, kLowerAlphaNumericCharSetSize, sizeof( tag ) - 1, tag ) );
+ require_action( context->serviceType, exit, err = kNoMemoryErr );
+
+ context->testStartTime = NanoTimeGetCurrent();
+ err = DNSServiceRegister( &context->registration, 0, context->ifIndex, serviceName, context->serviceType, "local.",
+ NULL, htons( kProbeConflictTestService_Port ), 0, NULL, _ProbeConflictTestRegisterCallback, context );
+ require_noerr( err, exit );
+
+ err = DNSServiceSetDispatchQueue( context->registration, dispatch_get_main_queue() );
+ require_noerr( err, exit );
+
+ dispatch_main();
+
+exit:
+ exit( 1 );
+}
+
+//===========================================================================================================================
+// _ProbeConflictTestRegisterCallback
+//===========================================================================================================================
+
+static void DNSSD_API
+ _ProbeConflictTestRegisterCallback(
+ DNSServiceRef inSDRef,
+ DNSServiceFlags inFlags,
+ DNSServiceErrorType inError,
+ const char * inName,
+ const char * inType,
+ const char * inDomain,
+ void * inContext )
+{
+ OSStatus err;
+ ProbeConflictTestContext * const context = (ProbeConflictTestContext *) inContext;
+
+ Unused( inSDRef );
+ Unused( inType );
+ Unused( inDomain );
+
+ err = inError;
+ require_noerr( err, exit );
+
+ if( !context->registered )
+ {
+ if( inFlags & kDNSServiceFlagsAdd )
+ {
+ uint8_t * ptr;
+ size_t recordNameLen;
+ unsigned int len;
+ uint8_t name[ kDomainNameLengthMax ];
+
+ context->registered = true;
+
+ FreeNullSafe( context->serviceName );
+ context->serviceName = strdup( inName );
+ require_action( context->serviceName, exit, err = kNoMemoryErr );
+
+ err = DomainNameFromString( name, context->serviceName, NULL );
+ require_noerr( err, exit );
+
+ err = DomainNameAppendString( name, context->serviceType, NULL );
+ require_noerr( err, exit );
+
+ err = DomainNameAppendString( name, "local", NULL );
+ require_noerr( err, exit );
+
+ ForgetMem( &context->recordName );
+ err = DomainNameDup( name, &context->recordName, &recordNameLen );
+ require_noerr( err, exit );
+ require_fatal( recordNameLen > 0, "Record name length is zero." ); // Prevents dubious static analyzer warning.
+
+ // Make the first label all caps so that it's easier to spot in system logs.
+
+ ptr = context->recordName;
+ for( len = *ptr++; len > 0; --len, ++ptr ) *ptr = (uint8_t) toupper_safe( *ptr );
+
+ err = _ProbeConflictTestStartNextTest( context );
+ require_noerr( err, exit );
+ }
+ }
+ else
+ {
+ if( !( inFlags & kDNSServiceFlagsAdd ) )
+ {
+ context->registered = false;
+ err = _ProbeConflictTestStopCurrentTest( context, true );
+ require_noerr( err, exit );
+ }
+ }
+ err = kNoErr;
+
+exit:
+ if( err ) exit( 1 );
+}
+
+//===========================================================================================================================
+// _ProbeConflictTestColliderStopHandler
+//===========================================================================================================================
+
+static void _ProbeConflictTestColliderStopHandler( void *inContext, OSStatus inError )
+{
+ OSStatus err;
+ ProbeConflictTestContext * const context = (ProbeConflictTestContext *) inContext;
+
+ err = inError;
+ require_noerr_quiet( err, exit );
+
+ ForgetCF( &context->collider );
+
+ err = _ProbeConflictTestStopCurrentTest( context, false );
+ require_noerr( err, exit );
+
+ err = _ProbeConflictTestStartNextTest( context );
+ require_noerr( err, exit );
+
+exit:
+ if( err ) exit( 1 );
+}
+
+//===========================================================================================================================
+// _ProbeConflictTestStartNextTest
+//===========================================================================================================================
+
+static OSStatus _ProbeConflictTestStartNextTest( ProbeConflictTestContext *inContext )
+{
+ OSStatus err;
+ const ProbeConflictTestCase * testCase;
+
+ check( !inContext->collider );
+
+ if( inContext->testCaseIndex < kProbeConflictTestCaseCount )
+ {
+ testCase = &kProbeConflictTestCases[ inContext->testCaseIndex ];
+ }
+ else
+ {
+ _ProbeConflictTestFinalizeAndExit( inContext );
+ }
+
+ err = MDNSColliderCreate( dispatch_get_main_queue(), &inContext->collider );
+ require_noerr( err, exit );
+
+ err = MDNSColliderSetProgram( inContext->collider, testCase->program );
+ require_noerr( err, exit );
+
+ err = MDNSColliderSetRecord( inContext->collider, inContext->recordName, kDNSServiceType_TXT,
+ kProbeConflictTestTXTPtr, kProbeConflictTestTXTLen );
+ require_noerr( err, exit );
+
+ MDNSColliderSetProtocols( inContext->collider, kMDNSColliderProtocol_IPv4 );
+ MDNSColliderSetInterfaceIndex( inContext->collider, inContext->ifIndex );
+ MDNSColliderSetStopHandler( inContext->collider, _ProbeConflictTestColliderStopHandler, inContext );
+
+ inContext->startTime = NanoTimeGetCurrent();
+ err = MDNSColliderStart( inContext->collider );
+ require_noerr( err, exit );
+
+exit:
+ return( err );
+}
+
+//===========================================================================================================================
+// _ProbeConflictTestStopCurrentTest
+//===========================================================================================================================
+
+#define kProbeConflictTestCaseResultKey_Description CFSTR( "description" )
+#define kProbeConflictTestCaseResultKey_StartTime CFSTR( "startTime" )
+#define kProbeConflictTestCaseResultKey_EndTime CFSTR( "endTime" )
+#define kProbeConflictTestCaseResultKey_ExpectedRename CFSTR( "expectedRename" )
+#define kProbeConflictTestCaseResultKey_ServiceName CFSTR( "serviceName" )
+#define kProbeConflictTestCaseResultKey_Passed CFSTR( "passed" )
+
+static OSStatus _ProbeConflictTestStopCurrentTest( ProbeConflictTestContext *inContext, Boolean inRenamed )
+{
+ OSStatus err;
+ const ProbeConflictTestCase * testCase;
+ NanoTime64 endTime;
+ Boolean passed;
+ char startTimeStr[ 32 ];
+ char endTimeStr[ 32 ];
+
+ endTime = NanoTimeGetCurrent();
+
+ if( inContext->collider )
+ {
+ MDNSColliderSetStopHandler( inContext->collider, NULL, NULL );
+ MDNSColliderStop( inContext->collider );
+ CFRelease( inContext->collider );
+ inContext->collider = NULL;
+ }
+
+ testCase = &kProbeConflictTestCases[ inContext->testCaseIndex ];
+ passed = ( ( testCase->expectsRename && inRenamed ) || ( !testCase->expectsRename && !inRenamed ) ) ? true : false;
+ if( !passed ) inContext->testFailed = true;
+
+ _NanoTime64ToDateString( inContext->startTime, startTimeStr, sizeof( startTimeStr ) );
+ _NanoTime64ToDateString( endTime, endTimeStr, sizeof( endTimeStr ) );
+
+ err = CFPropertyListAppendFormatted( kCFAllocatorDefault, inContext->results,
+ "{"
+ "%kO=%s" // description
+ "%kO=%b" // expectedRename
+ "%kO=%s" // startTime
+ "%kO=%s" // endTime
+ "%kO=%s" // serviceName
+ "%kO=%b" // passed
+ "}",
+ kProbeConflictTestCaseResultKey_Description, testCase->description,
+ kProbeConflictTestCaseResultKey_ExpectedRename, testCase->expectsRename,
+ kProbeConflictTestCaseResultKey_StartTime, startTimeStr,
+ kProbeConflictTestCaseResultKey_EndTime, endTimeStr,
+ kProbeConflictTestCaseResultKey_ServiceName, inContext->serviceName,
+ kProbeConflictTestCaseResultKey_Passed, passed );
+ require_noerr( err, exit );
+
+ ++inContext->testCaseIndex;
+
+exit:
+ return( err );
+}
+
+//===========================================================================================================================
+// _ProbeConflictTestFinalizeAndExit
+//===========================================================================================================================
+
+#define kProbeConflictTestReportKey_StartTime CFSTR( "startTime" )
+#define kProbeConflictTestReportKey_EndTime CFSTR( "endTime" )
+#define kProbeConflictTestReportKey_ServiceType CFSTR( "serviceType" )
+#define kProbeConflictTestReportKey_Results CFSTR( "results" )
+#define kProbeConflictTestReportKey_Passed CFSTR( "passed" )
+
+static void _ProbeConflictTestFinalizeAndExit( ProbeConflictTestContext *inContext )
+{
+ OSStatus err;
+ CFPropertyListRef plist;
+ NanoTime64 endTime;
+ char startTimeStr[ 32 ];
+ char endTimeStr[ 32 ];
+
+ endTime = NanoTimeGetCurrent();
+
+ check( !inContext->collider );
+
+ _NanoTime64ToDateString( inContext->testStartTime, startTimeStr, sizeof( startTimeStr ) );
+ _NanoTime64ToDateString( endTime, endTimeStr, sizeof( endTimeStr ) );
+
+ err = CFPropertyListCreateFormatted( kCFAllocatorDefault, &plist,
+ "{"
+ "%kO=%s" // startTime
+ "%kO=%s" // endTime
+ "%kO=%s" // serviceType
+ "%kO=%O" // results
+ "%kO=%b" // passed
+ "}",
+ kProbeConflictTestReportKey_StartTime, startTimeStr,
+ kProbeConflictTestReportKey_EndTime, endTimeStr,
+ kProbeConflictTestReportKey_ServiceType, inContext->serviceType,
+ kProbeConflictTestReportKey_Results, inContext->results,
+ kProbeConflictTestReportKey_Passed, inContext->testFailed ? false : true );
+ require_noerr( err, exit );
+ ForgetCF( &inContext->results );
+
+ err = OutputPropertyList( plist, inContext->outputFormat, inContext->appendNewline, inContext->outputFilePath );
+ CFRelease( plist );
+ require_noerr( err, exit );
+
+ exit( inContext->testFailed ? 2 : 0 );
+
+exit:
+ ErrQuit( 1, "error: %#m\n", err );
+}
+
+//===========================================================================================================================
+// SSDPDiscoverCmd
+//===========================================================================================================================
+
+#define kSSDPPort 1900
+
+typedef struct
+{
+ HTTPHeader header; // HTTP header object for sending and receiving.
+ dispatch_source_t readSourceV4; // Read dispatch source for IPv4 socket.
+ dispatch_source_t readSourceV6; // Read dispatch source for IPv6 socket.
+ int receiveSecs; // After send, the amount of time to spend receiving.
+ uint32_t ifindex; // Index of the interface over which to send the query.
+ Boolean useIPv4; // True if the query should be sent via IPv4 multicast.
+ Boolean useIPv6; // True if the query should be sent via IPv6 multicast.
+
+} SSDPDiscoverContext;
+
+static void SSDPDiscoverPrintPrologue( const SSDPDiscoverContext *inContext );
+static void SSDPDiscoverReadHandler( void *inContext );
+static int SocketToPortNumber( SocketRef inSock );
+static OSStatus WriteSSDPSearchRequest( HTTPHeader *inHeader, const void *inHostSA, int inMX, const char *inST );
+
+static void SSDPDiscoverCmd( void )
+{
+ OSStatus err;
+ struct timeval now;
+ SSDPDiscoverContext * context;
+ dispatch_source_t signalSource = NULL;
+ SocketRef sockV4 = kInvalidSocketRef;
+ SocketRef sockV6 = kInvalidSocketRef;
+ ssize_t n;
+ int sendCount;
+
+ // 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( gSSDPDiscover_ReceiveSecs < -1 )
+ {
+ FPrintF( stdout, "Invalid receive time: %d seconds.\n", gSSDPDiscover_ReceiveSecs );
+ err = kParamErr;
+ goto exit;
+ }
+
+ // Create context.
+
+ context = (SSDPDiscoverContext *) calloc( 1, sizeof( *context ) );
+ require_action( context, exit, err = kNoMemoryErr );
+
+ context->receiveSecs = gSSDPDiscover_ReceiveSecs;
+ context->useIPv4 = ( gSSDPDiscover_UseIPv4 || !gSSDPDiscover_UseIPv6 ) ? true : false;
+ context->useIPv6 = ( gSSDPDiscover_UseIPv6 || !gSSDPDiscover_UseIPv4 ) ? true : false;
+
+ err = InterfaceIndexFromArgString( gInterface, &context->ifindex );
+ require_noerr_quiet( err, exit );
+
+ // Set up IPv4 socket.
+
+ if( context->useIPv4 )
+ {
+ int port;
+ err = UDPClientSocketOpen( AF_INET, NULL, 0, -1, &port, &sockV4 );
+ require_noerr( err, exit );
+
+ err = SocketSetMulticastInterface( sockV4, NULL, 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 );
}
// Set up IPv6 socket.
- if( context->useIPv6 )
+ if( context->useIPv6 )
+ {
+ err = UDPClientSocketOpen( AF_INET6, NULL, 0, -1, NULL, &sockV6 );
+ require_noerr( err, exit );
+
+ err = SocketSetMulticastInterface( sockV6, NULL, 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 );
+ }
+
+ // Print prologue.
+
+ SSDPDiscoverPrintPrologue( context );
+
+ // Send mDNS query message.
+
+ sendCount = 0;
+ if( IsValidSocket( sockV4 ) )
+ {
+ struct sockaddr_in mcastAddr4;
+
+ memset( &mcastAddr4, 0, sizeof( mcastAddr4 ) );
+ SIN_LEN_SET( &mcastAddr4 );
+ mcastAddr4.sin_family = AF_INET;
+ mcastAddr4.sin_port = htons( kSSDPPort );
+ mcastAddr4.sin_addr.s_addr = htonl( 0xEFFFFFFA ); // 239.255.255.250
+
+ err = WriteSSDPSearchRequest( &context->header, &mcastAddr4, gSSDPDiscover_MX, gSSDPDiscover_ST );
+ require_noerr( err, exit );
+
+ n = sendto( sockV4, context->header.buf, context->header.len, 0, (const struct sockaddr *) &mcastAddr4,
+ (socklen_t) sizeof( mcastAddr4 ) );
+ err = map_socket_value_errno( sockV4, n == (ssize_t) context->header.len, n );
+ if( err )
+ {
+ FPrintF( stderr, "*** Failed to send query on IPv4 socket with error %#m\n", err );
+ ForgetSocket( &sockV4 );
+ }
+ else
+ {
+ if( gSSDPDiscover_Verbose )
+ {
+ gettimeofday( &now, NULL );
+ FPrintF( stdout, "---\n" );
+ FPrintF( stdout, "Send time: %{du:time}\n", &now );
+ FPrintF( stdout, "Source Port: %d\n", SocketToPortNumber( sockV4 ) );
+ FPrintF( stdout, "Destination: %##a\n", &mcastAddr4 );
+ FPrintF( stdout, "Message size: %zu\n", context->header.len );
+ FPrintF( stdout, "HTTP header:\n%1{text}", context->header.buf, context->header.len );
+ }
+ ++sendCount;
+ }
+ }
+
+ if( IsValidSocket( sockV6 ) )
+ {
+ struct sockaddr_in6 mcastAddr6;
+
+ memset( &mcastAddr6, 0, sizeof( mcastAddr6 ) );
+ SIN6_LEN_SET( &mcastAddr6 );
+ mcastAddr6.sin6_family = AF_INET6;
+ mcastAddr6.sin6_port = htons( kSSDPPort );
+ mcastAddr6.sin6_addr.s6_addr[ 0 ] = 0xFF; // SSDP IPv6 link-local multicast address FF02::C
+ mcastAddr6.sin6_addr.s6_addr[ 1 ] = 0x02;
+ mcastAddr6.sin6_addr.s6_addr[ 15 ] = 0x0C;
+
+ err = WriteSSDPSearchRequest( &context->header, &mcastAddr6, gSSDPDiscover_MX, gSSDPDiscover_ST );
+ require_noerr( err, exit );
+
+ n = sendto( sockV6, context->header.buf, context->header.len, 0, (const struct sockaddr *) &mcastAddr6,
+ (socklen_t) sizeof( mcastAddr6 ) );
+ err = map_socket_value_errno( sockV6, n == (ssize_t) context->header.len, n );
+ if( err )
+ {
+ FPrintF( stderr, "*** Failed to send query on IPv6 socket with error %#m\n", err );
+ ForgetSocket( &sockV6 );
+ }
+ else
+ {
+ if( gSSDPDiscover_Verbose )
+ {
+ gettimeofday( &now, NULL );
+ FPrintF( stdout, "---\n" );
+ FPrintF( stdout, "Send time: %{du:time}\n", &now );
+ FPrintF( stdout, "Source Port: %d\n", SocketToPortNumber( sockV6 ) );
+ FPrintF( stdout, "Destination: %##a\n", &mcastAddr6 );
+ FPrintF( stdout, "Message size: %zu\n", context->header.len );
+ FPrintF( stdout, "HTTP header:\n%1{text}", context->header.buf, context->header.len );
+ }
+ ++sendCount;
+ }
+ }
+ require_action_quiet( sendCount > 0, exit, err = kUnexpectedErr );
+
+ // If there's no wait period after the send, then exit.
+
+ if( context->receiveSecs == 0 ) goto exit;
+
+ // Create dispatch read sources for socket(s).
+
+ if( IsValidSocket( sockV4 ) )
+ {
+ SocketContext * sockCtx;
+
+ err = SocketContextCreate( sockV4, context, &sockCtx );
+ require_noerr( err, exit );
+ sockV4 = kInvalidSocketRef;
+
+ err = DispatchReadSourceCreate( sockCtx->sock, NULL, SSDPDiscoverReadHandler, SocketContextCancelHandler, sockCtx,
+ &context->readSourceV4 );
+ if( err ) ForgetSocketContext( &sockCtx );
+ require_noerr( err, exit );
+
+ dispatch_resume( context->readSourceV4 );
+ }
+
+ if( IsValidSocket( sockV6 ) )
+ {
+ SocketContext * sockCtx;
+
+ err = SocketContextCreate( sockV6, context, &sockCtx );
+ require_noerr( err, exit );
+ sockV6 = kInvalidSocketRef;
+
+ err = DispatchReadSourceCreate( sockCtx->sock, NULL, SSDPDiscoverReadHandler, SocketContextCancelHandler, sockCtx,
+ &context->readSourceV6 );
+ if( err ) ForgetSocketContext( &sockCtx );
+ require_noerr( err, exit );
+
+ dispatch_resume( context->readSourceV6 );
+ }
+
+ if( context->receiveSecs > 0 )
+ {
+ dispatch_after_f( dispatch_time_seconds( context->receiveSecs ), dispatch_get_main_queue(), kExitReason_Timeout,
+ Exit );
+ }
+ dispatch_main();
+
+exit:
+ ForgetSocket( &sockV4 );
+ ForgetSocket( &sockV6 );
+ dispatch_source_forget( &signalSource );
+ if( err ) exit( 1 );
+}
+
+static int SocketToPortNumber( SocketRef inSock )
+{
+ OSStatus err;
+ sockaddr_ip sip;
+ socklen_t len;
+
+ len = (socklen_t) sizeof( sip );
+ err = getsockname( inSock, &sip.sa, &len );
+ err = map_socket_noerr_errno( inSock, err );
+ check_noerr( err );
+ return( err ? -1 : SockAddrGetPort( &sip ) );
+}
+
+static OSStatus WriteSSDPSearchRequest( HTTPHeader *inHeader, const void *inHostSA, int inMX, const char *inST )
+{
+ OSStatus err;
+
+ err = HTTPHeader_InitRequest( inHeader, "M-SEARCH", "*", "HTTP/1.1" );
+ require_noerr( err, exit );
+
+ err = HTTPHeader_SetField( inHeader, "Host", "%##a", inHostSA );
+ require_noerr( err, exit );
+
+ err = HTTPHeader_SetField( inHeader, "ST", "%s", inST ? inST : "ssdp:all" );
+ require_noerr( err, exit );
+
+ err = HTTPHeader_SetField( inHeader, "Man", "\"ssdp:discover\"" );
+ require_noerr( err, exit );
+
+ err = HTTPHeader_SetField( inHeader, "MX", "%d", inMX );
+ require_noerr( err, exit );
+
+ err = HTTPHeader_Commit( inHeader );
+ require_noerr( err, exit );
+
+exit:
+ return( err );
+}
+
+//===========================================================================================================================
+// SSDPDiscoverPrintPrologue
+//===========================================================================================================================
+
+static void SSDPDiscoverPrintPrologue( const SSDPDiscoverContext *inContext )
+{
+ const int receiveSecs = inContext->receiveSecs;
+ const char * ifName;
+ char ifNameBuf[ IF_NAMESIZE + 1 ];
+ NetTransportType ifType;
+
+ ifName = if_indextoname( inContext->ifindex, ifNameBuf );
+
+ ifType = kNetTransportType_Undefined;
+ if( ifName ) SocketGetInterfaceInfo( kInvalidSocketRef, ifName, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &ifType );
+
+ FPrintF( stdout, "Interface: %s/%d/%s\n",
+ ifName ? ifName : "?", inContext->ifindex, NetTransportTypeToString( ifType ) );
+ FPrintF( stdout, "IP protocols: %?s%?s%?s\n",
+ inContext->useIPv4, "IPv4", ( inContext->useIPv4 && inContext->useIPv6 ), ", ", inContext->useIPv6, "IPv6" );
+ FPrintF( stdout, "Receive duration: " );
+ if( receiveSecs >= 0 ) FPrintF( stdout, "%d second%?c\n", receiveSecs, receiveSecs != 1, 's' );
+ else FPrintF( stdout, "â\n" );
+ FPrintF( stdout, "Start time: %{du:time}\n", NULL );
+}
+
+//===========================================================================================================================
+// SSDPDiscoverReadHandler
+//===========================================================================================================================
+
+static void SSDPDiscoverReadHandler( void *inContext )
+{
+ OSStatus err;
+ struct timeval now;
+ SocketContext * const sockCtx = (SocketContext *) inContext;
+ SSDPDiscoverContext * const context = (SSDPDiscoverContext *) sockCtx->userContext;
+ HTTPHeader * const header = &context->header;
+ sockaddr_ip fromAddr;
+ size_t msgLen;
+
+ gettimeofday( &now, NULL );
+
+ err = SocketRecvFrom( sockCtx->sock, header->buf, sizeof( header->buf ), &msgLen, &fromAddr, sizeof( fromAddr ),
+ NULL, NULL, NULL, NULL );
+ require_noerr( err, exit );
+
+ FPrintF( stdout, "---\n" );
+ FPrintF( stdout, "Receive time: %{du:time}\n", &now );
+ FPrintF( stdout, "Source: %##a\n", &fromAddr );
+ FPrintF( stdout, "Message size: %zu\n", msgLen );
+ header->len = msgLen;
+ if( HTTPHeader_Validate( header ) )
+ {
+ FPrintF( stdout, "HTTP header:\n%1{text}", header->buf, header->len );
+ if( header->extraDataLen > 0 )
+ {
+ FPrintF( stdout, "HTTP body: %1.1H", header->extraDataPtr, (int) header->extraDataLen, INT_MAX );
+ }
+ }
+ else
+ {
+ FPrintF( stdout, "Invalid HTTP message:\n%1.1H", header->buf, (int) msgLen, INT_MAX );
+ goto exit;
+ }
+
+exit:
+ if( err ) exit( 1 );
+}
+
+//===========================================================================================================================
+// HTTPHeader_Validate
+//
+// Parses for the end of an HTTP header and updates the HTTPHeader structure so it's ready to parse. Returns true if valid.
+// This assumes the "buf" and "len" fields are set. The other fields are set by this function.
+//
+// Note: This was copied from CoreUtils because the HTTPHeader_Validate function is currently not exported in the framework.
+//===========================================================================================================================
+
+Boolean HTTPHeader_Validate( HTTPHeader *inHeader )
+{
+ const char * src;
+ const char * end;
+
+ // Check for interleaved binary data (4 byte header that begins with $). See RFC 2326 section 10.12.
+
+ require( inHeader->len < sizeof( inHeader->buf ), exit );
+ src = inHeader->buf;
+ end = src + inHeader->len;
+ if( ( ( end - src ) >= 4 ) && ( src[ 0 ] == '$' ) )
+ {
+ src += 4;
+ }
+ else
+ {
+ // Search for an empty line (HTTP-style header/body separator). CRLFCRLF, LFCRLF, or LFLF accepted.
+ // $$$ TO DO: Start from the last search location to avoid re-searching the same data over and over.
+
+ for( ;; )
+ {
+ while( ( src < end ) && ( src[ 0 ] != '\n' ) ) ++src;
+ if( src >= end ) goto exit;
+ ++src;
+ if( ( ( end - src ) >= 2 ) && ( src[ 0 ] == '\r' ) && ( src[ 1 ] == '\n' ) ) // CFLFCRLF or LFCRLF
+ {
+ src += 2;
+ break;
+ }
+ else if( ( ( end - src ) >= 1 ) && ( src[ 0 ] == '\n' ) ) // LFLF
+ {
+ src += 1;
+ break;
+ }
+ }
+ }
+ inHeader->extraDataPtr = src;
+ inHeader->extraDataLen = (size_t)( end - src );
+ inHeader->len = (size_t)( src - inHeader->buf );
+ return( true );
+
+exit:
+ return( false );
+}
+
+#if( TARGET_OS_DARWIN )
+//===========================================================================================================================
+// ResQueryCmd
+//===========================================================================================================================
+
+// res_query() from libresolv is actually called res_9_query (see /usr/include/resolv.h).
+
+SOFT_LINK_LIBRARY_EX( "/usr/lib", resolv );
+SOFT_LINK_FUNCTION_EX( resolv, res_9_query,
+ int,
+ ( const char *dname, int class, int type, u_char *answer, int anslen ),
+ ( dname, class, type, answer, anslen ) );
+
+// res_query() from libinfo
+
+SOFT_LINK_LIBRARY_EX( "/usr/lib", info );
+SOFT_LINK_FUNCTION_EX( info, res_query,
+ int,
+ ( const char *dname, int class, int type, u_char *answer, int anslen ),
+ ( dname, class, type, answer, anslen ) );
+
+typedef int ( *res_query_f )( const char *dname, int class, int type, u_char *answer, int anslen );
+
+static void ResQueryCmd( void )
+{
+ OSStatus err;
+ res_query_f res_query_ptr;
+ int n;
+ uint16_t type, class;
+ uint8_t answer[ 1024 ];
+
+ // Get pointer to one of the res_query() functions.
+
+ if( gResQuery_UseLibInfo )
+ {
+ if( !SOFT_LINK_HAS_FUNCTION( info, res_query ) )
+ {
+ FPrintF( stderr, "Failed to soft link res_query from libinfo.\n" );
+ err = kNotFoundErr;
+ goto exit;
+ }
+ res_query_ptr = soft_res_query;
+ }
+ else
+ {
+ if( !SOFT_LINK_HAS_FUNCTION( resolv, res_9_query ) )
+ {
+ FPrintF( stderr, "Failed to soft link res_query from libresolv.\n" );
+ err = kNotFoundErr;
+ goto exit;
+ }
+ res_query_ptr = soft_res_9_query;
+ }
+
+ // Get record type.
+
+ err = RecordTypeFromArgString( gResQuery_Type, &type );
+ require_noerr( err, exit );
+
+ // Get record class.
+
+ if( gResQuery_Class )
+ {
+ err = RecordClassFromArgString( gResQuery_Class, &class );
+ require_noerr( err, exit );
+ }
+ else
+ {
+ class = kDNSServiceClass_IN;
+ }
+
+ // Print prologue.
+
+ FPrintF( stdout, "Name: %s\n", gResQuery_Name );
+ FPrintF( stdout, "Type: %s (%u)\n", RecordTypeToString( type ), type );
+ FPrintF( stdout, "Class: %s (%u)\n", ( class == kDNSServiceClass_IN ) ? "IN" : "???", class );
+ FPrintF( stdout, "Start time: %{du:time}\n", NULL );
+ FPrintF( stdout, "---\n" );
+
+ // Call res_query().
+
+ n = res_query_ptr( gResQuery_Name, class, type, (u_char *) answer, (int) sizeof( answer ) );
+ if( n < 0 )
+ {
+ FPrintF( stderr, "res_query() failed with error: %d (%s).\n", h_errno, hstrerror( h_errno ) );
+ err = kUnknownErr;
+ goto exit;
+ }
+
+ // Print result.
+
+ FPrintF( stdout, "Message size: %d\n\n%{du:dnsmsg}", n, answer, (size_t) n );
+
+exit:
+ if( err ) exit( 1 );
+}
+
+//===========================================================================================================================
+// ResolvDNSQueryCmd
+//===========================================================================================================================
+
+// dns_handle_t is defined as a pointer to a privately-defined struct in /usr/include/dns.h. It's defined as a void * here to
+// avoid including the header file.
+
+typedef void * dns_handle_t;
+
+SOFT_LINK_FUNCTION_EX( resolv, dns_open, dns_handle_t, ( const char *path ), ( path ) );
+SOFT_LINK_FUNCTION_VOID_RETURN_EX( resolv, dns_free, ( dns_handle_t *dns ), ( dns ) );
+SOFT_LINK_FUNCTION_EX( resolv, dns_query,
+ int32_t, (
+ dns_handle_t dns,
+ const char * name,
+ uint32_t dnsclass,
+ uint32_t dnstype,
+ char * buf,
+ uint32_t len,
+ struct sockaddr * from,
+ uint32_t * fromlen ),
+ ( dns, name, dnsclass, dnstype, buf, len, from, fromlen ) );
+
+static void ResolvDNSQueryCmd( void )
+{
+ OSStatus err;
+ int n;
+ dns_handle_t dns = NULL;
+ uint16_t type, class;
+ sockaddr_ip from;
+ uint32_t fromLen;
+ uint8_t answer[ 1024 ];
+
+ // Make sure that the required symbols are available.
+
+ if( !SOFT_LINK_HAS_FUNCTION( resolv, dns_open ) )
+ {
+ FPrintF( stderr, "Failed to soft link dns_open from libresolv.\n" );
+ err = kNotFoundErr;
+ goto exit;
+ }
+
+ if( !SOFT_LINK_HAS_FUNCTION( resolv, dns_free ) )
+ {
+ FPrintF( stderr, "Failed to soft link dns_free from libresolv.\n" );
+ err = kNotFoundErr;
+ goto exit;
+ }
+
+ if( !SOFT_LINK_HAS_FUNCTION( resolv, dns_query ) )
+ {
+ FPrintF( stderr, "Failed to soft link dns_query from libresolv.\n" );
+ err = kNotFoundErr;
+ goto exit;
+ }
+
+ // Get record type.
+
+ err = RecordTypeFromArgString( gResolvDNSQuery_Type, &type );
+ require_noerr( err, exit );
+
+ // Get record class.
+
+ if( gResolvDNSQuery_Class )
+ {
+ err = RecordClassFromArgString( gResolvDNSQuery_Class, &class );
+ require_noerr( err, exit );
+ }
+ else
+ {
+ class = kDNSServiceClass_IN;
+ }
+
+ // Get dns handle.
+
+ dns = soft_dns_open( gResolvDNSQuery_Path );
+ if( !dns )
+ {
+ FPrintF( stderr, "dns_open( %s ) failed.\n", gResolvDNSQuery_Path );
+ err = kUnknownErr;
+ goto exit;
+ }
+
+ // Print prologue.
+
+ FPrintF( stdout, "Name: %s\n", gResolvDNSQuery_Name );
+ FPrintF( stdout, "Type: %s (%u)\n", RecordTypeToString( type ), type );
+ FPrintF( stdout, "Class: %s (%u)\n", ( class == kDNSServiceClass_IN ) ? "IN" : "???", class );
+ FPrintF( stdout, "Path: %s\n", gResolvDNSQuery_Path ? gResolvDNSQuery_Name : "<NULL>" );
+ FPrintF( stdout, "Start time: %{du:time}\n", NULL );
+ FPrintF( stdout, "---\n" );
+
+ // Call dns_query().
+
+ memset( &from, 0, sizeof( from ) );
+ fromLen = (uint32_t) sizeof( from );
+ n = soft_dns_query( dns, gResolvDNSQuery_Name, class, type, (char *) answer, (uint32_t) sizeof( answer ), &from.sa,
+ &fromLen );
+ if( n < 0 )
+ {
+ FPrintF( stderr, "dns_query() failed with error: %d (%s).\n", h_errno, hstrerror( h_errno ) );
+ err = kUnknownErr;
+ goto exit;
+ }
+
+ // Print result.
+
+ FPrintF( stdout, "From: %##a\n", &from );
+ FPrintF( stdout, "Message size: %d\n\n%{du:dnsmsg}", n, answer, (size_t) n );
+
+exit:
+ if( dns ) soft_dns_free( dns );
+ if( err ) exit( 1 );
+}
+
+//===========================================================================================================================
+// CFHostCmd
+//===========================================================================================================================
+
+static void
+ _CFHostResolveCallback(
+ CFHostRef inHost,
+ CFHostInfoType inInfoType,
+ const CFStreamError * inError,
+ void * inInfo );
+
+static void CFHostCmd( void )
+{
+ OSStatus err;
+ CFStringRef name;
+ Boolean success;
+ CFHostRef host = NULL;
+ CFHostClientContext context;
+ CFStreamError streamErr;
+
+ name = CFStringCreateWithCString( kCFAllocatorDefault, gCFHost_Name, kCFStringEncodingUTF8 );
+ require_action( name, exit, err = kUnknownErr );
+
+ host = CFHostCreateWithName( kCFAllocatorDefault, name );
+ ForgetCF( &name );
+ require_action( host, exit, err = kUnknownErr );
+
+ memset( &context, 0, sizeof( context ) );
+ success = CFHostSetClient( host, _CFHostResolveCallback, &context );
+ require_action( success, exit, err = kUnknownErr );
+
+ CFHostScheduleWithRunLoop( host, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode );
+
+ // Print prologue.
+
+ FPrintF( stdout, "Hostname: %s\n", gCFHost_Name );
+ FPrintF( stdout, "Start time: %{du:time}\n", NULL );
+ FPrintF( stdout, "---\n" );
+
+ success = CFHostStartInfoResolution( host, kCFHostAddresses, &streamErr );
+ require_action( success, exit, err = kUnknownErr );
+ err = kNoErr;
+
+ CFRunLoopRun();
+
+exit:
+ CFReleaseNullSafe( host );
+ if( err ) exit( 1 );
+}
+
+static void _CFHostResolveCallback( CFHostRef inHost, CFHostInfoType inInfoType, const CFStreamError *inError, void *inInfo )
+{
+ OSStatus err;
+ struct timeval now;
+
+ gettimeofday( &now, NULL );
+
+ Unused( inInfoType );
+ Unused( inInfo );
+
+ if( inError && ( inError->domain != 0 ) && ( inError->error ) )
+ {
+ err = inError->error;
+ if( inError->domain == kCFStreamErrorDomainNetDB )
+ {
+ FPrintF( stderr, "Error %d: %s.\n", err, gai_strerror( err ) );
+ }
+ else
+ {
+ FPrintF( stderr, "Error %#m\n", err );
+ }
+ }
+ else
+ {
+ CFArrayRef addresses;
+ CFIndex count, i;
+ CFDataRef addrData;
+ const struct sockaddr * sockAddr;
+ Boolean wasResolved = false;
+
+ addresses = CFHostGetAddressing( inHost, &wasResolved );
+ check( wasResolved );
+
+ if( addresses )
+ {
+ count = CFArrayGetCount( addresses );
+ for( i = 0; i < count; ++i )
+ {
+ addrData = CFArrayGetCFDataAtIndex( addresses, i, &err );
+ require_noerr( err, exit );
+
+ sockAddr = (const struct sockaddr *) CFDataGetBytePtr( addrData );
+ FPrintF( stdout, "%##a\n", sockAddr );
+ }
+ }
+ err = kNoErr;
+ }
+
+ FPrintF( stdout, "---\n" );
+ FPrintF( stdout, "End time: %{du:time}\n", &now );
+
+ if( gCFHost_WaitSecs > 0 ) sleep( (unsigned int) gCFHost_WaitSecs );
+
+exit:
+ exit( err ? 1 : 0 );
+}
+
+//===========================================================================================================================
+// DNSConfigAddCmd
+//
+// Note: Based on ajn's supplemental test tool.
+//===========================================================================================================================
+
+static void DNSConfigAddCmd( void )
+{
+ OSStatus err;
+ CFMutableDictionaryRef dict = NULL;
+ CFMutableArrayRef array = NULL;
+ size_t i;
+ SCDynamicStoreRef store = NULL;
+ CFStringRef key = NULL;
+ Boolean success;
+
+ // Create dictionary.
+
+ dict = CFDictionaryCreateMutable( NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks );
+ require_action( dict, exit, err = kNoMemoryErr );
+
+ // Add DNS server IP addresses.
+
+ array = CFArrayCreateMutable( NULL, (CFIndex) gDNSConfigAdd_IPAddrCount, &kCFTypeArrayCallBacks );
+ require_action( array, exit, err = kNoMemoryErr );
+
+ for( i = 0; i < gDNSConfigAdd_IPAddrCount; ++i )
+ {
+ CFStringRef addrStr;
+
+ addrStr = CFStringCreateWithCString( NULL, gDNSConfigAdd_IPAddrArray[ i ], kCFStringEncodingUTF8 );
+ require_action( addrStr, exit, err = kUnknownErr );
+
+ CFArrayAppendValue( array, addrStr );
+ CFRelease( addrStr );
+ }
+
+ CFDictionarySetValue( dict, kSCPropNetDNSServerAddresses, array );
+ ForgetCF( &array );
+
+ // Add domains, if any.
+
+ array = CFArrayCreateMutable( NULL, (CFIndex) Min( gDNSConfigAdd_DomainCount, 1 ), &kCFTypeArrayCallBacks );
+ require_action( array, exit, err = kNoMemoryErr );
+
+ if( gDNSConfigAdd_DomainCount > 0 )
+ {
+ for( i = 0; i < gDNSConfigAdd_DomainCount; ++i )
+ {
+ CFStringRef domainStr;
+
+ domainStr = CFStringCreateWithCString( NULL, gDNSConfigAdd_DomainArray[ i ], kCFStringEncodingUTF8 );
+ require_action( domainStr, exit, err = kUnknownErr );
+
+ CFArrayAppendValue( array, domainStr );
+ CFRelease( domainStr );
+ }
+ }
+ else
+ {
+ // There are no domains, but the domain array needs to be non-empty, so add a zero-length string to the array.
+
+ CFArrayAppendValue( array, CFSTR( "" ) );
+ }
+
+ CFDictionarySetValue( dict, kSCPropNetDNSSupplementalMatchDomains, array );
+ ForgetCF( &array );
+
+ // Add interface, if any.
+
+ if( gDNSConfigAdd_Interface )
+ {
+ err = CFDictionarySetCString( dict, kSCPropInterfaceName, gDNSConfigAdd_Interface, kSizeCString );
+ require_noerr( err, exit );
+
+ CFDictionarySetValue( dict, kSCPropNetDNSConfirmedServiceID, gDNSConfigAdd_ID );
+ }
+
+ // Set dictionary in dynamic store.
+
+ store = SCDynamicStoreCreate( NULL, CFSTR( "com.apple.dnssdutil" ), NULL, NULL );
+ err = map_scerror( store );
+ require_noerr( err, exit );
+
+ key = SCDynamicStoreKeyCreateNetworkServiceEntity( NULL, kSCDynamicStoreDomainState, gDNSConfigAdd_ID, kSCEntNetDNS );
+ require_action( key, exit, err = kUnknownErr );
+
+ success = SCDynamicStoreSetValue( store, key, dict );
+ require_action( success, exit, err = kUnknownErr );
+
+exit:
+ CFReleaseNullSafe( dict );
+ CFReleaseNullSafe( array );
+ CFReleaseNullSafe( store );
+ CFReleaseNullSafe( key );
+ gExitCode = err ? 1 : 0;
+}
+
+//===========================================================================================================================
+// DNSConfigRemoveCmd
+//===========================================================================================================================
+
+static void DNSConfigRemoveCmd( void )
+{
+ OSStatus err;
+ SCDynamicStoreRef store = NULL;
+ CFStringRef key = NULL;
+ Boolean success;
+
+ store = SCDynamicStoreCreate( NULL, CFSTR( "com.apple.dnssdutil" ), NULL, NULL );
+ err = map_scerror( store );
+ require_noerr( err, exit );
+
+ key = SCDynamicStoreKeyCreateNetworkServiceEntity( NULL, kSCDynamicStoreDomainState, gDNSConfigRemove_ID, kSCEntNetDNS );
+ require_action( key, exit, err = kUnknownErr );
+
+ success = SCDynamicStoreRemoveValue( store, key );
+ require_action( success, exit, err = kUnknownErr );
+
+exit:
+ CFReleaseNullSafe( store );
+ CFReleaseNullSafe( key );
+ gExitCode = err ? 1 : 0;
+}
+#endif // TARGET_OS_DARWIN
+
+//===========================================================================================================================
+// 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;
+
+ FPrintF( stdout, "---\n" );
+ FPrintF( stdout, "End time: %{du:time}\n", NULL );
+ if( reason ) FPrintF( stdout, "End reason: %s\n", reason );
+ exit( gExitCode );
+}
+
+//===========================================================================================================================
+// PrintFTimestampHandler
+//===========================================================================================================================
+
+static int
+ PrintFTimestampHandler(
+ PrintFContext * inContext,
+ PrintFFormat * inFormat,
+ PrintFVAList * inArgs,
+ void * inUserContext )
+{
+ struct timeval now;
+ const struct timeval * tv;
+ struct tm * localTime;
+ size_t len;
+ int n;
+ char dateTimeStr[ 32 ];
+
+ Unused( inUserContext );
+
+ tv = va_arg( inArgs->args, const struct timeval * );
+ require_action_quiet( !inFormat->suppress, exit, n = 0 );
+
+ if( !tv )
+ {
+ gettimeofday( &now, NULL );
+ tv = &now;
+ }
+ localTime = localtime( &tv->tv_sec );
+ len = strftime( dateTimeStr, sizeof( dateTimeStr ), "%Y-%m-%d %H:%M:%S", localTime );
+ if( len == 0 ) dateTimeStr[ 0 ] = '\0';
+
+ n = PrintFCore( inContext, "%s.%06u", dateTimeStr, (unsigned int) tv->tv_usec );
+
+exit:
+ return( n );
+}
+
+//===========================================================================================================================
+// PrintFDNSMessageHandler
+//===========================================================================================================================
+
+static int
+ PrintFDNSMessageHandler(
+ PrintFContext * inContext,
+ PrintFFormat * inFormat,
+ PrintFVAList * inArgs,
+ void * inUserContext )
+{
+ OSStatus err;
+ const void * msgPtr;
+ size_t msgLen;
+ char * text;
+ int n;
+ Boolean isMDNS;
+ Boolean printRawRData;
+
+ Unused( inUserContext );
+
+ msgPtr = va_arg( inArgs->args, const void * );
+ msgLen = va_arg( inArgs->args, size_t );
+ require_action_quiet( !inFormat->suppress, exit, n = 0 );
+
+ isMDNS = ( inFormat->altForm > 0 ) ? true : false;
+ if( inFormat->precision == 0 ) printRawRData = false;
+ else if( inFormat->precision == 1 ) printRawRData = true;
+ else
+ {
+ n = PrintFCore( inContext, "<< BAD %%{du:dnsmsg} PRECISION >>" );
+ goto exit;
+ }
+
+ err = DNSMessageToText( msgPtr, msgLen, isMDNS, printRawRData, &text );
+ if( !err )
+ {
+ n = PrintFCore( inContext, "%*{text}", inFormat->fieldWidth, text, kSizeCString );
+ free( text );
+ }
+ else
+ {
+ n = PrintFCore( inContext, "%*.1H", inFormat->fieldWidth, msgPtr, (int) msgLen, (int) msgLen );
+ }
+
+exit:
+ return( n );
+}
+
+//===========================================================================================================================
+// PrintFAddRmvFlagsHandler
+//===========================================================================================================================
+
+static int
+ PrintFAddRmvFlagsHandler(
+ PrintFContext * inContext,
+ PrintFFormat * inFormat,
+ PrintFVAList * inArgs,
+ void * inUserContext )
+{
+ DNSServiceFlags flags;
+ int n;
+
+ Unused( inUserContext );
+
+ flags = va_arg( inArgs->args, DNSServiceFlags );
+ require_action_quiet( !inFormat->suppress, exit, n = 0 );
+
+ n = PrintFCore( inContext, "%08X %s%c%c", flags,
+ ( flags & kDNSServiceFlagsAdd ) ? "Add" : "Rmv",
+ ( flags & kDNSServiceFlagsMoreComing ) ? '+' : ' ',
+ ( flags & kDNSServiceFlagsExpiredAnswer ) ? '!' : ' ' );
+
+exit:
+ return( n );
+}
+
+//===========================================================================================================================
+// 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_AllowExpiredAnswers ) flags |= kDNSServiceFlagsAllowExpiredAnswers;
+ 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;
+ if( gDNSSDFlag_WakeOnResolve ) flags |= kDNSServiceFlagsWakeOnResolve;
+
+ 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, "error: Invalid interface value: %s\n", inString );
+ err = kParamErr;
+ goto exit;
+ }
+ }
+ }
+ else
+ {
+ ifIndex = 0;
+ }
+
+ *outIndex = ifIndex;
+ err = kNoErr;
+
+exit:
+ return( err );
+}
+
+//===========================================================================================================================
+// RecordDataFromArgString
+//===========================================================================================================================
+
+static OSStatus RecordDataFromArgString( const char *inString, uint8_t **outDataPtr, size_t *outDataLen )
+{
+ OSStatus err;
+ uint8_t * dataPtr = NULL;
+ size_t dataLen;
+
+ if( 0 ) {}
+
+ // Domain name
+
+ else if( stricmp_prefix( inString, kRDataArgPrefix_Domain ) == 0 )
+ {
+ const char * const str = inString + sizeof_string( kRDataArgPrefix_Domain );
+
+ err = StringToDomainName( str, &dataPtr, &dataLen );
+ require_noerr_quiet( err, exit );
+ }
+
+ // File path
+
+ 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 <= kDNSRecordDataLengthMax, exit, err = kSizeErr );
+ }
+
+ // Hexadecimal string
+
+ else if( stricmp_prefix( inString, kRDataArgPrefix_HexString ) == 0 )
+ {
+ const char * const str = inString + sizeof_string( kRDataArgPrefix_HexString );
+
+ err = HexToDataCopy( str, kSizeCString, kHexToData_DefaultFlags, &dataPtr, &dataLen, NULL );
+ require_noerr( err, exit );
+ require_action( dataLen <= kDNSRecordDataLengthMax, exit, err = kSizeErr );
+ }
+
+ // IPv4 address string
+
+ else if( stricmp_prefix( inString, kRDataArgPrefix_IPv4 ) == 0 )
+ {
+ const char * const str = inString + sizeof_string( kRDataArgPrefix_IPv4 );
+
+ err = StringToARecordData( str, &dataPtr, &dataLen );
+ require_noerr_quiet( err, exit );
+ }
+
+ // IPv6 address string
+
+ else if( stricmp_prefix( inString, kRDataArgPrefix_IPv6 ) == 0 )
+ {
+ const char * const str = inString + sizeof_string( kRDataArgPrefix_IPv6 );
+
+ err = StringToAAAARecordData( str, &dataPtr, &dataLen );
+ require_noerr_quiet( err, exit );
+ }
+
+ // SRV record
+
+ else if( stricmp_prefix( inString, kRDataArgPrefix_SRV ) == 0 )
+ {
+ const char * const str = inString + sizeof_string( kRDataArgPrefix_SRV );
+
+ err = CreateSRVRecordDataFromString( str, &dataPtr, &dataLen );
+ require_noerr( err, exit );
+ }
+
+ // String with escaped hex and octal bytes
+
+ else if( stricmp_prefix( inString, kRDataArgPrefix_String ) == 0 )
+ {
+ const char * const str = inString + sizeof_string( kRDataArgPrefix_String );
+ const char * const end = str + strlen( str );
+ size_t copiedLen;
+ size_t totalLen;
+ Boolean success;
+
+ if( str < end )
+ {
+ success = ParseQuotedEscapedString( str, end, "", NULL, 0, NULL, &totalLen, NULL );
+ require_action( success, exit, err = kParamErr );
+ require_action( totalLen <= kDNSRecordDataLengthMax, exit, err = kSizeErr );
+
+ dataLen = totalLen;
+ dataPtr = (uint8_t *) malloc( dataLen );
+ require_action( dataPtr, exit, err = kNoMemoryErr );
+
+ success = ParseQuotedEscapedString( str, end, "", (char *) dataPtr, dataLen, &copiedLen, NULL, NULL );
+ require_action( success, exit, err = kParamErr );
+ check( copiedLen == dataLen );
+ }
+ else
+ {
+ dataPtr = NULL;
+ dataLen = 0;
+ }
+ }
+
+ // TXT record
+
+ else if( stricmp_prefix( inString, kRDataArgPrefix_TXT ) == 0 )
+ {
+ const char * const str = inString + sizeof_string( kRDataArgPrefix_TXT );
+
+ err = CreateTXTRecordDataFromString( str, ',', &dataPtr, &dataLen );
+ require_noerr( err, exit );
+ }
+
+ // Unrecognized format
+
+ else
+ {
+ FPrintF( stderr, "Unrecognized record data string \"%s\".\n", inString );
+ err = kParamErr;
+ goto exit;
+ }
+
+ err = kNoErr;
+ *outDataLen = dataLen;
+ *outDataPtr = dataPtr;
+ dataPtr = NULL;
+
+exit:
+ 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
+//===========================================================================================================================
+
+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 );
+}
+
+//===========================================================================================================================
+// DNSMessageExtractQuestion
+//===========================================================================================================================
+
+static OSStatus
+ DNSMessageExtractQuestion(
+ const uint8_t * inMsgPtr,
+ size_t inMsgLen,
+ const uint8_t * inPtr,
+ uint8_t inNameBuf[ kDomainNameLengthMax ],
+ uint16_t * outType,
+ uint16_t * outClass,
+ const uint8_t ** outPtr )
+{
+ OSStatus err;
+ const uint8_t * const msgEnd = &inMsgPtr[ inMsgLen ];
+ const uint8_t * ptr;
+ const DNSQuestionFixedFields * fields;
+
+ err = DNSMessageExtractDomainName( inMsgPtr, inMsgLen, inPtr, inNameBuf, &ptr );
+ require_noerr_quiet( err, exit );
+ require_action_quiet( (size_t)( msgEnd - ptr ) >= sizeof( DNSQuestionFixedFields ), exit, err = kUnderrunErr );
+
+ fields = (const DNSQuestionFixedFields *) ptr;
+ if( outType ) *outType = DNSQuestionFixedFieldsGetType( fields );
+ if( outClass ) *outClass = DNSQuestionFixedFieldsGetClass( fields );
+ if( outPtr ) *outPtr = (const uint8_t *) &fields[ 1 ];
+
+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;
+ 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 = (const uint8_t *) &hdr[ 1 ];
+ for( i = 0; i < questionCount; ++i )
+ {
+ err = DNSMessageExtractQuestion( inMsgPtr, inMsgLen, ptr, NULL, NULL, NULL, &ptr );
+ require_noerr( err, exit );
+ }
+
+ 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 )
+ {
+ const SRVRecordDataFixedFields * fields;
+ const uint8_t * target;
+ unsigned int priority, weight, port;
+
+ require_action_quiet( inRDataLen > sizeof( SRVRecordDataFixedFields ), exit, err = kMalformedErr );
+
+ fields = (const SRVRecordDataFixedFields *) rdataPtr;
+ SRVRecordDataFixedFieldsGet( fields, &priority, &weight, &port );
+ target = (const uint8_t *) &fields[ 1 ];
+
+ 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( ( rdataEnd - ptr ) == sizeof( SOARecordDataFixedFields ), exit, err = kMalformedErr );
+
+ SOARecordDataFixedFieldsGet( (const SOARecordDataFixedFields *) ptr, &serial, &refresh, &retry, &expire, &minimum );
+
+ 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 * root;
+ const uint8_t * const nameLim = inDomainName + kDomainNameLengthMax;
+
+ for( root = inDomainName; ( root < nameLim ) && *root; root += ( 1 + *root ) ) {}
+ require_action_quiet( root < nameLim, exit, err = kMalformedErr );
+
+ // If the string is a single dot, denoting the root domain, then there are no non-empty labels.
+
+ src = inString;
+ if( ( src[ 0 ] == '.' ) && ( src[ 1 ] == '\0' ) ) ++src;
+ while( *src )
+ {
+ uint8_t * const label = root;
+ const uint8_t * const labelLim = Min( &label[ 1 + kDomainLabelLengthMax ], nameLim - 1 );
+ uint8_t * dst;
+ int c;
+ size_t labelLen;
+
+ dst = &label[ 1 ];
+ while( *src && ( ( c = *src++ ) != '.' ) )
+ {
+ if( c == '\\' )
+ {
+ require_action_quiet( *src != '\0', exit, err = kUnderrunErr );
+ c = *src++;
+ if( isdigit_safe( c ) && isdigit_safe( src[ 0 ] ) && isdigit_safe( src[ 1 ] ) )
+ {
+ const int decimal = ( ( c - '0' ) * 100 ) + ( ( src[ 0 ] - '0' ) * 10 ) + ( src[ 1 ] - '0' );
+
+ if( decimal <= 255 )
+ {
+ c = decimal;
+ src += 2;
+ }
+ }
+ }
+ require_action_quiet( dst < labelLim, exit, err = kOverrunErr );
+ *dst++ = (uint8_t) c;
+ }
+
+ labelLen = (size_t)( dst - &label[ 1 ] );
+ require_action_quiet( labelLen > 0, exit, err = kMalformedErr );
+
+ label[ 0 ] = (uint8_t) labelLen;
+ root = dst;
+ *root = 0;
+ }
+
+ if( outEndPtr ) *outEndPtr = root + 1;
+ 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 );
+}
+
+//===========================================================================================================================
+// DomainNameLength
+//===========================================================================================================================
+
+static size_t DomainNameLength( const uint8_t * const inName )
+{
+ const uint8_t * ptr;
+
+ for( ptr = inName; *ptr != 0; ptr += ( 1 + *ptr ) ) {}
+ return( (size_t)( ptr - inName ) + 1 );
+}
+
+//===========================================================================================================================
+// DomainNameDupEx
+//===========================================================================================================================
+
+static OSStatus DomainNameDupEx( const uint8_t *inName, Boolean inLower, uint8_t **outNamePtr, size_t *outNameLen )
+{
+ OSStatus err;
+ uint8_t * namePtr;
+ const size_t nameLen = DomainNameLength( inName );
+
+ if( inLower )
+ {
+ const uint8_t * src;
+ uint8_t * dst;
+ unsigned int len;
+
+ namePtr = (uint8_t *) malloc( nameLen );
+ require_action( namePtr, exit, err = kNoMemoryErr );
+
+ src = inName;
+ dst = namePtr;
+ while( ( len = *src ) != 0 )
+ {
+ *dst++ = *src++;
+ while( len-- )
+ {
+ *dst++ = (uint8_t) tolower_safe( *src );
+ ++src;
+ }
+ }
+ *dst = 0;
+ }
+ else
+ {
+ namePtr = (uint8_t *) memdup( inName, nameLen );
+ require_action( namePtr, exit, err = kNoMemoryErr );
+ }
+
+ *outNamePtr = namePtr;
+ if( outNameLen ) *outNameLen = nameLen;
+ err = kNoErr;
+
+exit:
+ return( err );
+}
+
+//===========================================================================================================================
+// DomainNameFromString
+//===========================================================================================================================
+
+static OSStatus
+ DomainNameFromString(
+ uint8_t inDomainName[ kDomainNameLengthMax ],
+ const char * inString,
+ uint8_t ** outEndPtr )
+{
+ inDomainName[ 0 ] = 0;
+ return( DomainNameAppendString( inDomainName, inString, outEndPtr ) );
+}
+
+//===========================================================================================================================
+// 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 );
+}
+
+//===========================================================================================================================
+// DNSMessageToText
+//===========================================================================================================================
+
+#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" : \
+ "???" )
+
+static OSStatus
+ DNSMessageToText(
+ const uint8_t * inMsgPtr,
+ size_t inMsgLen,
+ const Boolean inMDNS,
+ const Boolean inPrintRaw,
+ char ** outText )
+{
+ OSStatus err;
+ DataBuffer dataBuf;
+ size_t len;
+ const DNSHeader * hdr;
+ const uint8_t * ptr;
+ unsigned int id, flags, opcode, rcode;
+ unsigned int questionCount, answerCount, authorityCount, additionalCount, i, totalRRCount;
+ uint8_t name[ kDomainNameLengthMax ];
+ char nameStr[ kDNSServiceMaxDomainName ];
+
+ DataBuffer_Init( &dataBuf, NULL, 0, SIZE_MAX );
+ #define _Append( ... ) do { err = DataBuffer_AppendF( &dataBuf, __VA_ARGS__ ); require_noerr( err, exit ); } while( 0 )
+
+ 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 );
+
+ _Append( "ID: 0x%04X (%u)\n", id, id );
+ _Append( "Flags: 0x%04X %c/%s %cAA%cTC%cRD%cRA%?s%?s %s\n",
+ flags,
+ ( flags & kDNSHeaderFlag_Response ) ? 'R' : 'Q', DNSFlagsOpCodeToString( opcode ),
+ ( flags & kDNSHeaderFlag_AuthAnswer ) ? ' ' : '!',
+ ( flags & kDNSHeaderFlag_Truncation ) ? ' ' : '!',
+ ( flags & kDNSHeaderFlag_RecursionDesired ) ? ' ' : '!',
+ ( flags & kDNSHeaderFlag_RecursionAvailable ) ? ' ' : '!',
+ !inMDNS, ( flags & kDNSHeaderFlag_AuthenticData ) ? " AD" : "!AD",
+ !inMDNS, ( flags & kDNSHeaderFlag_CheckingDisabled ) ? " CD" : "!CD",
+ DNSFlagsRCodeToString( rcode ) );
+ _Append( "Question count: %u\n", questionCount );
+ _Append( "Answer count: %u\n", answerCount );
+ _Append( "Authority count: %u\n", authorityCount );
+ _Append( "Additional count: %u\n", additionalCount );
+
+ ptr = (const uint8_t *) &hdr[ 1 ];
+ for( i = 0; i < questionCount; ++i )
{
- err = UDPClientSocketOpen( AF_INET6, NULL, 0, -1, NULL, &sockV6 );
+ uint16_t qtype, qclass;
+ Boolean isQU;
+
+ err = DNSMessageExtractQuestion( inMsgPtr, inMsgLen, ptr, name, &qtype, &qclass, &ptr );
require_noerr( err, exit );
- err = SocketSetMulticastInterface( sockV6, NULL, context->ifindex );
+ err = DomainNameToString( name, NULL, nameStr, NULL );
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 );
+ isQU = ( inMDNS && ( qclass & kQClassUnicastResponseBit ) ) ? true : false;
+ if( inMDNS ) qclass &= ~kQClassUnicastResponseBit;
+
+ if( i == 0 ) _Append( "\nQUESTION SECTION\n" );
+
+ _Append( "%-30s %2s %?2s%?2u %-5s\n",
+ nameStr, inMDNS ? ( 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;
+
+ 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 = ( inMDNS && ( class & kRRClassCacheFlushBit ) ) ? true : false;
+ if( inMDNS ) 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 ) ) _Append( "\nANSWER SECTION\n" );
+ else if( authorityCount && ( i == answerCount ) ) _Append( "\nAUTHORITY SECTION\n" );
+ else if( additionalCount && ( i == ( answerCount + authorityCount ) ) ) _Append( "\nADDITIONAL SECTION\n" );
+
+ _Append( "%-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 );
}
+ _Append( "\n" );
+
+ err = DataBuffer_Append( &dataBuf, "", 1 );
+ require_noerr( err, exit );
+
+ err = DataBuffer_Detach( &dataBuf, (uint8_t **) outText, &len );
+ require_noerr( err, exit );
+
+exit:
+ DataBuffer_Free( &dataBuf );
+ 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;
+
+ memset( hdr, 0, sizeof( *hdr ) );
+ DNSHeaderSetID( hdr, inMsgID );
+ DNSHeaderSetFlags( hdr, inFlags );
+ DNSHeaderSetQuestionCount( hdr, 1 );
+
+ ptr = (uint8_t *)( hdr + 1 );
+ err = DomainNameFromString( ptr, inQName, &ptr );
+ require_noerr_quiet( err, exit );
+
+ DNSQuestionFixedFieldsInit( (DNSQuestionFixedFields *) ptr, inQType, inQClass );
+ ptr += 4;
+
+ 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 );
+}
+
+//===========================================================================================================================
+// DispatchSocketSourceCreate
+//===========================================================================================================================
+
+static OSStatus
+ DispatchSocketSourceCreate(
+ SocketRef inSock,
+ dispatch_source_type_t inType,
+ dispatch_queue_t inQueue,
+ DispatchHandler inEventHandler,
+ DispatchHandler inCancelHandler,
+ void * inContext,
+ dispatch_source_t * outSource )
+{
+ OSStatus err;
+ dispatch_source_t source;
+
+ source = dispatch_source_create( inType, (uintptr_t) inSock, 0, inQueue ? inQueue : 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,
+ dispatch_queue_t inQueue,
+ 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, inQueue ? inQueue : 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 );
+}
+
+//===========================================================================================================================
+// DispatchProcessMonitorCreate
+//===========================================================================================================================
+
+static OSStatus
+ DispatchProcessMonitorCreate(
+ pid_t inPID,
+ unsigned long inFlags,
+ dispatch_queue_t inQueue,
+ DispatchHandler inEventHandler,
+ DispatchHandler inCancelHandler,
+ void * inContext,
+ dispatch_source_t * outMonitor )
+{
+ OSStatus err;
+ dispatch_source_t monitor;
- // Print prologue.
+ monitor = dispatch_source_create( DISPATCH_SOURCE_TYPE_PROC, (uintptr_t) inPID, inFlags,
+ inQueue ? inQueue : dispatch_get_main_queue() );
+ require_action( monitor, exit, err = kUnknownErr );
- SSDPDiscoverPrintPrologue( context );
+ dispatch_set_context( monitor, inContext );
+ dispatch_source_set_event_handler_f( monitor, inEventHandler );
+ dispatch_source_set_cancel_handler_f( monitor, inCancelHandler );
- // Send mDNS query message.
+ *outMonitor = monitor;
+ err = kNoErr;
- sendCount = 0;
- if( IsValidSocket( sockV4 ) )
- {
- struct sockaddr_in mcastAddr4;
-
- memset( &mcastAddr4, 0, sizeof( mcastAddr4 ) );
- SIN_LEN_SET( &mcastAddr4 );
- mcastAddr4.sin_family = AF_INET;
- mcastAddr4.sin_port = htons( kSSDPPort );
- mcastAddr4.sin_addr.s_addr = htonl( 0xEFFFFFFA ); // 239.255.255.250
-
- err = WriteSSDPSearchRequest( &context->header, &mcastAddr4, gSSDPDiscover_MX, gSSDPDiscover_ST );
- require_noerr( err, exit );
-
- n = sendto( sockV4, context->header.buf, context->header.len, 0, (const struct sockaddr *) &mcastAddr4,
- (socklen_t) sizeof( mcastAddr4 ) );
- err = map_socket_value_errno( sockV4, n == (ssize_t) context->header.len, n );
- if( err )
- {
- FPrintF( stderr, "*** Failed to send query on IPv4 socket with error %#m\n", err );
- ForgetSocket( &sockV4 );
- }
- else
- {
- if( gSSDPDiscover_Verbose )
- {
- gettimeofday( &now, NULL );
- FPrintF( stdout, "---\n" );
- FPrintF( stdout, "Send time: %{du:time}\n", &now );
- FPrintF( stdout, "Source Port: %d\n", SocketToPortNumber( sockV4 ) );
- FPrintF( stdout, "Destination: %##a\n", &mcastAddr4 );
- FPrintF( stdout, "Message size: %zu\n", context->header.len );
- FPrintF( stdout, "HTTP header:\n%1{text}", context->header.buf, context->header.len );
- }
- ++sendCount;
- }
- }
+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.
- if( IsValidSocket( sockV6 ) )
+} 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 )
{
- struct sockaddr_in6 mcastAddr6;
-
- memset( &mcastAddr6, 0, sizeof( mcastAddr6 ) );
- SIN6_LEN_SET( &mcastAddr6 );
- mcastAddr6.sin6_family = AF_INET6;
- mcastAddr6.sin6_port = htons( kSSDPPort );
- mcastAddr6.sin6_addr.s6_addr[ 0 ] = 0xFF; // SSDP IPv6 link-local multicast address FF02::C
- mcastAddr6.sin6_addr.s6_addr[ 1 ] = 0x02;
- mcastAddr6.sin6_addr.s6_addr[ 15 ] = 0x0C;
-
- err = WriteSSDPSearchRequest( &context->header, &mcastAddr6, gSSDPDiscover_MX, gSSDPDiscover_ST );
- require_noerr( err, exit );
-
- n = sendto( sockV6, context->header.buf, context->header.len, 0, (const struct sockaddr *) &mcastAddr6,
- (socklen_t) sizeof( mcastAddr6 ) );
- err = map_socket_value_errno( sockV6, n == (ssize_t) context->header.len, n );
- if( err )
- {
- FPrintF( stderr, "*** Failed to send query on IPv6 socket with error %#m\n", err );
- ForgetSocket( &sockV6 );
- }
- else
+ if( ( stricmp_prefix( inName, serviceType->name ) == 0 ) )
{
- if( gSSDPDiscover_Verbose )
+ const size_t len = strlen( serviceType->name );
+
+ if( ( inName[ len ] == '\0' ) || ( strcmp( &inName[ len ], "." ) == 0 ) )
{
- gettimeofday( &now, NULL );
- FPrintF( stdout, "---\n" );
- FPrintF( stdout, "Send time: %{du:time}\n", &now );
- FPrintF( stdout, "Source Port: %d\n", SocketToPortNumber( sockV6 ) );
- FPrintF( stdout, "Destination: %##a\n", &mcastAddr6 );
- FPrintF( stdout, "Message size: %zu\n", context->header.len );
- FPrintF( stdout, "HTTP header:\n%1{text}", context->header.buf, context->header.len );
+ return( serviceType->description );
}
- ++sendCount;
}
}
- require_action_quiet( sendCount > 0, exit, err = kUnexpectedErr );
+ return( NULL );
+}
+
+//===========================================================================================================================
+// SocketContextCreate
+//===========================================================================================================================
+
+static OSStatus SocketContextCreate( SocketRef inSock, void * inUserContext, SocketContext **outContext )
+{
+ OSStatus err;
+ SocketContext * context;
- // If there's no wait period after the send, then exit.
+ context = (SocketContext *) calloc( 1, sizeof( *context ) );
+ require_action( context, exit, err = kNoMemoryErr );
- if( context->receiveSecs == 0 ) goto exit;
+ context->refCount = 1;
+ context->sock = inSock;
+ context->userContext = inUserContext;
- // Create dispatch read sources for socket(s).
+ *outContext = context;
+ err = kNoErr;
- if( IsValidSocket( sockV4 ) )
+exit:
+ return( err );
+}
+
+//===========================================================================================================================
+// SocketContextRetain
+//===========================================================================================================================
+
+static SocketContext * SocketContextRetain( SocketContext *inContext )
+{
+ ++inContext->refCount;
+ return( inContext );
+}
+
+//===========================================================================================================================
+// SocketContextRelease
+//===========================================================================================================================
+
+static void SocketContextRelease( SocketContext *inContext )
+{
+ if( --inContext->refCount == 0 )
{
- SocketContext * sockCtx;
-
- err = SocketContextCreate( sockV4, context, &sockCtx );
- require_noerr( err, exit );
- sockV4 = kInvalidSocketRef;
-
- err = DispatchReadSourceCreate( sockCtx->sock, NULL, SSDPDiscoverReadHandler, SocketContextCancelHandler, sockCtx,
- &context->readSourceV4 );
- if( err ) ForgetSocketContext( &sockCtx );
- require_noerr( err, exit );
-
- dispatch_resume( context->readSourceV4 );
+ ForgetSocket( &inContext->sock );
+ free( inContext );
}
+}
+
+//===========================================================================================================================
+// SocketContextCancelHandler
+//===========================================================================================================================
+
+static void SocketContextCancelHandler( void *inContext )
+{
+ SocketContextRelease( (SocketContext *) inContext );
+}
+
+//===========================================================================================================================
+// StringToInt32
+//===========================================================================================================================
+
+static OSStatus StringToInt32( const char *inString, int32_t *outValue )
+{
+ OSStatus err;
+ long value;
+ char * endPtr;
- if( IsValidSocket( sockV6 ) )
- {
- SocketContext * sockCtx;
-
- err = SocketContextCreate( sockV6, context, &sockCtx );
- require_noerr( err, exit );
- sockV6 = kInvalidSocketRef;
-
- err = DispatchReadSourceCreate( sockCtx->sock, NULL, SSDPDiscoverReadHandler, SocketContextCancelHandler, sockCtx,
- &context->readSourceV6 );
- if( err ) ForgetSocketContext( &sockCtx );
- require_noerr( err, exit );
-
- dispatch_resume( context->readSourceV6 );
- }
+ 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 );
- if( context->receiveSecs > 0 )
- {
- dispatch_after_f( dispatch_time_seconds( context->receiveSecs ), dispatch_get_main_queue(), kExitReason_Timeout,
- Exit );
- }
- dispatch_main();
+ *outValue = (int32_t) value;
+ err = kNoErr;
-exit:
- ForgetSocket( &sockV4 );
- ForgetSocket( &sockV6 );
- dispatch_source_forget( &signalSource );
- if( err ) exit( 1 );
+exit:
+ return( err );
}
-static int SocketToPortNumber( SocketRef inSock )
+//===========================================================================================================================
+// StringToUInt32
+//===========================================================================================================================
+
+static OSStatus StringToUInt32( const char *inString, uint32_t *outValue )
{
OSStatus err;
- sockaddr_ip sip;
- socklen_t len;
+ uint32_t value;
+ char * endPtr;
- len = (socklen_t) sizeof( sip );
- err = getsockname( inSock, &sip.sa, &len );
- err = map_socket_noerr_errno( inSock, err );
- check_noerr( err );
- return( err ? -1 : SockAddrGetPort( &sip ) );
+ value = (uint32_t) strtol( inString, &endPtr, 0 );
+ require_action_quiet( ( *endPtr == '\0' ) && ( endPtr != inString ), exit, err = kParamErr );
+
+ *outValue = value;
+ err = kNoErr;
+
+exit:
+ return( err );
}
-static OSStatus WriteSSDPSearchRequest( HTTPHeader *inHeader, const void *inHostSA, int inMX, const char *inST )
+#if( TARGET_OS_DARWIN )
+//===========================================================================================================================
+// StringToPID
+//===========================================================================================================================
+
+static OSStatus StringToPID( const char *inString, pid_t *outPID )
{
OSStatus err;
+ long long value;
+ char * endPtr;
- err = HTTPHeader_InitRequest( inHeader, "M-SEARCH", "*", "HTTP/1.1" );
- require_noerr( err, exit );
+ set_errno_compat( 0 );
+ value = strtoll( inString, &endPtr, 0 );
+ err = errno_compat();
+ require_noerr_quiet( err, exit );
+ require_action_quiet( ( *endPtr == '\0' ) && ( endPtr != inString ), exit, err = kMalformedErr );
+ require_action_quiet( value == (pid_t) value, exit, err = kRangeErr );
- err = HTTPHeader_SetField( inHeader, "Host", "%##a", inHostSA );
- require_noerr( err, exit );
+ *outPID = (pid_t) value;
+ err = kNoErr;
- err = HTTPHeader_SetField( inHeader, "ST", "%s", inST ? inST : "ssdp:all" );
- require_noerr( err, exit );
+exit:
+ return( err );
+}
+#endif
+
+//===========================================================================================================================
+// StringToARecordData
+//===========================================================================================================================
+
+static OSStatus StringToARecordData( const char *inString, uint8_t **outPtr, size_t *outLen )
+{
+ OSStatus err;
+ uint32_t * addrPtr;
+ const size_t addrLen = sizeof( *addrPtr );
+ const char * end;
- err = HTTPHeader_SetField( inHeader, "Man", "\"ssdp:discover\"" );
- require_noerr( err, exit );
+ addrPtr = (uint32_t *) malloc( addrLen );
+ require_action( addrPtr, exit, err = kNoMemoryErr );
- err = HTTPHeader_SetField( inHeader, "MX", "%d", inMX );
- require_noerr( err, exit );
+ err = StringToIPv4Address( inString, kStringToIPAddressFlagsNoPort | kStringToIPAddressFlagsNoPrefix, addrPtr,
+ NULL, NULL, NULL, &end );
+ if( !err && ( *end != '\0' ) ) err = kMalformedErr;
+ require_noerr_quiet( err, exit );
- err = HTTPHeader_Commit( inHeader );
- require_noerr( err, exit );
+ *addrPtr = HostToBig32( *addrPtr );
+
+ *outPtr = (uint8_t *) addrPtr;
+ addrPtr = NULL;
+ *outLen = addrLen;
exit:
+ FreeNullSafe( addrPtr );
return( err );
}
//===========================================================================================================================
-// SSDPDiscoverPrintPrologue
+// StringToAAAARecordData
//===========================================================================================================================
-static void SSDPDiscoverPrintPrologue( const SSDPDiscoverContext *inContext )
+static OSStatus StringToAAAARecordData( const char *inString, uint8_t **outPtr, size_t *outLen )
{
- const int receiveSecs = inContext->receiveSecs;
- const char * ifName;
- char ifNameBuf[ IF_NAMESIZE + 1 ];
- NetTransportType ifType;
+ OSStatus err;
+ uint8_t * addrPtr;
+ const size_t addrLen = 16;
+ const char * end;
- ifName = if_indextoname( inContext->ifindex, ifNameBuf );
+ addrPtr = (uint8_t *) malloc( addrLen );
+ require_action( addrPtr, exit, err = kNoMemoryErr );
- ifType = kNetTransportType_Undefined;
- if( ifName ) SocketGetInterfaceInfo( kInvalidSocketRef, ifName, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &ifType );
+ err = StringToIPv6Address( inString,
+ kStringToIPAddressFlagsNoPort | kStringToIPAddressFlagsNoPrefix | kStringToIPAddressFlagsNoScope,
+ addrPtr, NULL, NULL, NULL, &end );
+ if( !err && ( *end != '\0' ) ) err = kMalformedErr;
+ require_noerr_quiet( err, exit );
- FPrintF( stdout, "Interface: %s/%d/%s\n",
- ifName ? ifName : "?", inContext->ifindex, NetTransportTypeToString( ifType ) );
- FPrintF( stdout, "IP protocols: %?s%?s%?s\n",
- inContext->useIPv4, "IPv4", ( inContext->useIPv4 && inContext->useIPv6 ), ", ", inContext->useIPv6, "IPv6" );
- FPrintF( stdout, "Receive duration: " );
- if( receiveSecs >= 0 ) FPrintF( stdout, "%d second%?c\n", receiveSecs, receiveSecs != 1, 's' );
- else FPrintF( stdout, "â\n" );
- FPrintF( stdout, "Start time: %{du:time}\n", NULL );
+ *outPtr = addrPtr;
+ addrPtr = NULL;
+ *outLen = addrLen;
+
+exit:
+ FreeNullSafe( addrPtr );
+ return( err );
}
//===========================================================================================================================
-// SSDPDiscoverReadHandler
+// StringToDomainName
//===========================================================================================================================
-static void SSDPDiscoverReadHandler( void *inContext )
+static OSStatus StringToDomainName( const char *inString, uint8_t **outPtr, size_t *outLen )
{
- OSStatus err;
- struct timeval now;
- SocketContext * const sockCtx = (SocketContext *) inContext;
- SSDPDiscoverContext * const context = (SSDPDiscoverContext *) sockCtx->userContext;
- HTTPHeader * const header = &context->header;
- sockaddr_ip fromAddr;
- size_t msgLen;
+ OSStatus err;
+ uint8_t * namePtr;
+ size_t nameLen;
+ uint8_t * end;
+ uint8_t nameBuf[ kDomainNameLengthMax ];
- gettimeofday( &now, NULL );
+ err = DomainNameFromString( nameBuf, inString, &end );
+ require_noerr_quiet( err, exit );
- err = SocketRecvFrom( sockCtx->sock, header->buf, sizeof( header->buf ), &msgLen, &fromAddr, sizeof( fromAddr ),
- NULL, NULL, NULL, NULL );
- require_noerr( err, exit );
+ nameLen = (size_t)( end - nameBuf );
+ namePtr = memdup( nameBuf, nameLen );
+ require_action( namePtr, exit, err = kNoMemoryErr );
- FPrintF( stdout, "---\n" );
- FPrintF( stdout, "Receive time: %{du:time}\n", &now );
- FPrintF( stdout, "Source: %##a\n", &fromAddr );
- FPrintF( stdout, "Message size: %zu\n", msgLen );
- header->len = msgLen;
- if( HTTPHeader_Validate( header ) )
- {
- FPrintF( stdout, "HTTP header:\n%1{text}", header->buf, header->len );
- if( header->extraDataLen > 0 )
- {
- FPrintF( stdout, "HTTP body: %1.1H", header->extraDataPtr, (int) header->extraDataLen, INT_MAX );
- }
- }
- else
- {
- FPrintF( stdout, "Invalid HTTP message:\n%1.1H", header->buf, (int) msgLen, INT_MAX );
- goto exit;
- }
+ *outPtr = namePtr;
+ namePtr = NULL;
+ if( outLen ) *outLen = nameLen;
exit:
- if( err ) exit( 1 );
+ return( err );
}
+#if( TARGET_OS_DARWIN )
//===========================================================================================================================
-// HTTPHeader_Validate
-//
-// Parses for the end of an HTTP header and updates the HTTPHeader structure so it's ready to parse. Returns true if valid.
-// This assumes the "buf" and "len" fields are set. The other fields are set by this function.
-//
-// Note: This was copied from CoreUtils because the HTTPHeader_Validate function is currently not exported in the framework.
+// GetDefaultDNSServer
//===========================================================================================================================
-Boolean HTTPHeader_Validate( HTTPHeader *inHeader )
+static OSStatus GetDefaultDNSServer( sockaddr_ip *outAddr )
{
- const char * src;
- const char * end;
+ OSStatus err;
+ dns_config_t * config;
+ struct sockaddr * addr;
+ int32_t i;
- // Check for interleaved binary data (4 byte header that begins with $). See RFC 2326 section 10.12.
+ config = dns_configuration_copy();
+ require_action( config, exit, err = kUnknownErr );
- require( inHeader->len < sizeof( inHeader->buf ), exit );
- src = inHeader->buf;
- end = src + inHeader->len;
- if( ( ( end - src ) >= 4 ) && ( src[ 0 ] == '$' ) )
- {
- src += 4;
- }
- else
+ addr = NULL;
+ for( i = 0; i < config->n_resolver; ++i )
{
- // Search for an empty line (HTTP-style header/body separator). CRLFCRLF, LFCRLF, or LFLF accepted.
- // $$$ TO DO: Start from the last search location to avoid re-searching the same data over and over.
+ const dns_resolver_t * const resolver = config->resolver[ i ];
- for( ;; )
+ if( !resolver->domain && ( resolver->n_nameserver > 0 ) )
{
- while( ( src < end ) && ( src[ 0 ] != '\n' ) ) ++src;
- if( src >= end ) goto exit;
- ++src;
- if( ( ( end - src ) >= 2 ) && ( src[ 0 ] == '\r' ) && ( src[ 1 ] == '\n' ) ) // CFLFCRLF or LFCRLF
- {
- src += 2;
- break;
- }
- else if( ( ( end - src ) >= 1 ) && ( src[ 0 ] == '\n' ) ) // LFLF
- {
- src += 1;
- break;
- }
+ addr = resolver->nameserver[ 0 ];
+ break;
}
- }
- inHeader->extraDataPtr = src;
- inHeader->extraDataLen = (size_t)( end - src );
- inHeader->len = (size_t)( src - inHeader->buf );
- return( true );
+ }
+ require_action_quiet( addr, exit, err = kNotFoundErr );
+
+ SockAddrCopy( addr, outAddr );
+ err = kNoErr;
exit:
- return( false );
+ if( config ) dns_configuration_free( config );
+ return( err );
}
+#endif
-#if( TARGET_OS_DARWIN )
//===========================================================================================================================
-// ResQueryCmd
+// GetMDNSMulticastAddrV4
//===========================================================================================================================
-// res_query() from libresolv is actually called res_9_query (see /usr/include/resolv.h).
+static void _MDNSMulticastAddrV4Init( void *inContext );
-SOFT_LINK_LIBRARY_EX( "/usr/lib", resolv );
-SOFT_LINK_FUNCTION_EX( resolv, res_9_query,
- int,
- ( const char *dname, int class, int type, u_char *answer, int anslen ),
- ( dname, class, type, answer, anslen ) );
+static const struct sockaddr * GetMDNSMulticastAddrV4( void )
+{
+ static struct sockaddr_in sMDNSMulticastAddrV4;
+ static dispatch_once_t sMDNSMulticastAddrV4InitOnce = 0;
+
+ dispatch_once_f( &sMDNSMulticastAddrV4InitOnce, &sMDNSMulticastAddrV4, _MDNSMulticastAddrV4Init);
+ return( (const struct sockaddr *) &sMDNSMulticastAddrV4 );
+}
-// res_query() from libinfo
+static void _MDNSMulticastAddrV4Init( void *inContext )
+{
+ struct sockaddr_in * const addr = (struct sockaddr_in *) inContext;
+
+ memset( addr, 0, sizeof( *addr ) );
+ SIN_LEN_SET( addr );
+ addr->sin_family = AF_INET;
+ addr->sin_port = htons( kMDNSPort );
+ addr->sin_addr.s_addr = htonl( 0xE00000FB ); // The mDNS IPv4 multicast address is 224.0.0.251
+}
-SOFT_LINK_LIBRARY_EX( "/usr/lib", info );
-SOFT_LINK_FUNCTION_EX( info, res_query,
- int,
- ( const char *dname, int class, int type, u_char *answer, int anslen ),
- ( dname, class, type, answer, anslen ) );
+//===========================================================================================================================
+// GetMDNSMulticastAddrV6
+//===========================================================================================================================
-typedef int ( *res_query_f )( const char *dname, int class, int type, u_char *answer, int anslen );
+static void _MDNSMulticastAddrV6Init( void *inContext );
-static void ResQueryCmd( void )
+static const struct sockaddr * GetMDNSMulticastAddrV6( void )
{
- OSStatus err;
- res_query_f res_query_ptr;
- int n;
- uint16_t type, class;
- uint8_t answer[ 1024 ];
+ static struct sockaddr_in6 sMDNSMulticastAddrV6;
+ static dispatch_once_t sMDNSMulticastAddrV6InitOnce = 0;
+
+ dispatch_once_f( &sMDNSMulticastAddrV6InitOnce, &sMDNSMulticastAddrV6, _MDNSMulticastAddrV6Init);
+ return( (const struct sockaddr *) &sMDNSMulticastAddrV6 );
+}
+
+static void _MDNSMulticastAddrV6Init( void *inContext )
+{
+ struct sockaddr_in6 * const addr = (struct sockaddr_in6 *) inContext;
- // Get pointer to one of the res_query() functions.
+ memset( addr, 0, sizeof( *addr ) );
+ SIN6_LEN_SET( addr );
+ addr->sin6_family = AF_INET6;
+ addr->sin6_port = htons( kMDNSPort );
+ addr->sin6_addr.s6_addr[ 0 ] = 0xFF; // The mDNS IPv6 multicast address is FF02::FB.
+ addr->sin6_addr.s6_addr[ 1 ] = 0x02;
+ addr->sin6_addr.s6_addr[ 15 ] = 0xFB;
+}
+
+//===========================================================================================================================
+// GetAnyMDNSInterface
+//===========================================================================================================================
+
+static OSStatus GetAnyMDNSInterface( char inNameBuf[ IF_NAMESIZE + 1 ], uint32_t *outIndex )
+{
+ OSStatus err;
+ struct ifaddrs * ifaList;
+ const struct ifaddrs * ifa;
+ const struct ifaddrs * ifa2;
+ const char * ifname = NULL;
+ const unsigned int checkFlags = IFF_UP | IFF_MULTICAST | IFF_LOOPBACK | IFF_POINTOPOINT;
+ const unsigned int wantFlags = IFF_UP | IFF_MULTICAST;
+ int wantFamily;
+ NetTransportType type;
+
+ ifaList = NULL;
+ err = getifaddrs( &ifaList );
+ err = map_global_noerr_errno( err );
+ require_noerr( err, exit );
- if( gResQuery_UseLibInfo )
+ for( ifa = ifaList; ifa; ifa = ifa->ifa_next )
{
- if( !SOFT_LINK_HAS_FUNCTION( info, res_query ) )
+ if( ( ifa->ifa_flags & checkFlags ) != wantFlags ) continue;
+ if( !ifa->ifa_addr || !ifa->ifa_name ) continue;
+ if( ( ifa->ifa_addr->sa_family != AF_INET ) &&
+ ( ifa->ifa_addr->sa_family != AF_INET6 ) ) continue;
+
+ err = SocketGetInterfaceInfo( kInvalidSocketRef, ifa->ifa_name, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &type );
+ check_noerr( err );
+ if( err || ( type == kNetTransportType_AWDL ) ) continue;
+
+ if( !ifname ) ifname = ifa->ifa_name;
+ wantFamily = ( ifa->ifa_addr->sa_family == AF_INET ) ? AF_INET6 : AF_INET;
+
+ for( ifa2 = ifa->ifa_next; ifa2; ifa2 = ifa2->ifa_next )
{
- FPrintF( stderr, "Failed to soft link res_query from libinfo.\n" );
- err = kNotFoundErr;
- goto exit;
+ if( ( ifa2->ifa_flags & checkFlags ) != wantFlags ) continue;
+ if( !ifa2->ifa_addr || !ifa2->ifa_name ) continue;
+ if( ifa2->ifa_addr->sa_family != wantFamily ) continue;
+ if( strcmp( ifa2->ifa_name, ifa->ifa_name ) == 0 ) break;
}
- res_query_ptr = soft_res_query;
- }
- else
- {
- if( !SOFT_LINK_HAS_FUNCTION( resolv, res_9_query ) )
+ if( ifa2 )
{
- FPrintF( stderr, "Failed to soft link res_query from libresolv.\n" );
- err = kNotFoundErr;
- goto exit;
+ ifname = ifa->ifa_name;
+ break;
}
- res_query_ptr = soft_res_9_query;
}
+ require_action_quiet( ifname, exit, err = kNotFoundErr );
- // Get record type.
+ if( inNameBuf ) strlcpy( inNameBuf, ifname, IF_NAMESIZE + 1 );
+ if( outIndex ) *outIndex = if_nametoindex( ifname );
- err = RecordTypeFromArgString( gResQuery_Type, &type );
- require_noerr( err, exit );
+exit:
+ if( ifaList ) freeifaddrs( ifaList );
+ return( err );
+}
+
+//===========================================================================================================================
+// CreateMulticastSocket
+//===========================================================================================================================
+
+static OSStatus
+ CreateMulticastSocket(
+ const struct sockaddr * inAddr,
+ int inPort,
+ const char * inIfName,
+ uint32_t inIfIndex,
+ Boolean inJoin,
+ int * outPort,
+ SocketRef * outSock )
+{
+ OSStatus err;
+ SocketRef sock = kInvalidSocketRef;
+ const int family = inAddr->sa_family;
+ int port;
- // Get record class.
+ require_action_quiet( ( family == AF_INET ) ||( family == AF_INET6 ), exit, err = kUnsupportedErr );
- if( gResQuery_Class )
+ err = ServerSocketOpen( family, SOCK_DGRAM, IPPROTO_UDP, inPort, &port, kSocketBufferSize_DontSet, &sock );
+ require_noerr_quiet( err, exit );
+
+ err = SocketSetMulticastInterface( sock, inIfName, inIfIndex );
+ require_noerr_quiet( err, exit );
+
+ if( family == AF_INET )
{
- err = RecordClassFromArgString( gResQuery_Class, &class );
- require_noerr( err, exit );
+ err = setsockopt( sock, IPPROTO_IP, IP_MULTICAST_LOOP, (char *) &(uint8_t){ 1 }, (socklen_t) sizeof( uint8_t ) );
+ err = map_socket_noerr_errno( sock, err );
+ require_noerr_quiet( err, exit );
}
else
{
- class = kDNSServiceClass_IN;
+ err = setsockopt( sock, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, (char *) &(int){ 1 }, (socklen_t) sizeof( int ) );
+ err = map_socket_noerr_errno( sock, err );
+ require_noerr_quiet( err, exit );
}
- // Print prologue.
+ if( inJoin )
+ {
+ err = SocketJoinMulticast( sock, inAddr, inIfName, inIfIndex );
+ require_noerr_quiet( err, exit );
+ }
- FPrintF( stdout, "Name: %s\n", gResQuery_Name );
- FPrintF( stdout, "Type: %s (%u)\n", RecordTypeToString( type ), type );
- FPrintF( stdout, "Class: %s (%u)\n", ( class == kDNSServiceClass_IN ) ? "IN" : "???", class );
- FPrintF( stdout, "Start time: %{du:time}\n", NULL );
- FPrintF( stdout, "---\n" );
+ if( outPort ) *outPort = port;
+ *outSock = sock;
+ sock = kInvalidSocketRef;
- // Call res_query().
+exit:
+ ForgetSocket( &sock );
+ return( err );
+}
+
+//===========================================================================================================================
+// DecimalTextToUInt32
+//===========================================================================================================================
+
+static OSStatus DecimalTextToUInt32( const char *inSrc, const char *inEnd, uint32_t *outValue, const char **outPtr )
+{
+ OSStatus err;
+ uint64_t value;
+ const char * ptr = inSrc;
- n = res_query_ptr( gResQuery_Name, class, type, (u_char *) answer, (int) sizeof( answer ) );
- if( n < 0 )
+ require_action_quiet( ( ptr < inEnd ) && isdigit_safe( *ptr ), exit, err = kMalformedErr );
+
+ value = (uint64_t)( *ptr++ - '0' );
+ if( value == 0 )
{
- FPrintF( stderr, "res_query() failed with error: %d (%s).\n", h_errno, hstrerror( h_errno ) );
- err = kUnknownErr;
- goto exit;
+ if( ( ptr < inEnd ) && isdigit_safe( *ptr ) )
+ {
+ err = kMalformedErr;
+ goto exit;
+ }
+ }
+ else
+ {
+ while( ( ptr < inEnd ) && isdigit_safe( *ptr ) )
+ {
+ value = ( value * 10 ) + (uint64_t)( *ptr++ - '0' );
+ require_action_quiet( value <= UINT32_MAX, exit, err = kRangeErr );
+ }
}
- // Print result.
-
- FPrintF( stdout, "Message size: %d\n\n%{du:dnsmsg}", n, answer, (size_t) n );
+ *outValue = (uint32_t) value;
+ if( outPtr ) *outPtr = ptr;
+ err = kNoErr;
exit:
- if( err ) exit( 1 );
+ return( err );
}
//===========================================================================================================================
-// ResolvDNSQueryCmd
+// CheckIntegerArgument
//===========================================================================================================================
-// dns_handle_t is defined as a pointer to a privately-defined struct in /usr/include/dns.h. It's defined as a void * here to
-// avoid including the header file.
+static OSStatus CheckIntegerArgument( int inArgValue, const char *inArgName, int inMin, int inMax )
+{
+ if( ( inArgValue >= inMin ) && ( inArgValue <= inMax ) ) return( kNoErr );
+
+ FPrintF( stderr, "error: Invalid %s: %d. Valid range is [%d, %d].\n", inArgName, inArgValue, inMin, inMax );
+ return( kRangeErr );
+}
-typedef void * dns_handle_t;
+//===========================================================================================================================
+// CheckDoubleArgument
+//===========================================================================================================================
-SOFT_LINK_FUNCTION_EX( resolv, dns_open, dns_handle_t, ( const char *path ), ( path ) );
-SOFT_LINK_FUNCTION_VOID_RETURN_EX( resolv, dns_free, ( dns_handle_t *dns ), ( dns ) );
-SOFT_LINK_FUNCTION_EX( resolv, dns_query,
- int32_t, (
- dns_handle_t dns,
- const char * name,
- uint32_t dnsclass,
- uint32_t dnstype,
- char * buf,
- uint32_t len,
- struct sockaddr * from,
- uint32_t * fromlen ),
- ( dns, name, dnsclass, dnstype, buf, len, from, fromlen ) );
+static OSStatus CheckDoubleArgument( double inArgValue, const char *inArgName, double inMin, double inMax )
+{
+ if( ( inArgValue >= inMin ) && ( inArgValue <= inMax ) ) return( kNoErr );
+
+ FPrintF( stderr, "error: Invalid %s: %.1f. Valid range is [%.1f, %.1f].\n", inArgName, inArgValue, inMin, inMax );
+ return( kRangeErr );
+}
-static void ResolvDNSQueryCmd( void )
+//===========================================================================================================================
+// CheckRootUser
+//===========================================================================================================================
+
+static OSStatus CheckRootUser( void )
{
- OSStatus err;
- int n;
- dns_handle_t dns = NULL;
- uint16_t type, class;
- sockaddr_ip from;
- uint32_t fromLen;
- uint8_t answer[ 1024 ];
+ if( geteuid() == 0 ) return( kNoErr );
- // Make sure that the required symbols are available.
+ FPrintF( stderr, "error: This command must to be run as root.\n" );
+ return( kPermissionErr );
+}
+
+//===========================================================================================================================
+// SpawnCommand
+//
+// Note: Based on systemf() from CoreUtils framework.
+//===========================================================================================================================
+
+extern char ** environ;
+
+static OSStatus SpawnCommand( pid_t *outPID, const char *inFormat, ... )
+{
+ OSStatus err;
+ va_list args;
+ char * command;
+ char * argv[ 4 ];
+ pid_t pid;
- if( !SOFT_LINK_HAS_FUNCTION( resolv, dns_open ) )
- {
- FPrintF( stderr, "Failed to soft link dns_open from libresolv.\n" );
- err = kNotFoundErr;
- goto exit;
- }
+ command = NULL;
+ va_start( args, inFormat );
+ VASPrintF( &command, inFormat, args );
+ va_end( args );
+ require_action( command, exit, err = kUnknownErr );
- if( !SOFT_LINK_HAS_FUNCTION( resolv, dns_free ) )
- {
- FPrintF( stderr, "Failed to soft link dns_free from libresolv.\n" );
- err = kNotFoundErr;
- goto exit;
- }
+ argv[ 0 ] = "/bin/sh";
+ argv[ 1 ] = "-c";
+ argv[ 2 ] = command;
+ argv[ 3 ] = NULL;
+ err = posix_spawn( &pid, argv[ 0 ], NULL, NULL, argv, environ );
+ free( command );
+ require_noerr_quiet( err, exit );
- if( !SOFT_LINK_HAS_FUNCTION( resolv, dns_query ) )
- {
- FPrintF( stderr, "Failed to soft link dns_query from libresolv.\n" );
- err = kNotFoundErr;
- goto exit;
- }
+ if( outPID ) *outPID = pid;
- // Get record type.
+exit:
+ return( err );
+}
+
+//===========================================================================================================================
+// OutputPropertyList
+//===========================================================================================================================
+
+static OSStatus
+ OutputPropertyList(
+ CFPropertyListRef inPList,
+ OutputFormatType inType,
+ Boolean inAppendNewline,
+ const char * inOutputFilePath )
+{
+ OSStatus err;
+ CFDataRef results = NULL;
+ FILE * file = NULL;
- err = RecordTypeFromArgString( gResolvDNSQuery_Type, &type );
- require_noerr( err, exit );
+ // Convert plist to a specific format.
- // Get record class.
+ switch( inType )
+ {
+ case kOutputFormatType_JSON:
+ results = CFCreateJSONData( inPList, kJSONFlags_None, NULL );
+ require_action( results, exit, err = kUnknownErr );
+ break;
+
+ case kOutputFormatType_XML:
+ results = CFPropertyListCreateData( NULL, inPList, kCFPropertyListXMLFormat_v1_0, 0, NULL );
+ require_action( results, exit, err = kUnknownErr );
+ break;
+
+ case kOutputFormatType_Binary:
+ results = CFPropertyListCreateData( NULL, inPList, kCFPropertyListBinaryFormat_v1_0, 0, NULL );
+ require_action( results, exit, err = kUnknownErr );
+ break;
+
+ default:
+ err = kTypeErr;
+ goto exit;
+ }
- if( gResolvDNSQuery_Class )
+ // Write formatted results to file or stdout.
+
+ if( inOutputFilePath )
{
- err = RecordClassFromArgString( gResolvDNSQuery_Class, &class );
+ file = fopen( inOutputFilePath, "wb" );
+ err = map_global_value_errno( file, file );
require_noerr( err, exit );
}
else
{
- class = kDNSServiceClass_IN;
- }
-
- // Get dns handle.
-
- dns = soft_dns_open( gResolvDNSQuery_Path );
- if( !dns )
- {
- FPrintF( stderr, "dns_open( %s ) failed.\n", gResolvDNSQuery_Path );
- err = kUnknownErr;
- goto exit;
+ file = stdout;
}
- // Print prologue.
-
- FPrintF( stdout, "Name: %s\n", gResolvDNSQuery_Name );
- FPrintF( stdout, "Type: %s (%u)\n", RecordTypeToString( type ), type );
- FPrintF( stdout, "Class: %s (%u)\n", ( class == kDNSServiceClass_IN ) ? "IN" : "???", class );
- FPrintF( stdout, "Path: %s\n", gResolvDNSQuery_Path ? gResolvDNSQuery_Name : "<NULL>" );
- FPrintF( stdout, "Start time: %{du:time}\n", NULL );
- FPrintF( stdout, "---\n" );
+ err = WriteANSIFile( file, CFDataGetBytePtr( results ), (size_t) CFDataGetLength( results ) );
+ require_noerr_quiet( err, exit );
- // Call dns_query().
+ // Write a trailing newline for JSON-formatted results if requested.
- memset( &from, 0, sizeof( from ) );
- fromLen = (uint32_t) sizeof( from );
- n = soft_dns_query( dns, gResolvDNSQuery_Name, class, type, (char *) answer, (uint32_t) sizeof( answer ), &from.sa,
- &fromLen );
- if( n < 0 )
+ if( ( inType == kOutputFormatType_JSON ) && inAppendNewline )
{
- FPrintF( stderr, "dns_query() failed with error: %d (%s).\n", h_errno, hstrerror( h_errno ) );
- err = kUnknownErr;
- goto exit;
+ err = WriteANSIFile( file, "\n", 1 );
+ require_noerr_quiet( err, exit );
}
- // Print result.
-
- FPrintF( stdout, "From: %##a\n", &from );
- FPrintF( stdout, "Message size: %d\n\n%{du:dnsmsg}", n, answer, (size_t) n );
-
exit:
- if( dns ) soft_dns_free( dns );
- if( err ) exit( 1 );
+ if( file && ( file != stdout ) ) fclose( file );
+ CFReleaseNullSafe( results );
+ return( err );
+}
+
+//===========================================================================================================================
+// DNSRecordFixedFieldsSet
+//===========================================================================================================================
+
+static void
+ DNSRecordFixedFieldsSet(
+ DNSRecordFixedFields * inFields,
+ uint16_t inType,
+ uint16_t inClass,
+ uint32_t inTTL,
+ uint16_t inRDLength )
+{
+ WriteBig16( inFields->type, inType );
+ WriteBig16( inFields->class, inClass );
+ WriteBig32( inFields->ttl, inTTL );
+ WriteBig16( inFields->rdlength, inRDLength );
+}
+
+//===========================================================================================================================
+// SRVRecordDataFixedFieldsGet
+//===========================================================================================================================
+
+static void
+ SRVRecordDataFixedFieldsGet(
+ const SRVRecordDataFixedFields * inFields,
+ unsigned int * outPriority,
+ unsigned int * outWeight,
+ unsigned int * outPort )
+{
+ if( outPriority ) *outPriority = ReadBig16( inFields->priority );
+ if( outWeight ) *outWeight = ReadBig16( inFields->weight );
+ if( outPort ) *outPort = ReadBig16( inFields->port );
}
//===========================================================================================================================
-// CFHostCmd
+// SRVRecordDataFixedFieldsSet
//===========================================================================================================================
static void
- _CFHostResolveCallback(
- CFHostRef inHost,
- CFHostInfoType inInfoType,
- const CFStreamError * inError,
- void * inInfo );
+ SRVRecordDataFixedFieldsSet(
+ SRVRecordDataFixedFields * inFields,
+ uint16_t inPriority,
+ uint16_t inWeight,
+ uint16_t inPort )
+{
+ WriteBig16( inFields->priority, inPriority );
+ WriteBig16( inFields->weight, inWeight );
+ WriteBig16( inFields->port, inPort );
+}
-static void CFHostCmd( void )
+//===========================================================================================================================
+// SOARecordDataFixedFieldsGet
+//===========================================================================================================================
+
+static void
+ SOARecordDataFixedFieldsGet(
+ const SOARecordDataFixedFields * inFields,
+ uint32_t * outSerial,
+ uint32_t * outRefresh,
+ uint32_t * outRetry,
+ uint32_t * outExpire,
+ uint32_t * outMinimum )
+{
+ if( outSerial ) *outSerial = ReadBig32( inFields->serial );
+ if( outRefresh ) *outRefresh = ReadBig32( inFields->refresh );
+ if( outRetry ) *outRetry = ReadBig32( inFields->retry );
+ if( outExpire ) *outExpire = ReadBig32( inFields->expire );
+ if( outMinimum ) *outMinimum = ReadBig32( inFields->minimum );
+}
+
+//===========================================================================================================================
+// SOARecordDataFixedFieldsSet
+//===========================================================================================================================
+
+static void
+ SOARecordDataFixedFieldsSet(
+ SOARecordDataFixedFields * inFields,
+ uint32_t inSerial,
+ uint32_t inRefresh,
+ uint32_t inRetry,
+ uint32_t inExpire,
+ uint32_t inMinimum )
+{
+ WriteBig32( inFields->serial, inSerial );
+ WriteBig32( inFields->refresh, inRefresh );
+ WriteBig32( inFields->retry, inRetry );
+ WriteBig32( inFields->expire, inExpire );
+ WriteBig32( inFields->minimum, inMinimum );
+}
+
+//===========================================================================================================================
+// CreateSRVRecordDataFromString
+//===========================================================================================================================
+
+static OSStatus CreateSRVRecordDataFromString( const char *inString, uint8_t **outPtr, size_t *outLen )
{
- OSStatus err;
- CFStringRef name;
- Boolean success;
- CFHostRef host = NULL;
- CFHostClientContext context;
- CFStreamError streamErr;
-
- name = CFStringCreateWithCString( kCFAllocatorDefault, gCFHost_Name, kCFStringEncodingUTF8 );
- require_action( name, exit, err = kUnknownErr );
+ OSStatus err;
+ DataBuffer dataBuf;
+ const char * ptr;
+ int i;
+ uint8_t * end;
+ uint8_t target[ kDomainNameLengthMax ];
- host = CFHostCreateWithName( kCFAllocatorDefault, name );
- ForgetCF( &name );
- require_action( host, exit, err = kUnknownErr );
+ DataBuffer_Init( &dataBuf, NULL, 0, ( 3 * 2 ) + kDomainNameLengthMax );
- memset( &context, 0, sizeof( context ) );
- success = CFHostSetClient( host, _CFHostResolveCallback, &context );
- require_action( success, exit, err = kUnknownErr );
+ // Parse and set the priority, weight, and port values (all three are unsigned 16-bit values).
- CFHostScheduleWithRunLoop( host, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode );
+ ptr = inString;
+ for( i = 0; i < 3; ++i )
+ {
+ char * next;
+ long value;
+ uint8_t buf[ 2 ];
+
+ value = strtol( ptr, &next, 0 );
+ require_action_quiet( ( next != ptr ) && ( *next == ',' ), exit, err = kMalformedErr );
+ require_action_quiet( ( value >= 0 ) && ( value <= UINT16_MAX ), exit, err = kRangeErr );
+ ptr = next + 1;
+
+ WriteBig16( buf, value );
+
+ err = DataBuffer_Append( &dataBuf, buf, sizeof( buf ) );
+ require_noerr( err, exit );
+ }
- // Print prologue.
+ // Set the target domain name.
- FPrintF( stdout, "Hostname: %s\n", gCFHost_Name );
- FPrintF( stdout, "Start time: %{du:time}\n", NULL );
- FPrintF( stdout, "---\n" );
+ err = DomainNameFromString( target, ptr, &end );
+ require_noerr_quiet( err, exit );
- success = CFHostStartInfoResolution( host, kCFHostAddresses, &streamErr );
- require_action( success, exit, err = kUnknownErr );
- err = kNoErr;
+ err = DataBuffer_Append( &dataBuf, target, (size_t)( end - target ) );
+ require_noerr( err, exit );
- CFRunLoopRun();
+ err = DataBuffer_Detach( &dataBuf, outPtr, outLen );
+ require_noerr( err, exit );
exit:
- CFReleaseNullSafe( host );
- if( err ) exit( 1 );
+ DataBuffer_Free( &dataBuf );
+ return( err );
}
-static void _CFHostResolveCallback( CFHostRef inHost, CFHostInfoType inInfoType, const CFStreamError *inError, void *inInfo )
+//===========================================================================================================================
+// CreateTXTRecordDataFromString
+//===========================================================================================================================
+
+static OSStatus CreateTXTRecordDataFromString(const char *inString, int inDelimiter, uint8_t **outPtr, size_t *outLen )
{
OSStatus err;
- struct timeval now;
-
- gettimeofday( &now, NULL );
+ DataBuffer dataBuf;
+ const char * src;
+ uint8_t txtStr[ 256 ]; // Buffer for single TXT string: 1 length byte + up to 255 bytes of data.
- Unused( inInfoType );
- Unused( inInfo );
+ DataBuffer_Init( &dataBuf, NULL, 0, kDNSRecordDataLengthMax );
- if( inError && ( inError->domain != 0 ) && ( inError->error ) )
- {
- err = inError->error;
- if( inError->domain == kCFStreamErrorDomainNetDB )
- {
- FPrintF( stderr, "Error %d: %s.\n", err, gai_strerror( err ) );
- }
- else
- {
- FPrintF( stderr, "Error %#m\n", err );
- }
- }
- else
+ src = inString;
+ for( ;; )
{
- CFArrayRef addresses;
- CFIndex count, i;
- CFDataRef addrData;
- const struct sockaddr * sockAddr;
- Boolean wasResolved = false;
-
- addresses = CFHostGetAddressing( inHost, &wasResolved );
- check( wasResolved );
+ uint8_t * dst = &txtStr[ 1 ];
+ const uint8_t * const lim = &txtStr[ 256 ];
+ int c;
- if( addresses )
+ while( *src && ( *src != inDelimiter ) )
{
- count = CFArrayGetCount( addresses );
- for( i = 0; i < count; ++i )
+ if( ( c = *src++ ) == '\\' )
{
- addrData = CFArrayGetCFDataAtIndex( addresses, i, &err );
- require_noerr( err, exit );
-
- sockAddr = (const struct sockaddr *) CFDataGetBytePtr( addrData );
- FPrintF( stdout, "%##a\n", sockAddr );
+ require_action_quiet( *src != '\0', exit, err = kUnderrunErr );
+ c = *src++;
}
+ require_action_quiet( dst < lim, exit, err = kOverrunErr );
+ *dst++ = (uint8_t) c;
}
- err = kNoErr;
+ txtStr[ 0 ] = (uint8_t)( dst - &txtStr[ 1 ] );
+ err = DataBuffer_Append( &dataBuf, txtStr, 1 + txtStr[ 0 ] );
+ require_noerr( err, exit );
+
+ if( *src == '\0' ) break;
+ ++src;
}
- FPrintF( stdout, "---\n" );
- FPrintF( stdout, "End time: %{du:time}\n", &now );
-
- if( gCFHost_WaitSecs > 0 ) sleep( (unsigned int) gCFHost_WaitSecs );
+ err = DataBuffer_Detach( &dataBuf, outPtr, outLen );
+ require_noerr( err, exit );
exit:
- exit( err ? 1 : 0 );
+ DataBuffer_Free( &dataBuf );
+ return( err );
}
//===========================================================================================================================
-// DNSConfigAddCmd
-//
-// Note: Based on ajn's supplemental test tool.
+// CreateNSECRecordData
//===========================================================================================================================
-static void DNSConfigAddCmd( void )
+DECLARE_QSORT_NUMERIC_COMPARATOR( _QSortCmpUnsigned );
+DEFINE_QSORT_NUMERIC_COMPARATOR( unsigned int, _QSortCmpUnsigned )
+
+#define kNSECBitmapMaxLength 32 // 32 bytes (256 bits). See <https://tools.ietf.org/html/rfc4034#section-4.1.2>.
+
+static OSStatus
+ CreateNSECRecordData(
+ const uint8_t * inNextDomainName,
+ uint8_t ** outPtr,
+ size_t * outLen,
+ unsigned int inTypeCount,
+ ... )
{
- OSStatus err;
- CFMutableDictionaryRef dict = NULL;
- CFMutableArrayRef array = NULL;
- size_t i;
- SCDynamicStoreRef store = NULL;
- CFStringRef key = NULL;
- Boolean success;
-
- if( geteuid() != 0 )
- {
- FPrintF( stderr, "error: This command must to be run as root.\n" );
- err = kIDErr;
- goto exit;
- }
+ OSStatus err;
+ va_list args;
+ DataBuffer rdataDB;
+ unsigned int * array = NULL;
+ unsigned int i, type, maxBit, currBlock, bitmapLen;
+ uint8_t fields[ 2 + kNSECBitmapMaxLength ];
+ uint8_t * const bitmap = &fields[ 2 ];
- // Create dictionary.
+ va_start( args, inTypeCount );
+ DataBuffer_Init( &rdataDB, NULL, 0, kDNSRecordDataLengthMax );
- dict = CFDictionaryCreateMutable( NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks );
- require_action( dict, exit, err = kNoMemoryErr );
+ // Append Next Domain Name.
- // Add DNS server IP addresses.
+ err = DataBuffer_Append( &rdataDB, inNextDomainName, DomainNameLength( inNextDomainName ) );
+ require_noerr( err, exit );
- array = CFArrayCreateMutable( NULL, (CFIndex) gDNSConfigAdd_IPAddrCount, &kCFTypeArrayCallBacks );
- require_action( array, exit, err = kNoMemoryErr );
+ // Append Type Bit Maps.
- for( i = 0; i < gDNSConfigAdd_IPAddrCount; ++i )
+ maxBit = 0;
+ memset( bitmap, 0, kNSECBitmapMaxLength );
+ if( inTypeCount > 0 )
{
- CFStringRef addrStr;
+ array = (unsigned int *) malloc( inTypeCount * sizeof_element( array ) );
+ require_action( array, exit, err = kNoMemoryErr );
- addrStr = CFStringCreateWithCString( NULL, gDNSConfigAdd_IPAddrArray[ i ], kCFStringEncodingUTF8 );
- require_action( addrStr, exit, err = kUnknownErr );
+ for( i = 0; i < inTypeCount; ++i )
+ {
+ type = va_arg( args, unsigned int );
+ require_action_quiet( type <= UINT16_MAX, exit, err = kRangeErr );
+ array[ i ] = type;
+ }
+ qsort( array, inTypeCount, sizeof_element( array ), _QSortCmpUnsigned );
- CFArrayAppendValue( array, addrStr );
- CFRelease( addrStr );
- }
-
- CFDictionarySetValue( dict, kSCPropNetDNSServerAddresses, array );
- ForgetCF( &array );
-
- // Add domains, if any.
-
- array = CFArrayCreateMutable( NULL, (CFIndex) Min( gDNSConfigAdd_DomainCount, 1 ), &kCFTypeArrayCallBacks );
- require_action( array, exit, err = kNoMemoryErr );
-
- if( gDNSConfigAdd_DomainCount > 0 )
- {
- for( i = 0; i < gDNSConfigAdd_DomainCount; ++i )
+ currBlock = array[ 0 ] / 256;
+ for( i = 0; i < inTypeCount; ++i )
{
- CFStringRef domainStr;
+ const unsigned int block = array[ i ] / 256;
+ const unsigned int bit = array[ i ] % 256;
- domainStr = CFStringCreateWithCString( NULL, gDNSConfigAdd_DomainArray[ i ], kCFStringEncodingUTF8 );
- require_action( domainStr, exit, err = kUnknownErr );
-
- CFArrayAppendValue( array, domainStr );
- CFRelease( domainStr );
+ if( block != currBlock )
+ {
+ bitmapLen = BitArray_MaxBytes( maxBit + 1 );
+ fields[ 0 ] = (uint8_t) currBlock;
+ fields[ 1 ] = (uint8_t) bitmapLen;
+
+ err = DataBuffer_Append( &rdataDB, fields, 2 + bitmapLen );
+ require_noerr( err, exit );
+
+ maxBit = 0;
+ currBlock = block;
+ memset( bitmap, 0, bitmapLen );
+ }
+ BitArray_SetBit( bitmap, bit );
+ if( bit > maxBit ) maxBit = bit;
}
}
else
{
- // There are no domains, but the domain array needs to be non-empty, so add a zero-length string to the array.
-
- CFArrayAppendValue( array, CFSTR( "" ) );
- }
-
- CFDictionarySetValue( dict, kSCPropNetDNSSupplementalMatchDomains, array );
- ForgetCF( &array );
-
- // Add interface, if any.
-
- if( gDNSConfigAdd_Interface )
- {
- err = CFDictionarySetCString( dict, kSCPropInterfaceName, gDNSConfigAdd_Interface, kSizeCString );
- require_noerr( err, exit );
-
- CFDictionarySetValue( dict, kSCPropNetDNSConfirmedServiceID, gDNSConfigAdd_ID );
+ currBlock = 0;
}
- // Set dictionary in dynamic store.
+ bitmapLen = BitArray_MaxBytes( maxBit + 1 );
+ fields[ 0 ] = (uint8_t) currBlock;
+ fields[ 1 ] = (uint8_t) bitmapLen;
- store = SCDynamicStoreCreate( NULL, CFSTR( "com.apple.dnssdutil" ), NULL, NULL );
- err = map_scerror( store );
+ err = DataBuffer_Append( &rdataDB, fields, 2 + bitmapLen );
require_noerr( err, exit );
- key = SCDynamicStoreKeyCreateNetworkServiceEntity( NULL, kSCDynamicStoreDomainState, gDNSConfigAdd_ID, kSCEntNetDNS );
- require_action( key, exit, err = kUnknownErr );
-
- success = SCDynamicStoreSetValue( store, key, dict );
- require_action( success, exit, err = kUnknownErr );
+ err = DataBuffer_Detach( &rdataDB, outPtr, outLen );
+ require_noerr( err, exit );
exit:
- CFReleaseNullSafe( dict );
- CFReleaseNullSafe( array );
- CFReleaseNullSafe( store );
- CFReleaseNullSafe( key );
- gExitCode = err ? 1 : 0;
+ va_end( args );
+ DataBuffer_Free( &rdataDB );
+ FreeNullSafe( array );
+ return( err );
}
//===========================================================================================================================
-// DNSConfigRemoveCmd
+// AppendSOARecord
//===========================================================================================================================
-static void DNSConfigRemoveCmd( void )
-{
- OSStatus err;
- SCDynamicStoreRef store = NULL;
- CFStringRef key = NULL;
- Boolean success;
+static OSStatus
+ _AppendSOARecordData(
+ DataBuffer * inDB,
+ const uint8_t * inMName,
+ const uint8_t * inRName,
+ uint32_t inSerial,
+ uint32_t inRefresh,
+ uint32_t inRetry,
+ uint32_t inExpire,
+ uint32_t inMinimumTTL,
+ size_t * outLen );
+
+static OSStatus
+ AppendSOARecord(
+ DataBuffer * inDB,
+ const uint8_t * inNamePtr,
+ size_t inNameLen,
+ uint16_t inType,
+ uint16_t inClass,
+ uint32_t inTTL,
+ const uint8_t * inMName,
+ const uint8_t * inRName,
+ uint32_t inSerial,
+ uint32_t inRefresh,
+ uint32_t inRetry,
+ uint32_t inExpire,
+ uint32_t inMinimumTTL,
+ size_t * outLen )
+{
+ OSStatus err;
+ size_t rdataLen;
+ size_t rdlengthOffset = 0;
+ uint8_t * rdlengthPtr;
- if( geteuid() != 0 )
+ if( inDB )
{
- FPrintF( stderr, "error: This command must to be run as root.\n" );
- err = kIDErr;
- goto exit;
+ err = _DataBuffer_AppendDNSRecord( inDB, inNamePtr, inNameLen, inType, inClass, inTTL, NULL, 0 );
+ require_noerr( err, exit );
+
+ rdlengthOffset = DataBuffer_GetLen( inDB ) - 2;
}
- store = SCDynamicStoreCreate( NULL, CFSTR( "com.apple.dnssdutil" ), NULL, NULL );
- err = map_scerror( store );
+ err = _AppendSOARecordData( inDB, inMName, inRName, inSerial, inRefresh, inRetry, inExpire, inMinimumTTL, &rdataLen );
require_noerr( err, exit );
- key = SCDynamicStoreKeyCreateNetworkServiceEntity( NULL, kSCDynamicStoreDomainState, gDNSConfigRemove_ID, kSCEntNetDNS );
- require_action( key, exit, err = kUnknownErr );
+ if( inDB )
+ {
+ rdlengthPtr = DataBuffer_GetPtr( inDB ) + rdlengthOffset;
+ WriteBig16( rdlengthPtr, rdataLen );
+ }
- success = SCDynamicStoreRemoveValue( store, key );
- require_action( success, exit, err = kUnknownErr );
+ if( outLen ) *outLen = inNameLen + sizeof( DNSRecordFixedFields ) + rdataLen;
+ err = kNoErr;
exit:
- CFReleaseNullSafe( store );
- CFReleaseNullSafe( key );
- gExitCode = err ? 1 : 0;
+ return( err );
}
-#endif // TARGET_OS_DARWIN
-
-//===========================================================================================================================
-// DaemonVersionCmd
-//===========================================================================================================================
-static void DaemonVersionCmd( void )
+static OSStatus
+ _AppendSOARecordData(
+ DataBuffer * inDB,
+ const uint8_t * inMName,
+ const uint8_t * inRName,
+ uint32_t inSerial,
+ uint32_t inRefresh,
+ uint32_t inRetry,
+ uint32_t inExpire,
+ uint32_t inMinimumTTL,
+ size_t * outLen )
{
- OSStatus err;
- uint32_t size, version;
- char strBuf[ 16 ];
-
- size = (uint32_t) sizeof( version );
- err = DNSServiceGetProperty( kDNSServiceProperty_DaemonVersion, &version, &size );
- require_noerr( err, exit );
+ OSStatus err;
+ SOARecordDataFixedFields fields;
+ const size_t mnameLen = DomainNameLength( inMName );
+ const size_t rnameLen = DomainNameLength( inRName );
- FPrintF( stdout, "Daemon version: %s\n", SourceVersionToCString( version, strBuf ) );
+ if( inDB )
+ {
+ err = DataBuffer_Append( inDB, inMName, mnameLen );
+ require_noerr( err, exit );
+
+ err = DataBuffer_Append( inDB, inRName, rnameLen );
+ require_noerr( err, exit );
+
+ SOARecordDataFixedFieldsSet( &fields, inSerial, inRefresh, inRetry, inExpire, inMinimumTTL );
+ err = DataBuffer_Append( inDB, &fields, sizeof( fields ) );
+ require_noerr( err, exit );
+ }
+ if( outLen ) *outLen = mnameLen + rnameLen + sizeof( fields );
+ err = kNoErr;
exit:
- if( err ) exit( 1 );
+ return( err );
}
//===========================================================================================================================
-// Exit
+// CreateSOARecordData
//===========================================================================================================================
-static void Exit( void *inContext )
+static OSStatus
+ CreateSOARecordData(
+ const uint8_t * inMName,
+ const uint8_t * inRName,
+ uint32_t inSerial,
+ uint32_t inRefresh,
+ uint32_t inRetry,
+ uint32_t inExpire,
+ uint32_t inMinimumTTL,
+ uint8_t ** outPtr,
+ size_t * outLen )
{
- const char * const reason = (const char *) inContext;
+ OSStatus err;
+ DataBuffer rdataDB;
- FPrintF( stdout, "---\n" );
- FPrintF( stdout, "End time: %{du:time}\n", NULL );
- if( reason ) FPrintF( stdout, "End reason: %s\n", reason );
- exit( gExitCode );
+ DataBuffer_Init( &rdataDB, NULL, 0, kDNSRecordDataLengthMax );
+
+ err = _AppendSOARecordData( &rdataDB, inMName, inRName, inSerial, inRefresh, inRetry, inExpire, inMinimumTTL, NULL );
+ require_noerr( err, exit );
+
+ err = DataBuffer_Detach( &rdataDB, outPtr, outLen );
+ require_noerr( err, exit );
+
+exit:
+ DataBuffer_Free( &rdataDB );
+ return( err );
}
//===========================================================================================================================
-// PrintFTimestampHandler
+// _DataBuffer_AppendDNSQuestion
//===========================================================================================================================
-static int
- PrintFTimestampHandler(
- PrintFContext * inContext,
- PrintFFormat * inFormat,
- PrintFVAList * inArgs,
- void * inUserContext )
+static OSStatus
+ _DataBuffer_AppendDNSQuestion(
+ DataBuffer * inDB,
+ const uint8_t * inNamePtr,
+ size_t inNameLen,
+ uint16_t inType,
+ uint16_t inClass )
{
- struct timeval now;
- const struct timeval * tv;
- struct tm * localTime;
- size_t len;
- int n;
- char dateTimeStr[ 32 ];
-
- Unused( inUserContext );
-
- tv = va_arg( inArgs->args, const struct timeval * );
- require_action_quiet( !inFormat->suppress, exit, n = 0 );
+ OSStatus err;
+ DNSQuestionFixedFields fields;
- if( !tv )
- {
- gettimeofday( &now, NULL );
- tv = &now;
- }
- localTime = localtime( &tv->tv_sec );
- len = strftime( dateTimeStr, sizeof( dateTimeStr ), "%Y-%m-%d %H:%M:%S", localTime );
- if( len == 0 ) dateTimeStr[ 0 ] = '\0';
+ err = DataBuffer_Append( inDB, inNamePtr, inNameLen );
+ require_noerr( err, exit );
- n = PrintFCore( inContext, "%s.%06u", dateTimeStr, (unsigned int) tv->tv_usec );
+ DNSQuestionFixedFieldsInit( &fields, inType, inClass );
+ err = DataBuffer_Append( inDB, &fields, sizeof( fields ) );
+ require_noerr( err, exit );
exit:
- return( n );
+ return( err );
}
//===========================================================================================================================
-// PrintFDNSMessageHandler
+// _DataBuffer_AppendDNSRecord
//===========================================================================================================================
-static int
- PrintFDNSMessageHandler(
- PrintFContext * inContext,
- PrintFFormat * inFormat,
- PrintFVAList * inArgs,
- void * inUserContext )
+static OSStatus
+ _DataBuffer_AppendDNSRecord(
+ DataBuffer * inDB,
+ const uint8_t * inNamePtr,
+ size_t inNameLen,
+ uint16_t inType,
+ uint16_t inClass,
+ uint32_t inTTL,
+ const uint8_t * inRDataPtr,
+ size_t inRDataLen )
{
- OSStatus err;
- const void * msgPtr;
- size_t msgLen;
- char * text;
- int n;
- Boolean isMDNS;
- Boolean printRawRData;
+ OSStatus err;
+ DNSRecordFixedFields fields;
- Unused( inUserContext );
+ require_action_quiet( inRDataLen < kDNSRecordDataLengthMax, exit, err = kSizeErr );
- msgPtr = va_arg( inArgs->args, const void * );
- msgLen = va_arg( inArgs->args, size_t );
- require_action_quiet( !inFormat->suppress, exit, n = 0 );
+ err = DataBuffer_Append( inDB, inNamePtr, inNameLen );
+ require_noerr( err, exit );
- isMDNS = ( inFormat->altForm > 0 ) ? true : false;
- if( inFormat->precision == 0 ) printRawRData = false;
- else if( inFormat->precision == 1 ) printRawRData = true;
- else
- {
- n = PrintFCore( inContext, "<< BAD %%{du:dnsmsg} PRECISION >>" );
- goto exit;
- }
+ DNSRecordFixedFieldsSet( &fields, inType, inClass, inTTL, (uint16_t) inRDataLen );
+ err = DataBuffer_Append( inDB, &fields, sizeof( fields ) );
+ require_noerr( err, exit );
- err = DNSMessageToText( msgPtr, msgLen, isMDNS, printRawRData, &text );
- if( !err )
- {
- n = PrintFCore( inContext, "%*{text}", inFormat->fieldWidth, text, kSizeCString );
- free( text );
- }
- else
+ if( inRDataPtr )
{
- n = PrintFCore( inContext, "%*.1H", inFormat->fieldWidth, msgPtr, (int) msgLen, (int) msgLen );
+ err = DataBuffer_Append( inDB, inRDataPtr, inRDataLen );
+ require_noerr( err, exit );
}
exit:
- return( n );
+ return( err );
}
//===========================================================================================================================
-// GetDNSSDFlagsFromOpts
+// _NanoTime64ToDateString
//===========================================================================================================================
-static DNSServiceFlags GetDNSSDFlagsFromOpts( void )
+static char * _NanoTime64ToDateString( NanoTime64 inTime, char *inBuf, size_t inMaxLen )
{
- 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;
- if( gDNSSDFlag_WakeOnResolve ) flags |= kDNSServiceFlagsWakeOnResolve;
+ struct timeval tv;
- return( flags );
+ NanoTimeToTimeVal( inTime, &tv );
+ return( MakeFractionalDateString( &tv, inBuf, inMaxLen ) );
}
//===========================================================================================================================
-// CreateConnectionFromArgString
+// MDNSColliderCreate
//===========================================================================================================================
-static OSStatus
- CreateConnectionFromArgString(
- const char * inString,
- dispatch_queue_t inQueue,
- DNSServiceRef * outSDRef,
- ConnectionDesc * outDesc )
+typedef enum
{
- 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 );
+ kMDNSColliderOpCode_Invalid = 0,
+ kMDNSColliderOpCode_Send = 1,
+ kMDNSColliderOpCode_Wait = 2,
+ kMDNSColliderOpCode_SetProbeActions = 3,
+ kMDNSColliderOpCode_LoopPush = 4,
+ kMDNSColliderOpCode_LoopPop = 5,
+ kMDNSColliderOpCode_Exit = 6
- *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 );
-}
+} MDNSColliderOpCode;
+
+typedef struct
+{
+ MDNSColliderOpCode opcode;
+ uint32_t operand;
+
+} MDNSCInstruction;
+
+#define kMaxLoopDepth 16
+
+struct MDNSColliderPrivate
+{
+ CFRuntimeBase base; // CF object base.
+ dispatch_queue_t queue; // Queue for collider's events.
+ dispatch_source_t readSourceV4; // Read dispatch source for IPv4 socket.
+ dispatch_source_t readSourceV6; // Read dispatch source for IPv6 socket.
+ SocketRef sockV4; // IPv4 UDP socket for mDNS.
+ SocketRef sockV6; // IPv6 UDP socket for mDNS.
+ uint8_t * target; // Record name being targeted. (malloced)
+ uint8_t * responsePtr; // Response message pointer. (malloced)
+ size_t responseLen; // Response message length.
+ uint8_t * probePtr; // Probe query message pointer. (malloced)
+ size_t probeLen; // Probe query message length.
+ unsigned int probeCount; // Count of probe queries received for collider's record.
+ uint32_t probeActionMap; // Bitmap of actions to take for
+ MDNSCInstruction * program; // Program to execute.
+ uint32_t pc; // Program's program counter.
+ uint32_t loopCounts[ kMaxLoopDepth ]; // Stack of loop counters.
+ uint32_t loopDepth; // Current loop depth.
+ dispatch_source_t waitTimer; // Timer for program's wait commands.
+ uint32_t interfaceIndex; // Interface over which to send and receive mDNS msgs.
+ MDNSColliderStopHandler_f stopHandler; // User's stop handler.
+ void * stopContext; // User's stop handler context.
+ MDNSColliderProtocols protocols; // Protocols to use, i.e., IPv4, IPv6.
+ Boolean stopped; // True if the collider has been stopped.
+ uint8_t msgBuf[ kMDNSMessageSizeMax ]; // mDNS message buffer.
+};
-//===========================================================================================================================
-// InterfaceIndexFromArgString
-//===========================================================================================================================
+static void _MDNSColliderStop( MDNSColliderRef inCollider, OSStatus inError );
+static void _MDNSColliderReadHandler( void *inContext );
+static void _MDNSColliderExecuteProgram( void *inContext );
+static OSStatus _MDNSColliderSendResponse( MDNSColliderRef inCollider, SocketRef inSock, const struct sockaddr *inDest );
+static OSStatus _MDNSColliderSendProbe( MDNSColliderRef inCollider, SocketRef inSock, const struct sockaddr *inDest );
-static OSStatus InterfaceIndexFromArgString( const char *inString, uint32_t *outIndex )
+CF_CLASS_DEFINE( MDNSCollider );
+
+ulog_define_ex( "com.apple.dnssdutil", MDNSCollider, kLogLevelInfo, kLogFlags_None, "MDNSCollider", NULL );
+#define mc_ulog( LEVEL, ... ) ulog( &log_category_from_name( MDNSCollider ), (LEVEL), __VA_ARGS__ )
+
+static OSStatus MDNSColliderCreate( dispatch_queue_t inQueue, MDNSColliderRef *outCollider )
{
- OSStatus err;
- uint32_t ifIndex;
+ OSStatus err;
+ MDNSColliderRef obj = NULL;
- 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;
- }
+ CF_OBJECT_CREATE( MDNSCollider, obj, err, exit );
- *outIndex = ifIndex;
+ ReplaceDispatchQueue( &obj->queue, inQueue );
+ obj->sockV4 = kInvalidSocketRef;
+ obj->sockV6 = kInvalidSocketRef;
+
+ *outCollider = obj;
err = kNoErr;
exit:
@@ -11377,1221 +18050,1775 @@ exit:
}
//===========================================================================================================================
-// RecordDataFromArgString
+// _MDNSColliderFinalize
//===========================================================================================================================
-#define kRDataMaxLen UINT16_C( 0xFFFF )
+static void _MDNSColliderFinalize( CFTypeRef inObj )
+{
+ MDNSColliderRef const me = (MDNSColliderRef) inObj;
+
+ check( !me->waitTimer );
+ check( !me->readSourceV4 );
+ check( !me->readSourceV6 );
+ check( !IsValidSocket( me->sockV4 ) );
+ check( !IsValidSocket( me->sockV6 ) );
+ ForgetMem( &me->target );
+ ForgetMem( &me->responsePtr );
+ ForgetMem( &me->probePtr );
+ ForgetMem( &me->program );
+ dispatch_forget( &me->queue );
+}
+
+//===========================================================================================================================
+// MDNSColliderStart
+//===========================================================================================================================
-static OSStatus StringToSRVRData( const char *inString, uint8_t **outPtr, size_t *outLen );
-static OSStatus StringToTXTRData( const char *inString, char inDelimiter, uint8_t **outPtr, size_t *outLen );
+static void _MDNSColliderStart( void *inContext );
-static OSStatus RecordDataFromArgString( const char *inString, uint8_t **outDataPtr, size_t *outDataLen )
+static OSStatus MDNSColliderStart( MDNSColliderRef me )
{
OSStatus err;
- uint8_t * dataPtr = NULL;
- size_t dataLen;
-
- if( 0 ) {}
- // Domain name
+ require_action_quiet( me->target, exit, err = kNotPreparedErr );
+ require_action_quiet( me->responsePtr, exit, err = kNotPreparedErr );
+ require_action_quiet( me->probePtr, exit, err = kNotPreparedErr );
+ require_action_quiet( me->program, exit, err = kNotPreparedErr );
+ require_action_quiet( me->interfaceIndex, exit, err = kNotPreparedErr );
+ require_action_quiet( me->protocols, exit, err = kNotPreparedErr );
- else if( stricmp_prefix( inString, kRDataArgPrefix_Domain ) == 0 )
- {
- const char * const str = inString + sizeof_string( kRDataArgPrefix_Domain );
-
- err = StringToDomainName( str, &dataPtr, &dataLen );
- require_noerr_quiet( err, exit );
- }
+ CFRetain( me );
+ dispatch_async_f( me->queue, me, _MDNSColliderStart );
+ err = kNoErr;
- // File path
+exit:
+ return( err );
+}
+
+static void _MDNSColliderStart( void *inContext )
+{
+ OSStatus err;
+ MDNSColliderRef const me = (MDNSColliderRef) inContext;
+ SocketRef sock = kInvalidSocketRef;
+ SocketContext * sockCtx = NULL;
- else if( stricmp_prefix( inString, kRDataArgPrefix_File ) == 0 )
+ if( me->protocols & kMDNSColliderProtocol_IPv4 )
{
- const char * const path = inString + sizeof_string( kRDataArgPrefix_File );
+ err = CreateMulticastSocket( GetMDNSMulticastAddrV4(), kMDNSPort, NULL, me->interfaceIndex, true, NULL, &sock );
+ require_noerr( err, exit );
- err = CopyFileDataByPath( path, (char **) &dataPtr, &dataLen );
+ err = SocketContextCreate( sock, me, &sockCtx );
require_noerr( err, exit );
- require_action( dataLen <= kRDataMaxLen, exit, err = kSizeErr );
- }
-
- // Hexadecimal string
-
- else if( stricmp_prefix( inString, kRDataArgPrefix_HexString ) == 0 )
- {
- const char * const str = inString + sizeof_string( kRDataArgPrefix_HexString );
+ sock = kInvalidSocketRef;
- err = HexToDataCopy( str, kSizeCString, kHexToData_DefaultFlags, &dataPtr, &dataLen, NULL );
+ err = DispatchReadSourceCreate( sockCtx->sock, me->queue, _MDNSColliderReadHandler, SocketContextCancelHandler,
+ sockCtx, &me->readSourceV4 );
require_noerr( err, exit );
- require_action( dataLen <= kRDataMaxLen, exit, err = kSizeErr );
- }
-
- // IPv4 address string
-
- else if( stricmp_prefix( inString, kRDataArgPrefix_IPv4 ) == 0 )
- {
- const char * const str = inString + sizeof_string( kRDataArgPrefix_IPv4 );
+ me->sockV4 = sockCtx->sock;
+ sockCtx = NULL;
- err = StringToARecordData( str, &dataPtr, &dataLen );
- require_noerr_quiet( err, exit );
+ dispatch_resume( me->readSourceV4 );
}
- // IPv6 address string
-
- else if( stricmp_prefix( inString, kRDataArgPrefix_IPv6 ) == 0 )
+ if( me->protocols & kMDNSColliderProtocol_IPv6 )
{
- const char * const str = inString + sizeof_string( kRDataArgPrefix_IPv6 );
+ err = CreateMulticastSocket( GetMDNSMulticastAddrV6(), kMDNSPort, NULL, me->interfaceIndex, true, NULL, &sock );
+ require_noerr( err, exit );
- err = StringToAAAARecordData( str, &dataPtr, &dataLen );
- require_noerr_quiet( err, exit );
- }
-
- // SRV record
-
- else if( stricmp_prefix( inString, kRDataArgPrefix_SRV ) == 0 )
- {
- const char * const str = inString + sizeof_string( kRDataArgPrefix_SRV );
+ err = SocketContextCreate( sock, me, &sockCtx );
+ require_noerr( err, exit );
+ sock = kInvalidSocketRef;
- err = StringToSRVRData( str, &dataPtr, &dataLen );
+ err = DispatchReadSourceCreate( sockCtx->sock, me->queue, _MDNSColliderReadHandler, SocketContextCancelHandler,
+ sockCtx, &me->readSourceV6 );
require_noerr( err, exit );
+ me->sockV6 = sockCtx->sock;
+ sockCtx = NULL;
+
+ dispatch_resume( me->readSourceV6 );
}
- // String with escaped hex and octal bytes
+ _MDNSColliderExecuteProgram( me );
+ err = kNoErr;
- else if( stricmp_prefix( inString, kRDataArgPrefix_String ) == 0 )
- {
- const char * const str = inString + sizeof_string( kRDataArgPrefix_String );
- const char * const end = str + strlen( str );
- size_t copiedLen;
- size_t totalLen;
- Boolean success;
+exit:
+ ForgetSocket( &sock );
+ ForgetSocketContext( &sockCtx );
+ if( err ) _MDNSColliderStop( me, err );
+}
+
+//===========================================================================================================================
+// MDNSColliderStop
+//===========================================================================================================================
+
+static void _MDNSColliderUserStop( void *inContext );
+
+static void MDNSColliderStop( MDNSColliderRef me )
+{
+ CFRetain( me );
+ dispatch_async_f( me->queue, me, _MDNSColliderUserStop );
+}
+
+static void _MDNSColliderUserStop( void *inContext )
+{
+ MDNSColliderRef const me = (MDNSColliderRef) inContext;
+
+ _MDNSColliderStop( me, kCanceledErr );
+ CFRelease( me );
+}
+
+//===========================================================================================================================
+// MDNSColliderSetProtocols
+//===========================================================================================================================
+
+static void MDNSColliderSetProtocols( MDNSColliderRef me, MDNSColliderProtocols inProtocols )
+{
+ me->protocols = inProtocols;
+}
+
+//===========================================================================================================================
+// MDNSColliderSetInterfaceIndex
+//===========================================================================================================================
+
+static void MDNSColliderSetInterfaceIndex( MDNSColliderRef me, uint32_t inInterfaceIndex )
+{
+ me->interfaceIndex = inInterfaceIndex;
+}
+
+//===========================================================================================================================
+// MDNSColliderSetProgram
+//===========================================================================================================================
+
+#define kMDNSColliderProgCmd_Done "done"
+#define kMDNSColliderProgCmd_Loop "loop"
+#define kMDNSColliderProgCmd_Send "send"
+#define kMDNSColliderProgCmd_Probes "probes"
+#define kMDNSColliderProgCmd_Wait "wait"
+
+typedef uint32_t MDNSColliderProbeAction;
+
+#define kMDNSColliderProbeAction_None 0
+#define kMDNSColliderProbeAction_Respond 1
+#define kMDNSColliderProbeAction_RespondUnicast 2
+#define kMDNSColliderProbeAction_RespondMulticast 3
+#define kMDNSColliderProbeAction_Probe 4
+#define kMDNSColliderProbeAction_MaxValue kMDNSColliderProbeAction_Probe
+
+#define kMDNSColliderProbeActionBits_Count 3
+#define kMDNSColliderProbeActionBits_Mask ( ( 1U << kMDNSColliderProbeActionBits_Count ) - 1 )
+#define kMDNSColliderProbeActionMaxProbeCount ( 32 / kMDNSColliderProbeActionBits_Count )
+
+check_compile_time( kMDNSColliderProbeAction_MaxValue <= kMDNSColliderProbeActionBits_Mask );
+
+static OSStatus _MDNSColliderParseProbeActionString( const char *inString, size_t inLen, uint32_t *outBitmap );
+
+static OSStatus MDNSColliderSetProgram( MDNSColliderRef me, const char *inProgramStr )
+{
+ OSStatus err;
+ uint32_t insCount;
+ unsigned int loopDepth;
+ const char * cmd;
+ const char * end;
+ const char * next;
+ MDNSCInstruction * program = NULL;
+ uint32_t loopStart[ kMaxLoopDepth ];
+
+ insCount = 0;
+ for( cmd = inProgramStr; *cmd; cmd = next )
+ {
+ for( end = cmd; *end && ( *end != ';' ); ++end ) {}
+ require_action_quiet( end != cmd, exit, err = kMalformedErr );
+ next = ( *end == ';' ) ? ( end + 1 ) : end;
+ ++insCount;
+ }
+
+ program = (MDNSCInstruction *) calloc( insCount + 1, sizeof( *program ) );
+ require_action( program, exit, err = kNoMemoryErr );
+
+ insCount = 0;
+ loopDepth = 0;
+ for( cmd = inProgramStr; *cmd; cmd = next )
+ {
+ size_t cmdLen;
+ const char * ptr;
+ const char * arg;
+ size_t argLen;
+ uint32_t value;
+ MDNSCInstruction * const ins = &program[ insCount ];
- if( str < end )
+ while( isspace_safe( *cmd ) ) ++cmd;
+ for( end = cmd; *end && ( *end != ';' ); ++end ) {}
+ next = ( *end == ';' ) ? ( end + 1 ) : end;
+
+ for( ptr = cmd; ( ptr < end ) && !isspace_safe( *ptr ); ++ptr ) {}
+ cmdLen = (size_t)( ptr - cmd );
+
+ // Done statement
+
+ if( strnicmpx( cmd, cmdLen, kMDNSColliderProgCmd_Done ) == 0 )
{
- success = ParseQuotedEscapedString( str, end, "", NULL, 0, NULL, &totalLen, NULL );
- require_action( success, exit, err = kParamErr );
- require_action( totalLen <= kRDataMaxLen, exit, err = kSizeErr );
+ while( ( ptr < end ) && isspace_safe( *ptr ) ) ++ptr;
+ require_action_quiet( ptr == end, exit, err = kMalformedErr );
- dataLen = totalLen;
- dataPtr = (uint8_t *) malloc( dataLen );
- require_action( dataPtr, exit, err = kNoMemoryErr );
+ require_action_quiet( loopDepth > 0, exit, err = kMalformedErr );
- success = ParseQuotedEscapedString( str, end, "", (char *) dataPtr, dataLen, &copiedLen, NULL, NULL );
- require_action( success, exit, err = kParamErr );
- check( copiedLen == dataLen );
+ ins->opcode = kMDNSColliderOpCode_LoopPop;
+ ins->operand = loopStart[ --loopDepth ];
}
- else
+
+ // Loop command
+
+ else if( strnicmpx( cmd, cmdLen, kMDNSColliderProgCmd_Loop ) == 0 )
{
- dataPtr = NULL;
- dataLen = 0;
+ for( arg = ptr; ( arg < end ) && isspace_safe( *arg ); ++arg ) {}
+ err = DecimalTextToUInt32( arg, end, &value, &ptr );
+ require_noerr_quiet( err, exit );
+ require_action_quiet( value > 0, exit, err = kValueErr );
+
+ while( ( ptr < end ) && isspace_safe( *ptr ) ) ++ptr;
+ require_action_quiet( ptr == end, exit, err = kMalformedErr );
+
+ ins->opcode = kMDNSColliderOpCode_LoopPush;
+ ins->operand = value;
+
+ require_action_quiet( loopDepth < kMaxLoopDepth, exit, err = kNoSpaceErr );
+ loopStart[ loopDepth++ ] = insCount + 1;
}
- }
-
- // TXT record
-
- else if( stricmp_prefix( inString, kRDataArgPrefix_TXT ) == 0 )
- {
- const char * const str = inString + sizeof_string( kRDataArgPrefix_TXT );
- err = StringToTXTRData( str, ',', &dataPtr, &dataLen );
- require_noerr( err, exit );
- }
-
- // Unrecognized format
-
- else
- {
- FPrintF( stderr, "Unrecognized record data string \"%s\".\n", inString );
- err = kParamErr;
- goto exit;
- }
-
- err = kNoErr;
- *outDataLen = dataLen;
- *outDataPtr = dataPtr;
- dataPtr = NULL;
-
-exit:
- FreeNullSafe( dataPtr );
- return( err );
-}
-
-static OSStatus StringToSRVRData( const char *inString, uint8_t **outPtr, size_t *outLen )
-{
- OSStatus err;
- DataBuffer dataBuf;
- const char * ptr;
- int i;
- uint8_t * end;
- uint8_t target[ kDomainNameLengthMax ];
-
- DataBuffer_Init( &dataBuf, NULL, 0, ( 3 * 2 ) + kDomainNameLengthMax );
-
- // Parse and set the priority, weight, and port values (all three are unsigned 16-bit values).
-
- ptr = inString;
- for( i = 0; i < 3; ++i )
- {
- char * next;
- long value;
- uint8_t buf[ 2 ];
+ // Probes command
- value = strtol( ptr, &next, 0 );
- require_action_quiet( ( next != ptr ) && ( *next == ',' ), exit, err = kMalformedErr );
- require_action_quiet( ( value >= 0 ) && ( value <= UINT16_MAX ), exit, err = kRangeErr );
- ptr = next + 1;
+ else if( strnicmpx( cmd, cmdLen, kMDNSColliderProgCmd_Probes ) == 0 )
+ {
+ for( arg = ptr; ( arg < end ) && isspace_safe( *arg ); ++arg ) {}
+ for( ptr = arg; ( ptr < end ) && !isspace_safe( *ptr ); ++ptr ) {}
+ argLen = (size_t)( ptr - arg );
+ if( argLen > 0 )
+ {
+ err = _MDNSColliderParseProbeActionString( arg, argLen, &value );
+ require_noerr_quiet( err, exit );
+ }
+ else
+ {
+ value = 0;
+ }
+
+ while( ( ptr < end ) && isspace_safe( *ptr ) ) ++ptr;
+ require_action_quiet( ptr == end, exit, err = kMalformedErr );
+
+ ins->opcode = kMDNSColliderOpCode_SetProbeActions;
+ ins->operand = value;
+ }
- WriteBig16( buf, value );
+ // Send command
- err = DataBuffer_Append( &dataBuf, buf, sizeof( buf ) );
- require_noerr( err, exit );
+ else if( strnicmpx( cmd, cmdLen, kMDNSColliderProgCmd_Send ) == 0 )
+ {
+ while( ( ptr < end ) && isspace_safe( *ptr ) ) ++ptr;
+ require_action_quiet( ptr == end, exit, err = kMalformedErr );
+
+ ins->opcode = kMDNSColliderOpCode_Send;
+ }
+
+ // Wait command
+
+ else if( strnicmpx( cmd, cmdLen, kMDNSColliderProgCmd_Wait ) == 0 )
+ {
+ for( arg = ptr; ( arg < end ) && isspace_safe( *arg ); ++arg ) {}
+ err = DecimalTextToUInt32( arg, end, &value, &ptr );
+ require_noerr_quiet( err, exit );
+
+ while( ( ptr < end ) && isspace_safe( *ptr ) ) ++ptr;
+ require_action_quiet( ptr == end, exit, err = kMalformedErr );
+
+ ins->opcode = kMDNSColliderOpCode_Wait;
+ ins->operand = value;
+ }
+
+ // Unrecognized command
+
+ else
+ {
+ err = kCommandErr;
+ goto exit;
+ }
+ ++insCount;
}
+ require_action_quiet( loopDepth == 0, exit, err = kMalformedErr );
- // Set the target domain name.
-
- err = DomainNameFromString( target, ptr, &end );
- require_noerr_quiet( err, exit );
-
- err = DataBuffer_Append( &dataBuf, target, (size_t)( end - target ) );
- require_noerr( err, exit );
+ program[ insCount ].opcode = kMDNSColliderOpCode_Exit;
- err = DataBuffer_Detach( &dataBuf, outPtr, outLen );
- require_noerr( err, exit );
+ FreeNullSafe( me->program );
+ me->program = program;
+ program = NULL;
+ err = kNoErr;
exit:
- DataBuffer_Free( &dataBuf );
+ FreeNullSafe( program );
return( err );
}
-static OSStatus StringToTXTRData( const char *inString, char inDelimiter, uint8_t **outPtr, size_t *outLen )
+static OSStatus _MDNSColliderParseProbeActionString( const char *inString, size_t inLen, uint32_t *outBitmap )
{
- OSStatus err;
- DataBuffer dataBuf;
- const char * src;
- uint8_t txtStr[ 256 ]; // Buffer for single TXT string: 1 length byte + up to 255 bytes of data.
-
- DataBuffer_Init( &dataBuf, NULL, 0, kRDataMaxLen );
+ OSStatus err;
+ const char * ptr;
+ const char * const end = &inString[ inLen ];
+ uint32_t bitmap;
+ int index;
- src = inString;
- for( ;; )
+ bitmap = 0;
+ index = 0;
+ ptr = inString;
+ while( ptr < end )
{
- uint8_t * dst = &txtStr[ 1 ];
- const uint8_t * const lim = &txtStr[ 256 ];
- int c;
+ int c, count;
+ MDNSColliderProbeAction action;
- while( *src && ( *src != inDelimiter ) )
+ c = *ptr++;
+ if( isdigit_safe( c ) )
{
- if( ( c = *src++ ) == '\\' )
+ count = 0;
+ do
{
- require_action_quiet( *src != '\0', exit, err = kUnderrunErr );
- c = *src++;
- }
- require_action_quiet( dst < lim, exit, err = kOverrunErr );
- *dst++ = (uint8_t) c;
+ count = ( count * 10 ) + ( c - '0' );
+ require_action_quiet( count <= ( kMDNSColliderProbeActionMaxProbeCount - index ), exit, err = kCountErr );
+ require_action_quiet( ptr < end, exit, err = kUnderrunErr );
+ c = *ptr++;
+
+ } while( isdigit_safe( c ) );
+ require_action_quiet( count > 0, exit, err = kCountErr );
+ }
+ else
+ {
+ require_action_quiet( index < kMDNSColliderProbeActionMaxProbeCount, exit, err = kMalformedErr );
+ count = 1;
}
- txtStr[ 0 ] = (uint8_t)( dst - &txtStr[ 1 ] );
- err = DataBuffer_Append( &dataBuf, txtStr, 1 + txtStr[ 0 ] );
- require_noerr( err, exit );
- if( *src == '\0' ) break;
- ++src;
+ switch( c )
+ {
+ case 'n': action = kMDNSColliderProbeAction_None; break;
+ case 'r': action = kMDNSColliderProbeAction_Respond; break;
+ case 'u': action = kMDNSColliderProbeAction_RespondUnicast; break;
+ case 'm': action = kMDNSColliderProbeAction_RespondMulticast; break;
+ case 'p': action = kMDNSColliderProbeAction_Probe; break;
+ default: err = kMalformedErr; goto exit;
+ }
+ if( ptr < end )
+ {
+ c = *ptr++;
+ require_action_quiet( ( c == '-' ) && ( ptr < end ), exit, err = kMalformedErr );
+ }
+ while( count-- > 0 )
+ {
+ bitmap |= ( action << ( index * kMDNSColliderProbeActionBits_Count ) );
+ ++index;
+ }
}
- err = DataBuffer_Detach( &dataBuf, outPtr, outLen );
- require_noerr( err, exit );
+ *outBitmap = bitmap;
+ err = kNoErr;
exit:
- DataBuffer_Free( &dataBuf );
return( err );
}
//===========================================================================================================================
-// RecordTypeFromArgString
+// MDNSColliderSetStopHandler
//===========================================================================================================================
-typedef struct
+static void MDNSColliderSetStopHandler( MDNSColliderRef me, MDNSColliderStopHandler_f inStopHandler, void *inStopContext )
{
- uint16_t value; // Record type's numeric value.
- const char * name; // Record type's name as a string (e.g., "A", "PTR", "SRV").
-
-} RecordType;
+ me->stopHandler = inStopHandler;
+ me->stopContext = inStopContext;
+}
-static const RecordType kRecordTypes[] =
+//===========================================================================================================================
+// MDNSColliderSetRecord
+//===========================================================================================================================
+
+#define kMDNSColliderDummyStr "\x16" "mdnscollider-sent-this" kLocalStr
+#define kMDNSColliderDummyName ( (const uint8_t *) kMDNSColliderDummyStr )
+#define kMDNSColliderDummyNameLen sizeof( kMDNSColliderDummyStr )
+
+static OSStatus
+ MDNSColliderSetRecord(
+ MDNSColliderRef me,
+ const uint8_t * inName,
+ uint16_t inType,
+ const void * inRDataPtr,
+ size_t inRDataLen )
{
- // Common types.
+ OSStatus err;
+ DataBuffer msgDB;
+ DNSHeader header;
+ uint8_t * targetPtr = NULL;
+ size_t targetLen;
+ uint8_t * responsePtr = NULL;
+ size_t responseLen;
+ uint8_t * probePtr = NULL;
+ size_t probeLen;
+
+ DataBuffer_Init( &msgDB, NULL, 0, kMDNSMessageSizeMax );
+
+ err = DomainNameDup( inName, &targetPtr, &targetLen );
+ require_noerr_quiet( err, exit );
- { 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" },
+ // Create response message.
- // Less common types.
+ memset( &header, 0, sizeof( header ) );
+ DNSHeaderSetFlags( &header, kDNSHeaderFlag_Response | kDNSHeaderFlag_AuthAnswer );
+ DNSHeaderSetAnswerCount( &header, 1 );
- { 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 );
+ err = DataBuffer_Append( &msgDB, &header, sizeof( header ) );
+ require_noerr( err, exit );
- for( type = kRecordTypes; type < end; ++type )
- {
- if( strcasecmp( type->name, inString ) == 0 )
- {
- *outValue = type->value;
- return( kNoErr );
- }
- }
+ err = _DataBuffer_AppendDNSRecord( &msgDB, targetPtr, targetLen, inType, kDNSServiceClass_IN | kRRClassCacheFlushBit,
+ 1976, inRDataPtr, inRDataLen );
+ require_noerr( err, exit );
- err = StringToInt32( inString, &i32 );
- require_noerr_quiet( err, exit );
- require_action_quiet( ( i32 >= 0 ) && ( i32 <= UINT16_MAX ), exit, err = kParamErr );
+ err = DataBuffer_Detach( &msgDB, &responsePtr, &responseLen );
+ require_noerr( err, exit );
- *outValue = (uint16_t) i32;
+ // Create probe message.
+
+ memset( &header, 0, sizeof( header ) );
+ DNSHeaderSetQuestionCount( &header, 2 );
+ DNSHeaderSetAuthorityCount( &header, 1 );
+
+ err = DataBuffer_Append( &msgDB, &header, sizeof( header ) );
+ require_noerr( err, exit );
+
+ err = _DataBuffer_AppendDNSQuestion( &msgDB, targetPtr, targetLen, kDNSServiceType_ANY, kDNSServiceClass_IN );
+ require_noerr( err, exit );
+
+ err = _DataBuffer_AppendDNSQuestion( &msgDB, kMDNSColliderDummyName, kMDNSColliderDummyNameLen,
+ kDNSServiceType_NULL, kDNSServiceClass_IN );
+ require_noerr( err, exit );
+
+ err = _DataBuffer_AppendDNSRecord( &msgDB, targetPtr, targetLen, inType, kDNSServiceClass_IN,
+ 1976, inRDataPtr, inRDataLen );
+ require_noerr( err, exit );
+
+ err = DataBuffer_Detach( &msgDB, &probePtr, &probeLen );
+ require_noerr( err, exit );
+
+ FreeNullSafe( me->target );
+ me->target = targetPtr;
+ targetPtr = NULL;
+
+ FreeNullSafe( me->responsePtr );
+ me->responsePtr = responsePtr;
+ me->responseLen = responseLen;
+ responsePtr = NULL;
+
+ FreeNullSafe( me->probePtr );
+ me->probePtr = probePtr;
+ me->probeLen = probeLen;
+ probePtr = NULL;
exit:
+ DataBuffer_Free( &msgDB );
+ FreeNullSafe( targetPtr );
+ FreeNullSafe( responsePtr );
+ FreeNullSafe( probePtr );
return( err );
}
//===========================================================================================================================
-// RecordClassFromArgString
+// _MDNSColliderStop
//===========================================================================================================================
-static OSStatus RecordClassFromArgString( const char *inString, uint16_t *outValue )
+static void _MDNSColliderStop( MDNSColliderRef me, OSStatus inError )
{
- OSStatus err;
- int32_t i32;
+ dispatch_source_forget( &me->waitTimer );
+ dispatch_source_forget( &me->readSourceV4 );
+ dispatch_source_forget( &me->readSourceV6 );
+ me->sockV4 = kInvalidSocketRef;
+ me->sockV6 = kInvalidSocketRef;
- if( strcasecmp( inString, "IN" ) == 0 )
+ if( !me->stopped )
{
- *outValue = kDNSServiceClass_IN;
- err = kNoErr;
- goto exit;
+ me->stopped = true;
+ if( me->stopHandler ) me->stopHandler( me->stopContext, inError );
+ CFRelease( me );
}
-
- 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
+// _MDNSColliderReadHandler
//===========================================================================================================================
-static char * InterfaceIndexToName( uint32_t inIfIndex, char inNameBuf[ kInterfaceNameBufLen ] )
+static MDNSColliderProbeAction _MDNSColliderGetProbeAction( uint32_t inBitmap, unsigned int inProbeNumber );
+static const char * _MDNSColliderProbeActionToString( MDNSColliderProbeAction inAction );
+
+static void _MDNSColliderReadHandler( void *inContext )
{
- switch( inIfIndex )
+ OSStatus err;
+ struct timeval now;
+ SocketContext * const sockCtx = (SocketContext *) inContext;
+ MDNSColliderRef const me = (MDNSColliderRef) sockCtx->userContext;
+ size_t msgLen;
+ sockaddr_ip sender;
+ const DNSHeader * hdr;
+ const uint8_t * ptr;
+ const struct sockaddr * dest;
+ int probeFound, probeIsQU;
+ unsigned int qCount, i;
+ MDNSColliderProbeAction action;
+
+ gettimeofday( &now, NULL );
+
+ err = SocketRecvFrom( sockCtx->sock, me->msgBuf, sizeof( me->msgBuf ), &msgLen, &sender, sizeof( sender ),
+ NULL, NULL, NULL, NULL );
+ require_noerr( err, exit );
+
+ require_quiet( msgLen >= kDNSHeaderLength, exit );
+ hdr = (const DNSHeader *) me->msgBuf;
+
+ probeFound = false;
+ probeIsQU = false;
+ qCount = DNSHeaderGetQuestionCount( hdr );
+ ptr = (const uint8_t *) &hdr[ 1 ];
+ for( i = 0; i < qCount; ++i )
{
- case kDNSServiceInterfaceIndexAny:
- strlcpy( inNameBuf, "Any", kInterfaceNameBufLen );
- break;
-
- case kDNSServiceInterfaceIndexLocalOnly:
- strlcpy( inNameBuf, "LocalOnly", kInterfaceNameBufLen );
- break;
+ uint16_t qtype, qclass;
+ uint8_t qname[ kDomainNameLengthMax ];
- case kDNSServiceInterfaceIndexUnicast:
- strlcpy( inNameBuf, "Unicast", kInterfaceNameBufLen );
- break;
+ err = DNSMessageExtractQuestion( me->msgBuf, msgLen, ptr, qname, &qtype, &qclass, &ptr );
+ require_noerr_quiet( err, exit );
- case kDNSServiceInterfaceIndexP2P:
- strlcpy( inNameBuf, "P2P", kInterfaceNameBufLen );
+ if( ( qtype == kDNSServiceType_NULL ) && ( qclass == kDNSServiceClass_IN ) &&
+ DomainNameEqual( qname, kMDNSColliderDummyName ) )
+ {
+ probeFound = false;
break;
+ }
- #if( defined( kDNSServiceInterfaceIndexBLE ) )
- case kDNSServiceInterfaceIndexBLE:
- strlcpy( inNameBuf, "BLE", kInterfaceNameBufLen );
- break;
- #endif
+ if( qtype != kDNSServiceType_ANY ) continue;
+ if( ( qclass & ~kQClassUnicastResponseBit ) != kDNSServiceClass_IN ) continue;
+ if( !DomainNameEqual( qname, me->target ) ) continue;
- default:
+ if( !probeFound )
{
- const char * name;
-
- name = if_indextoname( inIfIndex, inNameBuf );
- if( !name ) strlcpy( inNameBuf, "NO NAME", kInterfaceNameBufLen );
- break;
+ probeFound = true;
+ probeIsQU = ( qclass & kQClassUnicastResponseBit ) ? true : false;
}
}
+ require_quiet( probeFound, exit );
- return( inNameBuf );
+ ++me->probeCount;
+ action = _MDNSColliderGetProbeAction( me->probeActionMap, me->probeCount );
+
+ mc_ulog( kLogLevelInfo, "Received probe from %##a at %{du:time} (action: %s):\n\n%#1{du:dnsmsg}",
+ &sender, &now, _MDNSColliderProbeActionToString( action ), me->msgBuf, msgLen );
+
+ if( ( action == kMDNSColliderProbeAction_Respond ) ||
+ ( action == kMDNSColliderProbeAction_RespondUnicast ) ||
+ ( action == kMDNSColliderProbeAction_RespondMulticast ) )
+ {
+ if( ( ( action == kMDNSColliderProbeAction_Respond ) && probeIsQU ) ||
+ ( action == kMDNSColliderProbeAction_RespondUnicast ) )
+ {
+ dest = &sender.sa;
+ }
+ else if( ( ( action == kMDNSColliderProbeAction_Respond ) && !probeIsQU ) ||
+ ( action == kMDNSColliderProbeAction_RespondMulticast ) )
+ {
+ dest = ( sender.sa.sa_family == AF_INET ) ? GetMDNSMulticastAddrV4() : GetMDNSMulticastAddrV6();
+ }
+
+ err = _MDNSColliderSendResponse( me, sockCtx->sock, dest );
+ require_noerr( err, exit );
+ }
+ else if( action == kMDNSColliderProbeAction_Probe )
+ {
+ dest = ( sender.sa.sa_family == AF_INET ) ? GetMDNSMulticastAddrV4() : GetMDNSMulticastAddrV6();
+
+ err = _MDNSColliderSendProbe( me, sockCtx->sock, dest );
+ require_noerr( err, exit );
+ }
+
+exit:
+ return;
}
-//===========================================================================================================================
-// RecordTypeToString
-//===========================================================================================================================
+static MDNSColliderProbeAction _MDNSColliderGetProbeAction( uint32_t inBitmap, unsigned int inProbeNumber )
+{
+ MDNSColliderProbeAction action;
+
+ if( ( inProbeNumber >= 1 ) && ( inProbeNumber <= kMDNSColliderProbeActionMaxProbeCount ) )
+ {
+ action = ( inBitmap >> ( ( inProbeNumber - 1 ) * kMDNSColliderProbeActionBits_Count ) ) &
+ kMDNSColliderProbeActionBits_Mask;
+ }
+ else
+ {
+ action = kMDNSColliderProbeAction_None;
+ }
+ return( action );
+}
-static const char * RecordTypeToString( unsigned int inValue )
+static const char * _MDNSColliderProbeActionToString( MDNSColliderProbeAction inAction )
{
- const RecordType * type;
- const RecordType * const end = kRecordTypes + countof( kRecordTypes );
-
- for( type = kRecordTypes; type < end; ++type )
+ switch( inAction )
{
- if( type->value == inValue ) return( type->name );
+ case kMDNSColliderProbeAction_None: return( "None" );
+ case kMDNSColliderProbeAction_Respond: return( "Respond" );
+ case kMDNSColliderProbeAction_RespondUnicast: return( "Respond (unicast)" );
+ case kMDNSColliderProbeAction_RespondMulticast: return( "Respond (multicast)" );
+ case kMDNSColliderProbeAction_Probe: return( "Probe" );
+ default: return( "???" );
}
- return( "???" );
}
//===========================================================================================================================
-// DNSMessageExtractDomainName
+// _MDNSColliderExecuteProgram
//===========================================================================================================================
-#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 )
+static void _MDNSColliderExecuteProgram( void *inContext )
{
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;
+ MDNSColliderRef const me = (MDNSColliderRef) inContext;
+ int stop;
- require_action( ( inNamePtr >= inMsgPtr ) && ( inNamePtr < msgEnd ), exit, err = kRangeErr );
+ dispatch_forget( &me->waitTimer );
- for( label = inNamePtr; ( labelLen = label[ 0 ] ) != 0; label = nextLabel )
+ stop = false;
+ for( ;; )
{
- 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 ) )
+ const MDNSCInstruction * const ins = &me->program[ me->pc++ ];
+ uint32_t waitMs;
+
+ switch( ins->opcode )
{
- uint16_t offset;
+ case kMDNSColliderOpCode_Send:
+ if( IsValidSocket( me->sockV4 ) )
+ {
+ err = _MDNSColliderSendResponse( me, me->sockV4, GetMDNSMulticastAddrV4() );
+ require_noerr( err, exit );
+ }
+ if( IsValidSocket( me->sockV6 ) )
+ {
+ err = _MDNSColliderSendResponse( me, me->sockV6, GetMDNSMulticastAddrV6() );
+ require_noerr( err, exit );
+ }
+ break;
- 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;
+ case kMDNSColliderOpCode_Wait:
+ waitMs = ins->operand;
+ if( waitMs > 0 )
+ {
+ err = DispatchTimerOneShotCreate( dispatch_time_milliseconds( waitMs ), 1, me->queue,
+ _MDNSColliderExecuteProgram, me, &me->waitTimer );
+ require_noerr( err, exit );
+ dispatch_resume( me->waitTimer );
+ goto exit;
+ }
+ break;
+
+ case kMDNSColliderOpCode_SetProbeActions:
+ me->probeCount = 0;
+ me->probeActionMap = ins->operand;
+ break;
+
+ case kMDNSColliderOpCode_LoopPush:
+ check( me->loopDepth < kMaxLoopDepth );
+ me->loopCounts[ me->loopDepth++ ] = ins->operand;
+ break;
+
+ case kMDNSColliderOpCode_LoopPop:
+ check( me->loopDepth > 0 );
+ if( --me->loopCounts[ me->loopDepth - 1 ] > 0 )
+ {
+ me->pc = ins->operand;
+ }
+ else
+ {
+ --me->loopDepth;
+ }
+ break;
+
+ case kMDNSColliderOpCode_Exit:
+ stop = true;
+ err = kNoErr;
+ goto exit;
+
+ default:
+ dlogassert( "Unhandled opcode %u\n", ins->opcode );
+ err = kCommandErr;
+ goto exit;
}
}
- if( dst ) *dst = 0;
- if( !nameEnd ) nameEnd = label + 1;
-
- if( outNextPtr ) *outNextPtr = nameEnd;
- err = kNoErr;
-
exit:
- return( err );
+ if( err || stop ) _MDNSColliderStop( me, err );
}
//===========================================================================================================================
-// DNSMessageExtractDomainNameString
+// _MDNSColliderSendResponse
//===========================================================================================================================
-static OSStatus
- DNSMessageExtractDomainNameString(
- const void * inMsgPtr,
- size_t inMsgLen,
- const void * inNamePtr,
- char inBuf[ kDNSServiceMaxDomainName ],
- const uint8_t ** outNextPtr )
+static OSStatus _MDNSColliderSendResponse( MDNSColliderRef me, SocketRef inSock, const struct sockaddr *inDest )
{
- 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 );
+ OSStatus err;
+ ssize_t n;
- if( outNextPtr ) *outNextPtr = nextPtr;
+ n = sendto( inSock, (char *) me->responsePtr, me->responseLen, 0, inDest, SockAddrGetSize( inDest ) );
+ err = map_socket_value_errno( inSock, n == (ssize_t) me->responseLen, n );
+ return( err );
+}
+
+//===========================================================================================================================
+// _MDNSColliderSendProbe
+//===========================================================================================================================
+
+static OSStatus _MDNSColliderSendProbe( MDNSColliderRef me, SocketRef inSock, const struct sockaddr *inDest )
+{
+ OSStatus err;
+ ssize_t n;
-exit:
+ n = sendto( inSock, (char *) me->probePtr, me->probeLen, 0, inDest, SockAddrGetSize( inDest ) );
+ err = map_socket_value_errno( inSock, n == (ssize_t) me->probeLen, n );
return( err );
}
//===========================================================================================================================
-// DNSMessageExtractRecord
+// ServiceBrowserCreate
//===========================================================================================================================
+typedef struct SBDomain SBDomain;
+typedef struct SBServiceType SBServiceType;
+typedef struct SBServiceBrowse SBServiceBrowse;
+typedef struct SBServiceInstance SBServiceInstance;
+typedef struct SBIPAddress SBIPAddress;
+
+struct ServiceBrowserPrivate
+{
+ CFRuntimeBase base; // CF object base.
+ dispatch_queue_t queue; // Queue for service browser's events.
+ DNSServiceRef connection; // Shared connection for DNS-SD ops.
+ DNSServiceRef domainsQuery; // Query for recommended browsing domains.
+ char * domain; // If non-null, then browsing is limited to this domain.
+ StringListItem * serviceTypeList; // If non-null, then browsing is limited to these service types.
+ ServiceBrowserCallback_f userCallback; // User's callback. Called when browsing stops.
+ void * userContext; // User's callback context.
+ SBDomain * domainList; // List of domains and their browse results.
+ dispatch_source_t stopTimer; // Timer to stop browsing after browseTimeSecs.
+ uint32_t ifIndex; // If non-zero, then browsing is limited to this interface.
+ unsigned int browseTimeSecs; // Amount of time to spend browsing in seconds.
+ Boolean includeAWDL; // True if the IncludeAWDL flag should be used for DNS-SD ops that
+ // use the "any" interface.
+};
+
+struct SBDomain
+{
+ SBDomain * next; // Next domain object in list.
+ ServiceBrowserRef browser; // Pointer to parent service browser.
+ char * name; // Name of the domain.
+ DNSServiceRef servicesQuery; // Query for services (_services._dns-sd._udp.<domain> PTR record) in domain.
+ SBServiceType * typeList; // List of service types to browse for in this domain.
+};
+
+struct SBServiceType
+{
+ SBServiceType * next; // Next service type object in list.
+ char * name; // Name of the service type.
+ SBServiceBrowse * browseList; // List of browses for this service type.
+};
+
+struct SBServiceBrowse
+{
+ SBServiceBrowse * next; // Next browse object in list.
+ ServiceBrowserRef browser; // Pointer to parent service browser.
+ DNSServiceRef browse; // Reference to DNSServiceBrowse op.
+ SBServiceInstance * instanceList; // List of service instances that were discovered by this browse.
+ uint64_t startTicks; // Value of UpTicks() when the browse op began.
+ uint32_t ifIndex; // If non-zero, then the browse is limited to this interface.
+};
+
+struct SBServiceInstance
+{
+ SBServiceInstance * next; // Next service instance object in list.
+ ServiceBrowserRef browser; // Pointer to parent service browser.
+ char * name; // Name of the service instance.
+ uint32_t ifIndex; // Index of interface over which this service instance was discovered.
+ uint64_t discoverTimeUs; // Time it took to discover this service instance in microseconds.
+ DNSServiceRef resolve; // Reference to DNSServiceResolve op for this service instance.
+ uint64_t resolveStartTicks; // Value of UpTicks() when the DNSServiceResolve op began.
+ uint64_t resolveTimeUs; // Time it took to resolve this service instance.
+ char * hostname; // Service instance's hostname. Result of DNSServiceResolve.
+ uint16_t port; // Service instance's port number. Result of DNSServiceResolve.
+ uint8_t * txtPtr; // Service instance's TXT record data. Result of DNSServiceResolve.
+ size_t txtLen; // Length of service instance's TXT record data.
+ DNSServiceRef getAddrInfo; // Reference to DNSServiceGetAddrInfo op for service instance's hostname.
+ uint64_t gaiStartTicks; // Value of UpTicks() when the DNSServiceGetAddrInfo op began.
+ SBIPAddress * ipaddrList; // List of IP addresses that the hostname resolved to.
+};
+
+struct SBIPAddress
+{
+ SBIPAddress * next; // Next IP address object in list.
+ sockaddr_ip sip; // IPv4 or IPv6 address.
+ uint64_t resolveTimeUs; // Time it took to resolve this IP address in microseconds.
+};
+
typedef struct
{
- uint8_t type[ 2 ];
- uint8_t class[ 2 ];
- uint8_t ttl[ 4 ];
- uint8_t rdLength[ 2 ];
- uint8_t rdata[ 1 ];
+ SBRDomain * domainList; // List of domains in which services were found.
+ int32_t refCount; // This object's reference count.
-} DNSRecordFields;
+} ServiceBrowserResultsPrivate;
-check_compile_time( offsetof( DNSRecordFields, rdata ) == 10 );
+static void _ServiceBrowserStop( ServiceBrowserRef me, OSStatus inError );
+static OSStatus _ServiceBrowserAddDomain( ServiceBrowserRef inBrowser, const char *inDomain );
+static OSStatus _ServiceBrowserRemoveDomain( ServiceBrowserRef inBrowser, const char *inName );
+static void _ServiceBrowserTimerHandler( void *inContext );
+static void DNSSD_API
+ _ServiceBrowserDomainsQueryCallback(
+ 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
+ _ServiceBrowserServicesQueryCallback(
+ 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
+ _ServiceBrowserBrowseCallback(
+ DNSServiceRef inSDRef,
+ DNSServiceFlags inFlags,
+ uint32_t inInterfaceIndex,
+ DNSServiceErrorType inError,
+ const char * inName,
+ const char * inRegType,
+ const char * inDomain,
+ void * inContext );
+static void DNSSD_API
+ _ServiceBrowserResolveCallback(
+ 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
+ _ServiceBrowserGAICallback(
+ DNSServiceRef inSDRef,
+ DNSServiceFlags inFlags,
+ uint32_t inInterfaceIndex,
+ DNSServiceErrorType inError,
+ const char * inHostname,
+ const struct sockaddr * inSockAddr,
+ uint32_t inTTL,
+ void * inContext );
+static OSStatus
+ _ServiceBrowserAddServiceType(
+ ServiceBrowserRef inBrowser,
+ SBDomain * inDomain,
+ const char * inName,
+ uint32_t inIfIndex );
+static OSStatus
+ _ServiceBrowserRemoveServiceType(
+ ServiceBrowserRef inBrowser,
+ SBDomain * inDomain,
+ const char * inName,
+ uint32_t inIfIndex );
+static OSStatus
+ _ServiceBrowserAddServiceInstance(
+ ServiceBrowserRef inBrowser,
+ SBServiceBrowse * inBrowse,
+ uint32_t inIfIndex,
+ const char * inName,
+ const char * inRegType,
+ const char * inDomain,
+ uint64_t inDiscoverTimeUs );
+static OSStatus
+ _ServiceBrowserRemoveServiceInstance(
+ ServiceBrowserRef inBrowser,
+ SBServiceBrowse * inBrowse,
+ const char * inName,
+ uint32_t inIfIndex );
+static OSStatus
+ _ServiceBrowserAddIPAddress(
+ ServiceBrowserRef inBrowser,
+ SBServiceInstance * inInstance,
+ const struct sockaddr * inSockAddr,
+ uint64_t inResolveTimeUs );
+static OSStatus
+ _ServiceBrowserRemoveIPAddress(
+ ServiceBrowserRef inBrowser,
+ SBServiceInstance * inInstance,
+ const struct sockaddr * inSockAddr );
+static OSStatus _ServiceBrowserCreateResults( ServiceBrowserRef me, ServiceBrowserResults **outResults );
+static OSStatus _SBDomainCreate( const char *inName, ServiceBrowserRef inBrowser, SBDomain **outDomain );
+static void _SBDomainFree( SBDomain *inDomain );
+static OSStatus _SBServiceTypeCreate( const char *inName, SBServiceType **outType );
+static void _SBServiceTypeFree( SBServiceType *inType );
+static OSStatus _SBServiceBrowseCreate( uint32_t inIfIndex, ServiceBrowserRef inBrowser, SBServiceBrowse **outBrowse );
+static void _SBServiceBrowseFree( SBServiceBrowse *inBrowse );
+static OSStatus
+ _SBServiceInstanceCreate(
+ const char * inName,
+ uint32_t inIfIndex,
+ uint64_t inDiscoverTimeUs,
+ ServiceBrowserRef inBrowser,
+ SBServiceInstance ** outInstance );
+static void _SBServiceInstanceFree( SBServiceInstance *inInstance );
+static OSStatus
+ _SBIPAddressCreate(
+ const struct sockaddr * inSockAddr,
+ uint64_t inResolveTimeUs,
+ SBIPAddress ** outIPAddress );
+static void _SBIPAddressFree( SBIPAddress *inIPAddress );
+static void _SBIPAddressFreeList( SBIPAddress *inList );
+static OSStatus _SBRDomainCreate( const char *inName, SBRDomain **outDomain );
+static void _SBRDomainFree( SBRDomain *inDomain );
+static OSStatus _SBRServiceTypeCreate( const char *inName, SBRServiceType **outType );
+static void _SBRServiceTypeFree( SBRServiceType *inType );
+static OSStatus
+ _SBRServiceInstanceCreate(
+ const char * inName,
+ uint32_t inInterfaceIndex,
+ const char * inHostname,
+ uint16_t inPort,
+ const uint8_t * inTXTPtr,
+ size_t inTXTLen,
+ uint64_t inDiscoverTimeUs,
+ uint64_t inResolveTimeUs,
+ SBRServiceInstance ** outInstance );
+static void _SBRServiceInstanceFree( SBRServiceInstance *inInstance );
+static OSStatus
+ _SBRIPAddressCreate(
+ const struct sockaddr * inSockAddr,
+ uint64_t inResolveTimeUs,
+ SBRIPAddress ** outIPAddress );
+static void _SBRIPAddressFree( SBRIPAddress *inIPAddress );
+
+#define ForgetSBIPAddressList( X ) ForgetCustom( X, _SBIPAddressFreeList )
+
+CF_CLASS_DEFINE( ServiceBrowser );
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 )
+ ServiceBrowserCreate(
+ dispatch_queue_t inQueue,
+ uint32_t inInterfaceIndex,
+ const char * inDomain,
+ unsigned int inBrowseTimeSecs,
+ Boolean inIncludeAWDL,
+ ServiceBrowserRef * outBrowser )
{
- OSStatus err;
- const uint8_t * const msgEnd = inMsgPtr + inMsgLen;
- const uint8_t * ptr;
- const DNSRecordFields * record;
- size_t rdLength;
+ OSStatus err;
+ ServiceBrowserRef obj;
- 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 );
+ CF_OBJECT_CREATE( ServiceBrowser, obj, err, exit );
- record = (DNSRecordFields *) ptr;
- rdLength = ReadBig16( record->rdLength );
- require_action_quiet( (size_t)( msgEnd - record->rdata ) >= rdLength , exit, err = kUnderrunErr );
+ ReplaceDispatchQueue( &obj->queue, inQueue );
+ obj->ifIndex = inInterfaceIndex;
+ if( inDomain )
+ {
+ obj->domain = strdup( inDomain );
+ require_action( obj->domain, exit, err = kNoMemoryErr );
+ }
+ obj->browseTimeSecs = inBrowseTimeSecs;
+ obj->includeAWDL = inIncludeAWDL;
- 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;
+ *outBrowser = obj;
+ obj = NULL;
+ err = kNoErr;
exit:
+ CFReleaseNullSafe( obj );
return( err );
}
//===========================================================================================================================
-// DNSMessageGetAnswerSection
+// _ServiceBrowserFinalize
//===========================================================================================================================
-static OSStatus DNSMessageGetAnswerSection( const uint8_t *inMsgPtr, size_t inMsgLen, const uint8_t **outPtr )
+static void _ServiceBrowserFinalize( CFTypeRef inObj )
+{
+ ServiceBrowserRef const me = (ServiceBrowserRef) inObj;
+ StringListItem * serviceType;
+
+ dispatch_forget( &me->queue );
+ check( !me->connection );
+ check( !me->domainsQuery );
+ ForgetMem( &me->domain );
+ while( ( serviceType = me->serviceTypeList ) != NULL )
+ {
+ me->serviceTypeList = serviceType->next;
+ ForgetMem( &serviceType->str );
+ free( serviceType );
+ }
+ check( !me->domainList );
+ check( !me->stopTimer );
+}
+
+//===========================================================================================================================
+// ServiceBrowserStart
+//===========================================================================================================================
+
+static void _ServiceBrowserStart( void *inContext );
+
+static void ServiceBrowserStart( ServiceBrowserRef me )
+{
+ CFRetain( me );
+ dispatch_async_f( me->queue, me, _ServiceBrowserStart );
+}
+
+static void _ServiceBrowserStart( void *inContext )
{
OSStatus err;
- const uint8_t * const msgEnd = inMsgPtr + inMsgLen;
- unsigned int questionCount, i;
- const DNSHeader * hdr;
- const uint8_t * ptr;
+ ServiceBrowserRef const me = (ServiceBrowserRef) inContext;
- require_action_quiet( inMsgLen >= kDNSHeaderLength, exit, err = kSizeErr );
+ err = DNSServiceCreateConnection( &me->connection );
+ require_noerr( err, exit );
- hdr = (DNSHeader *) inMsgPtr;
- questionCount = DNSHeaderGetQuestionCount( hdr );
+ err = DNSServiceSetDispatchQueue( me->connection, me->queue );
+ require_noerr( err, exit );
- ptr = (uint8_t *)( hdr + 1 );
- for( i = 0; i < questionCount; ++i )
+ if( me->domain )
+ {
+ err = _ServiceBrowserAddDomain( me, me->domain );
+ require_noerr( err, exit );
+ }
+ else
{
- err = DNSMessageExtractDomainName( inMsgPtr, inMsgLen, ptr, NULL, &ptr );
+ DNSServiceRef sdRef;
+
+ sdRef = me->connection;
+ err = DNSServiceQueryRecord( &sdRef, kDNSServiceFlagsShareConnection, kDNSServiceInterfaceIndexLocalOnly,
+ "b._dns-sd._udp.local.", kDNSServiceType_PTR, kDNSServiceClass_IN, _ServiceBrowserDomainsQueryCallback, me );
require_noerr( err, exit );
- require_action_quiet( ( msgEnd - ptr ) >= 4, exit, err = kUnderrunErr );
- ptr += 4;
+
+ me->domainsQuery = sdRef;
}
- if( outPtr ) *outPtr = ptr;
- err = kNoErr;
+ err = DispatchTimerCreate( dispatch_time_seconds( me->browseTimeSecs ), DISPATCH_TIME_FOREVER,
+ 100 * kNanosecondsPerMillisecond, me->queue, _ServiceBrowserTimerHandler, NULL, me, &me->stopTimer );
+ require_noerr( err, exit );
+ dispatch_resume( me->stopTimer );
exit:
- return( err );
+ if( err ) _ServiceBrowserStop( me, err );
}
//===========================================================================================================================
-// DNSRecordDataToString
+// ServiceBrowserAddServiceType
//===========================================================================================================================
-static OSStatus
- DNSRecordDataToString(
- const void * inRDataPtr,
- size_t inRDataLen,
- unsigned int inRDataType,
- const void * inMsgPtr,
- size_t inMsgLen,
- char ** outString )
+static OSStatus ServiceBrowserAddServiceType( ServiceBrowserRef me, const char *inServiceType )
{
- 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 ];
+ OSStatus err;
+ StringListItem * item;
+ StringListItem ** itemPtr;
+ StringListItem * newItem = NULL;
- rdataStr = NULL;
- if( inRDataType == kDNSServiceType_A )
+ for( itemPtr = &me->serviceTypeList; ( item = *itemPtr ) != NULL; itemPtr = &item->next )
{
- require_action_quiet( inRDataLen == 4, exit, err = kMalformedErr );
-
- ASPrintF( &rdataStr, "%.4a", rdataPtr );
- require_action( rdataStr, exit, err = kNoMemoryErr );
+ if( strcmp( item->str, inServiceType ) == 0 ) break;
}
- else if( inRDataType == kDNSServiceType_AAAA )
+ if( !item )
{
- require_action_quiet( inRDataLen == 16, exit, err = kMalformedErr );
+ newItem = (StringListItem *) calloc( 1, sizeof( *newItem ) );
+ require_action( newItem, exit, err = kNoMemoryErr );
- ASPrintF( &rdataStr, "%.16a", rdataPtr );
- require_action( rdataStr, exit, err = kNoMemoryErr );
+ newItem->str = strdup( inServiceType );
+ require_action( newItem->str, exit, err = kNoMemoryErr );
+
+ *itemPtr = newItem;
+ newItem = NULL;
}
- else if( ( inRDataType == kDNSServiceType_PTR ) || ( inRDataType == kDNSServiceType_CNAME ) ||
- ( inRDataType == kDNSServiceType_NS ) )
+ err = kNoErr;
+
+exit:
+ FreeNullSafe( newItem );
+ return( err );
+}
+
+//===========================================================================================================================
+// ServiceBrowserSetCallback
+//===========================================================================================================================
+
+static void ServiceBrowserSetCallback( ServiceBrowserRef me, ServiceBrowserCallback_f inCallback, void *inContext )
+{
+ me->userCallback = inCallback;
+ me->userContext = inContext;
+}
+
+//===========================================================================================================================
+// ServiceBrowserResultsRetain
+//===========================================================================================================================
+
+static void ServiceBrowserResultsRetain( ServiceBrowserResults *inResults )
+{
+ ServiceBrowserResultsPrivate * const results = (ServiceBrowserResultsPrivate *) inResults;
+
+ atomic_add_32( &results->refCount, 1 );
+}
+
+//===========================================================================================================================
+// ServiceBrowserResultsRelease
+//===========================================================================================================================
+
+static void ServiceBrowserResultsRelease( ServiceBrowserResults *inResults )
+{
+ ServiceBrowserResultsPrivate * const results = (ServiceBrowserResultsPrivate *) inResults;
+ SBRDomain * domain;
+
+ if( atomic_add_and_fetch_32( &results->refCount, -1 ) == 0 )
{
- if( inMsgPtr )
+ while( ( domain = inResults->domainList ) != NULL )
{
- err = DNSMessageExtractDomainNameString( inMsgPtr, inMsgLen, rdataPtr, domainNameStr, NULL );
- require_noerr( err, exit );
+ inResults->domainList = domain->next;
+ _SBRDomainFree( domain );
}
- else
+ free( inResults );
+ }
+}
+
+//===========================================================================================================================
+// _ServiceBrowserStop
+//===========================================================================================================================
+
+static void _ServiceBrowserStop( ServiceBrowserRef me, OSStatus inError )
+{
+ OSStatus err;
+ SBDomain * d;
+ SBServiceType * t;
+ SBServiceBrowse * b;
+ SBServiceInstance * i;
+
+ dispatch_source_forget( &me->stopTimer );
+ DNSServiceForget( &me->domainsQuery );
+ for( d = me->domainList; d; d = d->next )
+ {
+ DNSServiceForget( &d->servicesQuery );
+ for( t = d->typeList; t; t = t->next )
{
- err = DomainNameToString( rdataPtr, rdataEnd, domainNameStr, NULL );
- require_noerr( err, exit );
+ for( b = t->browseList; b; b = b->next )
+ {
+ DNSServiceForget( &b->browse );
+ for( i = b->instanceList; i; i = i->next )
+ {
+ DNSServiceForget( &i->resolve );
+ DNSServiceForget( &i->getAddrInfo );
+ }
+ }
}
-
- rdataStr = strdup( domainNameStr );
- require_action( rdataStr, exit, err = kNoMemoryErr );
}
- else if( inRDataType == kDNSServiceType_SRV )
+ DNSServiceForget( &me->connection );
+
+ if( me->userCallback )
{
- uint16_t priority, weight, port;
- const uint8_t * target;
+ ServiceBrowserResults * results = NULL;
- require_action_quiet( ( rdataPtr + 6 ) < rdataEnd, exit, err = kMalformedErr );
+ err = _ServiceBrowserCreateResults( me, &results );
+ if( !err ) err = inError;
- 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 );
+ me->userCallback( results, err, me->userContext );
+ me->userCallback = NULL;
+ me->userContext = NULL;
+ if( results ) ServiceBrowserResultsRelease( results );
}
- else if( inRDataType == kDNSServiceType_TXT )
+
+ while( ( d = me->domainList ) != NULL )
{
- 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 );
- }
+ me->domainList = d->next;
+ _SBDomainFree( d );
}
- else if( inRDataType == kDNSServiceType_SOA )
+ CFRelease( me );
+}
+
+//===========================================================================================================================
+// _ServiceBrowserAddDomain
+//===========================================================================================================================
+
+static OSStatus _ServiceBrowserAddDomain( ServiceBrowserRef me, const char *inDomain )
+{
+ OSStatus err;
+ SBDomain * domain;
+ SBDomain ** domainPtr;
+ SBDomain * newDomain = NULL;
+
+ for( domainPtr = &me->domainList; ( domain = *domainPtr ) != NULL; domainPtr = &domain->next )
{
- uint32_t serial, refresh, retry, expire, minimum;
+ if( strcasecmp( domain->name, inDomain ) == 0 ) break;
+ }
+ require_action_quiet( !domain, exit, err = kDuplicateErr );
+
+ err = _SBDomainCreate( inDomain, me, &newDomain );
+ require_noerr_quiet( err, exit );
+
+ if( me->serviceTypeList )
+ {
+ const StringListItem * item;
- 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
+ for( item = me->serviceTypeList; item; item = item->next )
{
- 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 );
+ err = _ServiceBrowserAddServiceType( me, newDomain, item->str, me->ifIndex );
+ if( err == kDuplicateErr ) err = kNoErr;
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 )
+ else
{
- unsigned int windowBlock, bitmapLen, i, recordType;
- const uint8_t * bitmapPtr;
+ char * recordName;
+ DNSServiceFlags flags;
+ DNSServiceRef sdRef;
- if( inMsgPtr )
- {
- err = DNSMessageExtractDomainNameString( inMsgPtr, inMsgLen, rdataPtr, domainNameStr, &ptr );
- require_noerr( err, exit );
- }
- else
- {
- err = DomainNameToString( rdataPtr, rdataEnd, domainNameStr, &ptr );
- require_noerr( err, exit );
- }
+ ASPrintF( &recordName, "_services._dns-sd._udp.%s", newDomain->name );
+ require_action( recordName, exit, err = kNoMemoryErr );
- require_action_quiet( ptr < rdataEnd, exit, err = kMalformedErr );
+ flags = kDNSServiceFlagsShareConnection;
+ if( ( me->ifIndex == kDNSServiceInterfaceIndexAny ) && me->includeAWDL ) flags |= kDNSServiceFlagsIncludeAWDL;
- rdataStr = strdup( domainNameStr );
- require_action( rdataStr, exit, err = kNoMemoryErr );
+ sdRef = newDomain->browser->connection;
+ err = DNSServiceQueryRecord( &sdRef, flags, me->ifIndex, recordName, kDNSServiceType_PTR, kDNSServiceClass_IN,
+ _ServiceBrowserServicesQueryCallback, newDomain );
+ free( recordName );
+ require_noerr( err, exit );
- 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 );
- }
- }
- }
+ newDomain->servicesQuery = sdRef;
+ }
+
+ *domainPtr = newDomain;
+ newDomain = NULL;
+ err = kNoErr;
+
+exit:
+ if( newDomain ) _SBDomainFree( newDomain );
+ return( err );
+}
+
+//===========================================================================================================================
+// _ServiceBrowserRemoveDomain
+//===========================================================================================================================
+
+static OSStatus _ServiceBrowserRemoveDomain( ServiceBrowserRef me, const char *inName )
+{
+ OSStatus err;
+ SBDomain * domain;
+ SBDomain ** domainPtr;
+
+ for( domainPtr = &me->domainList; ( domain = *domainPtr ) != NULL; domainPtr = &domain->next )
+ {
+ if( strcasecmp( domain->name, inName ) == 0 ) break;
}
- else if( inRDataType == kDNSServiceType_MX )
+
+ if( domain )
{
- 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 );
+ *domainPtr = domain->next;
+ _SBDomainFree( domain );
+ err = kNoErr;
}
else
{
- err = kNotHandledErr;
- goto exit;
+ err = kNotFoundErr;
}
- check( rdataStr );
- *outString = rdataStr;
- rdataStr = NULL;
- err = kNoErr;
+ return( err );
+}
+
+//===========================================================================================================================
+// _ServiceBrowserTimerHandler
+//===========================================================================================================================
+
+static void _ServiceBrowserTimerHandler( void *inContext )
+{
+ ServiceBrowserRef const me = (ServiceBrowserRef) inContext;
+
+ _ServiceBrowserStop( me, kNoErr );
+}
+
+//===========================================================================================================================
+// _ServiceBrowserDomainsQueryCallback
+//===========================================================================================================================
+
+static void DNSSD_API
+ _ServiceBrowserDomainsQueryCallback(
+ 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 )
+{
+ ServiceBrowserRef const me = (ServiceBrowserRef) inContext;
+ OSStatus err;
+ char domainStr[ kDNSServiceMaxDomainName ];
+
+ Unused( inSDRef );
+ Unused( inInterfaceIndex );
+ Unused( inFullName );
+ Unused( inType );
+ Unused( inClass );
+ Unused( inTTL );
+
+ require_noerr( inError, exit );
+
+ err = DomainNameToString( inRDataPtr, ( (const uint8_t *) inRDataPtr ) + inRDataLen, domainStr, NULL );
+ require_noerr( err, exit );
+
+ if( inFlags & kDNSServiceFlagsAdd )
+ {
+ err = _ServiceBrowserAddDomain( me, domainStr );
+ if( err == kDuplicateErr ) err = kNoErr;
+ require_noerr( err, exit );
+ }
+ else
+ {
+ err = _ServiceBrowserRemoveDomain( me, domainStr );
+ if( err == kNotFoundErr ) err = kNoErr;
+ require_noerr( err, exit );
+ }
exit:
- FreeNullSafe( rdataStr );
- return( err );
+ return;
}
//===========================================================================================================================
-// DomainNameAppendString
+// _ServiceBrowserServicesQueryCallback
//===========================================================================================================================
-static OSStatus
- DomainNameAppendString(
- uint8_t inDomainName[ kDomainNameLengthMax ],
- const char * inString,
- uint8_t ** outEndPtr )
+static void DNSSD_API
+ _ServiceBrowserServicesQueryCallback(
+ 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;
- const char * src;
- uint8_t * root;
- const uint8_t * const nameLim = inDomainName + kDomainNameLengthMax;
+ SBDomain * const domain = (SBDomain *) inContext;
+ ServiceBrowserRef const me = domain->browser;
+ const uint8_t * src;
+ const uint8_t * end;
+ uint8_t * dst;
+ int i;
+ uint8_t serviceType[ 2 * ( 1 + kDomainLabelLengthMax ) + 1 ];
+ char serviceTypeStr[ kDNSServiceMaxDomainName ];
- for( root = inDomainName; ( root < nameLim ) && *root; root += ( 1 + *root ) ) {}
- require_action_quiet( root < nameLim, exit, err = kMalformedErr );
+ Unused( inSDRef );
+ Unused( inFullName );
+ Unused( inTTL );
+ Unused( inType );
+ Unused( inClass );
- // If the string is a single dot, denoting the root domain, then there are no non-empty labels.
+ require_noerr( inError, exit );
- src = inString;
- if( ( src[ 0 ] == '.' ) && ( src[ 1 ] == '\0' ) ) ++src;
- while( *src )
+ check( inType == kDNSServiceType_PTR );
+ check( inClass == kDNSServiceClass_IN );
+
+ // The first two labels of the domain name in the RDATA describe a service type.
+ // See <https://tools.ietf.org/html/rfc6763#section-9>.
+
+ src = (const uint8_t *) inRDataPtr;
+ end = src + inRDataLen;
+ dst = serviceType;
+ for( i = 0; i < 2; ++i )
{
- uint8_t * const label = root;
- const uint8_t * const labelLim = Min( &label[ 1 + kDomainLabelLengthMax ], nameLim - 1 );
- uint8_t * dst;
- int c;
- size_t labelLen;
+ size_t labelLen;
- dst = &label[ 1 ];
- while( *src && ( ( c = *src++ ) != '.' ) )
- {
- if( c == '\\' )
- {
- require_action_quiet( *src != '\0', exit, err = kUnderrunErr );
- c = *src++;
- if( isdigit_safe( c ) && isdigit_safe( src[ 0 ] ) && isdigit_safe( src[ 1 ] ) )
- {
- const int decimal = ( ( c - '0' ) * 100 ) + ( ( src[ 0 ] - '0' ) * 10 ) + ( src[ 1 ] - '0' );
-
- if( decimal <= 255 )
- {
- c = decimal;
- src += 2;
- }
- }
- }
- require_action_quiet( dst < labelLim, exit, err = kOverrunErr );
- *dst++ = (uint8_t) c;
- }
+ require_action_quiet( ( end - src ) > 0, exit, err = kUnderrunErr );
- labelLen = (size_t)( dst - &label[ 1 ] );
- require_action_quiet( labelLen > 0, exit, err = kMalformedErr );
+ labelLen = *src;
+ require_action_quiet( ( labelLen > 0 ) && ( labelLen <= kDomainLabelLengthMax ), exit, err = kMalformedErr );
+ require_action_quiet( ( (size_t)( end - src ) ) >= ( 1 + labelLen ), exit, err = kUnderrunErr );
- label[ 0 ] = (uint8_t) labelLen;
- root = dst;
- *root = 0;
+ memcpy( dst, src, 1 + labelLen );
+ src += 1 + labelLen;
+ dst += 1 + labelLen;
}
+ *dst = 0;
- if( outEndPtr ) *outEndPtr = root + 1;
- err = kNoErr;
+ err = DomainNameToString( serviceType, NULL, serviceTypeStr, NULL );
+ require_noerr( err, exit );
+
+ if( inFlags & kDNSServiceFlagsAdd )
+ {
+ err = _ServiceBrowserAddServiceType( me, domain, serviceTypeStr, inInterfaceIndex );
+ if( err == kDuplicateErr ) err = kNoErr;
+ require_noerr( err, exit );
+ }
+ else
+ {
+ err = _ServiceBrowserRemoveServiceType( me, domain, serviceTypeStr, inInterfaceIndex );
+ if( err == kNotFoundErr ) err = kNoErr;
+ require_noerr( err, exit );
+ }
exit:
- return( err );
+ return;
}
//===========================================================================================================================
-// DomainNameEqual
+// _ServiceBrowserBrowseCallback
//===========================================================================================================================
-static Boolean DomainNameEqual( const uint8_t *inName1, const uint8_t *inName2 )
+static void DNSSD_API
+ _ServiceBrowserBrowseCallback(
+ DNSServiceRef inSDRef,
+ DNSServiceFlags inFlags,
+ uint32_t inInterfaceIndex,
+ DNSServiceErrorType inError,
+ const char * inName,
+ const char * inRegType,
+ const char * inDomain,
+ void * inContext )
{
- const uint8_t * p1 = inName1;
- const uint8_t * p2 = inName2;
- unsigned int len;
+ OSStatus err;
+ const uint64_t nowTicks = UpTicks();
+ SBServiceBrowse * const browse = (SBServiceBrowse *) inContext;
+ ServiceBrowserRef const me = (ServiceBrowserRef) browse->browser;
- for( ;; )
+ Unused( inSDRef );
+
+ require_noerr( inError, exit );
+
+ if( inFlags & kDNSServiceFlagsAdd )
{
- 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 );
- }
+ err = _ServiceBrowserAddServiceInstance( me, browse, inInterfaceIndex, inName, inRegType, inDomain,
+ UpTicksToMicroseconds( nowTicks - browse->startTicks ) );
+ if( err == kDuplicateErr ) err = kNoErr;
+ require_noerr( err, exit );
+ }
+ else
+ {
+ err = _ServiceBrowserRemoveServiceInstance( me, browse, inName, inInterfaceIndex );
+ if( err == kNotFoundErr ) err = kNoErr;
+ require_noerr( err, exit );
+ }
+
+exit:
+ return;
+}
+
+//===========================================================================================================================
+// _ServiceBrowserResolveCallback
+//===========================================================================================================================
+
+static void DNSSD_API
+ _ServiceBrowserResolveCallback(
+ 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();
+ SBServiceInstance * const instance = (SBServiceInstance *) inContext;
+ ServiceBrowserRef const me = (ServiceBrowserRef) instance->browser;
+
+ Unused( inSDRef );
+ Unused( inFlags );
+ Unused( inInterfaceIndex );
+ Unused( inFullName );
+
+ require_noerr( inError, exit );
+
+ if( !MemEqual( instance->txtPtr, instance->txtLen, inTXTPtr, inTXTLen ) )
+ {
+ FreeNullSafe( instance->txtPtr );
+ instance->txtPtr = memdup( inTXTPtr, inTXTLen );
+ require_action( instance->txtPtr, exit, err = kNoMemoryErr );
+
+ instance->txtLen = inTXTLen;
+ }
+
+ instance->port = ntohs( inPort );
+
+ if( !instance->hostname || ( strcasecmp( instance->hostname, inHostname ) != 0 ) )
+ {
+ DNSServiceRef sdRef;
+
+ if( !instance->hostname ) instance->resolveTimeUs = UpTicksToMicroseconds( nowTicks - instance->resolveStartTicks );
+
+ err = ReplaceString( &instance->hostname, NULL, inHostname, kSizeCString );
+ require_noerr( err, exit );
+
+ DNSServiceForget( &instance->getAddrInfo );
+ ForgetSBIPAddressList( &instance->ipaddrList );
+
+ sdRef = me->connection;
+ instance->gaiStartTicks = UpTicks();
+ err = DNSServiceGetAddrInfo( &sdRef, kDNSServiceFlagsShareConnection, instance->ifIndex,
+ kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6, instance->hostname, _ServiceBrowserGAICallback, instance );
+ require_noerr( err, exit );
+
+ instance->getAddrInfo = sdRef;
}
- return( true );
-}
-
-//===========================================================================================================================
-// DomainNameLength
-//===========================================================================================================================
-
-static size_t DomainNameLength( const uint8_t * const inName )
-{
- const uint8_t * ptr;
- for( ptr = inName; *ptr != 0; ptr += ( 1 + *ptr ) ) {}
- return( (size_t)( ptr - inName ) + 1 );
+exit:
+ return;
}
//===========================================================================================================================
-// DomainNameFromString
+// _ServiceBrowserGAICallback
//===========================================================================================================================
-static OSStatus
- DomainNameFromString(
- uint8_t inDomainName[ kDomainNameLengthMax ],
- const char * inString,
- uint8_t ** outEndPtr )
+static void DNSSD_API
+ _ServiceBrowserGAICallback(
+ DNSServiceRef inSDRef,
+ DNSServiceFlags inFlags,
+ uint32_t inInterfaceIndex,
+ DNSServiceErrorType inError,
+ const char * inHostname,
+ const struct sockaddr * inSockAddr,
+ uint32_t inTTL,
+ void * inContext )
{
- inDomainName[ 0 ] = 0;
- return( DomainNameAppendString( inDomainName, inString, outEndPtr ) );
+ OSStatus err;
+ const uint64_t nowTicks = UpTicks();
+ SBServiceInstance * const instance = (SBServiceInstance *) inContext;
+ ServiceBrowserRef const me = (ServiceBrowserRef) instance->browser;
+
+ Unused( inSDRef );
+ Unused( inInterfaceIndex );
+ Unused( inHostname );
+ Unused( inTTL );
+
+ require_noerr( inError, 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 = _ServiceBrowserAddIPAddress( me, instance, inSockAddr,
+ UpTicksToMicroseconds( nowTicks - instance->gaiStartTicks ) );
+ if( err == kDuplicateErr ) err = kNoErr;
+ require_noerr( err, exit );
+ }
+ else
+ {
+ err = _ServiceBrowserRemoveIPAddress( me, instance, inSockAddr );
+ if( err == kNotFoundErr ) err = kNoErr;
+ require_noerr( err, exit );
+ }
+
+exit:
+ return;
}
//===========================================================================================================================
-// DomainNameToString
+// _ServiceBrowserAddServiceType
//===========================================================================================================================
static OSStatus
- DomainNameToString(
- const uint8_t * inDomainName,
- const uint8_t * inEnd,
- char inBuf[ kDNSServiceMaxDomainName ],
- const uint8_t ** outNextPtr )
+ _ServiceBrowserAddServiceType(
+ ServiceBrowserRef me,
+ SBDomain * inDomain,
+ const char * inName,
+ uint32_t inIfIndex )
{
- 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.
+ OSStatus err;
+ SBServiceType * type;
+ SBServiceType ** typePtr;
+ SBServiceType * newType = NULL;
+ SBServiceBrowse * browse;
+ SBServiceBrowse ** browsePtr;
+ SBServiceBrowse * newBrowse = NULL;
+ DNSServiceRef sdRef;
+ DNSServiceFlags flags;
- dst = inBuf;
- for( label = inDomainName; ( labelLen = label[ 0 ] ) != 0; label = nextLabel )
+ for( typePtr = &inDomain->typeList; ( type = *typePtr ) != NULL; typePtr = &type->next )
{
- 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 );
+ if( strcasecmp( type->name, inName ) == 0 ) break;
+ }
+ if( !type )
+ {
+ err = _SBServiceTypeCreate( inName, &newType );
+ require_noerr_quiet( err, exit );
- 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++ = '.';
+ type = newType;
}
- // At this point, label points to the root label.
- // If the root label was the only label, then write a dot for it.
+ for( browsePtr = &type->browseList; ( browse = *browsePtr ) != NULL; browsePtr = &browse->next )
+ {
+ if( browse->ifIndex == inIfIndex ) break;
+ }
+ require_action_quiet( !browse, exit, err = kDuplicateErr );
- if( label == inDomainName ) *dst++ = '.';
- *dst = '\0';
- if( outNextPtr ) *outNextPtr = label + 1;
- err = kNoErr;
+ err = _SBServiceBrowseCreate( inIfIndex, me, &newBrowse );
+ require_noerr_quiet( err, exit );
+
+ flags = kDNSServiceFlagsShareConnection;
+ if( ( newBrowse->ifIndex == kDNSServiceInterfaceIndexAny ) && me->includeAWDL ) flags |= kDNSServiceFlagsIncludeAWDL;
+
+ sdRef = me->connection;
+ newBrowse->startTicks = UpTicks();
+ err = DNSServiceBrowse( &sdRef, flags, newBrowse->ifIndex, type->name, inDomain->name, _ServiceBrowserBrowseCallback,
+ newBrowse );
+ require_noerr( err, exit );
+
+ newBrowse->browse = sdRef;
+ *browsePtr = newBrowse;
+ newBrowse = NULL;
+
+ if( newType )
+ {
+ *typePtr = newType;
+ newType = NULL;
+ }
exit:
+ if( newBrowse ) _SBServiceBrowseFree( newBrowse );
+ if( newType ) _SBServiceTypeFree( newType );
return( err );
}
//===========================================================================================================================
-// DNSMessageToText
+// _ServiceBrowserRemoveServiceType
//===========================================================================================================================
-#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" : \
- "???" )
-
static OSStatus
- DNSMessageToText(
- const uint8_t * inMsgPtr,
- size_t inMsgLen,
- const Boolean inMDNS,
- const Boolean inPrintRaw,
- char ** outText )
+ _ServiceBrowserRemoveServiceType(
+ ServiceBrowserRef me,
+ SBDomain * inDomain,
+ const char * inName,
+ uint32_t inIfIndex )
{
- OSStatus err;
- DataBuffer dataBuf;
- size_t len;
- 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 ];
-
- DataBuffer_Init( &dataBuf, NULL, 0, SIZE_MAX );
- #define _Append( ... ) do { err = DataBuffer_AppendF( &dataBuf, __VA_ARGS__ ); require_noerr( err, exit ); } while( 0 )
-
- 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 );
-
- _Append( "ID: 0x%04X (%u)\n", id, id );
- _Append( "Flags: 0x%04X %c/%s %cAA%cTC%cRD%cRA%?s%?s %s\n",
- flags,
- ( flags & kDNSHeaderFlag_Response ) ? 'R' : 'Q', DNSFlagsOpCodeToString( opcode ),
- ( flags & kDNSHeaderFlag_AuthAnswer ) ? ' ' : '!',
- ( flags & kDNSHeaderFlag_Truncation ) ? ' ' : '!',
- ( flags & kDNSHeaderFlag_RecursionDesired ) ? ' ' : '!',
- ( flags & kDNSHeaderFlag_RecursionAvailable ) ? ' ' : '!',
- !inMDNS, ( flags & kDNSHeaderFlag_AuthenticData ) ? " AD" : "!AD",
- !inMDNS, ( flags & kDNSHeaderFlag_CheckingDisabled ) ? " CD" : "!CD",
- DNSFlagsRCodeToString( rcode ) );
- _Append( "Question count: %u\n", questionCount );
- _Append( "Answer count: %u\n", answerCount );
- _Append( "Authority count: %u\n", authorityCount );
- _Append( "Additional count: %u\n", additionalCount );
+ OSStatus err;
+ SBServiceType * type;
+ SBServiceType ** typePtr;
+ SBServiceBrowse * browse;
+ SBServiceBrowse ** browsePtr;
- ptr = (const 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 = DNSQuestionFixedFieldsGetType( (const DNSQuestionFixedFields *) ptr );
- qclass = DNSQuestionFixedFieldsGetClass( (const DNSQuestionFixedFields *) ptr );
- ptr += 4;
-
- isQU = ( inMDNS && ( qclass & kQClassUnicastResponseBit ) ) ? true : false;
- if( inMDNS ) qclass &= ~kQClassUnicastResponseBit;
-
- if( i == 0 ) _Append( "\nQUESTION SECTION\n" );
-
- _Append( "%s %2s %?2s%?2u %-5s\n",
- nameStr, inMDNS ? ( isQU ? "QU" : "QM" ) : "",
- ( qclass == kDNSServiceClass_IN ), "IN", ( qclass != kDNSServiceClass_IN ), qclass, RecordTypeToString( qtype ) );
- }
+ Unused( me );
- totalRRCount = answerCount + authorityCount + additionalCount;
- for( i = 0; i < totalRRCount; ++i )
+ for( typePtr = &inDomain->typeList; ( type = *typePtr ) != NULL; typePtr = &type->next )
{
- 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 = ( inMDNS && ( class & kRRClassCacheFlushBit ) ) ? true : false;
- if( inMDNS ) 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 ) ) _Append( "\nANSWER SECTION\n" );
- else if( authorityCount && ( i == answerCount ) ) _Append( "\nAUTHORITY SECTION\n" );
- else if( additionalCount && ( i == ( answerCount + authorityCount ) ) ) _Append( "\nADDITIONAL SECTION\n" );
-
- _Append( "%-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 );
+ if( strcasecmp( type->name, inName ) == 0 ) break;
}
- _Append( "\n" );
+ require_action_quiet( type, exit, err = kNotFoundErr );
- err = DataBuffer_Append( &dataBuf, "", 1 );
- require_noerr( err, exit );
+ for( browsePtr = &type->browseList; ( browse = *browsePtr ) != NULL; browsePtr = &browse->next )
+ {
+ if( browse->ifIndex == inIfIndex ) break;
+ }
+ require_action_quiet( browse, exit, err = kNotFoundErr );
- err = DataBuffer_Detach( &dataBuf, (uint8_t **) outText, &len );
- require_noerr( err, exit );
+ *browsePtr = browse->next;
+ _SBServiceBrowseFree( browse );
+ if( !type->browseList )
+ {
+ *typePtr = type->next;
+ _SBServiceTypeFree( type );
+ }
+ err = kNoErr;
exit:
- DataBuffer_Free( &dataBuf );
return( err );
}
//===========================================================================================================================
-// WriteDNSQueryMessage
+// _ServiceBrowserAddServiceInstance
//===========================================================================================================================
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 )
+ _ServiceBrowserAddServiceInstance(
+ ServiceBrowserRef me,
+ SBServiceBrowse * inBrowse,
+ uint32_t inIfIndex,
+ const char * inName,
+ const char * inRegType,
+ const char * inDomain,
+ uint64_t inDiscoverTimeUs )
{
- OSStatus err;
- DNSHeader * const hdr = (DNSHeader *) inMsg;
- uint8_t * ptr;
- size_t msgLen;
+ OSStatus err;
+ DNSServiceRef sdRef;
+ SBServiceInstance * instance;
+ SBServiceInstance ** instancePtr;
+ SBServiceInstance * newInstance = NULL;
- memset( hdr, 0, sizeof( *hdr ) );
- DNSHeaderSetID( hdr, inMsgID );
- DNSHeaderSetFlags( hdr, inFlags );
- DNSHeaderSetQuestionCount( hdr, 1 );
+ for( instancePtr = &inBrowse->instanceList; ( instance = *instancePtr ) != NULL; instancePtr = &instance->next )
+ {
+ if( ( instance->ifIndex == inIfIndex ) && ( strcasecmp( instance->name, inName ) == 0 ) ) break;
+ }
+ require_action_quiet( !instance, exit, err = kDuplicateErr );
- ptr = (uint8_t *)( hdr + 1 );
- err = DomainNameFromString( ptr, inQName, &ptr );
+ err = _SBServiceInstanceCreate( inName, inIfIndex, inDiscoverTimeUs, me, &newInstance );
require_noerr_quiet( err, exit );
- DNSQuestionFixedFieldsInit( (DNSQuestionFixedFields *) ptr, inQType, inQClass );
- ptr += 4;
-
- msgLen = (size_t)( ptr - inMsg );
- check( msgLen <= kDNSQueryMessageMaxLen );
+ sdRef = me->connection;
+ newInstance->resolveStartTicks = UpTicks();
+ err = DNSServiceResolve( &sdRef, kDNSServiceFlagsShareConnection, newInstance->ifIndex, inName, inRegType, inDomain,
+ _ServiceBrowserResolveCallback, newInstance );
+ require_noerr( err, exit );
- if( outMsgLen ) *outMsgLen = msgLen;
+ newInstance->resolve = sdRef;
+ *instancePtr = newInstance;
+ newInstance = NULL;
exit:
+ if( newInstance ) _SBServiceInstanceFree( newInstance );
return( err );
}
//===========================================================================================================================
-// DispatchSignalSourceCreate
+// _ServiceBrowserRemoveServiceInstance
//===========================================================================================================================
static OSStatus
- DispatchSignalSourceCreate(
- int inSignal,
- DispatchHandler inEventHandler,
- void * inContext,
- dispatch_source_t * outSource )
+ _ServiceBrowserRemoveServiceInstance(
+ ServiceBrowserRef me,
+ SBServiceBrowse * inBrowse,
+ const char * inName,
+ uint32_t inIfIndex )
{
- OSStatus err;
- dispatch_source_t source;
+ OSStatus err;
+ SBServiceInstance * instance;
+ SBServiceInstance ** ptr;
- source = dispatch_source_create( DISPATCH_SOURCE_TYPE_SIGNAL, (uintptr_t) inSignal, 0, dispatch_get_main_queue() );
- require_action( source, exit, err = kUnknownErr );
+ Unused( me );
- dispatch_set_context( source, inContext );
- dispatch_source_set_event_handler_f( source, inEventHandler );
+ for( ptr = &inBrowse->instanceList; ( instance = *ptr ) != NULL; ptr = &instance->next )
+ {
+ if( ( instance->ifIndex == inIfIndex ) && ( strcasecmp( instance->name, inName ) == 0 ) ) break;
+ }
+ require_action_quiet( instance, exit, err = kNotFoundErr );
- *outSource = source;
+ *ptr = instance->next;
+ _SBServiceInstanceFree( instance );
err = kNoErr;
exit:
@@ -12599,63 +19826,72 @@ exit:
}
//===========================================================================================================================
-// DispatchSocketSourceCreate
+// _ServiceBrowserAddIPAddress
//===========================================================================================================================
static OSStatus
- DispatchSocketSourceCreate(
- SocketRef inSock,
- dispatch_source_type_t inType,
- dispatch_queue_t inQueue,
- DispatchHandler inEventHandler,
- DispatchHandler inCancelHandler,
- void * inContext,
- dispatch_source_t * outSource )
+ _ServiceBrowserAddIPAddress(
+ ServiceBrowserRef me,
+ SBServiceInstance * inInstance,
+ const struct sockaddr * inSockAddr,
+ uint64_t inResolveTimeUs )
{
- OSStatus err;
- dispatch_source_t source;
+ OSStatus err;
+ SBIPAddress * ipaddr;
+ SBIPAddress ** ipaddrPtr;
+ SBIPAddress * newIPAddr = NULL;
- source = dispatch_source_create( inType, (uintptr_t) inSock, 0, inQueue ? inQueue : dispatch_get_main_queue() );
- require_action( source, exit, err = kUnknownErr );
+ Unused( me );
- dispatch_set_context( source, inContext );
- dispatch_source_set_event_handler_f( source, inEventHandler );
- dispatch_source_set_cancel_handler_f( source, inCancelHandler );
+ if( ( inSockAddr->sa_family != AF_INET ) && ( inSockAddr->sa_family != AF_INET6 ) )
+ {
+ dlogassert( "Unexpected address family: %d", inSockAddr->sa_family );
+ err = kTypeErr;
+ goto exit;
+ }
- *outSource = source;
+ for( ipaddrPtr = &inInstance->ipaddrList; ( ipaddr = *ipaddrPtr ) != NULL; ipaddrPtr = &ipaddr->next )
+ {
+ if( SockAddrCompareAddr( &ipaddr->sip, inSockAddr ) == 0 ) break;
+ }
+ require_action_quiet( !ipaddr, exit, err = kDuplicateErr );
+
+ err = _SBIPAddressCreate( inSockAddr, inResolveTimeUs, &newIPAddr );
+ require_noerr_quiet( err, exit );
+
+ *ipaddrPtr = newIPAddr;
+ newIPAddr = NULL;
err = kNoErr;
exit:
+ if( newIPAddr ) _SBIPAddressFree( newIPAddr );
return( err );
}
//===========================================================================================================================
-// DispatchTimerCreate
+// _ServiceBrowserRemoveIPAddress
//===========================================================================================================================
static OSStatus
- DispatchTimerCreate(
- dispatch_time_t inStart,
- uint64_t inIntervalNs,
- uint64_t inLeewayNs,
- dispatch_queue_t inQueue,
- DispatchHandler inEventHandler,
- DispatchHandler inCancelHandler,
- void * inContext,
- dispatch_source_t * outTimer )
+ _ServiceBrowserRemoveIPAddress(
+ ServiceBrowserRef me,
+ SBServiceInstance * inInstance,
+ const struct sockaddr * inSockAddr )
{
- OSStatus err;
- dispatch_source_t timer;
+ OSStatus err;
+ SBIPAddress * ipaddr;
+ SBIPAddress ** ipaddrPtr;
- timer = dispatch_source_create( DISPATCH_SOURCE_TYPE_TIMER, 0, 0, inQueue ? inQueue : dispatch_get_main_queue() );
- require_action( timer, exit, err = kUnknownErr );
+ Unused( me );
- 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 );
+ for( ipaddrPtr = &inInstance->ipaddrList; ( ipaddr = *ipaddrPtr ) != NULL; ipaddrPtr = &ipaddr->next )
+ {
+ if( SockAddrCompareAddr( &ipaddr->sip.sa, inSockAddr ) == 0 ) break;
+ }
+ require_action_quiet( ipaddr, exit, err = kNotFoundErr );
- *outTimer = timer;
+ *ipaddrPtr = ipaddr->next;
+ _SBIPAddressFree( ipaddr );
err = kNoErr;
exit:
@@ -12663,116 +19899,186 @@ exit:
}
//===========================================================================================================================
-// DispatchProcessMonitorCreate
+// _ServiceBrowserCreateResults
//===========================================================================================================================
-static OSStatus
- DispatchProcessMonitorCreate(
- pid_t inPID,
- unsigned long inFlags,
- dispatch_queue_t inQueue,
- DispatchHandler inEventHandler,
- DispatchHandler inCancelHandler,
- void * inContext,
- dispatch_source_t * outMonitor )
+static OSStatus _ServiceBrowserCreateResults( ServiceBrowserRef me, ServiceBrowserResults **outResults )
{
- OSStatus err;
- dispatch_source_t monitor;
+ OSStatus err;
+ SBDomain * d;
+ SBServiceType * t;
+ SBServiceBrowse * b;
+ SBServiceInstance * i;
+ SBIPAddress * a;
+ ServiceBrowserResultsPrivate * results;
+ SBRDomain ** domainPtr;
+
+ results = (ServiceBrowserResultsPrivate *) calloc( 1, sizeof( *results ) );
+ require_action( results, exit, err = kNoMemoryErr );
- monitor = dispatch_source_create( DISPATCH_SOURCE_TYPE_PROC, (uintptr_t) inPID, inFlags,
- inQueue ? inQueue : dispatch_get_main_queue() );
- require_action( monitor, exit, err = kUnknownErr );
+ results->refCount = 1;
- dispatch_set_context( monitor, inContext );
- dispatch_source_set_event_handler_f( monitor, inEventHandler );
- dispatch_source_set_cancel_handler_f( monitor, inCancelHandler );
+ domainPtr = &results->domainList;
+ for( d = me->domainList; d; d = d->next )
+ {
+ SBRDomain * domain;
+ SBRServiceType ** typePtr;
+
+ err = _SBRDomainCreate( d->name, &domain );
+ require_noerr_quiet( err, exit );
+ *domainPtr = domain;
+ domainPtr = &domain->next;
+
+ typePtr = &domain->typeList;
+ for( t = d->typeList; t; t = t->next )
+ {
+ SBRServiceType * type;
+ SBRServiceInstance ** instancePtr;
+
+ err = _SBRServiceTypeCreate( t->name, &type );
+ require_noerr_quiet( err, exit );
+ *typePtr = type;
+ typePtr = &type->next;
+
+ instancePtr = &type->instanceList;
+ for( b = t->browseList; b; b = b->next )
+ {
+ for( i = b->instanceList; i; i = i->next )
+ {
+ SBRServiceInstance * instance;
+ SBRIPAddress ** ipaddrPtr;
+
+ err = _SBRServiceInstanceCreate( i->name, i->ifIndex, i->hostname, i->port, i->txtPtr, i->txtLen,
+ i->discoverTimeUs, i->resolveTimeUs, &instance );
+ require_noerr_quiet( err, exit );
+ *instancePtr = instance;
+ instancePtr = &instance->next;
+
+ ipaddrPtr = &instance->ipaddrList;
+ for( a = i->ipaddrList; a; a = a->next )
+ {
+ SBRIPAddress * ipaddr;
+
+ err = _SBRIPAddressCreate( &a->sip.sa, a->resolveTimeUs, &ipaddr );
+ require_noerr_quiet( err, exit );
+
+ *ipaddrPtr = ipaddr;
+ ipaddrPtr = &ipaddr->next;
+ }
+ }
+ }
+ }
+ }
- *outMonitor = monitor;
+ *outResults = (ServiceBrowserResults *) results;
+ results = NULL;
err = kNoErr;
exit:
+ if( results ) ServiceBrowserResultsRelease( (ServiceBrowserResults *) results );
return( err );
}
//===========================================================================================================================
-// ServiceTypeDescription
+// _SBDomainCreate
//===========================================================================================================================
-typedef struct
+static OSStatus _SBDomainCreate( const char *inName, ServiceBrowserRef inBrowser, SBDomain **outDomain )
{
- const char * name; // Name of the service type in two-label "_service._proto" format.
- const char * description; // Description of the service type.
+ OSStatus err;
+ SBDomain * obj;
-} ServiceType;
+ obj = (SBDomain *) calloc( 1, sizeof( *obj ) );
+ require_action( obj, exit, err = kNoMemoryErr );
+
+ obj->name = strdup( inName );
+ require_action( obj->name, exit, err = kNoMemoryErr );
+
+ obj->browser = inBrowser;
+
+ *outDomain = obj;
+ obj = NULL;
+ err = kNoErr;
+
+exit:
+ if( obj ) _SBDomainFree( obj );
+ return( err );
+}
-// A Non-comprehensive table of DNS-SD service types
+//===========================================================================================================================
+// _SBDomainFree
+//===========================================================================================================================
-static const ServiceType kServiceTypes[] =
+static void _SBDomainFree( SBDomain *inDomain )
{
- { "_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" }
-};
+ SBServiceType * type;
+
+ ForgetMem( &inDomain->name );
+ DNSServiceForget( &inDomain->servicesQuery );
+ while( ( type = inDomain->typeList ) != NULL )
+ {
+ inDomain->typeList = type->next;
+ _SBServiceTypeFree( type );
+ }
+ free( inDomain );
+}
+
+//===========================================================================================================================
+// _SBServiceTypeCreate
+//===========================================================================================================================
+
+static OSStatus _SBServiceTypeCreate( const char *inName, SBServiceType **outType )
+{
+ OSStatus err;
+ SBServiceType * obj;
+
+ obj = (SBServiceType *) calloc( 1, sizeof( *obj ) );
+ require_action( obj, exit, err = kNoMemoryErr );
+
+ obj->name = strdup( inName );
+ require_action( obj->name, exit, err = kNoMemoryErr );
+
+ *outType = obj;
+ obj = NULL;
+ err = kNoErr;
+
+exit:
+ if( obj ) _SBServiceTypeFree( obj );
+ return( err );
+}
+
+//===========================================================================================================================
+// _SBServiceTypeFree
+//===========================================================================================================================
-static const char * ServiceTypeDescription( const char *inName )
+static void _SBServiceTypeFree( SBServiceType *inType )
{
- const ServiceType * serviceType;
- const ServiceType * const end = kServiceTypes + countof( kServiceTypes );
+ SBServiceBrowse * browse;
- for( serviceType = kServiceTypes; serviceType < end; ++serviceType )
+ ForgetMem( &inType->name );
+ while( ( browse = inType->browseList ) != NULL )
{
- if( strcasecmp( inName, serviceType->name ) == 0 ) return( serviceType->description );
+ inType->browseList = browse->next;
+ _SBServiceBrowseFree( browse );
}
- return( NULL );
+ free( inType );
}
//===========================================================================================================================
-// SocketContextCreate
+// _SBServiceBrowseCreate
//===========================================================================================================================
-static OSStatus SocketContextCreate( SocketRef inSock, void * inUserContext, SocketContext **outContext )
+static OSStatus _SBServiceBrowseCreate( uint32_t inIfIndex, ServiceBrowserRef inBrowser, SBServiceBrowse **outBrowse )
{
- OSStatus err;
- SocketContext * context;
-
- context = (SocketContext *) calloc( 1, sizeof( *context ) );
- require_action( context, exit, err = kNoMemoryErr );
+ OSStatus err;
+ SBServiceBrowse * obj;
- context->refCount = 1;
- context->sock = inSock;
- context->userContext = inUserContext;
+ obj = (SBServiceBrowse *) calloc( 1, sizeof( *obj ) );
+ require_action( obj, exit, err = kNoMemoryErr );
- *outContext = context;
+ obj->ifIndex = inIfIndex;
+ obj->browser = inBrowser;
+ *outBrowse = obj;
err = kNoErr;
exit:
@@ -12780,52 +20086,87 @@ exit:
}
//===========================================================================================================================
-// SocketContextRetain
+// _SBServiceBrowseFree
//===========================================================================================================================
-static SocketContext * SocketContextRetain( SocketContext *inContext )
+static void _SBServiceBrowseFree( SBServiceBrowse *inBrowse )
{
- ++inContext->refCount;
- return( inContext );
+ SBServiceInstance * instance;
+
+ DNSServiceForget( &inBrowse->browse );
+ while( ( instance = inBrowse->instanceList ) != NULL )
+ {
+ inBrowse->instanceList = instance->next;
+ _SBServiceInstanceFree( instance );
+ }
+ free( inBrowse );
}
//===========================================================================================================================
-// SocketContextRelease
+// _SBServiceInstanceCreate
//===========================================================================================================================
-static void SocketContextRelease( SocketContext *inContext )
+static OSStatus
+ _SBServiceInstanceCreate(
+ const char * inName,
+ uint32_t inIfIndex,
+ uint64_t inDiscoverTimeUs,
+ ServiceBrowserRef inBrowser,
+ SBServiceInstance ** outInstance )
{
- if( --inContext->refCount == 0 )
- {
- ForgetSocket( &inContext->sock );
- free( inContext );
- }
+ OSStatus err;
+ SBServiceInstance * obj;
+
+ obj = (SBServiceInstance *) calloc( 1, sizeof( *obj ) );
+ require_action( obj, exit, err = kNoMemoryErr );
+
+ obj->name = strdup( inName );
+ require_action( obj->name, exit, err = kNoMemoryErr );
+
+ obj->ifIndex = inIfIndex;
+ obj->discoverTimeUs = inDiscoverTimeUs;
+ obj->browser = inBrowser;
+
+ *outInstance = obj;
+ obj = NULL;
+ err = kNoErr;
+
+exit:
+ if( obj ) _SBServiceInstanceFree( obj );
+ return( err );
}
//===========================================================================================================================
-// SocketContextCancelHandler
+// _SBServiceInstanceFree
//===========================================================================================================================
-static void SocketContextCancelHandler( void *inContext )
+static void _SBServiceInstanceFree( SBServiceInstance *inInstance )
{
- SocketContextRelease( (SocketContext *) inContext );
+ ForgetMem( &inInstance->name );
+ DNSServiceForget( &inInstance->resolve );
+ ForgetMem( &inInstance->hostname );
+ ForgetMem( &inInstance->txtPtr );
+ DNSServiceForget( &inInstance->getAddrInfo );
+ ForgetSBIPAddressList( &inInstance->ipaddrList );
+ free( inInstance );
}
//===========================================================================================================================
-// StringToInt32
+// _SBIPAddressCreate
//===========================================================================================================================
-static OSStatus StringToInt32( const char *inString, int32_t *outValue )
+static OSStatus _SBIPAddressCreate( const struct sockaddr *inSockAddr, uint64_t inResolveTimeUs, SBIPAddress **outIPAddress )
{
- OSStatus err;
- long value;
- char * endPtr;
+ OSStatus err;
+ SBIPAddress * obj;
- 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 );
+ obj = (SBIPAddress *) calloc( 1, sizeof( *obj ) );
+ require_action( obj, exit, err = kNoMemoryErr );
- *outValue = (int32_t) value;
+ SockAddrCopy( inSockAddr, &obj->sip );
+ obj->resolveTimeUs = inResolveTimeUs;
+
+ *outIPAddress = obj;
err = kNoErr;
exit:
@@ -12833,183 +20174,213 @@ exit:
}
//===========================================================================================================================
-// StringToUInt32
+// _SBIPAddressFree
//===========================================================================================================================
-static OSStatus StringToUInt32( const char *inString, uint32_t *outValue )
+static void _SBIPAddressFree( SBIPAddress *inIPAddress )
{
- 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;
+ free( inIPAddress );
+}
+
+//===========================================================================================================================
+// _SBIPAddressFreeList
+//===========================================================================================================================
+
+static void _SBIPAddressFreeList( SBIPAddress *inList )
+{
+ SBIPAddress * ipaddr;
-exit:
- return( err );
+ while( ( ipaddr = inList ) != NULL )
+ {
+ inList = ipaddr->next;
+ _SBIPAddressFree( ipaddr );
+ }
}
//===========================================================================================================================
-// StringToLongLong
+// _SBRDomainCreate
//===========================================================================================================================
-static OSStatus StringToLongLong( const char *inString, long long *outValue )
+static OSStatus _SBRDomainCreate( const char *inName, SBRDomain **outDomain )
{
OSStatus err;
- long long value;
- char * endPtr;
+ SBRDomain * obj;
- set_errno_compat( 0 );
- value = strtol( inString, &endPtr, 0 );
- err = errno_compat();
- if( ( ( value == LLONG_MIN ) || ( value == LLONG_MAX ) ) && ( err == ERANGE ) ) goto exit;
- require_action_quiet( ( *endPtr == '\0' ) && ( endPtr != inString ), exit, err = kParamErr );
+ obj = (SBRDomain *) calloc( 1, sizeof( *obj ) );
+ require_action( obj, exit, err = kNoMemoryErr );
- *outValue = value;
+ obj->name = strdup( inName );
+ require_action( obj->name, exit, err = kNoMemoryErr );
+
+ *outDomain = obj;
+ obj = NULL;
err = kNoErr;
exit:
+ if( obj ) _SBRDomainFree( obj );
return( err );
}
//===========================================================================================================================
-// StringToARecordData
+// _SBRDomainFree
//===========================================================================================================================
-static OSStatus StringToARecordData( const char *inString, uint8_t **outPtr, size_t *outLen )
+static void _SBRDomainFree( SBRDomain *inDomain )
{
- OSStatus err;
- uint32_t * addrPtr;
- const size_t addrLen = sizeof( *addrPtr );
- const char * end;
+ SBRServiceType * type;
- addrPtr = (uint32_t *) malloc( addrLen );
- require_action( addrPtr, exit, err = kNoMemoryErr );
+ ForgetMem( &inDomain->name );
+ while( ( type = inDomain->typeList ) != NULL )
+ {
+ inDomain->typeList = type->next;
+ _SBRServiceTypeFree( type );
+ }
+ free( inDomain );
+}
+
+//===========================================================================================================================
+// _SBRServiceTypeCreate
+//===========================================================================================================================
+
+static OSStatus _SBRServiceTypeCreate( const char *inName, SBRServiceType **outType )
+{
+ OSStatus err;
+ SBRServiceType * obj;
- err = StringToIPv4Address( inString, kStringToIPAddressFlagsNoPort | kStringToIPAddressFlagsNoPrefix, addrPtr,
- NULL, NULL, NULL, &end );
- if( !err && ( *end != '\0' ) ) err = kMalformedErr;
- require_noerr_quiet( err, exit );
+ obj = (SBRServiceType *) calloc( 1, sizeof( *obj ) );
+ require_action( obj, exit, err = kNoMemoryErr );
- *addrPtr = HostToBig32( *addrPtr );
+ obj->name = strdup( inName );
+ require_action( obj->name, exit, err = kNoMemoryErr );
- *outPtr = (uint8_t *) addrPtr;
- addrPtr = NULL;
- *outLen = addrLen;
+ *outType = obj;
+ obj = NULL;
+ err = kNoErr;
exit:
- FreeNullSafe( addrPtr );
+ if( obj ) _SBRServiceTypeFree( obj );
return( err );
}
//===========================================================================================================================
-// StringToAAAARecordData
+// _SBRServiceTypeFree
//===========================================================================================================================
-static OSStatus StringToAAAARecordData( const char *inString, uint8_t **outPtr, size_t *outLen )
+static void _SBRServiceTypeFree( SBRServiceType *inType )
{
- OSStatus err;
- uint8_t * addrPtr;
- const size_t addrLen = 16;
- const char * end;
-
- addrPtr = (uint8_t *) malloc( addrLen );
- require_action( addrPtr, exit, err = kNoMemoryErr );
-
- err = StringToIPv6Address( inString,
- kStringToIPAddressFlagsNoPort | kStringToIPAddressFlagsNoPrefix | kStringToIPAddressFlagsNoScope,
- addrPtr, NULL, NULL, NULL, &end );
- if( !err && ( *end != '\0' ) ) err = kMalformedErr;
- require_noerr_quiet( err, exit );
-
- *outPtr = addrPtr;
- addrPtr = NULL;
- *outLen = addrLen;
+ SBRServiceInstance * instance;
-exit:
- FreeNullSafe( addrPtr );
- return( err );
+ ForgetMem( &inType->name );
+ while( ( instance = inType->instanceList ) != NULL )
+ {
+ inType->instanceList = instance->next;
+ _SBRServiceInstanceFree( instance );
+ }
+ free( inType );
}
//===========================================================================================================================
-// StringToDomainName
+// _SBRServiceInstanceCreate
//===========================================================================================================================
-static OSStatus StringToDomainName( const char *inString, uint8_t **outPtr, size_t *outLen )
+static OSStatus
+ _SBRServiceInstanceCreate(
+ const char * inName,
+ uint32_t inInterfaceIndex,
+ const char * inHostname,
+ uint16_t inPort,
+ const uint8_t * inTXTPtr,
+ size_t inTXTLen,
+ uint64_t inDiscoverTimeUs,
+ uint64_t inResolveTimeUs,
+ SBRServiceInstance ** outInstance )
{
- OSStatus err;
- uint8_t * namePtr;
- size_t nameLen;
- uint8_t * end;
- uint8_t nameBuf[ kDomainNameLengthMax ];
+ OSStatus err;
+ SBRServiceInstance * obj;
- err = DomainNameFromString( nameBuf, inString, &end );
- require_noerr_quiet( err, exit );
+ obj = (SBRServiceInstance *) calloc( 1, sizeof( *obj ) );
+ require_action( obj, exit, err = kNoMemoryErr );
- nameLen = (size_t)( end - nameBuf );
- namePtr = memdup( nameBuf, nameLen );
- require_action( namePtr, exit, err = kNoMemoryErr );
+ obj->name = strdup( inName );
+ require_action( obj->name, exit, err = kNoMemoryErr );
- *outPtr = namePtr;
- namePtr = NULL;
- if( outLen ) *outLen = nameLen;
+ if( inHostname )
+ {
+ obj->hostname = strdup( inHostname );
+ require_action( obj->hostname, exit, err = kNoMemoryErr );
+ }
+ if( inTXTLen > 0 )
+ {
+ obj->txtPtr = (uint8_t *) memdup( inTXTPtr, inTXTLen );
+ require_action( obj->txtPtr, exit, err = kNoMemoryErr );
+ obj->txtLen = inTXTLen;
+ }
+ obj->discoverTimeUs = inDiscoverTimeUs;
+ obj->resolveTimeUs = inResolveTimeUs;
+ obj->ifIndex = inInterfaceIndex;
+ obj->port = inPort;
+
+ *outInstance = obj;
+ obj = NULL;
+ err = kNoErr;
exit:
+ if( obj ) _SBRServiceInstanceFree( obj );
return( err );
}
-#if( TARGET_OS_DARWIN )
//===========================================================================================================================
-// GetDefaultDNSServer
+// _SBRServiceInstanceFree
//===========================================================================================================================
-static OSStatus GetDefaultDNSServer( sockaddr_ip *outAddr )
+static void _SBRServiceInstanceFree( SBRServiceInstance *inInstance )
{
- OSStatus err;
- dns_config_t * config;
- struct sockaddr * addr;
- int32_t i;
-
- config = dns_configuration_copy();
- require_action( config, exit, err = kUnknownErr );
+ SBRIPAddress * ipaddr;
- addr = NULL;
- for( i = 0; i < config->n_resolver; ++i )
+ ForgetMem( &inInstance->name );
+ ForgetMem( &inInstance->hostname );
+ ForgetMem( &inInstance->txtPtr );
+ while( ( ipaddr = inInstance->ipaddrList ) != NULL )
{
- const dns_resolver_t * const resolver = config->resolver[ i ];
-
- if( !resolver->domain && ( resolver->n_nameserver > 0 ) )
- {
- addr = resolver->nameserver[ 0 ];
- break;
- }
- }
- require_action_quiet( addr, exit, err = kNotFoundErr );
+ inInstance->ipaddrList = ipaddr->next;
+ _SBRIPAddressFree( ipaddr );
+ }
+ free( inInstance );
+}
+
+//===========================================================================================================================
+// _SBRIPAddressCreate
+//===========================================================================================================================
+
+static OSStatus
+ _SBRIPAddressCreate(
+ const struct sockaddr * inSockAddr,
+ uint64_t inResolveTimeUs,
+ SBRIPAddress ** outIPAddress )
+{
+ OSStatus err;
+ SBRIPAddress * obj;
- SockAddrCopy( addr, outAddr );
+ obj = (SBRIPAddress *) calloc( 1, sizeof( *obj ) );
+ require_action( obj, exit, err = kNoMemoryErr );
+
+ SockAddrCopy( inSockAddr, &obj->sip );
+ obj->resolveTimeUs = inResolveTimeUs;
+
+ *outIPAddress = obj;
err = kNoErr;
exit:
- if( config ) dns_configuration_free( config );
return( err );
}
-#endif
//===========================================================================================================================
-// GetCurrentMicroTime
+// _SBRIPAddressFree
//===========================================================================================================================
-static MicroTime64 GetCurrentMicroTime( void )
+static void _SBRIPAddressFree( SBRIPAddress *inIPAddress )
{
- struct timeval now;
-
- TIMEVAL_ZERO( now );
- gettimeofday( &now, NULL );
-
- return( (MicroTime64) TIMEVAL_USEC64( now ) );
+ free( inIPAddress );
}
//===========================================================================================================================
@@ -13853,3 +21224,24 @@ int memicmp( const void *inP1, const void *inP2, size_t inLen )
return( 0 );
}
#endif
+
+//===========================================================================================================================
+// FNV1
+//
+// Note: This was copied from CoreUtils because it's currently not exported in the framework.
+//===========================================================================================================================
+
+uint32_t FNV1( const void *inData, size_t inSize )
+{
+ const uint8_t * src = (const uint8_t *) inData;
+ const uint8_t * const end = src + inSize;
+ uint32_t hash;
+
+ hash = 0x811c9dc5U;
+ while( src != end )
+ {
+ hash *= 0x01000193;
+ hash ^= *src++;
+ }
+ return( hash );
+}
diff --git a/mDNSResponder/Makefile b/mDNSResponder/Makefile
index 78adbd5..9aaa08e 100644
--- a/mDNSResponder/Makefile
+++ b/mDNSResponder/Makefile
@@ -17,7 +17,7 @@
include $(MAKEFILEPATH)/pb_makefiles/platform.make
-MVERS = "mDNSResponder-878.240.1"
+MVERS = "mDNSResponder-878.250.4"
VER =
ifneq ($(strip $(GCC_VERSION)),)
diff --git a/mDNSResponder/mDNSMacOSX/BATS/mDNSResponder.plist b/mDNSResponder/mDNSMacOSX/BATS/mDNSResponder.plist
new file mode 100644
index 0000000..66a8c49
--- /dev/null
+++ b/mDNSResponder/mDNSMacOSX/BATS/mDNSResponder.plist
@@ -0,0 +1,627 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>Project</key>
+ <string>mDNSResponder</string>
+ <key>RadarComponents</key>
+ <dict>
+ <key>Name</key>
+ <string>mDNSResponder</string>
+ <key>Version</key>
+ <string>all</string>
+ </dict>
+ <key>Tests</key>
+ <array>
+ <dict>
+ <key>TestName</key>
+ <string>GAIPerf Advanced</string>
+ <key>Description</key>
+ <string>Tests correctness of resolving hostnames via DNS using the GAIPerf Advanced test suite.</string>
+ <key>AsRoot</key>
+ <true/>
+ <key>RequiresWiFi</key>
+ <false/>
+ <key>Timeout</key>
+ <integer>600</integer>
+ <key>IgnoreOutput</key>
+ <true/>
+ <key>Command</key>
+ <array>
+ <string>/usr/local/bin/dnssdutil</string>
+ <string>test</string>
+ <string>gaiperf</string>
+ <string>--suite</string>
+ <string>advanced</string>
+ <string>--format</string>
+ <string>json</string>
+ <string>--appendNewLine</string>
+ <string>--skipPathEval</string>
+ </array>
+ </dict>
+ <dict>
+ <key>TestName</key>
+ <string>mDNS Discovery 1-1-1</string>
+ <key>Description</key>
+ <string>Tests mDNS discovery and resolution of one service instance with a one-byte TXT record and one pair of A and AAAA records.</string>
+ <key>AsRoot</key>
+ <true/>
+ <key>RequiresWiFi</key>
+ <true/>
+ <key>Timeout</key>
+ <integer>10</integer>
+ <key>IgnoreOutput</key>
+ <true/>
+ <key>Command</key>
+ <array>
+ <string>/usr/local/bin/dnssdutil</string>
+ <string>test</string>
+ <string>mdnsdiscovery</string>
+ <string>--instanceCount</string>
+ <string>1</string>
+ <string>--txtSize</string>
+ <string>1</string>
+ <string>--browseTime</string>
+ <string>3</string>
+ <string>--countA</string>
+ <string>1</string>
+ <string>--countAAAA</string>
+ <string>1</string>
+ <string>--ipv4</string>
+ <string>--ipv6</string>
+ <string>--format</string>
+ <string>json</string>
+ <string>--appendNewLine</string>
+ <string>--flushCache</string>
+ </array>
+ </dict>
+ <dict>
+ <key>TestName</key>
+ <string>mDNS Discovery 1-1-1 (No Additionals)</string>
+ <key>Description</key>
+ <string>Tests mDNS discovery and resolution of one service instance with a one-byte TXT record and one pair of A and AAAA records. Responses from mdnsreplier contain no additional answers.</string>
+ <key>AsRoot</key>
+ <true/>
+ <key>RequiresWiFi</key>
+ <true/>
+ <key>Timeout</key>
+ <integer>10</integer>
+ <key>IgnoreOutput</key>
+ <true/>
+ <key>Command</key>
+ <array>
+ <string>/usr/local/bin/dnssdutil</string>
+ <string>test</string>
+ <string>mdnsdiscovery</string>
+ <string>--instanceCount</string>
+ <string>1</string>
+ <string>--txtSize</string>
+ <string>1</string>
+ <string>--browseTime</string>
+ <string>3</string>
+ <string>--countA</string>
+ <string>1</string>
+ <string>--countAAAA</string>
+ <string>1</string>
+ <string>--ipv4</string>
+ <string>--ipv6</string>
+ <string>--format</string>
+ <string>json</string>
+ <string>--appendNewLine</string>
+ <string>--noAdditionals</string>
+ <string>--flushCache</string>
+ </array>
+ </dict>
+ <dict>
+ <key>TestName</key>
+ <string>mDNS Discovery 10-100-2</string>
+ <key>Description</key>
+ <string>Tests mDNS discovery and resolution of ten service instances with 100-byte TXT records and two pairs of A and AAAA records.</string>
+ <key>AsRoot</key>
+ <true/>
+ <key>RequiresWiFi</key>
+ <true/>
+ <key>Timeout</key>
+ <integer>10</integer>
+ <key>IgnoreOutput</key>
+ <true/>
+ <key>Command</key>
+ <array>
+ <string>/usr/local/bin/dnssdutil</string>
+ <string>test</string>
+ <string>mdnsdiscovery</string>
+ <string>--instanceCount</string>
+ <string>10</string>
+ <string>--txtSize</string>
+ <string>100</string>
+ <string>--browseTime</string>
+ <string>3</string>
+ <string>--countA</string>
+ <string>2</string>
+ <string>--countAAAA</string>
+ <string>2</string>
+ <string>--ipv4</string>
+ <string>--ipv6</string>
+ <string>--format</string>
+ <string>json</string>
+ <string>--appendNewLine</string>
+ <string>--flushCache</string>
+ </array>
+ </dict>
+ <dict>
+ <key>TestName</key>
+ <string>mDNS Discovery 10-100-2 (No Additionals)</string>
+ <key>Description</key>
+ <string>Tests mDNS discovery and resolution of ten service instances with 100-byte TXT records and two pairs of A and AAAA records. Responses from mdnsreplier contain no additonal answers.</string>
+ <key>AsRoot</key>
+ <true/>
+ <key>RequiresWiFi</key>
+ <true/>
+ <key>Timeout</key>
+ <integer>10</integer>
+ <key>IgnoreOutput</key>
+ <true/>
+ <key>Command</key>
+ <array>
+ <string>/usr/local/bin/dnssdutil</string>
+ <string>test</string>
+ <string>mdnsdiscovery</string>
+ <string>--instanceCount</string>
+ <string>10</string>
+ <string>--txtSize</string>
+ <string>100</string>
+ <string>--browseTime</string>
+ <string>3</string>
+ <string>--countA</string>
+ <string>2</string>
+ <string>--countAAAA</string>
+ <string>2</string>
+ <string>--ipv4</string>
+ <string>--ipv6</string>
+ <string>--format</string>
+ <string>json</string>
+ <string>--appendNewLine</string>
+ <string>--noAdditionals</string>
+ <string>--flushCache</string>
+ </array>
+ </dict>
+ <dict>
+ <key>TestName</key>
+ <string>mDNS Discovery 100-500-5</string>
+ <key>Description</key>
+ <string>Tests mDNS discovery and resolution of 100 service instances with 500-byte TXT records and five pairs of A and AAAA records.</string>
+ <key>AsRoot</key>
+ <true/>
+ <key>RequiresWiFi</key>
+ <true/>
+ <key>Timeout</key>
+ <integer>10</integer>
+ <key>IgnoreOutput</key>
+ <true/>
+ <key>Command</key>
+ <array>
+ <string>/usr/local/bin/dnssdutil</string>
+ <string>test</string>
+ <string>mdnsdiscovery</string>
+ <string>--instanceCount</string>
+ <string>100</string>
+ <string>--txtSize</string>
+ <string>500</string>
+ <string>--browseTime</string>
+ <string>5</string>
+ <string>--countA</string>
+ <string>5</string>
+ <string>--countAAAA</string>
+ <string>5</string>
+ <string>--ipv4</string>
+ <string>--ipv6</string>
+ <string>--format</string>
+ <string>json</string>
+ <string>--appendNewLine</string>
+ <string>--flushCache</string>
+ </array>
+ </dict>
+ <dict>
+ <key>TestName</key>
+ <string>mDNS Discovery 100-500-5 (No Additionals)</string>
+ <key>Description</key>
+ <string>Tests mDNS discovery and resolution of 100 service instances with 500-byte TXT records and five pairs of A and AAAA records. Responses from mdnsreplier contain no additonal answers.</string>
+ <key>AsRoot</key>
+ <true/>
+ <key>RequiresWiFi</key>
+ <true/>
+ <key>Timeout</key>
+ <integer>10</integer>
+ <key>IgnoreOutput</key>
+ <true/>
+ <key>Command</key>
+ <array>
+ <string>/usr/local/bin/dnssdutil</string>
+ <string>test</string>
+ <string>mdnsdiscovery</string>
+ <string>--instanceCount</string>
+ <string>100</string>
+ <string>--txtSize</string>
+ <string>500</string>
+ <string>--browseTime</string>
+ <string>5</string>
+ <string>--countA</string>
+ <string>5</string>
+ <string>--countAAAA</string>
+ <string>5</string>
+ <string>--ipv4</string>
+ <string>--ipv6</string>
+ <string>--noAdditionals</string>
+ <string>--format</string>
+ <string>json</string>
+ <string>--appendNewLine</string>
+ <string>--flushCache</string>
+ </array>
+ </dict>
+ <dict>
+ <key>TestName</key>
+ <string>mDNS Discovery 1-1-1 (No Cache Flush)</string>
+ <key>Description</key>
+ <string>Tests mDNS discovery and resolution of one service instance with a one-byte TXT record and one pair of A and AAAA records. Cache is not flushed beforehand.</string>
+ <key>AsRoot</key>
+ <false/>
+ <key>RequiresWiFi</key>
+ <true/>
+ <key>Timeout</key>
+ <integer>10</integer>
+ <key>IgnoreOutput</key>
+ <true/>
+ <key>Command</key>
+ <array>
+ <string>/usr/local/bin/dnssdutil</string>
+ <string>test</string>
+ <string>mdnsdiscovery</string>
+ <string>--instanceCount</string>
+ <string>1</string>
+ <string>--txtSize</string>
+ <string>1</string>
+ <string>--browseTime</string>
+ <string>3</string>
+ <string>--countA</string>
+ <string>1</string>
+ <string>--countAAAA</string>
+ <string>1</string>
+ <string>--ipv4</string>
+ <string>--ipv6</string>
+ <string>--format</string>
+ <string>json</string>
+ <string>--appendNewLine</string>
+ </array>
+ </dict>
+ <dict>
+ <key>TestName</key>
+ <string>mDNS Discovery 1-1-1 (No Cache Flush, No Additionals)</string>
+ <key>Description</key>
+ <string>Tests mDNS discovery and resolution of one service instance with a one-byte TXT record and one pair of A and AAAA records. Cache is not flushed beforehand. Responses from mdnsreplier contain no additional answers.</string>
+ <key>AsRoot</key>
+ <false/>
+ <key>RequiresWiFi</key>
+ <true/>
+ <key>Timeout</key>
+ <integer>10</integer>
+ <key>IgnoreOutput</key>
+ <true/>
+ <key>Command</key>
+ <array>
+ <string>/usr/local/bin/dnssdutil</string>
+ <string>test</string>
+ <string>mdnsdiscovery</string>
+ <string>--instanceCount</string>
+ <string>1</string>
+ <string>--txtSize</string>
+ <string>1</string>
+ <string>--browseTime</string>
+ <string>3</string>
+ <string>--countA</string>
+ <string>1</string>
+ <string>--countAAAA</string>
+ <string>1</string>
+ <string>--ipv4</string>
+ <string>--ipv6</string>
+ <string>--format</string>
+ <string>json</string>
+ <string>--appendNewLine</string>
+ <string>--noAdditionals</string>
+ </array>
+ </dict>
+ <dict>
+ <key>TestName</key>
+ <string>mDNS Discovery 10-100-2 (No Cache Flush)</string>
+ <key>Description</key>
+ <string>Tests mDNS discovery and resolution of ten service instances with 100-byte TXT records and two pairs of A and AAAA records. Cache is not flushed beforehand.</string>
+ <key>AsRoot</key>
+ <false/>
+ <key>RequiresWiFi</key>
+ <true/>
+ <key>Timeout</key>
+ <integer>10</integer>
+ <key>IgnoreOutput</key>
+ <true/>
+ <key>Command</key>
+ <array>
+ <string>/usr/local/bin/dnssdutil</string>
+ <string>test</string>
+ <string>mdnsdiscovery</string>
+ <string>--instanceCount</string>
+ <string>10</string>
+ <string>--txtSize</string>
+ <string>100</string>
+ <string>--browseTime</string>
+ <string>3</string>
+ <string>--countA</string>
+ <string>2</string>
+ <string>--countAAAA</string>
+ <string>2</string>
+ <string>--ipv4</string>
+ <string>--ipv6</string>
+ <string>--format</string>
+ <string>json</string>
+ <string>--appendNewLine</string>
+ </array>
+ </dict>
+ <dict>
+ <key>TestName</key>
+ <string>mDNS Discovery 10-100-2 (No Cache Flush, No Additionals)</string>
+ <key>Description</key>
+ <string>Tests mDNS discovery and resolution of ten service instances with 100-byte TXT records and two pairs of A and AAAA records. Cache is not flushed beforehand. Responses from mdnsreplier contain no additional answers.</string>
+ <key>AsRoot</key>
+ <false/>
+ <key>RequiresWiFi</key>
+ <true/>
+ <key>Timeout</key>
+ <integer>10</integer>
+ <key>IgnoreOutput</key>
+ <true/>
+ <key>Command</key>
+ <array>
+ <string>/usr/local/bin/dnssdutil</string>
+ <string>test</string>
+ <string>mdnsdiscovery</string>
+ <string>--instanceCount</string>
+ <string>10</string>
+ <string>--txtSize</string>
+ <string>100</string>
+ <string>--browseTime</string>
+ <string>3</string>
+ <string>--countA</string>
+ <string>2</string>
+ <string>--countAAAA</string>
+ <string>2</string>
+ <string>--ipv4</string>
+ <string>--ipv6</string>
+ <string>--format</string>
+ <string>json</string>
+ <string>--appendNewLine</string>
+ <string>--noAdditionals</string>
+ </array>
+ </dict>
+ <dict>
+ <key>TestName</key>
+ <string>mDNS Discovery 100-500-5 (No Cache Flush)</string>
+ <key>Description</key>
+ <string>Tests mDNS discovery and resolution of 100 service instances with 500-byte TXT records and five pairs of A and AAAA records. Cache is not flushed beforehand.</string>
+ <key>AsRoot</key>
+ <false/>
+ <key>RequiresWiFi</key>
+ <true/>
+ <key>Timeout</key>
+ <integer>10</integer>
+ <key>IgnoreOutput</key>
+ <true/>
+ <key>Command</key>
+ <array>
+ <string>/usr/local/bin/dnssdutil</string>
+ <string>test</string>
+ <string>mdnsdiscovery</string>
+ <string>--instanceCount</string>
+ <string>100</string>
+ <string>--txtSize</string>
+ <string>500</string>
+ <string>--browseTime</string>
+ <string>5</string>
+ <string>--countA</string>
+ <string>5</string>
+ <string>--countAAAA</string>
+ <string>5</string>
+ <string>--ipv4</string>
+ <string>--ipv6</string>
+ <string>--format</string>
+ <string>json</string>
+ <string>--appendNewLine</string>
+ </array>
+ </dict>
+ <dict>
+ <key>TestName</key>
+ <string>mDNS Discovery 100-500-5 (No Cache Flush, No Additionals)</string>
+ <key>Description</key>
+ <string>Tests mDNS discovery and resolution of 100 service instances with 500-byte TXT records and five pairs of A and AAAA records. Cache is not flushed beforehand. Responses from mdnsreplier contain no additional answers.</string>
+ <key>AsRoot</key>
+ <false/>
+ <key>RequiresWiFi</key>
+ <true/>
+ <key>Timeout</key>
+ <integer>10</integer>
+ <key>IgnoreOutput</key>
+ <true/>
+ <key>Command</key>
+ <array>
+ <string>/usr/local/bin/dnssdutil</string>
+ <string>test</string>
+ <string>mdnsdiscovery</string>
+ <string>--instanceCount</string>
+ <string>100</string>
+ <string>--txtSize</string>
+ <string>500</string>
+ <string>--browseTime</string>
+ <string>5</string>
+ <string>--countA</string>
+ <string>5</string>
+ <string>--countAAAA</string>
+ <string>5</string>
+ <string>--ipv4</string>
+ <string>--ipv6</string>
+ <string>--noAdditionals</string>
+ <string>--format</string>
+ <string>json</string>
+ <string>--appendNewLine</string>
+ </array>
+ </dict>
+ <dict>
+ <key>TestName</key>
+ <string>mDNS Discovery w/Packet Drops 10</string>
+ <key>Description</key>
+ <string>Tests mDNS discovery and resolution of ten service instances with 100-byte TXT records and two pairs of A and AAAA records. The first three responses per service instance are subject to a 0.5 probability of being dropped to test query retries.</string>
+ <key>AsRoot</key>
+ <true/>
+ <key>RequiresWiFi</key>
+ <true/>
+ <key>Timeout</key>
+ <integer>30</integer>
+ <key>IgnoreOutput</key>
+ <true/>
+ <key>Command</key>
+ <array>
+ <string>/usr/local/bin/dnssdutil</string>
+ <string>test</string>
+ <string>mdnsdiscovery</string>
+ <string>--instanceCount</string>
+ <string>10</string>
+ <string>--txtSize</string>
+ <string>100</string>
+ <string>--browseTime</string>
+ <string>16</string>
+ <string>--countA</string>
+ <string>2</string>
+ <string>--countAAAA</string>
+ <string>2</string>
+ <string>--ipv6</string>
+ <string>--udrop</string>
+ <string>0.5</string>
+ <string>--mdrop</string>
+ <string>0.5</string>
+ <string>--maxDropCount</string>
+ <string>3</string>
+ <string>--format</string>
+ <string>json</string>
+ <string>--appendNewLine</string>
+ <string>--flushCache</string>
+ </array>
+ </dict>
+ <dict>
+ <key>TestName</key>
+ <string>mDNS Discovery w/Packet Drops 100</string>
+ <key>Description</key>
+ <string>Tests mDNS discovery and resolution of 100 service instances with 100-byte TXT records and two pairs of A and AAAA records. The first three responses per service instance are subject to a 0.5 probability of being dropped to test query retries.</string>
+ <key>AsRoot</key>
+ <true/>
+ <key>RequiresWiFi</key>
+ <true/>
+ <key>Timeout</key>
+ <integer>30</integer>
+ <key>IgnoreOutput</key>
+ <true/>
+ <key>Command</key>
+ <array>
+ <string>/usr/local/bin/dnssdutil</string>
+ <string>test</string>
+ <string>mdnsdiscovery</string>
+ <string>--instanceCount</string>
+ <string>100</string>
+ <string>--txtSize</string>
+ <string>100</string>
+ <string>--browseTime</string>
+ <string>18</string>
+ <string>--countA</string>
+ <string>2</string>
+ <string>--countAAAA</string>
+ <string>2</string>
+ <string>--ipv6</string>
+ <string>--udrop</string>
+ <string>0.5</string>
+ <string>--mdrop</string>
+ <string>0.5</string>
+ <string>--maxDropCount</string>
+ <string>3</string>
+ <string>--format</string>
+ <string>json</string>
+ <string>--appendNewLine</string>
+ <string>--flushCache</string>
+ </array>
+ </dict>
+ <dict>
+ <key>TestName</key>
+ <string>DotLocal Queries</string>
+ <key>Description</key>
+ <string>Tests DNS and mDNS queries for domain names in the local domain.</string>
+ <key>AsRoot</key>
+ <false/>
+ <key>RequiresWiFi</key>
+ <true/>
+ <key>Timeout</key>
+ <integer>40</integer>
+ <key>IgnoreOutput</key>
+ <true/>
+ <key>Command</key>
+ <array>
+ <string>/usr/local/bin/dnssdutil</string>
+ <string>test</string>
+ <string>dotlocal</string>
+ <string>--format</string>
+ <string>json</string>
+ <string>--appendNewLine</string>
+ </array>
+ </dict>
+ <dict>
+ <key>TestName</key>
+ <string>TCP Fallback</string>
+ <key>Description</key>
+ <string>Tests mDNSResponder's TCP fallback mechanism, which is triggered by UDP responses with invalid message IDs that would otherwise be acceptable.</string>
+ <key>AsRoot</key>
+ <true/>
+ <key>RequiresWiFi</key>
+ <false/>
+ <key>Timeout</key>
+ <integer>60</integer>
+ <key>IgnoreOutput</key>
+ <true/>
+ <key>Command</key>
+ <array>
+ <string>/usr/local/bin/dnssdutil</string>
+ <string>test</string>
+ <string>gaiperf</string>
+ <string>--suite</string>
+ <string>basic</string>
+ <string>--format</string>
+ <string>json</string>
+ <string>--appendNewLine</string>
+ <string>--skipPathEval</string>
+ <string>--badUDPMode</string>
+ </array>
+ </dict>
+ <dict>
+ <key>TestName</key>
+ <string>mDNSResponder Leaks</string>
+ <key>Description</key>
+ <string>Checks mDNSResponder for memory leaks.</string>
+ <key>AsRoot</key>
+ <true/>
+ <key>RequiresWiFi</key>
+ <false/>
+ <key>Timeout</key>
+ <integer>10</integer>
+ <key>IgnoreOutput</key>
+ <true/>
+ <key>Command</key>
+ <array>
+ <string>/usr/bin/leaks</string>
+ <string>mDNSResponder</string>
+ </array>
+ </dict>
+ </array>
+</dict>
+</plist>
diff --git a/mDNSResponder/mDNSMacOSX/dnssdutil-entitlements.plist b/mDNSResponder/mDNSMacOSX/dnssdutil-entitlements.plist
new file mode 100644
index 0000000..750f16a
--- /dev/null
+++ b/mDNSResponder/mDNSMacOSX/dnssdutil-entitlements.plist
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>com.apple.security.network.client</key>
+ <true/>
+ <key>com.apple.security.network.server</key>
+ <true/>
+ <key>com.apple.SystemConfiguration.SCDynamicStore-write-access</key>
+ <true/>
+</dict>
+</plist>
diff --git a/mDNSResponder/mDNSMacOSX/mDNSMacOSX.c b/mDNSResponder/mDNSMacOSX/mDNSMacOSX.c
index 51fc67a..4714293 100644
--- a/mDNSResponder/mDNSMacOSX/mDNSMacOSX.c
+++ b/mDNSResponder/mDNSMacOSX/mDNSMacOSX.c
@@ -6875,7 +6875,7 @@ typedef struct
#include <IOKit/IOKitLib.h>
#include <dns_util.h>
-mDNSlocal mDNSu16 GetPortArray(int trans, mDNSIPPort *portarray)
+mDNSlocal mDNSu16 GetPortArray(int trans, mDNSIPPort *portarray, mDNSBool TCPKAOnly, mDNSBool supportsTCPKA)
{
mDNS *const m = &mDNSStorage;
const domainlabel *const tp = (trans == mDNSTransport_UDP) ? (const domainlabel *)"\x4_udp" : (const domainlabel *)"\x4_tcp";
@@ -6884,6 +6884,14 @@ mDNSlocal mDNSu16 GetPortArray(int trans, mDNSIPPort *portarray)
AuthRecord *rr;
for (rr = m->ResourceRecords; rr; rr=rr->next)
{
+ mDNSBool isKeepAliveRecord = mDNS_KeepaliveRecord(&rr->resrec);
+ // Skip over all other records if we are registering TCP KeepAlive records only
+ // Skip over TCP KeepAlive records if the policy prohibits it or if the interface does not support TCP Keepalive
+ // supportsTCPKA is set to true if both policy and interface allow TCP Keepalive
+ if ((TCPKAOnly && !isKeepAliveRecord) || (isKeepAliveRecord && !supportsTCPKA)) {
+ continue;
+ }
+
if (rr->resrec.rrtype == kDNSType_SRV && SameDomainLabel(ThirdLabel(rr->resrec.name)->c, tp->c))
{
if (!portarray)
@@ -7013,6 +7021,7 @@ mDNSlocal void GetProxyRecords(DNSMessage *const msg, uint32_t *const numbytes,
// Skip over all other records if we are registering TCP KeepAlive records only
// Skip over TCP KeepAlive records if the policy prohibits it or if the interface does not support TCP Keepalive
+ // supportsTCPKA is set to true if both policy and interface allow TCP Keepalive
if ((TCPKAOnly && !isKeepAliveRecord) || (isKeepAliveRecord && !supportsTCPKA))
continue;
@@ -7134,8 +7143,8 @@ mDNSexport mStatus ActivateLocalProxy(NetworkInterfaceInfo *const intf, mDNSBool
mDNSOffloadCmd cmd;
mDNSPlatformMemZero(&cmd, sizeof(cmd)); // When compiling 32-bit, make sure top 32 bits of 64-bit pointers get initialized to zero
cmd.command = cmd_mDNSOffloadRR;
- cmd.numUDPPorts = GetPortArray(mDNSTransport_UDP, mDNSNULL);
- cmd.numTCPPorts = GetPortArray(mDNSTransport_TCP, mDNSNULL);
+ cmd.numUDPPorts = GetPortArray(mDNSTransport_UDP, mDNSNULL, TCPKAOnly, supportsTCPKA);
+ cmd.numTCPPorts = GetPortArray(mDNSTransport_TCP, mDNSNULL, TCPKAOnly, supportsTCPKA);
cmd.numRRRecords = CountProxyRecords(&cmd.rrBufferSize, intf, TCPKAOnly, supportsTCPKA);
cmd.compression = sizeof(DNSMessageHeader);
@@ -7151,8 +7160,8 @@ mDNSexport mStatus ActivateLocalProxy(NetworkInterfaceInfo *const intf, mDNSBool
cmd.tcpPorts.ptr, cmd.numTCPPorts);
if (msg && cmd.rrRecords.ptr) GetProxyRecords(msg, &cmd.rrBufferSize, cmd.rrRecords.ptr, TCPKAOnly, supportsTCPKA);
- if (cmd.udpPorts.ptr) cmd.numUDPPorts = GetPortArray(mDNSTransport_UDP, cmd.udpPorts.ptr);
- if (cmd.tcpPorts.ptr) cmd.numTCPPorts = GetPortArray(mDNSTransport_TCP, cmd.tcpPorts.ptr);
+ if (cmd.udpPorts.ptr) cmd.numUDPPorts = GetPortArray(mDNSTransport_UDP, cmd.udpPorts.ptr, TCPKAOnly, supportsTCPKA);
+ if (cmd.tcpPorts.ptr) cmd.numTCPPorts = GetPortArray(mDNSTransport_TCP, cmd.tcpPorts.ptr, TCPKAOnly, supportsTCPKA);
char outputData[2];
size_t outputDataSize = sizeof(outputData);
diff --git a/mDNSResponder/mDNSMacOSX/mDNSResponder.xcodeproj/project.pbxproj b/mDNSResponder/mDNSMacOSX/mDNSResponder.xcodeproj/project.pbxproj
index bbbd12f..8067d93 100644
--- a/mDNSResponder/mDNSMacOSX/mDNSResponder.xcodeproj/project.pbxproj
+++ b/mDNSResponder/mDNSMacOSX/mDNSResponder.xcodeproj/project.pbxproj
@@ -347,6 +347,7 @@
BDB04224203FF18000419961 /* dns_sd_private.h in Headers */ = {isa = PBXBuildFile; fileRef = BDA9A7871B3A923600523835 /* dns_sd_private.h */; settings = {ATTRIBUTES = (Private, ); }; };
BDB61845206ADB9D00AFF600 /* com.apple.mDNSResponder.plist in Copy Base Logging Profile */ = {isa = PBXBuildFile; fileRef = BDB61843206ADB7700AFF600 /* com.apple.mDNSResponder.plist */; };
BDBF9B941ED74B9C001498A8 /* DNS64State.h in Headers */ = {isa = PBXBuildFile; fileRef = BDBF9B931ED74B8C001498A8 /* DNS64State.h */; };
+ BDF8BB902208E2A800419B62 /* mDNSResponder.plist in Copy BATS test plist */ = {isa = PBXBuildFile; fileRef = BDF8BB8F2208E26E00419B62 /* mDNSResponder.plist */; };
D284BE540ADD80740027CCDF /* dnssd_ipc.h in Headers */ = {isa = PBXBuildFile; fileRef = F5E11B5B04A28126019798ED /* dnssd_ipc.h */; };
D284BE580ADD80740027CCDF /* mDNS.c in Sources */ = {isa = PBXBuildFile; fileRef = 6575FBE9022EAF5A00000109 /* mDNS.c */; };
D284BE590ADD80740027CCDF /* uDNS.c in Sources */ = {isa = PBXBuildFile; fileRef = 7F18A9F70587CEF6001880B3 /* uDNS.c */; };
@@ -774,6 +775,17 @@
name = "Copy AppleInternal Logging Profile";
runOnlyForDeploymentPostprocessing = 1;
};
+ BDF8BB8A2208E09D00419B62 /* Copy BATS test plist */ = {
+ isa = PBXCopyFilesBuildPhase;
+ buildActionMask = 8;
+ dstPath = /AppleInternal/CoreOS/BATS/unit_tests;
+ dstSubfolderSpec = 0;
+ files = (
+ BDF8BB902208E2A800419B62 /* mDNSResponder.plist in Copy BATS test plist */,
+ );
+ name = "Copy BATS test plist";
+ runOnlyForDeploymentPostprocessing = 1;
+ };
D284BE6A0ADD80740027CCDF /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 8;
@@ -1006,6 +1018,7 @@
BDB61846206ADDDF00AFF600 /* com.apple.mDNSResponder.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = com.apple.mDNSResponder.plist; sourceTree = "<group>"; };
BDBF9B931ED74B8C001498A8 /* DNS64State.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DNS64State.h; sourceTree = "<group>"; };
BDE238C11DF69D8300B9F696 /* dns_sd_internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = dns_sd_internal.h; path = ../mDNSShared/dns_sd_internal.h; sourceTree = "<group>"; };
+ BDF8BB8F2208E26E00419B62 /* mDNSResponder.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = mDNSResponder.plist; sourceTree = "<group>"; };
D284BE730ADD80740027CCDF /* mDNSResponder */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = mDNSResponder; sourceTree = BUILT_PRODUCTS_DIR; };
D284BEB00ADD80920027CCDF /* dns-sd */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "dns-sd"; sourceTree = BUILT_PRODUCTS_DIR; };
D284BEBE0ADD809A0027CCDF /* libjdns_sd.jnilib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = libjdns_sd.jnilib; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -1297,6 +1310,7 @@
BD9BA7561EAF929C00658CCF /* Frameworks */,
BDB61842206ADB7700AFF600 /* LoggingProfiles */,
BD28AE8D207B88F600F0B257 /* Scripts */,
+ BDF8BB8E2208E26E00419B62 /* BATS */,
);
name = mDNSResponder;
sourceTree = "<group>";
@@ -1656,6 +1670,14 @@
path = AppleInternal;
sourceTree = "<group>";
};
+ BDF8BB8E2208E26E00419B62 /* BATS */ = {
+ isa = PBXGroup;
+ children = (
+ BDF8BB8F2208E26E00419B62 /* mDNSResponder.plist */,
+ );
+ path = BATS;
+ sourceTree = "<group>";
+ };
DB2CC4420662DCE500335AB3 /* Java Support */ = {
isa = PBXGroup;
children = (
@@ -2203,6 +2225,7 @@
8418673D15AB8BFF00BB7F70 /* Copy Base Logging Profile */,
BD75E93F206ADEAD00656ED3 /* Copy AppleInternal Logging Profile */,
BD28AE8C207B888E00F0B257 /* Copy diagnose scripts */,
+ BDF8BB8A2208E09D00419B62 /* Copy BATS test plist */,
);
buildRules = (
);
@@ -3718,7 +3741,6 @@
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
- CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
@@ -3763,7 +3785,6 @@
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
- CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
@@ -4611,6 +4632,8 @@
CLANG_WARN_UNREACHABLE_CODE = YES_AGGRESSIVE;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES;
+ CODE_SIGN_ENTITLEMENTS = "dnssdutil-entitlements.plist";
+ CODE_SIGN_IDENTITY = "-";
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(SDKROOT)$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks",
@@ -4676,6 +4699,8 @@
CLANG_WARN_UNREACHABLE_CODE = YES_AGGRESSIVE;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES;
+ CODE_SIGN_ENTITLEMENTS = "dnssdutil-entitlements.plist";
+ CODE_SIGN_IDENTITY = "-";
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(SDKROOT)$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks",
diff --git a/mDNSResponder/mDNSShared/dns_sd.h b/mDNSResponder/mDNSShared/dns_sd.h
index 3495c6e..f76fbbd 100644
--- a/mDNSResponder/mDNSShared/dns_sd.h
+++ b/mDNSResponder/mDNSShared/dns_sd.h
@@ -66,7 +66,7 @@
*/
#ifndef _DNS_SD_H
-#define _DNS_SD_H 8804001
+#define _DNS_SD_H 8805004
#ifdef __cplusplus
extern "C" {
More information about the vc
mailing list