diff options
author | Sebastian Huber <sebastian.huber@embedded-brains.de> | 2020-06-18 13:12:03 +0200 |
---|---|---|
committer | Sebastian Huber <sebastian.huber@embedded-brains.de> | 2020-06-23 18:17:22 +0200 |
commit | 1350d527b841ca6cac92448ea5f6bc7bc31a0853 (patch) | |
tree | 3f483d45204af903d1847b2dd4565ffe6c281340 | |
parent | 4af311eaa716c382b6e84dd30b211258b07924ce (diff) |
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.
-rw-r--r-- | mDNSResponder/Clients/dnssdutil.c | 12680 | ||||
-rw-r--r-- | mDNSResponder/Makefile | 2 | ||||
-rw-r--r-- | mDNSResponder/mDNSMacOSX/BATS/mDNSResponder.plist | 627 | ||||
-rw-r--r-- | mDNSResponder/mDNSMacOSX/dnssdutil-entitlements.plist | 12 | ||||
-rw-r--r-- | mDNSResponder/mDNSMacOSX/mDNSMacOSX.c | 19 | ||||
-rw-r--r-- | mDNSResponder/mDNSMacOSX/mDNSResponder.xcodeproj/project.pbxproj | 29 | ||||
-rw-r--r-- | mDNSResponder/mDNSShared/dns_sd.h | 2 |
7 files changed, 10718 insertions, 2653 deletions
diff --git a/mDNSResponder/Clients/dnssdutil.c b/mDNSResponder/Clients/dnssdutil.c index 206e2b6c..16b48752 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,12 +2321,229 @@ 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) + +//=========================================================================================================================== +// MDNSCollider +//=========================================================================================================================== + +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 ); + +//=========================================================================================================================== +// ServiceBrowser +//=========================================================================================================================== + +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 +{ + 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 @@ -1685,8 +2557,9 @@ int main( int argc, const char **argv ) dlog_control( "DebugServices:output=file;stderr" ); - PrintFRegisterExtension( "du:time", PrintFTimestampHandler, NULL ); - PrintFRegisterExtension( "du:dnsmsg", PrintFDNSMessageHandler, NULL ); + 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 ); @@ -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 +struct BrowseAllConnection { - 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; + 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. }; -typedef enum -{ - kConnectStatus_None = 0, - kConnectStatus_Pending = 1, - kConnectStatus_Succeeded = 2, - kConnectStatus_Failed = 3 - -} ConnectStatus; - -struct BrowseIPAddr -{ - BrowseIPAddr * next; - sockaddr_ip sip; - int refCount; - BrowseAllContext * context; - uint64_t foundTicks; - AsyncConnectionRef connection; - ConnectStatus connectStatus; - CFTimeInterval connectTimeSecs; - OSStatus connectError; -}; - -static void BrowseAllPrintPrologue( const BrowseAllContext *inContext ); -static void DNSSD_API - BrowseAllQueryDomainsCallback( - DNSServiceRef inSDRef, - DNSServiceFlags inFlags, - uint32_t inInterfaceIndex, - DNSServiceErrorType inError, - const char * inFullName, - uint16_t inType, - uint16_t inClass, - uint16_t inRDataLen, - const void * inRDataPtr, - uint32_t inTTL, - void * inContext ); -static void DNSSD_API - BrowseAllQueryCallback( - DNSServiceRef inSDRef, - DNSServiceFlags inFlags, - uint32_t inInterfaceIndex, - DNSServiceErrorType inError, - const char * inFullName, - uint16_t inType, - uint16_t inClass, - uint16_t inRDataLen, - const void * inRDataPtr, - uint32_t inTTL, - void * inContext ); -static void DNSSD_API - BrowseAllBrowseCallback( - DNSServiceRef inSDRef, - DNSServiceFlags inFlags, - uint32_t inInterfaceIndex, - DNSServiceErrorType inError, - const char * inName, - const char * inRegType, - const char * inDomain, - void * inContext ); -static void DNSSD_API - BrowseAllResolveCallback( - DNSServiceRef inSDRef, - DNSServiceFlags inFlags, - uint32_t inInterfaceIndex, - DNSServiceErrorType inError, - const char * inFullName, - const char * inHostname, - uint16_t inPort, - uint16_t inTXTLen, - const unsigned char * inTXTPtr, - void * inContext ); -static void DNSSD_API - BrowseAllGAICallback( - DNSServiceRef inSDRef, - DNSServiceFlags inFlags, - uint32_t inInterfaceIndex, - DNSServiceErrorType inError, - const char * inHostname, - const struct sockaddr * inSockAddr, - uint32_t inTTL, - void * inContext ); -static void BrowseAllConnectionProgress( int inPhase, const void *inDetails, void *inArg ); -static void BrowseAllConnectionHandler( SocketRef inSock, OSStatus inError, void *inArg ); -static void BrowseAllStop( void *inContext ); -static void BrowseAllExit( void *inContext ); -static OSStatus BrowseAllAddDomain( BrowseAllContext *inContext, const char *inName ); -static OSStatus BrowseAllRemoveDomain( BrowseAllContext *inContext, const char *inName ); -static void BrowseAllContextRelease( BrowseAllContext *inContext ); -static OSStatus - BrowseAllAddServiceType( - BrowseAllContext * inContext, - BrowseDomain * inDomain, - const char * inName, - uint32_t inIfIndex, - Boolean inIncludeAWDL ); +static void _BrowseAllContextFree( BrowseAllContext *inContext ); +static void _BrowseAllServiceBrowserCallback( ServiceBrowserResults *inResults, OSStatus inError, void *inContext ); static OSStatus - BrowseAllRemoveServiceType( - BrowseAllContext * inContext, - BrowseDomain * inDomain, - const char * inName, - uint32_t inIfIndex ); -static OSStatus - BrowseAllAddServiceInstance( - BrowseAllContext * inContext, - BrowseOp * inBrowse, - const char * inName, - const char * inRegType, - const char * inDomain, - uint32_t inIfIndex ); -static OSStatus - BrowseAllRemoveServiceInstance( - BrowseAllContext * inContext, - BrowseOp * inBrowse, - const char * inName, - uint32_t inIfIndex ); -static OSStatus - BrowseAllAddIPAddress( - BrowseAllContext * inContext, - BrowseInstance * inInstance, - const struct sockaddr * inSockAddr ); -static OSStatus - BrowseAllRemoveIPAddress( + _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 ); + 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 ForgetIPAddressList( X ) ForgetCustom( X, BrowseIPAddrReleaseList ) -#define ForgetBrowseAllContext( X ) ForgetCustom( X, BrowseAllContextRelease ) - -#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,1126 +4895,401 @@ 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" ); - dispatch_after_f( dispatch_time_seconds( context->browseTimeSecs ), dispatch_get_main_queue(), context, BrowseAllStop ); + err = ServiceBrowserCreate( dispatch_get_main_queue(), ifIndex, gBrowseAll_Domain, + (unsigned int) gBrowseAll_BrowseTimeSecs, gDNSSDFlag_IncludeAWDL ? true : false, &context->browser ); + require_noerr( err, exit ); + + 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; - require_noerr( err, exit ); - } - else - { - err = BrowseAllRemoveDomain( context, domainStr ); - if( err == kNotFoundErr ) err = kNoErr; - require_noerr( err, exit ); + 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; + } + } + } + } + } } -exit: - if( err ) exit( 1 ); -} - -//=========================================================================================================================== -// BrowseAllQueryCallback -//=========================================================================================================================== - -static void DNSSD_API - BrowseAllQueryCallback( - DNSServiceRef inSDRef, - DNSServiceFlags inFlags, - uint32_t inInterfaceIndex, - DNSServiceErrorType inError, - const char * inFullName, - uint16_t inType, - uint16_t inClass, - uint16_t inRDataLen, - const void * inRDataPtr, - uint32_t inTTL, - void * inContext ) -{ - OSStatus err; - BrowseDomain * const domain = (BrowseDomain *) inContext; - const uint8_t * firstLabel; - const uint8_t * secondLabel; - char * serviceTypeStr = NULL; - const uint8_t * const end = ( (uint8_t * ) inRDataPtr ) + inRDataLen; - - Unused( inSDRef ); - Unused( inFullName ); - Unused( inTTL ); - Unused( inType ); - Unused( inClass ); - - err = inError; - require_noerr( err, exit ); - - check( inType == kDNSServiceType_PTR ); - check( inClass == kDNSServiceClass_IN ); - require_action( inRDataLen > 0, exit, err = kSizeErr ); - - firstLabel = inRDataPtr; - require_action_quiet( ( firstLabel + 1 + firstLabel[ 0 ] ) < end , exit, err = kUnderrunErr ); - - secondLabel = firstLabel + 1 + firstLabel[ 0 ]; - require_action_quiet( ( secondLabel + 1 + secondLabel[ 0 ] ) < end , exit, err = kUnderrunErr ); - - ASPrintF( &serviceTypeStr, "%#s.%#s", firstLabel, secondLabel ); - require_action( serviceTypeStr, exit, err = kNoMemoryErr ); - - if( inFlags & kDNSServiceFlagsAdd ) + if( context->connectionPendingCount > 0 ) { - err = BrowseAllAddServiceType( domain->context, domain, serviceTypeStr, inInterfaceIndex, false ); - if( err == kDuplicateErr ) err = kNoErr; + 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 = BrowseAllRemoveServiceType( domain->context, domain, serviceTypeStr, inInterfaceIndex ); - if( err == kNotFoundErr ) err = kNoErr; - require_noerr( err, exit ); + dispatch_async_f( dispatch_get_main_queue(), context, _BrowseAllExit ); } + err = kNoErr; exit: - FreeNullSafe( serviceTypeStr ); + ForgetCF( &context->browser ); + if( err ) exit( 1 ); } //=========================================================================================================================== -// BrowseAllBrowseCallback +// _BrowseAllConnectionCreate //=========================================================================================================================== -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 OSStatus + _BrowseAllConnectionCreate( + const struct sockaddr * inSockAddr, + uint16_t inPort, + BrowseAllContext * inContext, + BrowseAllConnection ** outConnection ) { - OSStatus err; - BrowseOp * const browse = (BrowseOp *) inContext; + OSStatus err; + 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 ) + { + SNPrintF_Add( &dst, lim, "the Internet" ); + } + else if( if_indextoname( instance->ifIndex, ifname ) ) { - char ifname[ IF_NAMESIZE + 1 ]; + 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; - - for( p = &inContext->domainList; ( domain = *p ) != NULL; p = &domain->next ) - { - if( strcasecmp( domain->name, inName ) == 0 ) break; - } - require_action_quiet( !domain, exit, err = kDuplicateErr ); - - newDomain = (BrowseDomain *) calloc( 1, sizeof( *newDomain ) ); - require_action( newDomain, exit, err = kNoMemoryErr ); + const uint8_t * secondLabel; + uint8_t name[ kDomainNameLengthMax ]; - ++inContext->refCount; - newDomain->context = inContext; - - newDomain->name = strdup( inName ); - require_action( newDomain->name, exit, err = kNoMemoryErr ); - - if( inContext->serviceTypesCount > 0 ) - { - size_t i; - - for( i = 0; i < inContext->serviceTypesCount; ++i ) - { - err = BrowseAllAddServiceType( inContext, newDomain, inContext->serviceTypes[ i ], inContext->ifIndex, - inContext->includeAWDL ); - if( err == kDuplicateErr ) err = kNoErr; - require_noerr( err, exit ); - } - } - else - { - char * recordName; - DNSServiceFlags flags; - DNSServiceRef sdRef; - - ASPrintF( &recordName, "_services._dns-sd._udp.%s", newDomain->name ); - require_action( recordName, exit, err = kNoMemoryErr ); - - flags = kDNSServiceFlagsShareConnection; - if( inContext->includeAWDL ) flags |= kDNSServiceFlagsIncludeAWDL; - - sdRef = newDomain->context->mainRef; - err = DNSServiceQueryRecord( &sdRef, flags, inContext->ifIndex, recordName, kDNSServiceType_PTR, kDNSServiceClass_IN, - BrowseAllQueryCallback, newDomain ); - free( recordName ); - require_noerr( err, exit ); - - newDomain->servicesQuery = sdRef; - } - - *p = newDomain; - newDomain = NULL; - err = kNoErr; - -exit: - if( newDomain ) BrowseDomainFree( newDomain ); - return( err ); -} - -//=========================================================================================================================== -// BrowseAllRemoveDomain -//=========================================================================================================================== - -static OSStatus BrowseAllRemoveDomain( BrowseAllContext *inContext, const char *inName ) -{ - OSStatus err; - BrowseDomain * domain; - BrowseDomain ** p; - - for( p = &inContext->domainList; ( domain = *p ) != NULL; p = &domain->next ) - { - if( strcasecmp( domain->name, inName ) == 0 ) break; - } - - if( domain ) - { - *p = domain->next; - BrowseDomainFree( domain ); - err = kNoErr; - } - else - { - err = kNotFoundErr; - } - - return( err ); -} - -//=========================================================================================================================== -// BrowseAllContextRelease -//=========================================================================================================================== - -static void BrowseAllContextRelease( BrowseAllContext *inContext ) -{ - if( --inContext->refCount == 0 ) - { - check( !inContext->domainsQuery ); - check( !inContext->domainList ); - check( !inContext->exitTimer ); - check( !inContext->pendingConnectCount ); - DNSServiceForget( &inContext->mainRef ); - if( inContext->serviceTypes ) - { - StringArray_Free( inContext->serviceTypes, inContext->serviceTypesCount ); - inContext->serviceTypes = NULL; - inContext->serviceTypesCount = 0; - } - free( inContext ); - } -} - -//=========================================================================================================================== -// BrowseAllAddServiceType -//=========================================================================================================================== - -static OSStatus - BrowseAllAddServiceType( - BrowseAllContext * inContext, - BrowseDomain * inDomain, - const char * inName, - uint32_t inIfIndex, - Boolean inIncludeAWDL ) -{ - OSStatus err; - DNSServiceRef sdRef; - DNSServiceFlags flags; - BrowseType * type; - BrowseType ** typePtr; - BrowseType * newType = NULL; - BrowseOp * browse; - BrowseOp ** browsePtr; - BrowseOp * newBrowse = NULL; - - for( typePtr = &inDomain->typeList; ( type = *typePtr ) != NULL; typePtr = &type->next ) - { - if( strcasecmp( type->name, inName ) == 0 ) break; - } - if( !type ) - { - newType = (BrowseType *) calloc( 1, sizeof( *newType ) ); - require_action( newType, exit, err = kNoMemoryErr ); - - newType->name = strdup( inName ); - require_action( newType->name, exit, err = kNoMemoryErr ); - - type = newType; - } - - for( browsePtr = &type->browseList; ( browse = *browsePtr ) != NULL; browsePtr = &browse->next ) - { - if( browse->ifIndex == inIfIndex ) break; - } - require_action_quiet( !browse, exit, err = kDuplicateErr ); - - newBrowse = (BrowseOp *) calloc( 1, sizeof( *newBrowse ) ); - require_action( newBrowse, exit, err = kNoMemoryErr ); - - ++inContext->refCount; - newBrowse->context = inContext; - newBrowse->ifIndex = inIfIndex; - if( stricmp_suffix( inName, "._tcp" ) == 0 ) newBrowse->isTCP = true; - - flags = kDNSServiceFlagsShareConnection; - if( inIncludeAWDL ) flags |= kDNSServiceFlagsIncludeAWDL; - - newBrowse->startTicks = UpTicks(); - - sdRef = inContext->mainRef; - err = DNSServiceBrowse( &sdRef, flags, newBrowse->ifIndex, type->name, inDomain->name, BrowseAllBrowseCallback, - newBrowse ); - require_noerr( err, exit ); - - newBrowse->browse = sdRef; - *browsePtr = newBrowse; - newBrowse = NULL; - - if( newType ) - { - *typePtr = newType; - newType = NULL; - } - -exit: - if( newBrowse ) BrowseOpFree( newBrowse ); - if( newType ) BrowseTypeFree( newType ); - return( err ); -} - -//=========================================================================================================================== -// BrowseAllRemoveServiceType -//=========================================================================================================================== - -static OSStatus - BrowseAllRemoveServiceType( - BrowseAllContext * inContext, - BrowseDomain * inDomain, - const char * inName, - uint32_t inIfIndex ) -{ - OSStatus err; - BrowseType * type; - BrowseType ** typePtr; - BrowseOp * browse; - BrowseOp ** browsePtr; - - Unused( inContext ); - - for( typePtr = &inDomain->typeList; ( type = *typePtr ) != NULL; typePtr = &type->next ) - { - if( strcasecmp( type->name, inName ) == 0 ) break; - } - require_action_quiet( type, exit, err = kNotFoundErr ); - - for( browsePtr = &type->browseList; ( browse = *browsePtr ) != NULL; browsePtr = &browse->next ) - { - if( browse->ifIndex == inIfIndex ) break; - } - require_action_quiet( browse, exit, err = kNotFoundErr ); - - *browsePtr = browse->next; - BrowseOpFree( browse ); - if( !type->browseList ) - { - *typePtr = type->next; - BrowseTypeFree( type ); - } - err = kNoErr; - -exit: - return( err ); -} - -//=========================================================================================================================== -// BrowseAllAddServiceInstance -//=========================================================================================================================== - -static OSStatus - BrowseAllAddServiceInstance( - BrowseAllContext * inContext, - BrowseOp * inBrowse, - const char * inName, - const char * inRegType, - const char * inDomain, - uint32_t inIfIndex ) -{ - OSStatus err; - DNSServiceRef sdRef; - BrowseInstance * instance; - BrowseInstance ** p; - const uint64_t nowTicks = UpTicks(); - BrowseInstance * newInstance = NULL; - - for( p = &inBrowse->instanceList; ( instance = *p ) != NULL; p = &instance->next ) - { - if( ( instance->ifIndex == inIfIndex ) && ( strcasecmp( instance->name, inName ) == 0 ) ) break; - } - require_action_quiet( !instance, exit, err = kDuplicateErr ); - - newInstance = (BrowseInstance *) calloc( 1, sizeof( *newInstance ) ); - require_action( newInstance, exit, err = kNoMemoryErr ); - - ++inContext->refCount; - newInstance->context = inContext; - newInstance->foundTicks = nowTicks; - newInstance->ifIndex = inIfIndex; - newInstance->isTCP = inBrowse->isTCP; - - newInstance->name = strdup( inName ); - require_action( newInstance->name, exit, err = kNoMemoryErr ); - - sdRef = inContext->mainRef; - newInstance->resolveStartTicks = UpTicks(); - err = DNSServiceResolve( &sdRef, kDNSServiceFlagsShareConnection, newInstance->ifIndex, inName, inRegType, inDomain, - BrowseAllResolveCallback, newInstance ); - require_noerr( err, exit ); - - newInstance->resolve = sdRef; - *p = newInstance; - newInstance = NULL; - -exit: - if( newInstance ) BrowseInstanceFree( newInstance ); - return( err ); -} - -//=========================================================================================================================== -// BrowseAllRemoveServiceInstance -//=========================================================================================================================== - -static OSStatus - BrowseAllRemoveServiceInstance( - BrowseAllContext * inContext, - BrowseOp * inBrowse, - const char * inName, - uint32_t inIfIndex ) -{ - OSStatus err; - BrowseInstance * instance; - BrowseInstance ** p; - - Unused( inContext ); - - for( p = &inBrowse->instanceList; ( instance = *p ) != NULL; p = &instance->next ) - { - if( ( instance->ifIndex == inIfIndex ) && ( strcasecmp( instance->name, inName ) == 0 ) ) break; - } - require_action_quiet( instance, exit, err = kNotFoundErr ); - - *p = instance->next; - BrowseInstanceFree( instance ); - err = kNoErr; - -exit: - return( err ); -} - -//=========================================================================================================================== -// BrowseAllAddIPAddress -//=========================================================================================================================== - -#define kDiscardProtocolPort 9 - -static OSStatus - BrowseAllAddIPAddress( - BrowseAllContext * inContext, - BrowseInstance * inInstance, - const struct sockaddr * inSockAddr ) -{ - OSStatus err; - BrowseIPAddr * addr; - BrowseIPAddr ** p; - const uint64_t nowTicks = UpTicks(); - BrowseIPAddr * newAddr = NULL; - - if( ( inSockAddr->sa_family != AF_INET ) && ( inSockAddr->sa_family != AF_INET6 ) ) - { - dlogassert( "Unexpected address family: %d", inSockAddr->sa_family ); - err = kTypeErr; - goto exit; - } - - for( p = &inInstance->addrList; ( addr = *p ) != NULL; p = &addr->next ) - { - if( SockAddrCompareAddr( &addr->sip, inSockAddr ) == 0 ) break; - } - require_action_quiet( !addr, exit, err = kDuplicateErr ); - - newAddr = (BrowseIPAddr *) calloc( 1, sizeof( *newAddr ) ); - require_action( newAddr, exit, err = kNoMemoryErr ); - - ++inContext->refCount; - newAddr->refCount = 1; - newAddr->context = inContext; - newAddr->foundTicks = nowTicks; - SockAddrCopy( inSockAddr, &newAddr->sip.sa ); - - if( ( inContext->maxConnectTimeSecs > 0 ) && inInstance->isTCP && ( inInstance->port != kDiscardProtocolPort ) ) - { - char destination[ kSockAddrStringMaxSize ]; - - err = SockAddrToString( &newAddr->sip, kSockAddrStringFlagsNoPort, destination ); - require_noerr( err, exit ); - - err = AsyncConnection_Connect( &newAddr->connection, destination, -inInstance->port, kAsyncConnectionFlag_P2P, - kAsyncConnectionNoTimeout, kSocketBufferSize_DontSet, kSocketBufferSize_DontSet, - BrowseAllConnectionProgress, newAddr, BrowseAllConnectionHandler, newAddr, dispatch_get_main_queue() ); - require_noerr( err, exit ); - - ++newAddr->refCount; - newAddr->connectStatus = kConnectStatus_Pending; - ++inContext->pendingConnectCount; - } - - *p = newAddr; - newAddr = NULL; - err = kNoErr; - -exit: - if( newAddr ) BrowseIPAddrRelease( newAddr ); - return( err ); -} - -//=========================================================================================================================== -// BrowseAllRemoveIPAddress -//=========================================================================================================================== - -static OSStatus - BrowseAllRemoveIPAddress( - BrowseAllContext * inContext, - BrowseInstance * inInstance, - const struct sockaddr * inSockAddr ) -{ - OSStatus err; - BrowseIPAddr * addr; - BrowseIPAddr ** p; - - Unused( inContext ); - - for( p = &inInstance->addrList; ( addr = *p ) != NULL; p = &addr->next ) - { - if( SockAddrCompareAddr( &addr->sip.sa, inSockAddr ) == 0 ) break; - } - require_action_quiet( addr, exit, err = kNotFoundErr ); - - *p = addr->next; - BrowseIPAddrRelease( addr ); - err = kNoErr; - -exit: - return( err ); -} - -//=========================================================================================================================== -// BrowseDomainFree -//=========================================================================================================================== - -static void BrowseDomainFree( BrowseDomain *inDomain ) -{ - BrowseType * type; - - ForgetBrowseAllContext( &inDomain->context ); - ForgetMem( &inDomain->name ); - DNSServiceForget( &inDomain->servicesQuery ); - while( ( type = inDomain->typeList ) != NULL ) - { - inDomain->typeList = type->next; - BrowseTypeFree( type ); - } - free( inDomain ); -} - -//=========================================================================================================================== -// BrowseTypeFree -//=========================================================================================================================== - -static void BrowseTypeFree( BrowseType *inType ) -{ - BrowseOp * browse; - - ForgetMem( &inType->name ); - while( ( browse = inType->browseList ) != NULL ) - { - inType->browseList = browse->next; - BrowseOpFree( browse ); - } - free( inType ); -} - -//=========================================================================================================================== -// BrowseOpFree -//=========================================================================================================================== - -static void BrowseOpFree( BrowseOp *inBrowse ) -{ - BrowseInstance * instance; - - ForgetBrowseAllContext( &inBrowse->context ); - DNSServiceForget( &inBrowse->browse ); - while( ( instance = inBrowse->instanceList ) != NULL ) - { - inBrowse->instanceList = instance->next; - BrowseInstanceFree( instance ); - } - free( inBrowse ); -} - -//=========================================================================================================================== -// BrowseInstanceFree -//=========================================================================================================================== - -static void BrowseInstanceFree( BrowseInstance *inInstance ) -{ - ForgetBrowseAllContext( &inInstance->context ); - ForgetMem( &inInstance->name ); - DNSServiceForget( &inInstance->resolve ); - DNSServiceForget( &inInstance->getAddr ); - ForgetMem( &inInstance->txtPtr ); - ForgetMem( &inInstance->hostname ); - ForgetIPAddressList( &inInstance->addrList ); - free( inInstance ); -} - -//=========================================================================================================================== -// BrowseIPAddrRelease -//=========================================================================================================================== - -static void BrowseIPAddrRelease( BrowseIPAddr *inAddr ) -{ - AsyncConnection_Forget( &inAddr->connection ); - if( --inAddr->refCount == 0 ) - { - ForgetBrowseAllContext( &inAddr->context ); - free( inAddr ); - } -} - -//=========================================================================================================================== -// BrowseIPAddrReleaseList -//=========================================================================================================================== - -static void BrowseIPAddrReleaseList( BrowseIPAddr *inList ) -{ - BrowseIPAddr * addr; - - while( ( addr = inList ) != NULL ) + err = DomainNameFromString( name, inServiceType, NULL ); + if( !err ) { - inList = addr->next; - BrowseIPAddrRelease( addr ); + secondLabel = NextLabel( name ); + if( secondLabel && DomainNameEqual( secondLabel, (const uint8_t *) "\x04" "_tcp" ) ) return( true ); } + return( false ); } //=========================================================================================================================== @@ -6497,7 +6485,7 @@ typedef struct 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. + uint8_t msgBuf[ kMDNSMessageSizeMax ]; // mDNS message buffer. } MDNSQueryContext; @@ -6508,12 +6496,10 @@ 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; + const char * ifname; size_t msgLen; unsigned int sendCount; @@ -6540,8 +6526,8 @@ static void MDNSQueryCmd( void ) err = InterfaceIndexFromArgString( gInterface, &context->ifIndex ); require_noerr_quiet( err, exit ); - ifNamePtr = if_indextoname( context->ifIndex, context->ifName ); - require_action( ifNamePtr, exit, err = kNameErr ); + ifname = if_indextoname( context->ifIndex, context->ifName ); + require_action( ifname, exit, err = kNameErr ); err = RecordTypeFromArgString( gMDNSQuery_Type, &context->qtype ); require_noerr( err, exit ); @@ -6550,60 +6536,20 @@ static void MDNSQueryCmd( void ) if( context->useIPv4 ) { - err = ServerSocketOpen( AF_INET, SOCK_DGRAM, IPPROTO_UDP, + err = CreateMulticastSocket( GetMDNSMulticastAddrV4(), 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 ); + ifname, context->ifIndex, !context->isQU, &context->localPort, &sockV4 ); 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. if( context->useIPv6 ) { - err = ServerSocketOpen( AF_INET6, SOCK_DGRAM, IPPROTO_UDP, + err = CreateMulticastSocket( GetMDNSMulticastAddrV6(), gMDNSQuery_SourcePort ? gMDNSQuery_SourcePort : ( context->isQU ? context->localPort : kMDNSPort ), - &context->localPort, kSocketBufferSize_DontSet, &sockV6 ); + ifname, context->ifIndex, !context->isQU, &context->localPort, &sockV6 ); require_noerr( err, exit ); - - err = SocketSetMulticastInterface( sockV6, ifNamePtr, context->ifIndex ); - require_noerr( err, exit ); - - err = setsockopt( sockV6, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, (char *) &(int){ 1 }, (socklen_t) sizeof( int ) ); - err = map_socket_noerr_errno( sockV6, err ); - require_noerr( err, exit ); - - memset( &mcastAddr6, 0, sizeof( mcastAddr6 ) ); - SIN6_LEN_SET( &mcastAddr6 ); - mcastAddr6.sin6_family = AF_INET6; - mcastAddr6.sin6_port = htons( kMDNSPort ); - mcastAddr6.sin6_addr.s6_addr[ 0 ] = 0xFF; // mDNS IPv6 multicast address FF02::FB - mcastAddr6.sin6_addr.s6_addr[ 1 ] = 0x02; - mcastAddr6.sin6_addr.s6_addr[ 15 ] = 0xFB; - - if( !context->isQU && ( context->localPort == kMDNSPort ) ) - { - err = SocketJoinMulticast( sockV6, &mcastAddr6, ifNamePtr, context->ifIndex ); - require_noerr( err, exit ); - } } // Craft mDNS query message. @@ -6622,7 +6568,9 @@ static void MDNSQueryCmd( void ) sendCount = 0; if( IsValidSocket( sockV4 ) ) { - n = sendto( sockV4, context->msgBuf, msgLen, 0, (struct sockaddr *) &mcastAddr4, (socklen_t) sizeof( mcastAddr4 ) ); + 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 ) { @@ -6636,7 +6584,9 @@ static void MDNSQueryCmd( void ) } if( IsValidSocket( sockV6 ) ) { - n = sendto( sockV6, context->msgBuf, msgLen, 0, (struct sockaddr *) &mcastAddr6, (socklen_t) sizeof( mcastAddr6 ) ); + 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 ) { @@ -6702,6 +6652,92 @@ exit: } //=========================================================================================================================== +// MDNSColliderCmd +//=========================================================================================================================== + +static void _MDNSColliderCmdStopHandler( void *inContext, OSStatus inError ); + +static void MDNSColliderCmd( void ) +{ + 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 ]; + + err = InterfaceIndexFromArgString( gInterface, &ifIndex ); + require_noerr_quiet( err, exit ); + + ifname = if_indextoname( ifIndex, ifName ); + if( !ifname ) + { + FPrintF( stderr, "error: Invalid interface name or index: %s\n", gInterface ); + err = kNameErr; + goto exit; + } + + err = DomainNameFromString( name, gMDNSCollider_Name, NULL ); + if( err ) + { + FPrintF( stderr, "error: Invalid record name: %s\n", gMDNSCollider_Name ); + goto exit; + } + + err = RecordTypeFromArgString( gMDNSCollider_Type, &type ); + require_noerr_quiet( err, exit ); + + if( gMDNSCollider_RecordData ) + { + err = RecordDataFromArgString( gMDNSCollider_RecordData, &rdataPtr, &rdataLen ); + require_noerr_quiet( err, exit ); + } + + err = MDNSColliderCreate( dispatch_get_main_queue(), &collider ); + require_noerr( err, exit ); + + err = MDNSColliderSetProgram( collider, gMDNSCollider_Program ); + if( err ) + { + FPrintF( stderr, "error: Failed to set program string: '%s'\n", gMDNSCollider_Program ); + goto exit; + } + + err = MDNSColliderSetRecord( collider, name, type, rdataPtr, rdataLen ); + require_noerr( err, exit ); + ForgetMem( &rdataPtr ); + + 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 = MDNSColliderStart( collider ); + require_noerr( err, exit ); + + dispatch_main(); + +exit: + 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 ); +} + +//=========================================================================================================================== // MDNSQueryPrintPrologue //=========================================================================================================================== @@ -6810,29 +6846,31 @@ exit: // DNSServerCmd //=========================================================================================================================== -typedef uint32_t DNSServerEventType; -#define kDNSServerEvent_Started 1 -#define kDNSServerEvent_Stopped 2 - typedef struct DNSServerPrivate * DNSServerRef; typedef struct { - DNSServerRef server; // Reference to the DNS server. - dispatch_source_t sigIntSource; // Dispatch SIGINT source. - dispatch_source_t sigTermSource; // Dispatch SIGTERM source. + 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 (we exit when they exit), if any. - Boolean resolverRegistered; // True if system DNS settings contains a resolver entry for server. + 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. - Boolean serverStarted; // True if the server was successfully started. - Boolean calledStop; // True if the server was explicitly stopped. + Boolean loopbackOnly; // True if the server should be bound to the loopback interface. } DNSServerCmdContext; -typedef void ( *DNSServerEventHandler_f )( DNSServerEventType inType, void *inContext ); +typedef enum +{ + kDNSServerEvent_Started = 1, + kDNSServerEvent_Stopped = 2 + +} DNSServerEventType; + +typedef void ( *DNSServerEventHandler_f )( DNSServerEventType inType, uintptr_t inEventData, void *inContext ); CFTypeID DNSServerGetTypeID( void ); static OSStatus @@ -6840,14 +6878,20 @@ static OSStatus dispatch_queue_t inQueue, DNSServerEventHandler_f inEventHandler, void * inEventContext, - int inResponseDelayMs, + 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 ); +#define ForgetDNSServer( X ) ForgetCustomEx( X, DNSServerStop, CFRelease ) + static void DNSServerCmdContextFree( DNSServerCmdContext *inContext ); -static void DNSServerCmdEventHandler( DNSServerEventType inType, void *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 ) @@ -6860,26 +6904,38 @@ ulog_define_ex( "com.apple.dnssdutil", DNSServer, kLogLevelInfo, kLogFlags_None, static void DNSServerCmd( void ) { OSStatus err; - DNSServerCmdContext * context; + DNSServerCmdContext * context = NULL; + + if( gDNSServer_Foreground ) + { + LogControl( "DNSServer:output=file;stdout,DNSServer:flags=time;prefix" ); + } + + err = CheckIntegerArgument( gDNSServer_ResponseDelayMs, "response delay (ms)", 0, INT_MAX ); + require_noerr_quiet( err, exit ); + + err = CheckIntegerArgument( gDNSServer_DefaultTTL, "default TTL", 0, INT32_MAX ); + require_noerr_quiet( err, exit ); + + err = CheckIntegerArgument( gDNSServer_Port, "port number", -UINT16_MAX, UINT16_MAX ); + require_noerr_quiet( err, exit ); context = (DNSServerCmdContext *) calloc( 1, sizeof( *context ) ); require_action( context, exit, err = kNoMemoryErr ); - context->loopbackOnly = gDNSServer_LoopbackOnly ? true : false; + context->domainOverride = gDNSServer_DomainOverride; + context->loopbackOnly = gDNSServer_LoopbackOnly ? true : false; #if( TARGET_OS_DARWIN ) if( gDNSServer_FollowPID ) { - long long value; - - err = StringToLongLong( gDNSServer_FollowPID, &value ); - if( !err && ( value < 0 ) ) err = kValueErr; - if( err ) + err = StringToPID( gDNSServer_FollowPID, &context->followPID ); + if( err || ( context->followPID < 0 ) ) { - FPrintF( stderr, "Invalid followPID argument \"%s\".\n", gDNSServer_FollowPID ); + FPrintF( stderr, "error: Invalid follow PID: %s\n", gDNSServer_FollowPID ); + err = kParamErr; goto exit; } - context->followPID = (pid_t) value; err = DispatchProcessMonitorCreate( context->followPID, DISPATCH_PROC_EXIT, dispatch_get_main_queue(), DNSServerCmdFollowedProcessHandler, NULL, context, &context->processMonitor ); @@ -6902,27 +6958,16 @@ static void DNSServerCmd( void ) require_noerr( err, exit ); dispatch_resume( context->sigTermSource ); - if( gDNSServer_Foreground ) - { - LogControl( "DNSServer:output=file;stdout,DNSServer:flags=time;prefix" ); - } - - if( ( gDNSServer_DefaultTTL < 0 ) || ( gDNSServer_DefaultTTL > INT32_MAX ) ) - { - 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 = DNSServerCreate( dispatch_get_main_queue(), DNSServerCmdEventHandler, context, gDNSServer_ResponseDelayMs, - context->loopbackOnly, &context->server ); + 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: - ds_ulog( kLogLevelError, "Failed to start DNS server: %#m\n", err ); + FPrintF( stderr, "Failed to start DNS server: %#m\n", err ); if( context ) DNSServerCmdContextFree( context ); if( err ) exit( 1 ); } @@ -6936,7 +6981,9 @@ 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 ); } @@ -6945,65 +6992,69 @@ static void DNSServerCmdContextFree( DNSServerCmdContext *inContext ) //=========================================================================================================================== #if( TARGET_OS_DARWIN ) -static OSStatus _DNSServerCmdRegisterResolver( void ); -static OSStatus _DNSServerCmdUnregisterResolver( void ); +static OSStatus _DNSServerCmdLoopbackResolverAdd( const char *inDomain, int inPort ); +static OSStatus _DNSServerCmdLoopbackResolverRemove( void ); #endif -static void DNSServerCmdEventHandler( DNSServerEventType inType, void *inContext ) +static void DNSServerCmdEventHandler( DNSServerEventType inType, uintptr_t inEventData, void *inContext ) { - DNSServerCmdContext * const context = (DNSServerCmdContext *) inContext; -#if( TARGET_OS_DARWIN ) OSStatus err; -#endif + DNSServerCmdContext * const context = (DNSServerCmdContext *) inContext; if( inType == kDNSServerEvent_Started ) { - context->serverStarted = true; #if( TARGET_OS_DARWIN ) - err = _DNSServerCmdRegisterResolver(); + const int port = (int) inEventData; + + err = _DNSServerCmdLoopbackResolverAdd( context->domainOverride ? context->domainOverride : "d.test.", port ); if( err ) { - ds_ulog( kLogLevelError, "Failed to add resolver to DNS configuration for \"d.test.\" domain: %#m\n", err ); - if( context->loopbackOnly ) exit( 1 ); + 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 { - context->resolverRegistered = true; + context->addedResolver = true; } #endif } else if( inType == kDNSServerEvent_Stopped ) { + 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->resolverRegistered ) + if( context->addedResolver ) { - err = _DNSServerCmdUnregisterResolver(); + err = _DNSServerCmdLoopbackResolverRemove(); if( err ) { - ds_ulog( kLogLevelError, "Failed to remove resolver from DNS configuration: %#m\n", err ); + ds_ulog( kLogLevelError, "Failed to remove loopback resolver from DNS configuration: %#m\n", err ); } else { - context->resolverRegistered = false; + context->addedResolver = false; } } - - if( !context->calledStop ) + else if( context->loopbackOnly ) { - ds_ulog( kLogLevelError, "The server stopped unexpectedly.\n" ); - exit( 1 ); + err = kUnknownErr; } #endif DNSServerCmdContextFree( context ); + exit( ( stopError || err ) ? 1 : 0 ); } } #if( TARGET_OS_DARWIN ) //=========================================================================================================================== -// _DNSServerCmdRegisterResolver +// _DNSServerCmdLoopbackResolverAdd //=========================================================================================================================== -static OSStatus _DNSServerCmdRegisterResolver( void ) +static OSStatus _DNSServerCmdLoopbackResolverAdd( const char *inDomain, int inPort ) { OSStatus err; SCDynamicStoreRef store; @@ -7027,9 +7078,11 @@ static OSStatus _DNSServerCmdRegisterResolver( void ) "%.4a" "%.16a" "]" + "%kO=%i" "}", - kSCPropNetDNSSupplementalMatchDomains, "d.test.", - kSCPropNetDNSServerAddresses, &loopbackV4, in6addr_loopback.s6_addr ); + kSCPropNetDNSSupplementalMatchDomains, inDomain, + kSCPropNetDNSServerAddresses, &loopbackV4, in6addr_loopback.s6_addr, + kSCPropNetDNSServerPort, inPort ); require_noerr( err, exit ); key = SCDynamicStoreKeyCreateNetworkServiceEntity( NULL, kSCDynamicStoreDomainState, @@ -7047,10 +7100,10 @@ exit: } //=========================================================================================================================== -// _DNSServerCmdUnregisterResolver +// _DNSServerCmdLoopbackResolverRemove //=========================================================================================================================== -static OSStatus _DNSServerCmdUnregisterResolver( void ) +static OSStatus _DNSServerCmdLoopbackResolverRemove( void ) { OSStatus err; SCDynamicStoreRef store; @@ -7079,11 +7132,11 @@ exit: // DNSServerCmdSigIntHandler //=========================================================================================================================== -static void _DNSServerCmdExternalExit( DNSServerCmdContext *inContext, int inSignal ); +static void _DNSServerCmdShutdown( DNSServerCmdContext *inContext, int inSignal ); static void DNSServerCmdSigIntHandler( void *inContext ) { - _DNSServerCmdExternalExit( (DNSServerCmdContext *) inContext, SIGINT ); + _DNSServerCmdShutdown( (DNSServerCmdContext *) inContext, SIGINT ); } //=========================================================================================================================== @@ -7092,7 +7145,7 @@ static void DNSServerCmdSigIntHandler( void *inContext ) static void DNSServerCmdSigTermHandler( void *inContext ) { - _DNSServerCmdExternalExit( (DNSServerCmdContext *) inContext, SIGTERM ); + _DNSServerCmdShutdown( (DNSServerCmdContext *) inContext, SIGTERM ); } #if( TARGET_OS_DARWIN ) @@ -7104,10 +7157,7 @@ static void DNSServerCmdFollowedProcessHandler( void *inContext ) { DNSServerCmdContext * const context = (DNSServerCmdContext *) inContext; - if( dispatch_source_get_data( context->processMonitor ) & DISPATCH_PROC_EXIT ) - { - _DNSServerCmdExternalExit( context, 0 ); - } + if( dispatch_source_get_data( context->processMonitor ) & DISPATCH_PROC_EXIT ) _DNSServerCmdShutdown( context, 0 ); } #endif @@ -7120,11 +7170,13 @@ static void DNSServerCmdFollowedProcessHandler( void *inContext ) ( (X) == SIGTERM ) ? "SIGTERM" : \ "???" ) -static void _DNSServerCmdExternalExit( DNSServerCmdContext *inContext, int inSignal ) +static void _DNSServerCmdShutdown( DNSServerCmdContext *inContext, int inSignal ) { - OSStatus err; - + dispatch_source_forget( &inContext->sigIntSource ); + dispatch_source_forget( &inContext->sigTermSource ); #if( TARGET_OS_DARWIN ) + dispatch_source_forget( &inContext->processMonitor ); + if( inSignal == 0 ) { ds_ulog( kLogLevelNotice, "Exiting: followed process (%lld) exited\n", (int64_t) inContext->followPID ); @@ -7135,62 +7187,55 @@ static void _DNSServerCmdExternalExit( DNSServerCmdContext *inContext, int inSig 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 ); + ForgetDNSServer( &inContext->server ); } //=========================================================================================================================== // DNSServerCreate //=========================================================================================================================== +#define kDDotTestDomainName (const uint8_t *) "\x01" "d" "\x04" "test" + typedef struct DNSDelayedResponse DNSDelayedResponse; struct DNSDelayedResponse { DNSDelayedResponse * next; - sockaddr_ip clientAddr; + sockaddr_ip destAddr; 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. + 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; - int responseDelayMs; dispatch_source_t responseTimer; - Boolean loopbackOnly; + 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 ); static OSStatus @@ -7198,20 +7243,40 @@ static OSStatus dispatch_queue_t inQueue, DNSServerEventHandler_f inEventHandler, void * inEventContext, - int inResponseDelayMs, + unsigned int inResponseDelayMs, + uint32_t inDefaultTTL, + int inPort, Boolean inLoopbackOnly, + const char * inDomain, + Boolean inBadUDPMode, DNSServerRef * outServer ) { OSStatus err; DNSServerRef obj = NULL; + require_action_quiet( inDefaultTTL <= INT32_MAX, exit, err = kRangeErr ); + 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; + obj->defaultTTL = inDefaultTTL; + obj->port = inPort; + obj->loopbackOnly = inLoopbackOnly; + obj->badUDPMode = inBadUDPMode; + + if( inDomain ) + { + err = StringToDomainName( inDomain, &obj->domain, NULL ); + require_noerr_quiet( err, exit ); + } + else + { + err = DomainNameDup( kDDotTestDomainName, &obj->domain, NULL ); + require_noerr_quiet( err, exit ); + } *outServer = obj; obj = NULL; @@ -7235,6 +7300,7 @@ static void _DNSServerFinalize( CFTypeRef inObj ) check( !me->readSourceTCPv4 ); check( !me->readSourceTCPv6 ); check( !me->responseTimer ); + ForgetMem( &me->domain ); dispatch_forget( &me->queue ); } @@ -7243,8 +7309,7 @@ static void _DNSServerFinalize( CFTypeRef inObj ) //=========================================================================================================================== static void _DNSServerStart( void *inContext ); -static void _DNSServerUDPReadHandler( void *inContext ); -static void _DNSServerTCPReadHandler( void *inContext ); +static void _DNSServerStop( void *inContext, OSStatus inError ); static void DNSServerStart( DNSServerRef me ) { @@ -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 _DNSServerUserStop( void *inContext ) +{ + DNSServerRef const me = (DNSServerRef) inContext; + + _DNSServerStop( me, kNoErr ); + CFRelease( me ); } -static void _DNSServerStop( void *inContext ) +static void _DNSServerStop( void *inContext, OSStatus inError ) { - DNSServerRef const me = (DNSServerRef) inContext; - DNSDelayedResponse * resp; + 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( aliasTTLCount > 0 ) { - if( nameHasA || nameHasAAAA ) break; // Valid names have at most one IPv4 or IPv6 label. - nameHasA = true; - continue; + aliasCount = (uint32_t) aliasTTLCount; + useAliasTTLs = true; } - if( MemIEqual( labelStr, labelLen, kLabel_IPv6, sizeof_string( kLabel_IPv6 ) ) ) - { - if( nameHasA || nameHasAAAA ) break; // Valid names have at most one IPv4 or IPv6 label. - nameHasAAAA = true; - continue; - } - - // 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,12 +8242,279 @@ exit: return( err ); } +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; + 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. @@ -8120,6 +8542,7 @@ 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; @@ -8128,6 +8551,9 @@ static void _DNSServerTCPReadHandler( void *inContext ) 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 ); @@ -8137,13 +8563,13 @@ static void _DNSServerTCPReadHandler( void *inContext ) require_noerr( err, exit ); newSock = kInvalidSocketRef; - err = DispatchReadSourceCreate( newSockCtx->sock, NULL, TCPConnectionReadHandler, SocketContextCancelHandler, + 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, NULL, TCPConnectionWriteHandler, SocketContextCancelHandler, + err = DispatchWriteSourceCreate( newSockCtx->sock, me->queue, TCPConnectionWriteHandler, SocketContextCancelHandler, newSockCtx, &connection->writeSource ); require_noerr( err, exit ); SocketContextRetain( newSockCtx ); @@ -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,36 +8709,1690 @@ exit: } //=========================================================================================================================== +// MDNSReplierCmd +//=========================================================================================================================== + +typedef struct +{ + 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 + +} MDNSReplierContext; + +typedef struct MRResourceRecord MRResourceRecord; +struct MRResourceRecord +{ + 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 MRNameOffsetItem MRNameOffsetItem; +struct MRNameOffsetItem +{ + 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. +}; + +#if( TARGET_OS_DARWIN ) +static void _MDNSReplierFollowedProcessHandler( void *inContext ); +#endif +static void _MDNSReplierReadHandler( void *inContext ); +static OSStatus + _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 + _MDNSReplierAnswerListRemovePTR( + MRResourceRecord ** inAnswerListPtr, + const uint8_t * inName, + const uint8_t * inRData ); +static OSStatus + _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 ); + +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 MDNSReplierCmd( void ) +{ + 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 ]; + + err = CheckIntegerArgument( gMDNSReplier_MaxInstanceCount, "max instance count", 1, UINT16_MAX ); + require_noerr_quiet( err, exit ); + + err = CheckIntegerArgument( gMDNSReplier_RecordCountA, "A record count", 0, 255 ); + require_noerr_quiet( err, exit ); + + err = CheckIntegerArgument( gMDNSReplier_RecordCountAAAA, "AAAA record count", 0, 255 ); + require_noerr_quiet( err, exit ); + + err = CheckDoubleArgument( gMDNSReplier_UnicastDropRate, "unicast drop rate", 0.0, 1.0 ); + require_noerr_quiet( err, exit ); + + 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 ) + { + LogControl( "MDNSReplier:output=file;stdout,MDNSReplier:flags=time;prefix" ); + } + + context = (MDNSReplierContext *) calloc( 1, sizeof( *context ) ); + require_action( context, exit, err = kNoMemoryErr ); + + 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; + +#if( TARGET_OS_DARWIN ) + if( gMDNSReplier_FollowPID ) + { + err = StringToPID( gMDNSReplier_FollowPID, &context->followPID ); + if( err || ( context->followPID < 0 ) ) + { + FPrintF( stderr, "error: Invalid follow PID: %s\n", gMDNSReplier_FollowPID ); + goto exit; + } + + 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; + + // 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 ); + } + + 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; + } + + // Create the service label. + + 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 ); + } + + dispatch_main(); + +exit: + ForgetSocket( &sockV4 ); + ForgetSocket( &sockV6 ); + exit( 1 ); +} + +#if( TARGET_OS_DARWIN ) +//=========================================================================================================================== +// _MDNSReplierFollowedProcessHandler +//=========================================================================================================================== + +static void _MDNSReplierFollowedProcessHandler( void *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 + +//=========================================================================================================================== +// _MDNSReplierReadHandler +//=========================================================================================================================== + +#define ShouldDrop( P ) ( ( (P) > 0.0 ) && ( ( (P) >= 1.0 ) || RandomlyTrue( P ) ) ) + +static void _MDNSReplierReadHandler( void *inContext ) +{ + 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; + + err = SocketRecvFrom( sockCtx->sock, context->msgBuf, sizeof( context->msgBuf ), &msgLen, &sender, sizeof( sender ), + NULL, NULL, NULL, NULL ); + require_noerr( err, exit ); + + if( msgLen < kDNSHeaderLength ) + { + 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 ) ) + { + if( ( index >= 1 ) && ( index <= context->maxInstanceCount ) ) + { + context->bitmaps[ ( index - 1 ) / 64 ] |= ( UINT64_C( 1 ) << ( ( index - 1 ) % 64 ) ); + } + } + 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; + } + } + + // Attempt to answer the query message using selected record sets. + + if( isMetaQuery ) + { + err = _MDNSReplierAnswerQuery( context, context->msgBuf, msgLen, &sender, sockCtx->sock, 0 ); + check_noerr( err ); + } + if( drop ) goto exit; + + 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: + return; +} + +//=========================================================================================================================== +// _MDNSReplierAnswerQuery +//=========================================================================================================================== + +static OSStatus + _MDNSReplierAnswerQuery( + MDNSReplierContext * inContext, + const uint8_t * inQueryPtr, + size_t inQueryLen, + sockaddr_ip * inSender, + SocketRef inSock, + unsigned int inIndex ) +{ + OSStatus err; + const DNSHeader * hdr; + const uint8_t * ptr; + unsigned int questionCount, answerCount, i; + MRResourceRecord * ucastAnswerList = NULL; + MRResourceRecord * mcastAnswerList = NULL; + + require_action( inIndex <= inContext->maxInstanceCount, exit, err = kRangeErr ); + + // Get answers for questions. + + check( inQueryLen >= kDNSHeaderLength ); + hdr = (const DNSHeader *) inQueryPtr; + questionCount = DNSHeaderGetQuestionCount( hdr ); + + ptr = (const uint8_t *) &hdr[ 1 ]; + for( i = 0; i < questionCount; ++i ) + { + 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 ); + + // 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>. + + answerCount = DNSHeaderGetAnswerCount( hdr ); + for( i = 0; i < answerCount; ++i ) + { + const uint8_t * rdataPtr; + const uint8_t * recordPtr; + uint16_t type, class; + uint8_t name[ kDomainNameLengthMax ]; + uint8_t instance[ kDomainNameLengthMax ]; + + recordPtr = ptr; + err = DNSMessageExtractRecord( inQueryPtr, inQueryLen, ptr, NULL, &type, &class, NULL, NULL, NULL, &ptr ); + require_noerr_quiet( err, exit ); + + if( ( type != kDNSServiceType_PTR ) || ( class != kDNSServiceClass_IN ) ) continue; + + 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 ); + + // Send or drop responses. + + if( ucastAnswerList ) + { + err = _MDNSReplierSendOrDropResponse( inContext, ucastAnswerList, inSender, inSock, inIndex, true ); + require_noerr( err, exit ); + } + + if( mcastAnswerList ) + { + err = _MDNSReplierSendOrDropResponse( inContext, mcastAnswerList, inSender, inSock, inIndex, false ); + require_noerr( err, exit ); + } + err = kNoErr; + +exit: + _MRResourceRecordFreeList( ucastAnswerList ); + _MRResourceRecordFreeList( mcastAnswerList ); + return( err ); +} + +//=========================================================================================================================== +// _MDNSReplierAnswerListAdd +//=========================================================================================================================== + +static OSStatus + _MDNSReplierAnswerListAdd( + MDNSReplierContext * inContext, + MRResourceRecord ** inAnswerList, + unsigned int inIndex, + const uint8_t * inName, + unsigned int inType, + unsigned int inClass ) +{ + OSStatus err; + 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 ) ) + { + err = kNoErr; + goto exit; + } + } + + // 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. + + 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 ) ) + { + int listHasA = false; + int listHasAAAA = false; + + 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; + } + } + + 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; + } + } + + 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; + } + } + else if( _MDNSReplierServiceTypeMatch( inContext, inName, NULL, &count ) && ( count >= inIndex ) ) + { + int listHasPTR = false; + + if( inType == kDNSServiceType_ANY ) + { + 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; + + err = DomainNameDupLower( inName, &recordName, &recordNameLen ); + require_noerr( err, exit ); + + 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; + } + } + 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: + FreeNullSafe( recordName ); + FreeNullSafe( rdataPtr ); + return( err ); +} + +//=========================================================================================================================== +// _MDNSReplierAnswerListRemovePTR +//=========================================================================================================================== + +static void + _MDNSReplierAnswerListRemovePTR( + MRResourceRecord ** inAnswerListPtr, + const uint8_t * inName, + const uint8_t * inRData ) +{ + MRResourceRecord * answer; + MRResourceRecord ** answerPtr; + + for( answerPtr = inAnswerListPtr; ( answer = *answerPtr ) != NULL; answerPtr = &answer->next ) + { + if( ( answer->type == kDNSServiceType_PTR ) && ( answer->class == kDNSServiceClass_IN ) && + DomainNameEqual( answer->name, inName ) && DomainNameEqual( answer->rdata, inRData ) ) break; + } + if( answer ) + { + *answerPtr = answer->next; + _MRResourceRecordFree( answer ); + } +} + +//=========================================================================================================================== +// _MDNSReplierSendOrDropResponse +//=========================================================================================================================== + +static OSStatus + _MDNSReplierSendOrDropResponse( + MDNSReplierContext * inContext, + MRResourceRecord * inAnswerList, + sockaddr_ip * inQuerier, + SocketRef inSock, + unsigned int inIndex, + Boolean inUnicast ) +{ + 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; + + check( inIndex <= inContext->maxInstanceCount ); + + // 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. + + 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; + } + } + + err = _MDNSReplierCreateResponse( inContext, inAnswerList, inIndex, &responsePtr, &responseLen ); + require_noerr( err, exit ); + + if( inUnicast ) + { + destAddr = &inQuerier->sa; + } + else + { + destAddr = ( inQuerier->sa.sa_family == AF_INET ) ? GetMDNSMulticastAddrV4() : GetMDNSMulticastAddrV6(); + } + + 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: + FreeNullSafe( responsePtr ); + return( err ); +} + +//=========================================================================================================================== +// _MDNSReplierCreateResponse +//=========================================================================================================================== + +static OSStatus + _MDNSReplierCreateResponse( + MDNSReplierContext * inContext, + MRResourceRecord * inAnswerList, + unsigned int inIndex, + uint8_t ** outResponsePtr, + size_t * outResponseLen ) +{ + OSStatus err; + DataBuffer responseDB; + DNSHeader hdr; + MRResourceRecord * answer; + uint8_t * responsePtr; + size_t responseLen, len; + unsigned int answerCount, recordCount; + MRNameOffsetItem * nameOffsetList = 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 ) + { + 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; + } + } + } + + // Append a provisional header to the response message. + + memset( &hdr, 0, sizeof( hdr ) ); + DNSHeaderSetFlags( &hdr, kDNSHeaderFlag_Response | kDNSHeaderFlag_AuthAnswer ); + + err = DataBuffer_Append( &responseDB, &hdr, sizeof( hdr ) ); + require_noerr( err, exit ); + + // 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; + } + + // 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. + + if( recordCount <= answerCount ) + { + DNSHeaderSetAnswerCount( (DNSHeader *) DataBuffer_GetPtr( &responseDB ), recordCount ); + } + else + { + DNSHeaderSetAnswerCount( (DNSHeader *) DataBuffer_GetPtr( &responseDB ), answerCount ); + DNSHeaderSetAdditionalCount( (DNSHeader *) DataBuffer_GetPtr( &responseDB ), recordCount - answerCount ); + } + + err = DataBuffer_Detach( &responseDB, &responsePtr, &len ); + require_noerr( err, exit ); + + if( outResponsePtr ) *outResponsePtr = responsePtr; + if( outResponseLen ) *outResponseLen = responseLen; + +exit: + _MRNameOffsetItemFreeList( nameOffsetList ); + DataBuffer_Free( &responseDB ); + return( err ); +} + +//=========================================================================================================================== +// _MDNSReplierAppendNameToResponse +//=========================================================================================================================== + +static OSStatus + _MDNSReplierAppendNameToResponse( + DataBuffer * inResponse, + const uint8_t * inName, + MRNameOffsetItem ** inNameOffsetListPtr ) +{ + OSStatus err; + const uint8_t * subname; + const uint8_t * limit; + size_t nameOffset; + MRNameOffsetItem * item; + uint8_t compressionPtr[ 2 ]; + + nameOffset = DataBuffer_GetLen( inResponse ); + + // 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 ] ) ) + { + 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( subname[ 0 ] == 0 ) + { + err = DataBuffer_Append( inResponse, "", 1 ); + require_noerr( err, exit ); + } + + // 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 ); +} + +//=========================================================================================================================== +// _MDNSReplierServiceTypeMatch +//=========================================================================================================================== + +static Boolean + _MDNSReplierServiceTypeMatch( + const MDNSReplierContext * inContext, + const uint8_t * inName, + unsigned int * outTXTSize, + unsigned int * outCount ) +{ + 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 ); +} + +//=========================================================================================================================== +// _MDNSReplierServiceInstanceNameMatch +//=========================================================================================================================== + +static Boolean + _MDNSReplierServiceInstanceNameMatch( + const MDNSReplierContext * inContext, + const uint8_t * inName, + unsigned int * outIndex, + unsigned int * outTXTSize, + unsigned int * outCount ) +{ + 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 ); +} + +//=========================================================================================================================== +// _MDNSReplierAboutRecordNameMatch +//=========================================================================================================================== + +static Boolean _MDNSReplierAboutRecordNameMatch( const MDNSReplierContext *inContext, const uint8_t *inName ) +{ + const uint8_t * subname; + const uint8_t * const hostname = inContext->hostname; + int nameMatches = false; + + if( strnicmpx( &inName[ 1 ], inName[ 0 ], "about" ) != 0 ) goto exit; + subname = NextLabel( inName ); + + if( !MemIEqual( &subname[ 1 ], subname[ 0 ], &hostname[ 1 ], hostname[ 0 ] ) ) goto exit; + subname = NextLabel( subname ); + + if( !DomainNameEqual( subname, kLocalName ) ) goto exit; + nameMatches = true; + +exit: + return( nameMatches ? true : false ); +} + +//=========================================================================================================================== +// _MDNSReplierHostnameMatch +//=========================================================================================================================== + +static Boolean + _MDNSReplierHostnameMatch( + const MDNSReplierContext * inContext, + const uint8_t * inName, + unsigned int * outIndex ) +{ + OSStatus err; + const uint8_t * ptr; + const uint8_t * end; + uint32_t index; + 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( *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; + } + + if( !DomainNameEqual( ptr, kLocalName ) ) goto exit; + nameMatches = true; + + if( outIndex ) *outIndex = index; + +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 ]; + + require_action_quiet( inSize > 0, exit, err = kSizeErr ); + + txt = (uint8_t *) malloc( inSize ); + require_action( txt, exit, err = kNoMemoryErr ); + + hash = FNV1( inRecordName, DomainNameLength( inRecordName ) ); + + txtStr[ 0 ] = 15; + n = MemPrintF( &txtStr[ 1 ], 15, "hash=0x%08X", hash ); + check( n == 15 ); + + 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: + return( err ); +} + +//=========================================================================================================================== +// _MRResourceRecordCreate +//=========================================================================================================================== + +static OSStatus + _MRResourceRecordCreate( + uint8_t * inName, + uint16_t inType, + uint16_t inClass, + uint32_t inTTL, + uint16_t inRDLength, + uint8_t * inRData, + MRResourceRecord ** outRecord ) +{ + 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 ); + } + + *outRecord = obj; + obj = NULL; + err = kNoErr; + +exit: + FreeNullSafe( obj ); + return( err ); +} + +//=========================================================================================================================== +// _MRResourceRecordFree +//=========================================================================================================================== + +static void _MRResourceRecordFree( MRResourceRecord *inRecord ) +{ + ForgetMem( &inRecord->name ); + ForgetMem( &inRecord->rdata ); + free( inRecord ); +} + +//=========================================================================================================================== +// _MRResourceRecordFreeList +//=========================================================================================================================== + +static void _MRResourceRecordFreeList( MRResourceRecord *inList ) +{ + MRResourceRecord * record; + + while( ( record = inList ) != NULL ) + { + inList = record->next; + _MRResourceRecordFree( record ); + } +} + +//=========================================================================================================================== +// _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 _MRNameOffsetItemFreeList( MRNameOffsetItem *inList ) +{ + MRNameOffsetItem * item; + + while( ( item = inList ) != NULL ) + { + inList = item->next; + _MRNameOffsetItemFree( item ); + } +} + +//=========================================================================================================================== // GAIPerfCmd //=========================================================================================================================== -#define kGAIPerfStandardTTL ( 1 * kSecondsPerHour ) +#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 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. + 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; } GAITestItemResult; -typedef void ( *GAITesterEventHandler_f )( GAITesterEventType inType, void *inContext ); +typedef void ( *GAITesterStopHandler_f )( void *inContext, OSStatus inError ); typedef void ( *GAITesterResultsHandler_f )( const char * inCaseTitle, - MicroTime64 inCaseStartTime, - MicroTime64 inCaseEndTime, - const GAITestItemResult * inResults, + NanoTime64 inCaseStartTime, + NanoTime64 inCaseEndTime, + const GAITestItemResult * inResultArray, size_t inResultCount, - size_t inItemCount, void * inContext ); typedef unsigned int GAITestAddrType; @@ -8322,43 +10404,35 @@ typedef unsigned int GAITestAddrType; #define GAITestAddrTypeIsValid( X ) \ ( ( (X) & kGAITestAddrType_Both ) && ( ( (X) & ~kGAITestAddrType_Both ) == 0 ) ) -typedef enum -{ - kGAIPerfOutputFormat_JSON = 1, - kGAIPerfOutputFormat_XML = 2, - kGAIPerfOutputFormat_Binary = 3 - -} GAIPerfOutputFormatType; - typedef struct { - 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. + 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. } 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 GAIPerfTesterStopHandler( void *inContext, OSStatus inError ); static void GAIPerfResultsHandler( const char * inCaseTitle, - MicroTime64 inCaseStartTime, - MicroTime64 inCaseEndTime, - const GAITestItemResult * inResults, + NanoTime64 inCaseStartTime, + NanoTime64 inCaseEndTime, + const GAITestItemResult * inResultArray, size_t inResultCount, - size_t inItemCount, void * inContext ); static void GAIPerfSignalHandler( void *inContext ); @@ -8369,14 +10443,16 @@ static OSStatus int inCallDelayMs, int inServerDelayMs, int inServerDefaultTTL, + Boolean inSkipPathEvaluation, + Boolean inBadUDPMode, GAITesterRef * outTester ); static void GAITesterStart( GAITesterRef inTester ); static void GAITesterStop( GAITesterRef inTester ); -static void GAITesterAddCase( GAITesterRef inTester, GAITestCase *inCase ); +static OSStatus GAITesterAddTestCase( GAITesterRef inTester, GAITestCase *inCase ); static void - GAITesterSetEventHandler( + GAITesterSetStopHandler( GAITesterRef inTester, - GAITesterEventHandler_f inEventHandler, + GAITesterStopHandler_f inEventHandler, void * inEventContext ); static void GAITesterSetResultsHandler( @@ -8384,7 +10460,7 @@ static void GAITesterResultsHandler_f inResultsHandler, void * inResultsContext ); -static OSStatus GAITestCaseCreate( const char *inTitle, unsigned int inTimeLimitMs, GAITestCase **outSet ); +static OSStatus GAITestCaseCreate( const char *inTitle, GAITestCase **outCase ); static void GAITestCaseFree( GAITestCase *inCase ); static OSStatus GAITestCaseAddItem( @@ -8394,35 +10470,44 @@ static OSStatus 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 OSStatus GAITestCaseAddLocalHostItem( GAITestCase *inCase, GAITestAddrType inWantAddrs, unsigned int inItemCount ); - -#define kGAIPerfTestSuite_Basic 1 -#define kGAIPerfTestSuite_Advanced 2 static void GAIPerfCmd( void ) { OSStatus err; - GAIPerfContext * context; - int suiteValue; + GAIPerfContext * context = NULL; - context = (GAIPerfContext *) calloc( 1, sizeof( *context ) ); - require_action( context, exit, err = kNoMemoryErr ); + err = CheckRootUser(); + require_noerr_quiet( err, exit ); - context->caseResults = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks ); - require_action( context->caseResults, exit, err = kNoMemoryErr ); + err = CheckIntegerArgument( gGAIPerf_CallDelayMs, "call delay (ms)", 0, INT_MAX ); + 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( gGAIPerf_ServerDelayMs, "server delay (ms)", 0, INT_MAX ); + require_noerr_quiet( err, exit ); + + err = CheckIntegerArgument( gGAIPerf_IterationCount, "iteration count", 1, INT_MAX ); 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; + context = (GAIPerfContext *) calloc( 1, sizeof( *context ) ); + require_action( context, exit, err = kNoMemoryErr ); + + context->testCaseResults = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks ); + require_action( context->testCaseResults, exit, err = kNoMemoryErr ); + + 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 ) { @@ -8430,35 +10515,36 @@ static void GAIPerfCmd( void ) require_action( context->outputFilePath, exit, err = kNoMemoryErr ); } + 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 ); + err = GAITesterCreate( dispatch_get_main_queue(), (int) context->callDelayMs, (int) context->serverDelayMs, - kGAIPerfStandardTTL, &context->tester ); + kGAIPerfStandardTTL, context->skipPathEval, context->badUDPMode, &context->tester ); require_noerr( err, exit ); check( gGAIPerf_TestSuite ); - suiteValue = CLIArgToValue( "suite", gGAIPerf_TestSuite, &err, - "basic", kGAIPerfTestSuite_Basic, - "advanced", kGAIPerfTestSuite_Advanced, - NULL ); - require_noerr_quiet( err, exit ); - - switch( suiteValue ) + if( strcasecmp( gGAIPerf_TestSuite, kGAIPerfTestSuiteName_Basic ) == 0 ) { - case kGAIPerfTestSuite_Basic: - err = GAIPerfAddBasicTestCases( context ); - require_noerr( err, exit ); - break; - - case kGAIPerfTestSuite_Advanced: - err = GAIPerfAddAdvancedTestCases( context ); - require_noerr( err, exit ); - break; - - default: - err = kValueErr; - break; + err = GAIPerfAddBasicTestCases( context ); + require_noerr( err, exit ); + } + else if( strcasecmp( gGAIPerf_TestSuite, kGAIPerfTestSuiteName_Advanced ) == 0 ) + { + err = GAIPerfAddAdvancedTestCases( context ); + require_noerr( err, exit ); + } + else + { + FPrintF( stderr, "error: Invalid test suite name: %s.\n", gGAIPerf_TestSuite ); + err = kParamErr; + goto exit; } - GAITesterSetEventHandler( context->tester, GAIPerfEventHandler, context ); + GAITesterSetStopHandler( context->tester, GAIPerfTesterStopHandler, context ); GAITesterSetResultsHandler( context->tester, GAIPerfResultsHandler, context ); signal( SIGINT, SIG_IGN ); @@ -8476,7 +10562,7 @@ static void GAIPerfCmd( void ) exit: if( context ) GAIPerfContextFree( context ); - if( err ) exit( 1 ); + exit( 1 ); } //=========================================================================================================================== @@ -8486,7 +10572,7 @@ exit: static void GAIPerfContextFree( GAIPerfContext *inContext ) { ForgetCF( &inContext->tester ); - ForgetCF( &inContext->caseResults ); + ForgetCF( &inContext->testCaseResults ); ForgetMem( &inContext->outputFilePath ); dispatch_source_forget( &inContext->sigIntSource ); dispatch_source_forget( &inContext->sigTermSource ); @@ -8513,11 +10599,6 @@ static void 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 @@ -8525,7 +10606,7 @@ static unsigned int static OSStatus GAIPerfAddAdvancedTestCases( GAIPerfContext *inContext ) { OSStatus err; - unsigned int aliasCount, addressCount, timeLimitMs, i; + unsigned int aliasCount, addressCount, i; GAITestCase * testCase = NULL; char title[ kTestCaseTitleBufferSize ]; @@ -8544,19 +10625,18 @@ static OSStatus GAIPerfAddAdvancedTestCases( GAIPerfContext *inContext ) _GAIPerfWriteTestCaseTitle( title, aliasCount, addressCount, addressCount, kGAITestAddrType_Both, inContext->defaultIterCount, true ); - timeLimitMs = _GAIPerfTimeLimitMs( inContext->callDelayMs, inContext->serverDelayMs, - inContext->defaultIterCount ); - err = GAITestCaseCreate( title, timeLimitMs, &testCase ); + 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, 1 ); + kGAITestAddrType_Both, kGAITestAddrType_Both, kGAIPerfGAITimeLimitMs, 1 ); require_noerr( err, exit ); } - GAITesterAddCase( inContext->tester, testCase ); + err = GAITesterAddTestCase( inContext->tester, testCase ); + require_noerr( err, exit ); testCase = NULL; // Add a test case to resolve a domain name with @@ -8570,35 +10650,34 @@ static OSStatus GAIPerfAddAdvancedTestCases( GAIPerfContext *inContext ) _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 ); + err = GAITestCaseCreate( title, &testCase ); require_noerr( err, exit ); err = GAITestCaseAddItem( testCase, aliasCount, addressCount, kGAIPerfStandardTTL, - kGAITestAddrType_Both, kGAITestAddrType_Both, inContext->defaultIterCount + 1 ); + kGAITestAddrType_Both, kGAITestAddrType_Both, kGAIPerfGAITimeLimitMs, inContext->defaultIterCount + 1 ); require_noerr( err, exit ); - GAITesterAddCase( inContext->tester, testCase ); + err = GAITesterAddTestCase( inContext->tester, testCase ); + require_noerr( err, exit ); testCase = NULL; } - if( aliasCount == 0 ) aliasCount = 1; - else aliasCount *= 2; + aliasCount = ( aliasCount == 0 ) ? 1 : ( 2 * aliasCount ); } // 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 ); + err = GAITestCaseCreate( title, &testCase ); require_noerr( err, exit ); - err = GAITestCaseAddLocalHostItem( testCase, kGAITestAddrType_Both, inContext->defaultIterCount ); + err = GAITestCaseAddLocalHostItem( testCase, kGAITestAddrType_Both, kGAIPerfGAITimeLimitMs, + inContext->defaultIterCount ); require_noerr( err, exit ); - GAITesterAddCase( inContext->tester, testCase ); + err = GAITesterAddTestCase( inContext->tester, testCase ); + require_noerr( err, exit ); testCase = NULL; exit: @@ -8646,21 +10725,6 @@ static void } //=========================================================================================================================== -// _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 //=========================================================================================================================== @@ -8672,7 +10736,7 @@ static OSStatus GAIPerfAddBasicTestCases( GAIPerfContext *inContext ) OSStatus err; GAITestCase * testCase = NULL; char title[ kTestCaseTitleBufferSize ]; - unsigned int timeLimitMs, i; + unsigned int i; // Test Case #1: // Resolve a domain name with @@ -8686,18 +10750,18 @@ static OSStatus GAIPerfAddBasicTestCases( GAIPerfContext *inContext ) kGAIPerfBasicTestSuite_AddrCount, kGAIPerfBasicTestSuite_AddrCount, kGAITestAddrType_Both, inContext->defaultIterCount, true ); - timeLimitMs = _GAIPerfTimeLimitMs( inContext->callDelayMs, inContext->serverDelayMs, inContext->defaultIterCount ); - err = GAITestCaseCreate( title, timeLimitMs, &testCase ); + err = GAITestCaseCreate( title, &testCase ); require_noerr( err, exit ); for( i = 0; i < inContext->defaultIterCount; ++i ) { err = GAITestCaseAddItem( testCase, kGAIPerfBasicTestSuite_AliasCount, kGAIPerfBasicTestSuite_AddrCount, - kGAIPerfStandardTTL, kGAITestAddrType_Both, kGAITestAddrType_Both, 1 ); + kGAIPerfStandardTTL, kGAITestAddrType_Both, kGAITestAddrType_Both, kGAIPerfGAITimeLimitMs, 1 ); require_noerr( err, exit ); } - GAITesterAddCase( inContext->tester, testCase ); + err = GAITesterAddTestCase( inContext->tester, testCase ); + require_noerr( err, exit ); testCase = NULL; // Test Case #2: @@ -8713,16 +10777,16 @@ static OSStatus GAIPerfAddBasicTestCases( GAIPerfContext *inContext ) 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 ); + err = GAITestCaseCreate( title, &testCase ); require_noerr( err, exit ); err = GAITestCaseAddItem( testCase, kGAIPerfBasicTestSuite_AliasCount, kGAIPerfBasicTestSuite_AddrCount, - kGAIPerfStandardTTL, kGAITestAddrType_Both, kGAITestAddrType_Both, inContext->defaultIterCount + 1 ); + kGAIPerfStandardTTL, kGAITestAddrType_Both, kGAITestAddrType_Both, kGAIPerfGAITimeLimitMs, + inContext->defaultIterCount + 1 ); require_noerr( err, exit ); - GAITesterAddCase( inContext->tester, testCase ); + err = GAITesterAddTestCase( inContext->tester, testCase ); + require_noerr( err, exit ); testCase = NULL; // Test Case #3: @@ -8730,14 +10794,15 @@ static OSStatus GAIPerfAddBasicTestCases( GAIPerfContext *inContext ) _GAIPerfWriteLocalHostTestCaseTitle( title, kGAITestAddrType_Both, inContext->defaultIterCount ); - timeLimitMs = _GAIPerfTimeLimitMs( inContext->callDelayMs, 0, inContext->defaultIterCount ); - err = GAITestCaseCreate( title, timeLimitMs, &testCase ); + err = GAITestCaseCreate( title, &testCase ); require_noerr( err, exit ); - err = GAITestCaseAddLocalHostItem( testCase, kGAITestAddrType_Both, inContext->defaultIterCount ); + err = GAITestCaseAddLocalHostItem( testCase, kGAITestAddrType_Both, kGAIPerfGAITimeLimitMs, + inContext->defaultIterCount ); require_noerr( err, exit ); - GAITesterAddCase( inContext->tester, testCase ); + err = GAITesterAddTestCase( inContext->tester, testCase ); + require_noerr( err, exit ); testCase = NULL; exit: @@ -8746,112 +10811,57 @@ exit: } //=========================================================================================================================== -// GAIPerfEventHandler +// GAIPerfTesterStopHandler //=========================================================================================================================== -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 kGAIPerfResultsKey_TestCases CFSTR( "testCases" ) +#define kGAIPerfResultsKey_Success CFSTR( "success" ) -#define kGAIPerfInfoKey_CallDelay CFSTR( "callDelayMs" ) -#define kGAIPerfInfoKey_ServerDelay CFSTR( "serverDelayMs" ) +#define kGAIPerfInfoKey_CallDelay CFSTR( "callDelayMs" ) +#define kGAIPerfInfoKey_ServerDelay CFSTR( "serverDelayMs" ) +#define kGAIPerfInfoKey_SkippedPathEval CFSTR( "skippedPathEval" ) +#define kGAIPerfInfoKey_UsedBadUDPMode CFSTR( "usedBadUPDMode" ) -static void _GAIPerfOutputResultsAndExit( GAIPerfContext *inContext ) +static void GAIPerfTesterStopHandler( void *inContext, OSStatus inError ) { - OSStatus err; - CFPropertyListRef plist = NULL; - CFDataRef results = NULL; - FILE * file = NULL; + OSStatus err; + GAIPerfContext * const context = (GAIPerfContext *) inContext; + CFPropertyListRef plist; + int exitCode; + + err = inError; + require_noerr_quiet( err, exit ); err = CFPropertyListCreateFormatted( kCFAllocatorDefault, &plist, "{" - "%kO=%O" - "%kO=" + "%kO=" // info "{" - "%kO=%lli" - "%kO=%lli" + "%kO=%lli" // callDelayMs + "%kO=%lli" // serverDelayMs + "%kO=%b" // skippedPathEval + "%kO=%b" // usedBadUPDMode "}" + "%kO=%O" // testCases + "%kO=%b" // success "}", - kGAIPerfResultsKey_TestCases, inContext->caseResults, kGAIPerfResultsKey_Info, - kGAIPerfInfoKey_CallDelay, (int64_t) inContext->callDelayMs, - kGAIPerfInfoKey_ServerDelay, (int64_t) inContext->serverDelayMs ); + 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 ); - // Convert results to a specific format. - - switch( inContext->outputFormat ) - { - case kGAIPerfOutputFormat_JSON: - results = CFCreateJSONData( plist, kJSONFlags_None, NULL ); - require_action( results, exit, err = kUnknownErr ); - break; - - case kGAIPerfOutputFormat_XML: - results = CFPropertyListCreateData( NULL, plist, kCFPropertyListXMLFormat_v1_0, 0, NULL ); - require_action( results, exit, err = kUnknownErr ); - break; - - case kGAIPerfOutputFormat_Binary: - results = CFPropertyListCreateData( NULL, plist, kCFPropertyListBinaryFormat_v1_0, 0, NULL ); - require_action( results, exit, err = kUnknownErr ); - break; - - default: - err = kValueErr; - goto exit; - } - - // Write formatted results to file or stdout. - - if( inContext->outputFilePath ) - { - file = fopen( inContext->outputFilePath, "wb" ); - err = map_global_value_errno( file, file ); - require_noerr( err, exit ); - } - else - { - file = stdout; - } - - err = WriteANSIFile( file, CFDataGetBytePtr( results ), (size_t) CFDataGetLength( results ) ); + err = OutputPropertyList( plist, context->outputFormat, context->appendNewline, context->outputFilePath ); + CFRelease( plist ); require_noerr( err, exit ); - // Write a trailing newline for JSON-formatted results if requested. - - if( ( inContext->outputFormat == kGAIPerfOutputFormat_JSON ) && inContext->appendNewLine ) - { - err = WriteANSIFile( file, "\n", 1 ); - require_noerr( err, exit ); - } - exit: - CFReleaseNullSafe( plist ); - CFReleaseNullSafe( results ); - if( file && ( file != stdout ) ) fclose( file ); - GAIPerfContextFree( inContext ); - exit( err ? 1 : 0 ); + exitCode = err ? 1 : ( context->testFailed ? 2 : 0 ); + GAIPerfContextFree( context ); + exit( exitCode ); } //=========================================================================================================================== @@ -8861,13 +10871,12 @@ exit: // Keys for test case dictionary #define kGAIPerfTestCaseKey_Title CFSTR( "title" ) -#define kGAIPerfTestCaseKey_StartTime CFSTR( "startTimeUs" ) -#define kGAIPerfTestCaseKey_EndTime CFSTR( "endTimeUs" ) +#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" ) -#define kGAIPerfTestCaseKey_TimedOut CFSTR( "timedOut" ) // Keys for test case results array entry dictionaries @@ -8899,27 +10908,29 @@ typedef struct static void GAIPerfResultsHandler( const char * inCaseTitle, - MicroTime64 inCaseStartTime, - MicroTime64 inCaseEndTime, - const GAITestItemResult * inResults, + NanoTime64 inCaseStartTime, + NanoTime64 inCaseEndTime, + const GAITestItemResult * inResultArray, size_t inResultCount, - size_t inItemCount, void * inContext ) { - 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. + 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; // 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. + // 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; @@ -8937,18 +10948,8 @@ static void 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 ); + startIndex = ( ( inResultCount > 0 ) && namesAreDynamic && !namesAreUnique ) ? 1 : 0; + results = CFArrayCreateMutable( NULL, (CFIndex)( inResultCount - startIndex ), &kCFTypeArrayCallBacks ); require_action( results, exit, err = kNoMemoryErr ); GAIPerfStatsInit( &stats ); @@ -8958,35 +10959,51 @@ static void sum = 0.0; firstSum = 0.0; connSum = 0.0; - for( i = startIndex; i < count; ++i ) + count = 0; + for( i = startIndex; i < inResultCount; ++i ) { - value = (double) inResults[ i ].timeUs; - if( value < stats.min ) stats.min = value; - if( value > stats.max ) stats.max = value; - sum += value; - - value = (double) inResults[ i ].firstTimeUs; - if( value < firstStats.min ) firstStats.min = value; - if( value > firstStats.max ) firstStats.max = value; - firstSum += value; + double value; - value = (double) inResults[ i ].connectionTimeUs; - if( value < connStats.min ) connStats.min = value; - if( value > connStats.max ) connStats.max = value; - connSum += value; + result = &inResultArray[ i ]; err = CFPropertyListAppendFormatted( kCFAllocatorDefault, results, "{" - "%kO=%s" - "%kO=%lli" - "%kO=%lli" - "%kO=%lli" + "%kO=%s" // name + "%kO=%lli" // connectionTimeUs + "%kO=%lli" // firstTimeUs + "%kO=%lli" // timeUs + "%kO=%lli" // error "}", - kGAIPerfTestCaseResultKey_Name, inResults[ i ].name, - kGAIPerfTestCaseResultKey_ConnectionTime, inResults[ i ].connectionTimeUs, - kGAIPerfTestCaseResultKey_FirstTime, inResults[ i ].firstTimeUs, - kGAIPerfTestCaseResultKey_Time, inResults[ i ].timeUs ); + 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 ) @@ -8998,15 +11015,20 @@ static void sum = 0.0; firstSum = 0.0; connSum = 0.0; - for( i = startIndex; i < count; ++i ) + for( i = startIndex; i < inResultCount; ++i ) { - diff = stats.mean - (double) inResults[ i ].timeUs; + double diff; + + result = &inResultArray[ i ]; + if( result->error ) continue; + + diff = stats.mean - (double) result->timeUs; sum += ( diff * diff ); - diff = firstStats.mean - (double) inResults[ i ].firstTimeUs; + diff = firstStats.mean - (double) result->firstTimeUs; firstSum += ( diff * diff ); - diff = connStats.mean - (double) inResults[ i ].connectionTimeUs; + diff = connStats.mean - (double) result->connectionTimeUs; connSum += ( diff * diff ); } stats.stdDev = sqrt( sum / count ); @@ -9014,11 +11036,11 @@ static void connStats.stdDev = sqrt( connSum / count ); } - err = CFPropertyListAppendFormatted( kCFAllocatorDefault, context->caseResults, + err = CFPropertyListAppendFormatted( kCFAllocatorDefault, context->testCaseResults, "{" "%kO=%s" - "%kO=%lli" - "%kO=%lli" + "%kO=%s" + "%kO=%s" "%kO=%O" "%kO=" "{" @@ -9044,11 +11066,10 @@ static void "%kO=%f" "%kO=%f" "}" - "%kO=%b" "}", kGAIPerfTestCaseKey_Title, inCaseTitle, - kGAIPerfTestCaseKey_StartTime, (int64_t) inCaseStartTime, - kGAIPerfTestCaseKey_EndTime, (int64_t) inCaseEndTime, + kGAIPerfTestCaseKey_StartTime, _NanoTime64ToDateString( inCaseStartTime, startTimeStr, sizeof( startTimeStr ) ), + kGAIPerfTestCaseKey_EndTime, _NanoTime64ToDateString( inCaseEndTime, endTimeStr, sizeof( endTimeStr ) ), kGAIPerfTestCaseKey_Results, results, kGAIPerfTestCaseKey_Stats, kGAIPerfTestCaseStatsKey_Count, (int64_t) count, @@ -9067,12 +11088,12 @@ static void kGAIPerfTestCaseStatsKey_Min, connStats.min, kGAIPerfTestCaseStatsKey_Max, connStats.max, kGAIPerfTestCaseStatsKey_Mean, connStats.mean, - kGAIPerfTestCaseStatsKey_StdDev, connStats.stdDev, - kGAIPerfTestCaseKey_TimedOut, ( inResultCount < inItemCount ) ? true : false ); + kGAIPerfTestCaseStatsKey_StdDev, connStats.stdDev ); require_noerr( err, exit ); exit: CFReleaseNullSafe( results ); + if( err ) exit( 1 ); } //=========================================================================================================================== @@ -9083,37 +11104,31 @@ static void GAIPerfSignalHandler( void *inContext ) { GAIPerfContext * const context = (GAIPerfContext *) inContext; - context->gotSignal = true; - if( context->tester && context->testerStarted ) - { - GAITesterStop( context->tester ); - } - else - { - exit( 1 ); - } + if( !context->tester ) exit( 1 ); + GAITesterStop( context->tester ); + context->tester = NULL; } //=========================================================================================================================== // GAITesterCreate //=========================================================================================================================== -typedef enum -{ - kGAITestConnType_UseMainConnection = 1, - kGAITestConnType_OwnSharedConnection = 2 - -} GAITestConnType; +// 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. - 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. + 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. @@ -9122,33 +11137,33 @@ struct GAITestItem 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. + 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 mainRef; // Reference to the main shared DNS-SD connection. - DNSServiceRef opRef; // Reference to the current DNSServiceGetAddrInfo operation. + 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. - 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. + 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(). - dispatch_source_t caseTimer; // Timer for enforcing a test case time limits. + 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. - GAITesterEventHandler_f eventHandler; // User's event handler. - void * eventContext; // User's event handler context. + 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. @@ -9165,12 +11180,20 @@ struct GAITesterPrivate CF_CLASS_DEFINE( GAITester ); -static void _GAITesterRun( void *inContext ); +static void _GAITesterStartNextTest( GAITesterRef inTester ); static OSStatus _GAITesterCreatePacketCapture( pcap_t **outPCap ); +static void _GAITesterFirstGAITimeout( void *inContext ); static void _GAITesterTimeout( void *inContext ); -static void _GAITesterAdvanceCurrentItem( GAITesterRef inTester ); -static void _GAITesterAdvanceCurrentSet( GAITesterRef inTester ); -static void _GAITesterInitializeCurrentTest( GAITesterRef inTester ); +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, @@ -9181,7 +11204,7 @@ static void DNSSD_API const struct sockaddr * inSockAddr, uint32_t inTTL, void * inContext ); -static void _GAITesterCompleteCurrentTest( GAITesterRef inTester, Boolean inTimedOut ); +static void _GAITesterCompleteCurrentTest( GAITesterRef inTester, OSStatus inError ); #define ForgetPacketCapture( X ) ForgetCustom( X, pcap_close ) @@ -9191,8 +11214,9 @@ static OSStatus unsigned int inAddressCount, GAITestAddrType inHasAddrs, GAITestAddrType inWantAddrs, + unsigned int inTimeLimitMs, GAITestItem ** outItem ); -static OSStatus GAITestItemDuplicate( const GAITestItem *inItem, GAITestItem **outItem ); +static OSStatus GAITestItemDup( const GAITestItem *inItem, GAITestItem **outItem ); static void GAITestItemFree( GAITestItem *inItem ); static OSStatus @@ -9201,6 +11225,8 @@ static OSStatus int inCallDelayMs, int inServerDelayMs, int inServerDefaultTTL, + Boolean inSkipPathEvaluation, + Boolean inBadUDPMode, GAITesterRef * outTester ) { OSStatus err; @@ -9213,6 +11239,8 @@ static OSStatus obj->serverPID = -1; obj->serverDelayMs = inServerDelayMs; obj->serverDefaultTTL = inServerDefaultTTL; + obj->skipPathEval = inSkipPathEvaluation; + obj->badUDPMode = inBadUDPMode; *outTester = obj; obj = NULL; @@ -9232,9 +11260,9 @@ static void _GAITesterFinalize( CFTypeRef inObj ) GAITesterRef const me = (GAITesterRef) inObj; GAITestCase * testCase; - check( !me->opRef ); - check( !me->mainRef ); - check( !me->caseTimer ); + check( !me->getAddrInfo ); + check( !me->connection ); + check( !me->timer ); dispatch_forget( &me->queue ); while( ( testCase = me->caseList ) != NULL ) { @@ -9248,7 +11276,7 @@ static void _GAITesterFinalize( CFTypeRef inObj ) //=========================================================================================================================== static void _GAITesterStart( void *inContext ); -static void _GAITesterStop( GAITesterRef me ); +static void _GAITesterStop( GAITesterRef me, OSStatus inError ); static void GAITesterStart( GAITesterRef me ) { @@ -9256,48 +11284,46 @@ static void GAITesterStart( GAITesterRef me ) dispatch_async_f( me->queue, me, _GAITesterStart ); } -extern char ** environ; +#define kGAITesterFirstGAITimeoutSecs 4 static void _GAITesterStart( void *inContext ) { OSStatus err; GAITesterRef const me = (GAITesterRef) inContext; - char * argv[ 4 ]; - char * ptr; - char * end; - char command[ 128 ]; + 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 ); - 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 ); + SNPrintF( name, sizeof( name ), "tag-gaitester-probe-%s.ipv4.d.test", + _RandomStringExact( kLowerAlphaNumericCharSet, kLowerAlphaNumericCharSetSize, sizeof( tag ) - 1, tag ) ); - argv[ 0 ] = "/bin/sh"; - argv[ 1 ] = "-c"; - argv[ 2 ] = command; - argv[ 3 ] = NULL; - err = posix_spawn( &me->serverPID, argv[ 0 ], NULL, NULL, argv, environ ); - require_noerr( err, exit ); - - me->currentCase = me->caseList; - me->currentItem = me->currentCase ? me->currentCase->itemList : NULL; - _GAITesterInitializeCurrentTest( me ); + flags = 0; + if( me->skipPathEval ) flags |= kDNSServiceFlagsPathEvaluationDone; - // 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. + err = DNSServiceGetAddrInfo( &me->getAddrInfo, flags, kDNSServiceInterfaceIndexAny, kDNSServiceProtocol_IPv4, name, + _GAITesterFirstGAICallback, me ); + require_noerr( err, exit ); - CFRetain( me ); - dispatch_after_f( dispatch_time_seconds( 3 ), me->queue, me, _GAITesterRun ); + err = DNSServiceSetDispatchQueue( me->getAddrInfo, me->queue ); + require_noerr( err, exit ); - CFRetain( me ); - me->started = true; - if( me->eventHandler ) me->eventHandler( kGAITesterEvent_Started, me->eventContext ); + 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 ); - CFRelease( me ); + if( err ) _GAITesterStop( me, err ); } //=========================================================================================================================== @@ -9316,53 +11342,61 @@ static void _GAITesterUserStop( void *inContext ) { GAITesterRef const me = (GAITesterRef) inContext; - _GAITesterStop( me ); + _GAITesterStop( me, kCanceledErr ); CFRelease( me ); } -static void _GAITesterStop( GAITesterRef me ) +static void _GAITesterStop( GAITesterRef me, OSStatus inError ) { OSStatus err; - DNSServiceForget( &me->opRef ); - DNSServiceForget( &me->mainRef ); ForgetPacketCapture( &me->pcap ); - dispatch_source_forget( &me->caseTimer ); + 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->eventHandler ) me->eventHandler( kGAITesterEvent_Stopped, me->eventContext ); - if( me->started ) CFRelease( me ); + if( me->stopHandler ) me->stopHandler( me->stopContext, inError ); + CFRelease( me ); } } //=========================================================================================================================== -// GAITesterAddCase +// GAITesterAddTestCase //=========================================================================================================================== -static void GAITesterAddCase( GAITesterRef me, GAITestCase *inCase ) +static OSStatus GAITesterAddTestCase( GAITesterRef me, GAITestCase *inCase ) { + OSStatus err; GAITestCase ** ptr; - for( ptr = &me->caseList; *ptr != NULL; ptr = &( *ptr )->next ) {} + require_action_quiet( inCase->itemList, exit, err = kCountErr ); + + for( ptr = &me->caseList; *ptr; ptr = &( *ptr )->next ) {} *ptr = inCase; + err = kNoErr; + +exit: + return( err ); } //=========================================================================================================================== -// GAITesterSetEventHandler +// GAITesterSetStopHandler //=========================================================================================================================== -static void GAITesterSetEventHandler( GAITesterRef me, GAITesterEventHandler_f inEventHandler, void *inEventContext ) +static void GAITesterSetStopHandler( GAITesterRef me, GAITesterStopHandler_f inStopHandler, void *inStopContext ) { - me->eventHandler = inEventHandler; - me->eventContext = inEventContext; + me->stopHandler = inStopHandler; + me->stopContext = inStopContext; } //=========================================================================================================================== @@ -9376,146 +11410,153 @@ static void GAITesterSetResultsHandler( GAITesterRef me, GAITesterResultsHandler } //=========================================================================================================================== -// _GAITesterRun +// _GAITesterStartNextTest //=========================================================================================================================== -static void _GAITesterRun( void *inContext ) +static void _GAITesterStartNextTest( GAITesterRef me ) { OSStatus err; - GAITesterRef const me = (GAITesterRef) inContext; GAITestItem * item; - GAITestItemResult * results = NULL; + DNSServiceFlags flags; + DNSServiceProtocol protocols; + int done = false; - require_action_quiet( !me->stopped, exit, err = kNoErr ); + if( me->currentItem ) me->currentItem = me->currentItem->next; - for( ;; ) + if( !me->currentItem ) { - item = me->currentItem; - if( item ) + if( me->currentCase ) { - DNSServiceProtocol protocols; - - check( !me->opRef ); - check( ( me->bitmapV4 != 0 ) || ( me->bitmapV6 != 0 ) ); + // No more test items means that the current test case has completed. - // Perform preliminary tasks if this is the start of a new test case. + me->caseEndTime = NanoTimeGetCurrent(); - if( item == me->currentCase->itemList ) + if( me->resultsHandler ) { - // Flush mDNSResponder's cache. - - err = systemf( NULL, "killall -HUP mDNSResponder" ); - require_noerr( err, exit ); - usleep( kMicrosecondsPerSecond ); - - // Start a packet capture. + size_t resultCount, i; + GAITestItemResult * resultArray; - check( !me->pcap ); - err = _GAITesterCreatePacketCapture( &me->pcap ); - require_noerr( err, exit ); + resultCount = 0; + for( item = me->currentCase->itemList; item; item = item->next ) ++resultCount; + check( resultCount > 0 ); - // Start the test case time limit timer. + resultArray = (GAITestItemResult *) calloc( resultCount, sizeof( *resultArray ) ); + require_action( resultArray, exit, err = kNoMemoryErr ); - check( !me->caseTimer ); - if( me->currentCase->timeLimitMs > 0 ) + item = me->currentCase->itemList; + for( i = 0; i < resultCount; ++i ) { - 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 ); + 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->caseStartTime = GetCurrentMicroTime(); + me->resultsHandler( me->currentCase->title, me->caseStartTime, me->caseEndTime, resultArray, resultCount, + me->resultsContext ); + ForgetMem( &resultArray ); } - // 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; + me->currentCase = me->currentCase->next; + if( !me->currentCase ) + { + done = true; + err = kNoErr; + goto exit; + } } 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 ); + 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: - FreeNullSafe( results ); - if( err ) _GAITesterStop( me ); - CFRelease( me ); + if( err || done ) _GAITesterStop( me, err ); } //=========================================================================================================================== @@ -9548,7 +11589,7 @@ static OSStatus _GAITesterCreatePacketCapture( pcap_t **outPCap ) 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, pcap_geterr( pcap ) ); + 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 ) ); @@ -9566,79 +11607,56 @@ exit: } //=========================================================================================================================== -// _GAITesterTimeout +// _GAITesterFirstGAITimeout //=========================================================================================================================== -static void _GAITesterTimeout( void *inContext ) +static void _GAITesterFirstGAITimeout( void *inContext ) { GAITesterRef const me = (GAITesterRef) inContext; - dispatch_source_forget( &me->caseTimer ); - - _GAITesterCompleteCurrentTest( me, true ); -} - -//=========================================================================================================================== -// _GAITesterAdvanceCurrentItem -//=========================================================================================================================== - -static void _GAITesterAdvanceCurrentItem( GAITesterRef me ) -{ - if( me->currentItem ) - { - me->currentItem = me->currentItem->next; - _GAITesterInitializeCurrentTest( me ); - } + _GAITesterStop( me, kNoResourcesErr ); } //=========================================================================================================================== -// _GAITesterAdvanceCurrentSet +// _GAITesterTimeout //=========================================================================================================================== -static void _GAITesterAdvanceCurrentSet( GAITesterRef me ) +static void _GAITesterTimeout( void *inContext ) { - if( me->currentCase ) - { - me->caseStartTime = 0; - me->caseEndTime = 0; - me->currentCase = me->currentCase->next; - if( me->currentCase ) - { - me->currentItem = me->currentCase->itemList; - _GAITesterInitializeCurrentTest( me ); - } - } + GAITesterRef const me = (GAITesterRef) inContext; + + _GAITesterCompleteCurrentTest( me, kTimeoutErr ); } //=========================================================================================================================== -// _GAITesterInitializeCurrentTest +// _GAITesterFirstGAICallback //=========================================================================================================================== -static void _GAITesterInitializeCurrentTest( GAITesterRef me ) +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 ) { - GAITestItem * const item = me->currentItem; + GAITesterRef const me = (GAITesterRef) inContext; - if( item ) + Unused( inSDRef ); + Unused( inInterfaceIndex ); + Unused( inHostname ); + Unused( inSockAddr ); + Unused( inTTL ); + + if( ( inFlags & kDNSServiceFlagsAdd ) && !inError ) { - check( item->addressCount > 0 ); - if( item->wantV4 ) - { - me->bitmapV4 = item->hasV4 ? ( ( UINT64_C( 1 ) << item->addressCount ) - 1 ) : 1; - } - else - { - me->bitmapV4 = 0; - } + dispatch_source_forget( &me->timer ); + DNSServiceForget( &me->getAddrInfo ); - if( item->wantV6 ) - { - me->bitmapV6 = item->hasV6 ? ( ( UINT64_C( 1 ) << item->addressCount ) - 1 ) : 1; - } - else - { - me->bitmapV6 = 0; - } - me->gotFirstResult = false; + _GAITesterStartNextTest( me ); } } @@ -9657,13 +11675,14 @@ static void DNSSD_API 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; - unsigned int addrOffset; + int hasAddr; Unused( inSDRef ); Unused( inInterfaceIndex ); @@ -9672,96 +11691,90 @@ static void DNSSD_API nowTicks = UpTicks(); - require_quiet( inFlags & kDNSServiceFlagsAdd, exit ); - require_quiet( !inError || ( inError == kDNSServiceErr_NoSuchRecord ), exit ); + require_action_quiet( inFlags & kDNSServiceFlagsAdd, exit, err = kFlagErr ); + + // Check if we were expecting an IP address result of this type. - bitmapPtr = NULL; - bitmask = 0; - if( ( sip->sa.sa_family == AF_INET ) && item->wantV4 ) + if( sip->sa.sa_family == AF_INET ) { - if( item->hasV4 ) + 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 ) { - if( !inError ) + const uint32_t addrV4 = ntohl( sip->v4.sin_addr.s_addr ); + + if( strcasecmp( item->name, "localhost." ) == 0 ) { - 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 + if( addrV4 == INADDR_LOOPBACK ) bitmask = 1; + } + else + { + addrOffset = addrV4 - kDNSServerBaseAddrV4; + if( ( addrOffset >= 1 ) && ( addrOffset <= item->addressCount ) ) { - addrOffset = addrV4 - kTestDNSServerBaseAddrV4; - if( ( addrOffset >= 1 ) && ( addrOffset <= item->addressCount ) ) - { - bitmask = UINT64_C( 1 ) << ( addrOffset - 1 ); - bitmapPtr = &me->bitmapV4; - } + bitmask = UINT64_C( 1 ) << ( addrOffset - 1 ); } } } - else if( inError == kDNSServiceErr_NoSuchRecord ) - { - bitmask = 1; - bitmapPtr = &me->bitmapV4; - } - } - else if( ( sip->sa.sa_family == AF_INET6 ) && item->wantV6 ) - { - if( item->hasV6 ) + else { - if( !inError ) + const uint8_t * const addrV6 = sip->v6.sin6_addr.s6_addr; + + if( strcasecmp( item->name, "localhost." ) == 0 ) { - 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 ) ) { - 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; - } + bitmask = UINT64_C( 1 ) << ( addrOffset - 1 ); } } } - else if( inError == kDNSServiceErr_NoSuchRecord ) - { - bitmask = 1; - bitmapPtr = &me->bitmapV6; - } } + 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 ); - if( bitmapPtr && ( *bitmapPtr & bitmask ) ) + *bitmapPtr &= ~bitmask; + if( !me->gotFirstResult ) { - *bitmapPtr &= ~bitmask; - if( !me->gotFirstResult ) - { - me->firstTicks = nowTicks; - me->gotFirstResult = true; - } - - if( ( me->bitmapV4 == 0 ) && ( me->bitmapV6 == 0 ) ) - { - me->endTicks = nowTicks; - _GAITesterCompleteCurrentTest( me, false ); - } + me->firstTicks = nowTicks; + me->gotFirstResult = true; } + err = kNoErr; exit: - return; + if( err || ( ( me->bitmapV4 == 0 ) && ( me->bitmapV6 == 0 ) ) ) + { + me->endTicks = nowTicks; + _GAITesterCompleteCurrentTest( me, err ); + } } //=========================================================================================================================== @@ -9775,55 +11788,48 @@ static OSStatus const uint8_t ** outMsgPtr, size_t * outMsgLen ); -static void _GAITesterCompleteCurrentTest( GAITesterRef me, Boolean inTimedOut ) +static void _GAITesterCompleteCurrentTest( GAITesterRef me, OSStatus inError ) { OSStatus err; - GAITestItem * item; + 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 timeStamps[ 4 ]; - struct timeval * tsPtr = &timeStamps[ 0 ]; - struct timeval * tsQ; - struct timeval * tsR; + struct timeval * t1; + struct timeval * t2; int64_t idleTimeUs; uint8_t name[ kDomainNameLengthMax ]; - DNSServiceForget( &me->opRef ); - DNSServiceForget( &me->mainRef ); + dispatch_source_forget( &me->timer ); + DNSServiceForget( &me->getAddrInfo ); + DNSServiceForget( &me->connection ); - if( inTimedOut ) + item->error = inError; + if( item->error ) { - 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 = kNoErr; + goto exit; } - item = me->currentItem; err = DomainNameFromString( name, item->name, NULL ); - require_noerr( err, exit ); + 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; - const DNSQuestionFixedFields * qfields; - unsigned int qtype; - uint8_t qname[ kDomainNameLengthMax ]; + 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; @@ -9836,13 +11842,10 @@ static void _GAITesterCompleteCurrentTest( GAITesterRef me, Boolean inTimedOut ) if( DNSHeaderGetQuestionCount( hdr ) < 1 ) continue; ptr = (const uint8_t *) &hdr[ 1 ]; - if( DNSMessageExtractDomainName( msgPtr, msgLen, ptr, qname, &ptr ) != kNoErr ) continue; + if( DNSMessageExtractQuestion( msgPtr, msgLen, ptr, qname, &qtype, &qclass, NULL ) != kNoErr ) continue; + if( qclass != kDNSServiceClass_IN ) 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 ) @@ -9877,15 +11880,19 @@ static void _GAITesterCompleteCurrentTest( GAITesterRef me, Boolean inTimedOut ) } } - if( tsQA && tsQAAAA ) tsQ = TIMEVAL_GT( *tsQA, *tsQAAAA ) ? tsQA : tsQAAAA; - else tsQ = tsQA ? tsQA : tsQAAAA; + // t1 is the time when the last query was sent. - if( tsRA && tsRAAAA ) tsR = TIMEVAL_LT( *tsRA, *tsRAAAA ) ? tsRA : tsRAAAA; - else tsR = tsQA ? tsQA : tsQAAAA; + if( tsQA && tsQAAAA ) t1 = TIMEVAL_GT( *tsQA, *tsQAAAA ) ? tsQA : tsQAAAA; + else t1 = tsQA ? tsQA : tsQAAAA; - if( tsQ && tsR ) + // 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( *tsR, *tsQ ); + idleTimeUs = TIMEVAL_USEC64_DIFF( *t2, *t1 ); if( idleTimeUs < 0 ) idleTimeUs = 0; } else @@ -9893,16 +11900,14 @@ static void _GAITesterCompleteCurrentTest( GAITesterRef me, Boolean inTimedOut ) idleTimeUs = 0; } - 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 ); + 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: - if( err ) _GAITesterStop( me ); + ForgetPacketCapture( &me->pcap ); + if( err ) _GAITesterStop( me, err ); + else _GAITesterStartNextTest( me ); } //=========================================================================================================================== @@ -9971,7 +11976,7 @@ exit: // GAITestCaseCreate //=========================================================================================================================== -static OSStatus GAITestCaseCreate( const char *inTitle, unsigned int inTimeLimitMs, GAITestCase **outSet ) +static OSStatus GAITestCaseCreate( const char *inTitle, GAITestCase **outCase ) { OSStatus err; GAITestCase * obj; @@ -9982,9 +11987,7 @@ static OSStatus GAITestCaseCreate( const char *inTitle, unsigned int inTimeLimit obj->title = strdup( inTitle ); require_action( obj->title, exit, err = kNoMemoryErr ); - obj->timeLimitMs = inTimeLimitMs; - - *outSet = obj; + *outCase = obj; obj = NULL; err = kNoErr; @@ -10014,13 +12017,6 @@ static void GAITestCaseFree( GAITestCase *inCase ) // GAITestCaseAddItem //=========================================================================================================================== -// 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 kUniqueStringCharSet "abcdefghijklmnopqrstuvwxyz0123456789" -#define kUniqueStringCharSetLen sizeof_string( kUniqueStringCharSet ) -#define kUniqueStringLen 6 - static OSStatus GAITestCaseAddItem( GAITestCase * inCase, @@ -10029,6 +12025,7 @@ static OSStatus int inTTL, GAITestAddrType inHasAddrs, GAITestAddrType inWantAddrs, + unsigned int inTimeLimitMs, unsigned int inItemCount ) { OSStatus err; @@ -10040,7 +12037,7 @@ static OSStatus char * end; unsigned int i; char name[ 64 ]; - char uniqueStr[ kUniqueStringLen + 1 ]; + char tag[ kGAITesterTagStringLen + 1 ]; require_action_quiet( inItemCount > 0, exit, err = kNoErr ); @@ -10068,29 +12065,21 @@ static OSStatus // Add Tag label. - RandomString( kUniqueStringCharSet, kUniqueStringCharSetLen, kUniqueStringLen, kUniqueStringLen, uniqueStr ); - SNPrintF_Add( &ptr, end, "tag-%s.", uniqueStr ); + SNPrintF_Add( &ptr, end, "tag-%s.", + _RandomStringExact( kLowerAlphaNumericCharSet, kLowerAlphaNumericCharSetSize, sizeof( tag ) - 1, tag ) ); // Add IPv4 or IPv6 label if necessary. - switch( inHasAddrs ) - { - case kGAITestAddrType_IPv4: - SNPrintF_Add( &ptr, end, "ipv4." ); - break; - - case kGAITestAddrType_IPv6: - SNPrintF_Add( &ptr, end, "ipv6." ); - break; - } + if( inHasAddrs == kGAITestAddrType_IPv4 ) SNPrintF_Add( &ptr, end, "ipv4." ); + else if( inHasAddrs == kGAITestAddrType_IPv6 ) SNPrintF_Add( &ptr, end, "ipv6." ); - // Add d.test. labels. + // Finally, add the d.test. labels. SNPrintF_Add( &ptr, end, "d.test." ); // Create item. - err = GAITestItemCreate( name, inAddressCount, inHasAddrs, inWantAddrs, &item ); + err = GAITestItemCreate( name, inAddressCount, inHasAddrs, inWantAddrs, inTimeLimitMs, &item ); require_noerr( err, exit ); newItemList = item; @@ -10100,7 +12089,7 @@ static OSStatus for( i = 1; i < inItemCount; ++i ) { - err = GAITestItemDuplicate( item, &item2 ); + err = GAITestItemDup( item, &item2 ); require_noerr( err, exit ); *itemPtr = item2; @@ -10126,7 +12115,12 @@ exit: // GAITestCaseAddLocalHostItem //=========================================================================================================================== -static OSStatus GAITestCaseAddLocalHostItem( GAITestCase *inCase, GAITestAddrType inWantAddrs, unsigned int inItemCount ) +static OSStatus + GAITestCaseAddLocalHostItem( + GAITestCase * inCase, + GAITestAddrType inWantAddrs, + unsigned int inTimeLimitMs, + unsigned int inItemCount ) { OSStatus err; GAITestItem * item; @@ -10137,7 +12131,7 @@ static OSStatus GAITestCaseAddLocalHostItem( GAITestCase *inCase, GAITestAddrTyp require_action_quiet( inItemCount > 1, exit, err = kNoErr ); - err = GAITestItemCreate( "localhost.", 1, kGAITestAddrType_Both, inWantAddrs, &item ); + err = GAITestItemCreate( "localhost.", 1, kGAITestAddrType_Both, inWantAddrs, inTimeLimitMs, &item ); require_noerr( err, exit ); newItemList = item; @@ -10147,7 +12141,7 @@ static OSStatus GAITestCaseAddLocalHostItem( GAITestCase *inCase, GAITestAddrTyp for( i = 1; i < inItemCount; ++i ) { - err = GAITestItemDuplicate( item, &item2 ); + err = GAITestItemDup( item, &item2 ); require_noerr( err, exit ); *itemPtr = item2; @@ -10177,6 +12171,7 @@ static OSStatus unsigned int inAddressCount, GAITestAddrType inHasAddrs, GAITestAddrType inWantAddrs, + unsigned int inTimeLimitMs, GAITestItem ** outItem ) { OSStatus err; @@ -10197,6 +12192,8 @@ static OSStatus 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; @@ -10208,10 +12205,10 @@ exit: } //=========================================================================================================================== -// GAITestItemDuplicate +// GAITestItemDup //=========================================================================================================================== -static OSStatus GAITestItemDuplicate( const GAITestItem *inItem, GAITestItem **outItem ) +static OSStatus GAITestItemDup( const GAITestItem *inItem, GAITestItem **outItem ) { OSStatus err; GAITestItem * obj; @@ -10247,6 +12244,2152 @@ static void GAITestItemFree( GAITestItem *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 //=========================================================================================================================== @@ -10972,13 +15115,6 @@ static void DNSConfigAddCmd( void ) CFStringRef key = NULL; Boolean success; - if( geteuid() != 0 ) - { - FPrintF( stderr, "error: This command must to be run as root.\n" ); - err = kIDErr; - goto exit; - } - // Create dictionary. dict = CFDictionaryCreateMutable( NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks ); @@ -11072,13 +15208,6 @@ static void DNSConfigRemoveCmd( void ) CFStringRef key = NULL; Boolean success; - if( geteuid() != 0 ) - { - FPrintF( stderr, "error: This command must to be run as root.\n" ); - err = kIDErr; - goto exit; - } - store = SCDynamicStoreCreate( NULL, CFSTR( "com.apple.dnssdutil" ), NULL, NULL ); err = map_scerror( store ); require_noerr( err, exit ); @@ -11218,6 +15347,34 @@ exit: } //=========================================================================================================================== +// 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 //=========================================================================================================================== @@ -11232,6 +15389,7 @@ static DNSServiceFlags GetDNSSDFlagsFromOpts( void ) kDNSServiceFlagsShareConnection ); } + if( gDNSSDFlag_AllowExpiredAnswers ) flags |= kDNSServiceFlagsAllowExpiredAnswers; if( gDNSSDFlag_BrowseDomains ) flags |= kDNSServiceFlagsBrowseDomains; if( gDNSSDFlag_DenyCellular ) flags |= kDNSServiceFlagsDenyCellular; if( gDNSSDFlag_DenyExpensive ) flags |= kDNSServiceFlagsDenyExpensive; @@ -11358,7 +15516,7 @@ static OSStatus InterfaceIndexFromArgString( const char *inString, uint32_t *out err = StringToUInt32( inString, &ifIndex ); if( err ) { - FPrintF( stderr, "Invalid interface value: %s\n", inString ); + FPrintF( stderr, "error: Invalid interface value: %s\n", inString ); err = kParamErr; goto exit; } @@ -11380,11 +15538,6 @@ exit: // RecordDataFromArgString //=========================================================================================================================== -#define kRDataMaxLen UINT16_C( 0xFFFF ) - -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 OSStatus RecordDataFromArgString( const char *inString, uint8_t **outDataPtr, size_t *outDataLen ) { OSStatus err; @@ -11411,7 +15564,7 @@ static OSStatus RecordDataFromArgString( const char *inString, uint8_t **outData err = CopyFileDataByPath( path, (char **) &dataPtr, &dataLen ); require_noerr( err, exit ); - require_action( dataLen <= kRDataMaxLen, exit, err = kSizeErr ); + require_action( dataLen <= kDNSRecordDataLengthMax, exit, err = kSizeErr ); } // Hexadecimal string @@ -11422,7 +15575,7 @@ static OSStatus RecordDataFromArgString( const char *inString, uint8_t **outData err = HexToDataCopy( str, kSizeCString, kHexToData_DefaultFlags, &dataPtr, &dataLen, NULL ); require_noerr( err, exit ); - require_action( dataLen <= kRDataMaxLen, exit, err = kSizeErr ); + require_action( dataLen <= kDNSRecordDataLengthMax, exit, err = kSizeErr ); } // IPv4 address string @@ -11451,7 +15604,7 @@ static OSStatus RecordDataFromArgString( const char *inString, uint8_t **outData { const char * const str = inString + sizeof_string( kRDataArgPrefix_SRV ); - err = StringToSRVRData( str, &dataPtr, &dataLen ); + err = CreateSRVRecordDataFromString( str, &dataPtr, &dataLen ); require_noerr( err, exit ); } @@ -11469,7 +15622,7 @@ static OSStatus RecordDataFromArgString( const char *inString, uint8_t **outData { success = ParseQuotedEscapedString( str, end, "", NULL, 0, NULL, &totalLen, NULL ); require_action( success, exit, err = kParamErr ); - require_action( totalLen <= kRDataMaxLen, exit, err = kSizeErr ); + require_action( totalLen <= kDNSRecordDataLengthMax, exit, err = kSizeErr ); dataLen = totalLen; dataPtr = (uint8_t *) malloc( dataLen ); @@ -11492,7 +15645,7 @@ static OSStatus RecordDataFromArgString( const char *inString, uint8_t **outData { const char * const str = inString + sizeof_string( kRDataArgPrefix_TXT ); - err = StringToTXTRData( str, ',', &dataPtr, &dataLen ); + err = CreateTXTRecordDataFromString( str, ',', &dataPtr, &dataLen ); require_noerr( err, exit ); } @@ -11515,95 +15668,6 @@ exit: 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 ]; - - 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 ); - } - - // 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 ); - - err = DataBuffer_Detach( &dataBuf, outPtr, outLen ); - require_noerr( err, exit ); - -exit: - DataBuffer_Free( &dataBuf ); - return( err ); -} - -static OSStatus StringToTXTRData( const char *inString, char inDelimiter, uint8_t **outPtr, size_t *outLen ) -{ - 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 ); - - src = inString; - for( ;; ) - { - uint8_t * dst = &txtStr[ 1 ]; - const uint8_t * const lim = &txtStr[ 256 ]; - int c; - - while( *src && ( *src != inDelimiter ) ) - { - if( ( c = *src++ ) == '\\' ) - { - require_action_quiet( *src != '\0', exit, err = kUnderrunErr ); - c = *src++; - } - require_action_quiet( dst < lim, exit, err = kOverrunErr ); - *dst++ = (uint8_t) c; - } - txtStr[ 0 ] = (uint8_t)( dst - &txtStr[ 1 ] ); - err = DataBuffer_Append( &dataBuf, txtStr, 1 + txtStr[ 0 ] ); - require_noerr( err, exit ); - - if( *src == '\0' ) break; - ++src; - } - - err = DataBuffer_Detach( &dataBuf, outPtr, outLen ); - require_noerr( err, exit ); - -exit: - DataBuffer_Free( &dataBuf ); - return( err ); -} - //=========================================================================================================================== // RecordTypeFromArgString //=========================================================================================================================== @@ -11805,8 +15869,6 @@ static const char * RecordTypeToString( unsigned int inValue ) // DNSMessageExtractDomainName //=========================================================================================================================== -#define IsCompressionByte( X ) ( ( ( X ) & 0xC0 ) == 0xC0 ) - static OSStatus DNSMessageExtractDomainName( const uint8_t * inMsgPtr, @@ -11901,6 +15963,38 @@ exit: } //=========================================================================================================================== +// 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 //=========================================================================================================================== @@ -11960,24 +16054,21 @@ exit: static OSStatus DNSMessageGetAnswerSection( const uint8_t *inMsgPtr, size_t inMsgLen, const uint8_t **outPtr ) { - OSStatus err; - const uint8_t * const msgEnd = inMsgPtr + inMsgLen; - unsigned int questionCount, i; - const DNSHeader * hdr; - const uint8_t * ptr; + 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 = (uint8_t *)( hdr + 1 ); + ptr = (const uint8_t *) &hdr[ 1 ]; for( i = 0; i < questionCount; ++i ) { - err = DNSMessageExtractDomainName( inMsgPtr, inMsgLen, ptr, NULL, &ptr ); + err = DNSMessageExtractQuestion( inMsgPtr, inMsgLen, ptr, NULL, NULL, NULL, &ptr ); require_noerr( err, exit ); - require_action_quiet( ( msgEnd - ptr ) >= 4, exit, err = kUnderrunErr ); - ptr += 4; } if( outPtr ) *outPtr = ptr; @@ -12042,15 +16133,15 @@ static OSStatus } else if( inRDataType == kDNSServiceType_SRV ) { - uint16_t priority, weight, port; - const uint8_t * target; + const SRVRecordDataFixedFields * fields; + const uint8_t * target; + unsigned int priority, weight, port; - require_action_quiet( ( rdataPtr + 6 ) < rdataEnd, exit, err = kMalformedErr ); + require_action_quiet( inRDataLen > sizeof( SRVRecordDataFixedFields ), exit, err = kMalformedErr ); - priority = ReadBig16( rdataPtr ); - weight = ReadBig16( rdataPtr + 2 ); - port = ReadBig16( rdataPtr + 4 ); - target = rdataPtr + 6; + fields = (const SRVRecordDataFixedFields *) rdataPtr; + SRVRecordDataFixedFieldsGet( fields, &priority, &weight, &port ); + target = (const uint8_t *) &fields[ 1 ]; if( inMsgPtr ) { @@ -12110,13 +16201,9 @@ static OSStatus require_noerr( err, exit ); } - require_action_quiet( ( ptr + 20 ) == rdataEnd, exit, err = kMalformedErr ); + require_action_quiet( ( rdataEnd - ptr ) == sizeof( SOARecordDataFixedFields ), exit, err = kMalformedErr ); - serial = ReadBig32( ptr ); - refresh = ReadBig32( ptr + 4 ); - retry = ReadBig32( ptr + 8 ); - expire = ReadBig32( ptr + 12 ); - minimum = ReadBig32( ptr + 16 ); + 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 ); @@ -12146,8 +16233,8 @@ static OSStatus { require_action_quiet( ( ptr + 2 ) < rdataEnd, exit, err = kMalformedErr ); - windowBlock = ptr[ 0 ]; - bitmapLen = ptr[ 1 ]; + windowBlock = ptr[ 0 ]; + bitmapLen = ptr[ 1 ]; bitmapPtr = &ptr[ 2 ]; require_action_quiet( ( bitmapLen >= 1 ) && ( bitmapLen <= 32 ) , exit, err = kMalformedErr ); @@ -12306,6 +16393,52 @@ static size_t DomainNameLength( const uint8_t * const inName ) } //=========================================================================================================================== +// 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 //=========================================================================================================================== @@ -12409,15 +16542,15 @@ static OSStatus const Boolean inPrintRaw, char ** outText ) { - 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 ]; + 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 ) @@ -12453,28 +16586,21 @@ static OSStatus ptr = (const uint8_t *) &hdr[ 1 ]; for( i = 0; i < questionCount; ++i ) { - unsigned int qtype, qclass; - Boolean isQU; + uint16_t qtype, qclass; + Boolean isQU; - err = DNSMessageExtractDomainNameString( inMsgPtr, inMsgLen, ptr, nameStr, &ptr ); + err = DNSMessageExtractQuestion( inMsgPtr, inMsgLen, ptr, name, &qtype, &qclass, &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; + err = DomainNameToString( name, NULL, nameStr, NULL ); + require_noerr( err, exit ); isQU = ( inMDNS && ( qclass & kQClassUnicastResponseBit ) ) ? true : false; if( inMDNS ) qclass &= ~kQClassUnicastResponseBit; if( i == 0 ) _Append( "\nQUESTION SECTION\n" ); - _Append( "%s %2s %?2s%?2u %-5s\n", + _Append( "%-30s %2s %?2s%?2u %-5s\n", nameStr, inMDNS ? ( isQU ? "QU" : "QM" ) : "", ( qclass == kDNSServiceClass_IN ), "IN", ( qclass != kDNSServiceClass_IN ), qclass, RecordTypeToString( qtype ) ); } @@ -12489,7 +16615,6 @@ static OSStatus 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 ); @@ -12751,7 +16876,15 @@ static const char * ServiceTypeDescription( const char *inName ) for( serviceType = kServiceTypes; serviceType < end; ++serviceType ) { - if( strcasecmp( inName, serviceType->name ) == 0 ) return( serviceType->description ); + if( ( stricmp_prefix( inName, serviceType->name ) == 0 ) ) + { + const size_t len = strlen( serviceType->name ); + + if( ( inName[ len ] == '\0' ) || ( strcmp( &inName[ len ], "." ) == 0 ) ) + { + return( serviceType->description ); + } + } } return( NULL ); } @@ -12852,28 +16985,31 @@ exit: return( err ); } +#if( TARGET_OS_DARWIN ) //=========================================================================================================================== -// StringToLongLong +// StringToPID //=========================================================================================================================== -static OSStatus StringToLongLong( const char *inString, long long *outValue ) +static OSStatus StringToPID( const char *inString, pid_t *outPID ) { OSStatus err; long long value; char * endPtr; set_errno_compat( 0 ); - value = strtol( inString, &endPtr, 0 ); + value = strtoll( 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 ); + 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 ); - *outValue = value; + *outPID = (pid_t) value; err = kNoErr; exit: return( err ); } +#endif //=========================================================================================================================== // StringToARecordData @@ -12999,17 +17135,3252 @@ exit: #endif //=========================================================================================================================== -// GetCurrentMicroTime +// GetMDNSMulticastAddrV4 //=========================================================================================================================== -static MicroTime64 GetCurrentMicroTime( void ) +static void _MDNSMulticastAddrV4Init( void *inContext ); + +static const struct sockaddr * GetMDNSMulticastAddrV4( void ) { - struct timeval now; + static struct sockaddr_in sMDNSMulticastAddrV4; + static dispatch_once_t sMDNSMulticastAddrV4InitOnce = 0; + + dispatch_once_f( &sMDNSMulticastAddrV4InitOnce, &sMDNSMulticastAddrV4, _MDNSMulticastAddrV4Init); + return( (const struct sockaddr *) &sMDNSMulticastAddrV4 ); +} + +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 +} + +//=========================================================================================================================== +// GetMDNSMulticastAddrV6 +//=========================================================================================================================== + +static void _MDNSMulticastAddrV6Init( void *inContext ); + +static const struct sockaddr * GetMDNSMulticastAddrV6( void ) +{ + 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; + + 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 ); + + for( ifa = ifaList; ifa; ifa = ifa->ifa_next ) + { + 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 ) + { + 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; + } + if( ifa2 ) + { + ifname = ifa->ifa_name; + break; + } + } + require_action_quiet( ifname, exit, err = kNotFoundErr ); + + if( inNameBuf ) strlcpy( inNameBuf, ifname, IF_NAMESIZE + 1 ); + if( outIndex ) *outIndex = if_nametoindex( ifname ); + +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; + + require_action_quiet( ( family == AF_INET ) ||( family == AF_INET6 ), exit, err = kUnsupportedErr ); + + 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 = 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 + { + 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 ); + } + + if( inJoin ) + { + err = SocketJoinMulticast( sock, inAddr, inIfName, inIfIndex ); + require_noerr_quiet( err, exit ); + } + + if( outPort ) *outPort = port; + *outSock = sock; + sock = kInvalidSocketRef; + +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; + + require_action_quiet( ( ptr < inEnd ) && isdigit_safe( *ptr ), exit, err = kMalformedErr ); + + value = (uint64_t)( *ptr++ - '0' ); + if( value == 0 ) + { + 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 ); + } + } + + *outValue = (uint32_t) value; + if( outPtr ) *outPtr = ptr; + err = kNoErr; + +exit: + return( err ); +} + +//=========================================================================================================================== +// CheckIntegerArgument +//=========================================================================================================================== + +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 ); +} + +//=========================================================================================================================== +// CheckDoubleArgument +//=========================================================================================================================== + +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 ); +} + +//=========================================================================================================================== +// CheckRootUser +//=========================================================================================================================== + +static OSStatus CheckRootUser( void ) +{ + if( geteuid() == 0 ) return( kNoErr ); + + 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; + + command = NULL; + va_start( args, inFormat ); + VASPrintF( &command, inFormat, args ); + va_end( args ); + require_action( command, exit, err = kUnknownErr ); + + 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( outPID ) *outPID = pid; + +exit: + return( err ); +} + +//=========================================================================================================================== +// OutputPropertyList +//=========================================================================================================================== + +static OSStatus + OutputPropertyList( + CFPropertyListRef inPList, + OutputFormatType inType, + Boolean inAppendNewline, + const char * inOutputFilePath ) +{ + OSStatus err; + CFDataRef results = NULL; + FILE * file = NULL; + + // Convert plist to a specific format. + + 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; + } + + // Write formatted results to file or stdout. + + if( inOutputFilePath ) + { + file = fopen( inOutputFilePath, "wb" ); + err = map_global_value_errno( file, file ); + require_noerr( err, exit ); + } + else + { + file = stdout; + } + + err = WriteANSIFile( file, CFDataGetBytePtr( results ), (size_t) CFDataGetLength( results ) ); + require_noerr_quiet( err, exit ); + + // Write a trailing newline for JSON-formatted results if requested. + + if( ( inType == kOutputFormatType_JSON ) && inAppendNewline ) + { + err = WriteANSIFile( file, "\n", 1 ); + require_noerr_quiet( err, exit ); + } + +exit: + 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 ); +} + +//=========================================================================================================================== +// SRVRecordDataFixedFieldsSet +//=========================================================================================================================== + +static void + SRVRecordDataFixedFieldsSet( + SRVRecordDataFixedFields * inFields, + uint16_t inPriority, + uint16_t inWeight, + uint16_t inPort ) +{ + WriteBig16( inFields->priority, inPriority ); + WriteBig16( inFields->weight, inWeight ); + WriteBig16( inFields->port, inPort ); +} + +//=========================================================================================================================== +// 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; + 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 ]; + + 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 ); + } + + // 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 ); + + err = DataBuffer_Detach( &dataBuf, outPtr, outLen ); + require_noerr( err, exit ); + +exit: + DataBuffer_Free( &dataBuf ); + return( err ); +} + +//=========================================================================================================================== +// CreateTXTRecordDataFromString +//=========================================================================================================================== + +static OSStatus CreateTXTRecordDataFromString(const char *inString, int inDelimiter, uint8_t **outPtr, size_t *outLen ) +{ + 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, kDNSRecordDataLengthMax ); + + src = inString; + for( ;; ) + { + uint8_t * dst = &txtStr[ 1 ]; + const uint8_t * const lim = &txtStr[ 256 ]; + int c; + + while( *src && ( *src != inDelimiter ) ) + { + if( ( c = *src++ ) == '\\' ) + { + require_action_quiet( *src != '\0', exit, err = kUnderrunErr ); + c = *src++; + } + require_action_quiet( dst < lim, exit, err = kOverrunErr ); + *dst++ = (uint8_t) c; + } + txtStr[ 0 ] = (uint8_t)( dst - &txtStr[ 1 ] ); + err = DataBuffer_Append( &dataBuf, txtStr, 1 + txtStr[ 0 ] ); + require_noerr( err, exit ); + + if( *src == '\0' ) break; + ++src; + } + + err = DataBuffer_Detach( &dataBuf, outPtr, outLen ); + require_noerr( err, exit ); + +exit: + DataBuffer_Free( &dataBuf ); + return( err ); +} + +//=========================================================================================================================== +// CreateNSECRecordData +//=========================================================================================================================== + +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; + 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 ]; + + va_start( args, inTypeCount ); + DataBuffer_Init( &rdataDB, NULL, 0, kDNSRecordDataLengthMax ); + + // Append Next Domain Name. + + err = DataBuffer_Append( &rdataDB, inNextDomainName, DomainNameLength( inNextDomainName ) ); + require_noerr( err, exit ); + + // Append Type Bit Maps. + + maxBit = 0; + memset( bitmap, 0, kNSECBitmapMaxLength ); + if( inTypeCount > 0 ) + { + array = (unsigned int *) malloc( inTypeCount * sizeof_element( array ) ); + require_action( array, exit, err = kNoMemoryErr ); + + 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 ); + + currBlock = array[ 0 ] / 256; + for( i = 0; i < inTypeCount; ++i ) + { + const unsigned int block = array[ i ] / 256; + const unsigned int bit = array[ i ] % 256; + + 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 + { + currBlock = 0; + } + + 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 ); + + err = DataBuffer_Detach( &rdataDB, outPtr, outLen ); + require_noerr( err, exit ); + +exit: + va_end( args ); + DataBuffer_Free( &rdataDB ); + FreeNullSafe( array ); + return( err ); +} + +//=========================================================================================================================== +// AppendSOARecord +//=========================================================================================================================== + +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( inDB ) + { + err = _DataBuffer_AppendDNSRecord( inDB, inNamePtr, inNameLen, inType, inClass, inTTL, NULL, 0 ); + require_noerr( err, exit ); + + rdlengthOffset = DataBuffer_GetLen( inDB ) - 2; + } + + err = _AppendSOARecordData( inDB, inMName, inRName, inSerial, inRefresh, inRetry, inExpire, inMinimumTTL, &rdataLen ); + require_noerr( err, exit ); + + if( inDB ) + { + rdlengthPtr = DataBuffer_GetPtr( inDB ) + rdlengthOffset; + WriteBig16( rdlengthPtr, rdataLen ); + } + + if( outLen ) *outLen = inNameLen + sizeof( DNSRecordFixedFields ) + rdataLen; + err = kNoErr; + +exit: + return( err ); +} + +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; + SOARecordDataFixedFields fields; + const size_t mnameLen = DomainNameLength( inMName ); + const size_t rnameLen = DomainNameLength( inRName ); + + 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: + return( err ); +} + +//=========================================================================================================================== +// CreateSOARecordData +//=========================================================================================================================== + +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 ) +{ + OSStatus err; + DataBuffer rdataDB; + + 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 ); +} + +//=========================================================================================================================== +// _DataBuffer_AppendDNSQuestion +//=========================================================================================================================== + +static OSStatus + _DataBuffer_AppendDNSQuestion( + DataBuffer * inDB, + const uint8_t * inNamePtr, + size_t inNameLen, + uint16_t inType, + uint16_t inClass ) +{ + OSStatus err; + DNSQuestionFixedFields fields; + + err = DataBuffer_Append( inDB, inNamePtr, inNameLen ); + require_noerr( err, exit ); + + DNSQuestionFixedFieldsInit( &fields, inType, inClass ); + err = DataBuffer_Append( inDB, &fields, sizeof( fields ) ); + require_noerr( err, exit ); + +exit: + return( err ); +} + +//=========================================================================================================================== +// _DataBuffer_AppendDNSRecord +//=========================================================================================================================== + +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; + DNSRecordFixedFields fields; + + require_action_quiet( inRDataLen < kDNSRecordDataLengthMax, exit, err = kSizeErr ); + + err = DataBuffer_Append( inDB, inNamePtr, inNameLen ); + require_noerr( err, exit ); + + DNSRecordFixedFieldsSet( &fields, inType, inClass, inTTL, (uint16_t) inRDataLen ); + err = DataBuffer_Append( inDB, &fields, sizeof( fields ) ); + require_noerr( err, exit ); + + if( inRDataPtr ) + { + err = DataBuffer_Append( inDB, inRDataPtr, inRDataLen ); + require_noerr( err, exit ); + } + +exit: + return( err ); +} + +//=========================================================================================================================== +// _NanoTime64ToDateString +//=========================================================================================================================== + +static char * _NanoTime64ToDateString( NanoTime64 inTime, char *inBuf, size_t inMaxLen ) +{ + struct timeval tv; + + NanoTimeToTimeVal( inTime, &tv ); + return( MakeFractionalDateString( &tv, inBuf, inMaxLen ) ); +} + +//=========================================================================================================================== +// MDNSColliderCreate +//=========================================================================================================================== + +typedef enum +{ + kMDNSColliderOpCode_Invalid = 0, + kMDNSColliderOpCode_Send = 1, + kMDNSColliderOpCode_Wait = 2, + kMDNSColliderOpCode_SetProbeActions = 3, + kMDNSColliderOpCode_LoopPush = 4, + kMDNSColliderOpCode_LoopPop = 5, + kMDNSColliderOpCode_Exit = 6 + +} 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. +}; + +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 ); + +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; + MDNSColliderRef obj = NULL; + + CF_OBJECT_CREATE( MDNSCollider, obj, err, exit ); + + ReplaceDispatchQueue( &obj->queue, inQueue ); + obj->sockV4 = kInvalidSocketRef; + obj->sockV6 = kInvalidSocketRef; + + *outCollider = obj; + err = kNoErr; + +exit: + return( err ); +} + +//=========================================================================================================================== +// _MDNSColliderFinalize +//=========================================================================================================================== + +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 void _MDNSColliderStart( void *inContext ); + +static OSStatus MDNSColliderStart( MDNSColliderRef me ) +{ + OSStatus err; + + 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 ); + + CFRetain( me ); + dispatch_async_f( me->queue, me, _MDNSColliderStart ); + err = kNoErr; + +exit: + return( err ); +} + +static void _MDNSColliderStart( void *inContext ) +{ + OSStatus err; + MDNSColliderRef const me = (MDNSColliderRef) inContext; + SocketRef sock = kInvalidSocketRef; + SocketContext * sockCtx = NULL; + + if( me->protocols & kMDNSColliderProtocol_IPv4 ) + { + err = CreateMulticastSocket( GetMDNSMulticastAddrV4(), kMDNSPort, NULL, me->interfaceIndex, true, NULL, &sock ); + require_noerr( err, exit ); + + err = SocketContextCreate( sock, me, &sockCtx ); + require_noerr( err, exit ); + sock = kInvalidSocketRef; + + err = DispatchReadSourceCreate( sockCtx->sock, me->queue, _MDNSColliderReadHandler, SocketContextCancelHandler, + sockCtx, &me->readSourceV4 ); + require_noerr( err, exit ); + me->sockV4 = sockCtx->sock; + sockCtx = NULL; + + dispatch_resume( me->readSourceV4 ); + } + + if( me->protocols & kMDNSColliderProtocol_IPv6 ) + { + err = CreateMulticastSocket( GetMDNSMulticastAddrV6(), kMDNSPort, NULL, me->interfaceIndex, true, NULL, &sock ); + require_noerr( err, exit ); + + err = SocketContextCreate( sock, me, &sockCtx ); + require_noerr( err, exit ); + sock = kInvalidSocketRef; + + 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 ); + } + + _MDNSColliderExecuteProgram( me ); + err = kNoErr; + +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 ]; + + 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 ) + { + while( ( ptr < end ) && isspace_safe( *ptr ) ) ++ptr; + require_action_quiet( ptr == end, exit, err = kMalformedErr ); + + require_action_quiet( loopDepth > 0, exit, err = kMalformedErr ); + + ins->opcode = kMDNSColliderOpCode_LoopPop; + ins->operand = loopStart[ --loopDepth ]; + } + + // Loop command + + else if( strnicmpx( cmd, cmdLen, kMDNSColliderProgCmd_Loop ) == 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; + } + + // Probes command + + 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; + } + + // Send command + + 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 ); + + program[ insCount ].opcode = kMDNSColliderOpCode_Exit; + + FreeNullSafe( me->program ); + me->program = program; + program = NULL; + err = kNoErr; + +exit: + FreeNullSafe( program ); + return( err ); +} + +static OSStatus _MDNSColliderParseProbeActionString( const char *inString, size_t inLen, uint32_t *outBitmap ) +{ + OSStatus err; + const char * ptr; + const char * const end = &inString[ inLen ]; + uint32_t bitmap; + int index; + + bitmap = 0; + index = 0; + ptr = inString; + while( ptr < end ) + { + int c, count; + MDNSColliderProbeAction action; + + c = *ptr++; + if( isdigit_safe( c ) ) + { + count = 0; + do + { + 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; + } + + 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; + } + } + + *outBitmap = bitmap; + err = kNoErr; + +exit: + return( err ); +} + +//=========================================================================================================================== +// MDNSColliderSetStopHandler +//=========================================================================================================================== + +static void MDNSColliderSetStopHandler( MDNSColliderRef me, MDNSColliderStopHandler_f inStopHandler, void *inStopContext ) +{ + me->stopHandler = inStopHandler; + me->stopContext = inStopContext; +} + +//=========================================================================================================================== +// 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 ) +{ + 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 ); + + // Create response message. + + memset( &header, 0, sizeof( header ) ); + DNSHeaderSetFlags( &header, kDNSHeaderFlag_Response | kDNSHeaderFlag_AuthAnswer ); + DNSHeaderSetAnswerCount( &header, 1 ); + + err = DataBuffer_Append( &msgDB, &header, sizeof( header ) ); + require_noerr( err, exit ); + + err = _DataBuffer_AppendDNSRecord( &msgDB, targetPtr, targetLen, inType, kDNSServiceClass_IN | kRRClassCacheFlushBit, + 1976, inRDataPtr, inRDataLen ); + require_noerr( err, exit ); + + err = DataBuffer_Detach( &msgDB, &responsePtr, &responseLen ); + require_noerr( err, exit ); + + // 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 ); +} + +//=========================================================================================================================== +// _MDNSColliderStop +//=========================================================================================================================== + +static void _MDNSColliderStop( MDNSColliderRef me, OSStatus inError ) +{ + dispatch_source_forget( &me->waitTimer ); + dispatch_source_forget( &me->readSourceV4 ); + dispatch_source_forget( &me->readSourceV6 ); + me->sockV4 = kInvalidSocketRef; + me->sockV6 = kInvalidSocketRef; + + if( !me->stopped ) + { + me->stopped = true; + if( me->stopHandler ) me->stopHandler( me->stopContext, inError ); + CFRelease( me ); + } +} + +//=========================================================================================================================== +// _MDNSColliderReadHandler +//=========================================================================================================================== + +static MDNSColliderProbeAction _MDNSColliderGetProbeAction( uint32_t inBitmap, unsigned int inProbeNumber ); +static const char * _MDNSColliderProbeActionToString( MDNSColliderProbeAction inAction ); + +static void _MDNSColliderReadHandler( void *inContext ) +{ + 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; - TIMEVAL_ZERO( now ); gettimeofday( &now, NULL ); - return( (MicroTime64) TIMEVAL_USEC64( now ) ); + 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 ) + { + uint16_t qtype, qclass; + uint8_t qname[ kDomainNameLengthMax ]; + + err = DNSMessageExtractQuestion( me->msgBuf, msgLen, ptr, qname, &qtype, &qclass, &ptr ); + require_noerr_quiet( err, exit ); + + if( ( qtype == kDNSServiceType_NULL ) && ( qclass == kDNSServiceClass_IN ) && + DomainNameEqual( qname, kMDNSColliderDummyName ) ) + { + probeFound = false; + break; + } + + if( qtype != kDNSServiceType_ANY ) continue; + if( ( qclass & ~kQClassUnicastResponseBit ) != kDNSServiceClass_IN ) continue; + if( !DomainNameEqual( qname, me->target ) ) continue; + + if( !probeFound ) + { + probeFound = true; + probeIsQU = ( qclass & kQClassUnicastResponseBit ) ? true : false; + } + } + require_quiet( probeFound, exit ); + + ++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; +} + +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 * _MDNSColliderProbeActionToString( MDNSColliderProbeAction inAction ) +{ + switch( inAction ) + { + 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( "???" ); + } +} + +//=========================================================================================================================== +// _MDNSColliderExecuteProgram +//=========================================================================================================================== + +static void _MDNSColliderExecuteProgram( void *inContext ) +{ + OSStatus err; + MDNSColliderRef const me = (MDNSColliderRef) inContext; + int stop; + + dispatch_forget( &me->waitTimer ); + + stop = false; + for( ;; ) + { + const MDNSCInstruction * const ins = &me->program[ me->pc++ ]; + uint32_t waitMs; + + switch( ins->opcode ) + { + 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; + + 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; + } + } + +exit: + if( err || stop ) _MDNSColliderStop( me, err ); +} + +//=========================================================================================================================== +// _MDNSColliderSendResponse +//=========================================================================================================================== + +static OSStatus _MDNSColliderSendResponse( MDNSColliderRef me, SocketRef inSock, const struct sockaddr *inDest ) +{ + OSStatus err; + ssize_t n; + + 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; + + 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 ); +} + +//=========================================================================================================================== +// 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 +{ + SBRDomain * domainList; // List of domains in which services were found. + int32_t refCount; // This object's reference count. + +} ServiceBrowserResultsPrivate; + +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 + ServiceBrowserCreate( + dispatch_queue_t inQueue, + uint32_t inInterfaceIndex, + const char * inDomain, + unsigned int inBrowseTimeSecs, + Boolean inIncludeAWDL, + ServiceBrowserRef * outBrowser ) +{ + OSStatus err; + ServiceBrowserRef obj; + + CF_OBJECT_CREATE( ServiceBrowser, obj, err, exit ); + + 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; + + *outBrowser = obj; + obj = NULL; + err = kNoErr; + +exit: + CFReleaseNullSafe( obj ); + return( err ); +} + +//=========================================================================================================================== +// _ServiceBrowserFinalize +//=========================================================================================================================== + +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; + ServiceBrowserRef const me = (ServiceBrowserRef) inContext; + + err = DNSServiceCreateConnection( &me->connection ); + require_noerr( err, exit ); + + err = DNSServiceSetDispatchQueue( me->connection, me->queue ); + require_noerr( err, exit ); + + if( me->domain ) + { + err = _ServiceBrowserAddDomain( me, me->domain ); + require_noerr( err, exit ); + } + else + { + 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 ); + + me->domainsQuery = sdRef; + } + + 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: + if( err ) _ServiceBrowserStop( me, err ); +} + +//=========================================================================================================================== +// ServiceBrowserAddServiceType +//=========================================================================================================================== + +static OSStatus ServiceBrowserAddServiceType( ServiceBrowserRef me, const char *inServiceType ) +{ + OSStatus err; + StringListItem * item; + StringListItem ** itemPtr; + StringListItem * newItem = NULL; + + for( itemPtr = &me->serviceTypeList; ( item = *itemPtr ) != NULL; itemPtr = &item->next ) + { + if( strcmp( item->str, inServiceType ) == 0 ) break; + } + if( !item ) + { + newItem = (StringListItem *) calloc( 1, sizeof( *newItem ) ); + require_action( newItem, exit, err = kNoMemoryErr ); + + newItem->str = strdup( inServiceType ); + require_action( newItem->str, exit, err = kNoMemoryErr ); + + *itemPtr = newItem; + newItem = NULL; + } + 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 ) + { + while( ( domain = inResults->domainList ) != NULL ) + { + inResults->domainList = domain->next; + _SBRDomainFree( domain ); + } + 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 ) + { + 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 ); + } + } + } + } + DNSServiceForget( &me->connection ); + + if( me->userCallback ) + { + ServiceBrowserResults * results = NULL; + + err = _ServiceBrowserCreateResults( me, &results ); + if( !err ) err = inError; + + me->userCallback( results, err, me->userContext ); + me->userCallback = NULL; + me->userContext = NULL; + if( results ) ServiceBrowserResultsRelease( results ); + } + + while( ( d = me->domainList ) != NULL ) + { + me->domainList = d->next; + _SBDomainFree( d ); + } + 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 ) + { + 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; + + for( item = me->serviceTypeList; item; item = item->next ) + { + err = _ServiceBrowserAddServiceType( me, newDomain, item->str, me->ifIndex ); + 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( ( me->ifIndex == kDNSServiceInterfaceIndexAny ) && me->includeAWDL ) flags |= kDNSServiceFlagsIncludeAWDL; + + sdRef = newDomain->browser->connection; + err = DNSServiceQueryRecord( &sdRef, flags, me->ifIndex, recordName, kDNSServiceType_PTR, kDNSServiceClass_IN, + _ServiceBrowserServicesQueryCallback, newDomain ); + free( recordName ); + require_noerr( err, exit ); + + 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; + } + + if( domain ) + { + *domainPtr = domain->next; + _SBDomainFree( domain ); + err = kNoErr; + } + else + { + err = kNotFoundErr; + } + + 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: + return; +} + +//=========================================================================================================================== +// _ServiceBrowserServicesQueryCallback +//=========================================================================================================================== + +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; + 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 ]; + + Unused( inSDRef ); + Unused( inFullName ); + Unused( inTTL ); + Unused( inType ); + Unused( inClass ); + + require_noerr( inError, exit ); + + 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 ) + { + size_t labelLen; + + require_action_quiet( ( end - src ) > 0, exit, err = kUnderrunErr ); + + labelLen = *src; + require_action_quiet( ( labelLen > 0 ) && ( labelLen <= kDomainLabelLengthMax ), exit, err = kMalformedErr ); + require_action_quiet( ( (size_t)( end - src ) ) >= ( 1 + labelLen ), exit, err = kUnderrunErr ); + + memcpy( dst, src, 1 + labelLen ); + src += 1 + labelLen; + dst += 1 + labelLen; + } + *dst = 0; + + 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; +} + +//=========================================================================================================================== +// _ServiceBrowserBrowseCallback +//=========================================================================================================================== + +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 ) +{ + OSStatus err; + const uint64_t nowTicks = UpTicks(); + SBServiceBrowse * const browse = (SBServiceBrowse *) inContext; + ServiceBrowserRef const me = (ServiceBrowserRef) browse->browser; + + Unused( inSDRef ); + + require_noerr( inError, exit ); + + if( inFlags & kDNSServiceFlagsAdd ) + { + 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; + } + +exit: + return; +} + +//=========================================================================================================================== +// _ServiceBrowserGAICallback +//=========================================================================================================================== + +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 ) +{ + 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; +} + +//=========================================================================================================================== +// _ServiceBrowserAddServiceType +//=========================================================================================================================== + +static OSStatus + _ServiceBrowserAddServiceType( + ServiceBrowserRef me, + SBDomain * inDomain, + const char * inName, + uint32_t inIfIndex ) +{ + OSStatus err; + SBServiceType * type; + SBServiceType ** typePtr; + SBServiceType * newType = NULL; + SBServiceBrowse * browse; + SBServiceBrowse ** browsePtr; + SBServiceBrowse * newBrowse = NULL; + DNSServiceRef sdRef; + DNSServiceFlags flags; + + for( typePtr = &inDomain->typeList; ( type = *typePtr ) != NULL; typePtr = &type->next ) + { + if( strcasecmp( type->name, inName ) == 0 ) break; + } + if( !type ) + { + err = _SBServiceTypeCreate( inName, &newType ); + require_noerr_quiet( err, exit ); + + type = newType; + } + + for( browsePtr = &type->browseList; ( browse = *browsePtr ) != NULL; browsePtr = &browse->next ) + { + if( browse->ifIndex == inIfIndex ) break; + } + require_action_quiet( !browse, exit, err = kDuplicateErr ); + + 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 ); +} + +//=========================================================================================================================== +// _ServiceBrowserRemoveServiceType +//=========================================================================================================================== + +static OSStatus + _ServiceBrowserRemoveServiceType( + ServiceBrowserRef me, + SBDomain * inDomain, + const char * inName, + uint32_t inIfIndex ) +{ + OSStatus err; + SBServiceType * type; + SBServiceType ** typePtr; + SBServiceBrowse * browse; + SBServiceBrowse ** browsePtr; + + Unused( me ); + + for( typePtr = &inDomain->typeList; ( type = *typePtr ) != NULL; typePtr = &type->next ) + { + if( strcasecmp( type->name, inName ) == 0 ) break; + } + require_action_quiet( type, exit, err = kNotFoundErr ); + + for( browsePtr = &type->browseList; ( browse = *browsePtr ) != NULL; browsePtr = &browse->next ) + { + if( browse->ifIndex == inIfIndex ) break; + } + require_action_quiet( browse, exit, err = kNotFoundErr ); + + *browsePtr = browse->next; + _SBServiceBrowseFree( browse ); + if( !type->browseList ) + { + *typePtr = type->next; + _SBServiceTypeFree( type ); + } + err = kNoErr; + +exit: + return( err ); +} + +//=========================================================================================================================== +// _ServiceBrowserAddServiceInstance +//=========================================================================================================================== + +static OSStatus + _ServiceBrowserAddServiceInstance( + ServiceBrowserRef me, + SBServiceBrowse * inBrowse, + uint32_t inIfIndex, + const char * inName, + const char * inRegType, + const char * inDomain, + uint64_t inDiscoverTimeUs ) +{ + OSStatus err; + DNSServiceRef sdRef; + SBServiceInstance * instance; + SBServiceInstance ** instancePtr; + SBServiceInstance * newInstance = NULL; + + 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 ); + + err = _SBServiceInstanceCreate( inName, inIfIndex, inDiscoverTimeUs, me, &newInstance ); + require_noerr_quiet( err, exit ); + + sdRef = me->connection; + newInstance->resolveStartTicks = UpTicks(); + err = DNSServiceResolve( &sdRef, kDNSServiceFlagsShareConnection, newInstance->ifIndex, inName, inRegType, inDomain, + _ServiceBrowserResolveCallback, newInstance ); + require_noerr( err, exit ); + + newInstance->resolve = sdRef; + *instancePtr = newInstance; + newInstance = NULL; + +exit: + if( newInstance ) _SBServiceInstanceFree( newInstance ); + return( err ); +} + +//=========================================================================================================================== +// _ServiceBrowserRemoveServiceInstance +//=========================================================================================================================== + +static OSStatus + _ServiceBrowserRemoveServiceInstance( + ServiceBrowserRef me, + SBServiceBrowse * inBrowse, + const char * inName, + uint32_t inIfIndex ) +{ + OSStatus err; + SBServiceInstance * instance; + SBServiceInstance ** ptr; + + Unused( me ); + + 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 ); + + *ptr = instance->next; + _SBServiceInstanceFree( instance ); + err = kNoErr; + +exit: + return( err ); +} + +//=========================================================================================================================== +// _ServiceBrowserAddIPAddress +//=========================================================================================================================== + +static OSStatus + _ServiceBrowserAddIPAddress( + ServiceBrowserRef me, + SBServiceInstance * inInstance, + const struct sockaddr * inSockAddr, + uint64_t inResolveTimeUs ) +{ + OSStatus err; + SBIPAddress * ipaddr; + SBIPAddress ** ipaddrPtr; + SBIPAddress * newIPAddr = NULL; + + Unused( me ); + + if( ( inSockAddr->sa_family != AF_INET ) && ( inSockAddr->sa_family != AF_INET6 ) ) + { + dlogassert( "Unexpected address family: %d", inSockAddr->sa_family ); + err = kTypeErr; + goto exit; + } + + 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 ); +} + +//=========================================================================================================================== +// _ServiceBrowserRemoveIPAddress +//=========================================================================================================================== + +static OSStatus + _ServiceBrowserRemoveIPAddress( + ServiceBrowserRef me, + SBServiceInstance * inInstance, + const struct sockaddr * inSockAddr ) +{ + OSStatus err; + SBIPAddress * ipaddr; + SBIPAddress ** ipaddrPtr; + + Unused( me ); + + 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 ); + + *ipaddrPtr = ipaddr->next; + _SBIPAddressFree( ipaddr ); + err = kNoErr; + +exit: + return( err ); +} + +//=========================================================================================================================== +// _ServiceBrowserCreateResults +//=========================================================================================================================== + +static OSStatus _ServiceBrowserCreateResults( ServiceBrowserRef me, ServiceBrowserResults **outResults ) +{ + 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 ); + + results->refCount = 1; + + 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; + } + } + } + } + } + + *outResults = (ServiceBrowserResults *) results; + results = NULL; + err = kNoErr; + +exit: + if( results ) ServiceBrowserResultsRelease( (ServiceBrowserResults *) results ); + return( err ); +} + +//=========================================================================================================================== +// _SBDomainCreate +//=========================================================================================================================== + +static OSStatus _SBDomainCreate( const char *inName, ServiceBrowserRef inBrowser, SBDomain **outDomain ) +{ + OSStatus err; + SBDomain * obj; + + 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 ); +} + +//=========================================================================================================================== +// _SBDomainFree +//=========================================================================================================================== + +static void _SBDomainFree( SBDomain *inDomain ) +{ + 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 void _SBServiceTypeFree( SBServiceType *inType ) +{ + SBServiceBrowse * browse; + + ForgetMem( &inType->name ); + while( ( browse = inType->browseList ) != NULL ) + { + inType->browseList = browse->next; + _SBServiceBrowseFree( browse ); + } + free( inType ); +} + +//=========================================================================================================================== +// _SBServiceBrowseCreate +//=========================================================================================================================== + +static OSStatus _SBServiceBrowseCreate( uint32_t inIfIndex, ServiceBrowserRef inBrowser, SBServiceBrowse **outBrowse ) +{ + OSStatus err; + SBServiceBrowse * obj; + + obj = (SBServiceBrowse *) calloc( 1, sizeof( *obj ) ); + require_action( obj, exit, err = kNoMemoryErr ); + + obj->ifIndex = inIfIndex; + obj->browser = inBrowser; + *outBrowse = obj; + err = kNoErr; + +exit: + return( err ); +} + +//=========================================================================================================================== +// _SBServiceBrowseFree +//=========================================================================================================================== + +static void _SBServiceBrowseFree( SBServiceBrowse *inBrowse ) +{ + SBServiceInstance * instance; + + DNSServiceForget( &inBrowse->browse ); + while( ( instance = inBrowse->instanceList ) != NULL ) + { + inBrowse->instanceList = instance->next; + _SBServiceInstanceFree( instance ); + } + free( inBrowse ); +} + +//=========================================================================================================================== +// _SBServiceInstanceCreate +//=========================================================================================================================== + +static OSStatus + _SBServiceInstanceCreate( + const char * inName, + uint32_t inIfIndex, + uint64_t inDiscoverTimeUs, + ServiceBrowserRef inBrowser, + SBServiceInstance ** outInstance ) +{ + 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 ); +} + +//=========================================================================================================================== +// _SBServiceInstanceFree +//=========================================================================================================================== + +static void _SBServiceInstanceFree( SBServiceInstance *inInstance ) +{ + ForgetMem( &inInstance->name ); + DNSServiceForget( &inInstance->resolve ); + ForgetMem( &inInstance->hostname ); + ForgetMem( &inInstance->txtPtr ); + DNSServiceForget( &inInstance->getAddrInfo ); + ForgetSBIPAddressList( &inInstance->ipaddrList ); + free( inInstance ); +} + +//=========================================================================================================================== +// _SBIPAddressCreate +//=========================================================================================================================== + +static OSStatus _SBIPAddressCreate( const struct sockaddr *inSockAddr, uint64_t inResolveTimeUs, SBIPAddress **outIPAddress ) +{ + OSStatus err; + SBIPAddress * obj; + + obj = (SBIPAddress *) calloc( 1, sizeof( *obj ) ); + require_action( obj, exit, err = kNoMemoryErr ); + + SockAddrCopy( inSockAddr, &obj->sip ); + obj->resolveTimeUs = inResolveTimeUs; + + *outIPAddress = obj; + err = kNoErr; + +exit: + return( err ); +} + +//=========================================================================================================================== +// _SBIPAddressFree +//=========================================================================================================================== + +static void _SBIPAddressFree( SBIPAddress *inIPAddress ) +{ + free( inIPAddress ); +} + +//=========================================================================================================================== +// _SBIPAddressFreeList +//=========================================================================================================================== + +static void _SBIPAddressFreeList( SBIPAddress *inList ) +{ + SBIPAddress * ipaddr; + + while( ( ipaddr = inList ) != NULL ) + { + inList = ipaddr->next; + _SBIPAddressFree( ipaddr ); + } +} + +//=========================================================================================================================== +// _SBRDomainCreate +//=========================================================================================================================== + +static OSStatus _SBRDomainCreate( const char *inName, SBRDomain **outDomain ) +{ + OSStatus err; + SBRDomain * obj; + + obj = (SBRDomain *) calloc( 1, sizeof( *obj ) ); + require_action( obj, exit, err = kNoMemoryErr ); + + obj->name = strdup( inName ); + require_action( obj->name, exit, err = kNoMemoryErr ); + + *outDomain = obj; + obj = NULL; + err = kNoErr; + +exit: + if( obj ) _SBRDomainFree( obj ); + return( err ); +} + +//=========================================================================================================================== +// _SBRDomainFree +//=========================================================================================================================== + +static void _SBRDomainFree( SBRDomain *inDomain ) +{ + SBRServiceType * type; + + 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; + + obj = (SBRServiceType *) 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 ) _SBRServiceTypeFree( obj ); + return( err ); +} + +//=========================================================================================================================== +// _SBRServiceTypeFree +//=========================================================================================================================== + +static void _SBRServiceTypeFree( SBRServiceType *inType ) +{ + SBRServiceInstance * instance; + + ForgetMem( &inType->name ); + while( ( instance = inType->instanceList ) != NULL ) + { + inType->instanceList = instance->next; + _SBRServiceInstanceFree( instance ); + } + free( inType ); +} + +//=========================================================================================================================== +// _SBRServiceInstanceCreate +//=========================================================================================================================== + +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; + SBRServiceInstance * obj; + + obj = (SBRServiceInstance *) calloc( 1, sizeof( *obj ) ); + require_action( obj, exit, err = kNoMemoryErr ); + + obj->name = strdup( inName ); + require_action( obj->name, exit, err = kNoMemoryErr ); + + 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 ); +} + +//=========================================================================================================================== +// _SBRServiceInstanceFree +//=========================================================================================================================== + +static void _SBRServiceInstanceFree( SBRServiceInstance *inInstance ) +{ + SBRIPAddress * ipaddr; + + ForgetMem( &inInstance->name ); + ForgetMem( &inInstance->hostname ); + ForgetMem( &inInstance->txtPtr ); + while( ( ipaddr = inInstance->ipaddrList ) != NULL ) + { + 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; + + obj = (SBRIPAddress *) calloc( 1, sizeof( *obj ) ); + require_action( obj, exit, err = kNoMemoryErr ); + + SockAddrCopy( inSockAddr, &obj->sip ); + obj->resolveTimeUs = inResolveTimeUs; + + *outIPAddress = obj; + err = kNoErr; + +exit: + return( err ); +} + +//=========================================================================================================================== +// _SBRIPAddressFree +//=========================================================================================================================== + +static void _SBRIPAddressFree( SBRIPAddress *inIPAddress ) +{ + 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 78adbd5e..9aaa08eb 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 00000000..66a8c49d --- /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 00000000..750f16a2 --- /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 51fc67a8..47142935 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 bbbd12ff..8067d931 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 3495c6e6..f76fbbdd 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" { |