/* * Copyright (c) 2016-2024 Apple Inc. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "DNSServerDNSSEC.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include CF_RUNTIME_HEADER #if( TARGET_OS_DARWIN ) #include #include #include #include #include #include #include #include #include #endif #if( TARGET_OS_POSIX ) #include #include #endif #if( !defined( DNSSDUTIL_INCLUDE_DNSCRYPT ) ) #define DNSSDUTIL_INCLUDE_DNSCRYPT 0 #endif #if( DNSSDUTIL_INCLUDE_DNSCRYPT ) #include "tweetnacl.h" // TweetNaCl from . #endif #if( !defined( MDNSRESPONDER_PROJECT ) ) #define MDNSRESPONDER_PROJECT 0 #endif #if( MDNSRESPONDER_PROJECT ) #include #include "dnssd_private.h" #include #include #include "TestUtils.h" // Set ENABLE_DNSSDUTIL_DNSSEC_TEST to 1 to enable DNSSEC test functionality. #define ENABLE_DNSSDUTIL_DNSSEC_TEST 1 #endif //=========================================================================================================================== // Versioning //=========================================================================================================================== #define kDNSSDUtilNumVersion NumVersionBuild( 2, 0, 0, kVersionStageBeta, 0 ) #if( !MDNSRESPONDER_PROJECT && !defined( DNSSDUTIL_SOURCE_VERSION ) ) #define DNSSDUTIL_SOURCE_VERSION "0.0.0" #endif #define kDNSSDUtilIdentifier "com.apple.dnssdutil" //=========================================================================================================================== // DNS-SD //=========================================================================================================================== // DNS-SD API flag descriptors #define kDNSServiceFlagsDescriptors \ "\x00" "AutoTrigger\0" \ "\x01" "Add\0" \ "\x02" "Default\0" \ "\x03" "NoAutoRename\0" \ "\x04" "Shared\0" \ "\x05" "Unique\0" \ "\x06" "BrowseDomains\0" \ "\x07" "RegistrationDomains\0" \ "\x08" "LongLivedQuery\0" \ "\x09" "AllowRemoteQuery\0" \ "\x0A" "ForceMulticast\0" \ "\x0B" "KnownUnique\0" \ "\x0C" "ReturnIntermediates\0" \ "\x0D" "DenyConstrained\0" \ "\x0E" "ShareConnection\0" \ "\x0F" "SuppressUnusable\0" \ "\x10" "Timeout\0" \ "\x11" "IncludeP2P\0" \ "\x12" "WakeOnResolve\0" \ "\x13" "BackgroundTrafficClass\0" \ "\x14" "IncludeAWDL\0" \ "\x15" "EnableDNSSEC\0" \ "\x16" "UnicastResponse\0" \ "\x17" "ValidateOptional\0" \ "\x18" "WakeOnlyService\0" \ "\x19" "ThresholdOne\0" \ "\x1A" "ThresholdFinder\0" \ "\x1B" "DenyCellular\0" \ "\x1C" "ServiceIndex\0" \ "\x1D" "DenyExpensive\0" \ "\x1E" "PathEvaluationDone\0" \ "\x1F" "AllowExpiredAnswers\0" \ "\x00" #define DNSServiceFlagsToAddRmvStr( FLAGS ) ( ( (FLAGS) & kDNSServiceFlagsAdd ) ? "Add" : "Rmv" ) #define kDNSServiceProtocolDescriptors \ "\x00" "IPv4\0" \ "\x01" "IPv6\0" \ "\x04" "UDP\0" \ "\x05" "TCP\0" \ "\x00" #define kBadDNSServiceRef ( (DNSServiceRef)(intptr_t) -1 ) //=========================================================================================================================== // DNS //=========================================================================================================================== #define kDNSPort_Do53 53 // See . #define kDNSPort_DoT 853 // See . #define kDNSPort_DoH 443 // See . #define kDNSMaxUDPMessageSize 512 #define kDNSMaxTCPMessageSize UINT16_MAX #define kDNSRecordDataLengthMax UINT16_MAX //=========================================================================================================================== // mDNS //=========================================================================================================================== #define kMDNSPort 5353 #define kDefaultMDNSMessageID 0 #define kDefaultMDNSQueryFlags 0 // Recommended Resource Record TTL values. See . #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 . #define kMDNSMessageSizeMax 8952 // 9000 B (Ethernet jumbo frame max size) - 40 B (IPv6 header) - 8 B (UDP header) #define kLocalStr "\x05" "local" #define kLocalLabel ( (const uint8_t *) kLocalStr ) #define kLocalName ( (const uint8_t *) kLocalStr ) #define kLocalNameLen sizeof( kLocalStr ) //=========================================================================================================================== // Test Address Blocks //=========================================================================================================================== // IPv4 address block 203.0.113.0/24 (TEST-NET-3) is reserved for documentation. See . #define kDNSServerBaseAddrV4 UINT32_C( 0xCB007100 ) // 203.0.113.0/24 #define kDNSServerReverseIPv4DomainStr "113.0.203.in-addr.arpa." #define kDNSServerReverseIPv4DomainName \ ( (const uint8_t *) "\x3" "113" "\x1" "0" "\x3" "203" "\x7" "in-addr" "\x4" "arpa" ) // IPv6 address block 2001:db8::/32 is reserved for documentation. See . static const uint8_t kDNSServerBaseAddrV6[] = { 0x20, 0x01, 0x0D, 0xB8, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // 2001:db8:1::/96 }; check_compile_time( sizeof( kDNSServerBaseAddrV6 ) == 16 ); #define kDNSServerReverseIPv6DomainStr "0.0.0.0.0.0.0.0.0.0.0.0.1.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa." #define kDNSServerReverseIPv6DomainName \ ( (const uint8_t *) "\x1" "0" "\x1" "0" "\x1" "0" "\x1" "0" "\x1" "0" "\x1" "0" \ "\x1" "0" "\x1" "0" "\x1" "0" "\x1" "0" "\x1" "0" "\x1" "0" "\x1" "0" "\x1" "0" \ "\x1" "0" "\x1" "0" "\x1" "0" "\x1" "0" "\x1" "1" "\x1" "0" "\x1" "0" "\x1" "0" \ "\x1" "8" "\x1" "b" "\x1" "d" "\x1" "0" "\x1" "1" "\x1" "0" "\x1" "0" "\x1" "2" \ "\x3" "ip6" "\x4" "arpa" ) 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( kMDNSReplierBaseAddrV6 ) == 16 ); static const uint8_t kMDNSReplierLinkLocalBaseAddrV6[] = { 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // fe80::/96 }; check_compile_time( sizeof( kMDNSReplierLinkLocalBaseAddrV6 ) == 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 ); #if( TARGET_OS_DARWIN ) // IPv6 Unique Local Address for assigning extra randomly-generated IPv6 addresses to the loopback interface. // 40-bit Global ID: 0xEDF03555E4 (randomly-generated) // 16-bit Subnet ID: 0 // See . static const uint8_t kExtraLoopbackIPv6Prefix[] = { 0xFD, 0xED, 0xF0, 0x35, 0x55, 0xE4, 0x00, 0x00 // fded:f035:55e4::/64 }; #define kExtraLoopbackIPv6PrefixBitLen 64 check_compile_time( ( sizeof( kExtraLoopbackIPv6Prefix ) * 8 ) == kExtraLoopbackIPv6PrefixBitLen ); #endif //=========================================================================================================================== // DNS Server Domains //=========================================================================================================================== #define kDNSServerDomain_Default ( (const uint8_t *) "\x01" "d" "\x04" "test" ) #define kDNSServerDomain_DNSSEC ( (const uint8_t *) "\x06" "dnssec" "\x04" "test" ) //=========================================================================================================================== // Misc. //=========================================================================================================================== #define kLowerAlphaNumericCharSet "abcdefghijklmnopqrstuvwxyz0123456789" #define kLowerAlphaNumericCharSetSize sizeof_string( kLowerAlphaNumericCharSet ) #if( !defined( kWhiteSpaceCharSet ) ) #define kWhiteSpaceCharSet "\t\n\v\f\r " #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 kNoSuchNameStr "No Such Name" #define kRootLabel ( (const uint8_t *) "" ) #if !defined( nw_forget ) #define nw_forget( X ) ForgetCustom( X, nw_release ) #endif // When running a Do53 or DoT server for testing on tvOS and iOS, use an alternate server port instead of the standard // UDP/TCP server ports of 53 and 853 respectively, which may be in use by the Unicast Bonjour discovery proxy. #if( TARGET_OS_TV || TARGET_OS_IOS || TARGET_OS_OSX ) #define DNSSDUTIL_TEST_USE_ALTERNATE_SERVER_PORT_FOR_DO53 1 #define DNSSDUTIL_TEST_USE_ALTERNATE_SERVER_PORT_FOR_DOT 1 #else #define DNSSDUTIL_TEST_USE_ALTERNATE_SERVER_PORT_FOR_DO53 0 #define DNSSDUTIL_TEST_USE_ALTERNATE_SERVER_PORT_FOR_DOT 0 #endif // Most tests that spawn a DNS server for their testing don't actually care about the port used by the DNS protocol, so // when we need to use an alternate listening port, just use a random ephemeral port (--port 0). If an alternate // listening port isn't necessary, just use the default port, which is the common case. #if( DNSSDUTIL_TEST_USE_ALTERNATE_SERVER_PORT_FOR_DO53 ) #define DNSSDUTIL_TEST_DNS_SERVER_COMMAND_PREAMBLE "dnssdutil server --port 0" #else #define DNSSDUTIL_TEST_DNS_SERVER_COMMAND_PREAMBLE "dnssdutil server" #endif // When it's necessary to use a specific alternate port for Do53 for testing, use port 202, which is the AppleTalk Name // Binding UDP/TCP port (https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml). // AppleTalk is discontinued, so it should be OK to squat on one of its ports, at least for testing. #define kDNSPort_Do53Alt 202 // IDs and keys for mDNSResponder preferences. See mDNSResponder(8) man page. #define kMDNSResponderPrefAppIDStr "com.apple.mDNSResponder" #define kMDNSResponderPrefStr_AlwaysAppendSearchDomains "AlwaysAppendSearchDomains" //=========================================================================================================================== // Gerneral Command Options //=========================================================================================================================== // Command option macros #define Command( NAME, CALLBACK, SUB_OPTIONS, SHORT_HELP, IS_NOTCOMMON ) \ CLI_COMMAND_EX( NAME, CALLBACK, SUB_OPTIONS, (IS_NOTCOMMON) ? kCLIOptionFlags_NotCommon : kCLIOptionFlags_None, \ (SHORT_HELP), NULL ) #define kRequiredOptionSuffix " [REQUIRED]" #define MultiStringOptionEx( SHORT_CHAR, LONG_NAME, VAL_PTR, VAL_COUNT_PTR, ARG_HELP, SHORT_HELP, IS_REQUIRED, LONG_HELP ) \ CLI_OPTION_MULTI_STRING_EX( SHORT_CHAR, LONG_NAME, VAL_PTR, VAL_COUNT_PTR, ARG_HELP, \ (IS_REQUIRED) ? SHORT_HELP kRequiredOptionSuffix : SHORT_HELP, \ (IS_REQUIRED) ? kCLIOptionFlags_Required : kCLIOptionFlags_None, LONG_HELP ) #define MultiStringOption( SHORT_CHAR, LONG_NAME, VAL_PTR, VAL_COUNT_PTR, ARG_HELP, SHORT_HELP, IS_REQUIRED ) \ MultiStringOptionEx( SHORT_CHAR, LONG_NAME, VAL_PTR, VAL_COUNT_PTR, ARG_HELP, SHORT_HELP, IS_REQUIRED, NULL ) #define IntegerOptionEx( SHORT_CHAR, LONG_NAME, VAL_PTR, ARG_HELP, SHORT_HELP, IS_REQUIRED, LONG_HELP ) \ CLI_OPTION_INTEGER_EX( SHORT_CHAR, LONG_NAME, VAL_PTR, ARG_HELP, \ (IS_REQUIRED) ? SHORT_HELP kRequiredOptionSuffix : SHORT_HELP, \ (IS_REQUIRED) ? kCLIOptionFlags_Required : kCLIOptionFlags_None, LONG_HELP ) #define IntegerOption( SHORT_CHAR, LONG_NAME, VAL_PTR, ARG_HELP, SHORT_HELP, IS_REQUIRED ) \ IntegerOptionEx( SHORT_CHAR, LONG_NAME, VAL_PTR, ARG_HELP, SHORT_HELP, IS_REQUIRED, 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 ) #define StringOptionEx( SHORT_CHAR, LONG_NAME, VAL_PTR, ARG_HELP, SHORT_HELP, IS_REQUIRED, LONG_HELP ) \ CLI_OPTION_STRING_EX( SHORT_CHAR, LONG_NAME, VAL_PTR, ARG_HELP, \ (IS_REQUIRED) ? SHORT_HELP kRequiredOptionSuffix : SHORT_HELP, \ (IS_REQUIRED) ? kCLIOptionFlags_Required : kCLIOptionFlags_None, LONG_HELP ) #define StringOption( SHORT_CHAR, LONG_NAME, VAL_PTR, ARG_HELP, SHORT_HELP, IS_REQUIRED ) \ StringOptionEx( SHORT_CHAR, LONG_NAME, VAL_PTR, ARG_HELP, SHORT_HELP, IS_REQUIRED, NULL ) #define CFStringOption( SHORT_CHAR, LONG_NAME, VAL_PTR, ARG_HELP, SHORT_HELP, IS_REQUIRED ) \ CLI_OPTION_CFSTRING_EX( SHORT_CHAR, LONG_NAME, VAL_PTR, ARG_HELP, \ (IS_REQUIRED) ? SHORT_HELP kRequiredOptionSuffix : SHORT_HELP, \ (IS_REQUIRED) ? kCLIOptionFlags_Required : kCLIOptionFlags_None, NULL ) // 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_DenyConstrained = false; static int gDNSSDFlag_DenyExpensive = false; static int gDNSSDFlag_ForceMulticast = false; static int gDNSSDFlag_IncludeAWDL = false; static int gDNSSDFlag_KnownUnique = false; static int gDNSSDFlag_NoAutoRename = false; static int gDNSSDFlag_PathEvaluationDone = false; static int gDNSSDFlag_RegistrationDomains = false; static int gDNSSDFlag_ReturnIntermediates = false; static int gDNSSDFlag_Shared = false; static int gDNSSDFlag_SuppressUnusable = false; static int gDNSSDFlag_Timeout = false; static int gDNSSDFlag_UnicastResponse = false; static int gDNSSDFlag_Unique = false; static int gDNSSDFlag_WakeOnResolve = false; static int gDNSSDFlag_EnableDNSSEC = false; #define DNSSDFlagsOption() \ IntegerOption( 'f', "flags", &gDNSSDFlags, "flags", \ "DNSServiceFlags as an integer. This value is bitwise ORed with other single flag options.", false ) #define DNSSDFlagOption( SHORT_CHAR, FLAG_NAME ) \ BooleanOption( SHORT_CHAR, # FLAG_NAME, &gDNSSDFlag_ ## FLAG_NAME, "Use kDNSServiceFlags" # FLAG_NAME "." ) #define DNSSDFlagsOption_AllowExpiredAnswers() DNSSDFlagOption( 'X', AllowExpiredAnswers ) #define DNSSDFlagsOption_DenyCellular() DNSSDFlagOption( 'C', DenyCellular ) #define DNSSDFlagsOption_DenyConstrained() DNSSDFlagOption( 'R', DenyConstrained) #define DNSSDFlagsOption_DenyExpensive() DNSSDFlagOption( 'E', DenyExpensive ) #define DNSSDFlagsOption_EnableDNSSEC() DNSSDFlagOption( 'D', EnableDNSSEC ) #define DNSSDFlagsOption_ForceMulticast() DNSSDFlagOption( 'M', ForceMulticast ) #define DNSSDFlagsOption_IncludeAWDL() DNSSDFlagOption( 'A', IncludeAWDL ) #define DNSSDFlagsOption_KnownUnique() DNSSDFlagOption( 'K', KnownUnique ) #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 static const char * gInterface = NULL; #define InterfaceOption() \ StringOption( 'i', "interface", &gInterface, "interface", \ "Network interface by name or index. Use index -1 for local-only.", false ) // Connection options #define kConnectionArg_Normal "" #define kConnectionArgPrefix_PID "pid:" #define kConnectionArgPrefix_UUID "uuid:" static const char * gConnectionOpt = kConnectionArg_Normal; #define ConnectionOptions() \ { kCLIOptionType_String, 0, "connection", &gConnectionOpt, NULL, (intptr_t) kConnectionArg_Normal, "type", \ kCLIOptionFlags_OptionalArgument, NULL, NULL, NULL, NULL, \ "Specifies the type of main connection to use. See " kConnectionSection_Name " below.", NULL } #define kConnectionSection_Name "Connection Option" #define kConnectionSection_Text \ "The default behavior is to create a main connection with DNSServiceCreateConnection() and perform operations on\n" \ "the main connection using the kDNSServiceFlagsShareConnection flag. This behavior can be explicitly invoked by\n" \ "specifying the connection option without an argument, i.e.,\n" \ "\n" \ " --connection\n" \ "\n" \ "To instead use a delegate connection created with DNSServiceCreateDelegateConnection(), use\n" \ "\n" \ " --connection=pid:\n" \ "\n" \ "to specify the delegator by PID, or use\n" \ "\n" \ " --connection=uuid:\n" \ "\n" \ "to specify the delegator by UUID.\n" \ "\n" \ "To not use a main connection at all, but instead perform operations on their own implicit connections, use\n" \ "\n" \ " --no-connection\n" #define ConnectionSection() CLI_SECTION( kConnectionSection_Name, kConnectionSection_Text ) // Help text for record data options #define kRDataArgPrefix_Domain "domain:" #define kRDataArgPrefix_File "file:" #define kRDataArgPrefix_HexString "hex:" #define kRDataArgPrefix_IPv4 "ipv4:" #define kRDataArgPrefix_IPv6 "ipv6:" #define kRDataArgPrefix_SRV "srv:" #define kRDataArgPrefix_String "string:" #define kRDataArgPrefix_TXT "txt:" #define kRecordDataSection_Name "Record Data Arguments" #define kRecordDataSection_Text \ "A record data argument is specified in one of the following formats:\n" \ "\n" \ "Format Syntax Example\n" \ "Domain name domain: domain:demo._test._tcp.local\n" \ "File containing record data file: file:/path/to/binary-rdata-file\n" \ "Hexadecimal string hex: hex:c0000201 or hex:'C0 00 02 01'\n" \ "IPv4 address ipv4: ipv4:192.0.2.1\n" \ "IPv6 address ipv6: ipv6:2001:db8::1\n" \ "SRV record srv:,,, srv:0,0,64206,example.local\n" \ "String string: string:'\\x09color=red'\n" \ "TXT record strings txt: txt:'vers=1.0,lang=en\\,es\\,fr,passreq'\n" \ "\n" \ "Note: The string format converts each \\xHH escape sequence into the octet represented by the HH hex digit pair.\n" #define RecordDataSection() CLI_SECTION( kRecordDataSection_Name, kRecordDataSection_Text ) // Fallback DNS service option #if( MDNSRESPONDER_PROJECT ) static const char * gFallbackDNSService = NULL; #define kFallbackDNSServiceArgPrefix_DoH "doh:" #define kFallbackDNSServiceArgPrefix_DoT "dot:" #define FallbackDNSServiceGroup() CLI_OPTION_GROUP( "Default Fallback DNS Service" ) #define FallbackDNSServiceOption() \ StringOptionEx( 0, "fallback", &gFallbackDNSService, "DNS service", "Default fallback DNS service to set.", false, \ "\n" \ "When this option is used, an nw_resolver_config is created for the specified DoH or DoT service.\n" \ "DNSServiceSetResolverDefaults() is then used to set the DNS service described by nw_resolver_config as the\n" \ "default fallback DNS service for the dnssdutil process.\n" \ "\n" \ "To specify a DNS over HTTPS (DoH) service, use\n" \ "\n" \ " --fallback=doh:\n" \ "\n" \ "Example: --fallback=doh:https://dns.example.com/dns-query\n" \ "\n" \ "To specify a DNS over TLS (DoT) service, use\n" \ "\n" \ " --fallback=dot:\n" \ "\n" \ "Example: --fallback=dot:dns.example.com\n" \ ) #endif //=========================================================================================================================== // 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 //=========================================================================================================================== static char ** gBrowse_ServiceTypes = NULL; static size_t gBrowse_ServiceTypesCount = 0; static const char * gBrowse_Domain = NULL; static int gBrowse_DoResolve = false; static int gBrowse_QueryTXT = false; static int gBrowse_TimeLimitSecs = 0; static int gBrowse_ValidateResults = false; static int gBrowse_ResolveDelayMs = 0; static CLIOption kBrowseOpts[] = { InterfaceOption(), MultiStringOption( 't', "type", &gBrowse_ServiceTypes, &gBrowse_ServiceTypesCount, "service type", "Service type(s), e.g., \"_ssh._tcp\".", true ), StringOption( 'd', "domain", &gBrowse_Domain, "domain", "Domain in which to browse for the service type(s).", false ), CLI_OPTION_GROUP( "Flags" ), DNSSDFlagsOption(), DNSSDFlagsOption_IncludeAWDL(), CLI_OPTION_GROUP( "Operation" ), ConnectionOptions(), BooleanOption( 0 , "resolve", &gBrowse_DoResolve, "Resolve service instances." ), BooleanOption( 0 , "queryTXT", &gBrowse_QueryTXT, "Query TXT records of service instances." ), IntegerOption( 'l', "timeLimit", &gBrowse_TimeLimitSecs, "seconds", "Specifies the max duration of the browse operation. Use '0' for no time limit.", false ), BooleanOption( 'v', "validate", &gBrowse_ValidateResults, "Validate results." ), IntegerOption( 0 , "resolveDelay", &gBrowse_ResolveDelayMs, "ms", "The amount of time to wait before a resolve or TXT query in milliseconds. (default: 0)", false ), ConnectionSection(), CLI_OPTION_END() }; //=========================================================================================================================== // GetAddrInfo Command Options //=========================================================================================================================== static const char * gGetAddrInfo_Name = NULL; static int gGetAddrInfo_ProtocolIPv4 = false; static int gGetAddrInfo_ProtocolIPv6 = false; static int gGetAddrInfo_OneShot = false; static int gGetAddrInfo_TimeLimitSecs = 0; static CLIOption kGetAddrInfoOpts[] = { InterfaceOption(), StringOption( 'n', "name", &gGetAddrInfo_Name, "domain name", "Domain name to resolve.", true ), BooleanOption( 0 , "ipv4", &gGetAddrInfo_ProtocolIPv4, "Use kDNSServiceProtocol_IPv4." ), BooleanOption( 0 , "ipv6", &gGetAddrInfo_ProtocolIPv6, "Use kDNSServiceProtocol_IPv6." ), CLI_OPTION_GROUP( "Flags" ), DNSSDFlagsOption(), DNSSDFlagsOption_AllowExpiredAnswers(), DNSSDFlagsOption_DenyCellular(), DNSSDFlagsOption_DenyConstrained(), DNSSDFlagsOption_DenyExpensive(), DNSSDFlagsOption_EnableDNSSEC(), DNSSDFlagsOption_IncludeAWDL(), DNSSDFlagsOption_PathEvalDone(), DNSSDFlagsOption_ReturnIntermediates(), DNSSDFlagsOption_SuppressUnusable(), DNSSDFlagsOption_Timeout(), CLI_OPTION_GROUP( "Operation" ), ConnectionOptions(), BooleanOption( 'o', "oneshot", &gGetAddrInfo_OneShot, "Finish after first set of results." ), IntegerOption( 'l', "timeLimit", &gGetAddrInfo_TimeLimitSecs, "seconds", "Maximum duration of the GetAddrInfo operation. Use '0' for no time limit.", false ), #if( MDNSRESPONDER_PROJECT ) FallbackDNSServiceGroup(), FallbackDNSServiceOption(), #endif ConnectionSection(), CLI_OPTION_END() }; //=========================================================================================================================== // QueryRecord Command Options //=========================================================================================================================== static const char * gQueryRecord_Name = NULL; static const char * gQueryRecord_Type = NULL; static int gQueryRecord_AAAAFallback = false; static int gQueryRecord_UseFailover = false; static const char * gQueryRecord_ResolverOverride = NULL; static int gQueryRecord_OneShot = false; static int gQueryRecord_TimeLimitSecs = 0; static int gQueryRecord_RawRData = false; static CLIOption kQueryRecordOpts[] = { InterfaceOption(), StringOption( 'n', "name", &gQueryRecord_Name, "domain name", "Full domain name of record to query.", true ), StringOption( 't', "type", &gQueryRecord_Type, "record type", "Record type by name (e.g., TXT, SRV, etc.) or number.", true ), CLI_OPTION_GROUP( "Flags" ), DNSSDFlagsOption(), DNSSDFlagsOption_AllowExpiredAnswers(), DNSSDFlagsOption_DenyCellular(), DNSSDFlagsOption_DenyConstrained(), DNSSDFlagsOption_DenyExpensive(), DNSSDFlagsOption_EnableDNSSEC(), DNSSDFlagsOption_ForceMulticast(), DNSSDFlagsOption_IncludeAWDL(), DNSSDFlagsOption_PathEvalDone(), DNSSDFlagsOption_ReturnIntermediates(), DNSSDFlagsOption_SuppressUnusable(), DNSSDFlagsOption_Timeout(), DNSSDFlagsOption_UnicastResponse(), CLI_OPTION_GROUP( "Attributes" ), BooleanOption( 0, "aaaaFallback", &gQueryRecord_AAAAFallback, "If a AAAA record doesn't exist, try querying for an A record of the same name and type." ), BooleanOption( 0, "useFailover", &gQueryRecord_UseFailover, "Use DNS service failover if necessary and applicable." ), StringOption( 0, "resolverOverride", &gQueryRecord_ResolverOverride, "UUID", "UUID of libnetwork resolver configuration to use as override.", false ), CLI_OPTION_GROUP( "Operation" ), ConnectionOptions(), BooleanOption( 'o', "oneshot", &gQueryRecord_OneShot, "Finish after first set of results." ), IntegerOption( 'l', "timeLimit", &gQueryRecord_TimeLimitSecs, "seconds", "Maximum duration of the query record operation. Use '0' for no time limit.", false ), BooleanOption( 0 , "raw", &gQueryRecord_RawRData, "Show record data as a hexdump." ), #if( MDNSRESPONDER_PROJECT ) FallbackDNSServiceGroup(), FallbackDNSServiceOption(), #endif ConnectionSection(), CLI_OPTION_END() }; //=========================================================================================================================== // Register Command Options //=========================================================================================================================== static const char * gRegister_Name = NULL; static const char * gRegister_Type = NULL; static const char * gRegister_Domain = NULL; static int gRegister_Port = 0; static const char * gRegister_TXT = NULL; static int gRegister_LifetimeMs = -1; static const char * gRegister_TimeOfReceipt = NULL; static const char * gRegister_HostKeyHash = NULL; static const char ** gAddRecord_Types = NULL; static size_t gAddRecord_TypesCount = 0; static const char ** gAddRecord_Data = NULL; static size_t gAddRecord_DataCount = 0; static const char ** gAddRecord_TTLs = NULL; static size_t gAddRecord_TTLsCount = 0; static const char ** gUpdateRecord_Datas = NULL; static size_t gUpdateRecord_DataCount = 0; static const char ** gUpdateRecord_DelaysMs = NULL; static size_t gUpdateRecord_DelayCount = 0; static const char ** gUpdateRecord_TTLs = NULL; static size_t gUpdateRecord_TTLCount = 0; static CLIOption kRegisterOpts[] = { InterfaceOption(), StringOption( 'n', "name", &gRegister_Name, "service name", "Name of service.", false ), StringOption( 't', "type", &gRegister_Type, "service type", "Service type, e.g., \"_ssh._tcp\".", true ), StringOption( 'd', "domain", &gRegister_Domain, "domain", "Domain in which to advertise the service.", false ), IntegerOption( 'p', "port", &gRegister_Port, "port number", "Service's port number.", true ), StringOption( 0 , "txt", &gRegister_TXT, "record data", "The TXT record data. See " kRecordDataSection_Name " below.", false ), CLI_OPTION_GROUP( "Attributes" ), StringOption( 0, "timestamp", &gRegister_TimeOfReceipt, "Unix time", "Time since epoch in seconds to indicate when the service registration request is received, should be used with flag kDNSServiceFlagsNoAutoRename", false ), StringOption( 0, "hostKeyHash", &gRegister_HostKeyHash, "32-bit hash", "Unique hostkey hash value.", false ), CLI_OPTION_GROUP( "Flags" ), DNSSDFlagsOption(), DNSSDFlagsOption_IncludeAWDL(), DNSSDFlagsOption_KnownUnique(), DNSSDFlagsOption_NoAutoRename(), CLI_OPTION_GROUP( "Operation" ), IntegerOption( 'l', "lifetime", &gRegister_LifetimeMs, "ms", "Lifetime of the service registration in milliseconds.", false ), CLI_OPTION_GROUP( "Options for adding extra record(s) to the registered service with DNSServiceAddRecord()\n" ), MultiStringOption( 0, "addType", &gAddRecord_Types, &gAddRecord_TypesCount, "record type", "Type of additional record by name (e.g., TXT, SRV, etc.) or number.", false ), MultiStringOptionEx( 0, "addData", &gAddRecord_Data, &gAddRecord_DataCount, "record data", "Additional record's data. See " kRecordDataSection_Name " below.", false, NULL ), MultiStringOption( 0, "addTTL", &gAddRecord_TTLs, &gAddRecord_TTLsCount, "seconds", "Time-to-live of additional record in seconds. Use '0' for the system default.", false ), CLI_OPTION_GROUP( "Options for updating the service's primary TXT record with DNSServiceUpdateRecord()\n" ), MultiStringOption( 0, "updateData", &gUpdateRecord_Datas, &gUpdateRecord_DataCount, "record data", "Record data for the record update. See " kRecordDataSection_Name " below.", false ), MultiStringOption( 0, "updateDelay", &gUpdateRecord_DelaysMs, &gUpdateRecord_DelayCount, "ms", "Number of milliseconds after registration to wait before the update.", false ), MultiStringOption( 0, "updateTTL", &gUpdateRecord_TTLs, &gUpdateRecord_TTLCount, "seconds", "Time-to-live of the updated record. Use '0' for the system default.", false ), CLI_SECTION( "Rules for multiple extra records", "1. The --addType, --addData, and --addTTL options can be specified more than once to add multiple extra records.\n" "2. The i-th --addType, --addData, and --addTTL options are used for the i-th extra record.\n" "3. The number of --addType options must equal the number of --addData options.\n" "4. The number of --addTTL options must equal the number of --addType options or not be specified at all, in\n" " which case a TTL of 0 is used for all extra records.\n" ), CLI_SECTION( "Rules for multiple primary TXT record updates", "1. The --updateData, --updateDelay, and --updateTTL options can be specified more than once to specify multiple\n" " primary TXT record updates.\n" "2. The i-th --updateData, --updateDelay, and --updateTTL options are used for the i-th update.\n" "3. The number of --updateData options must equal the number of --updateDelay options.\n" "4. The number of --updateTTL options must equal the number of --updateData options or not be specified at all,\n" " in which case a TTL of 0 is used for all updates.\n" ), RecordDataSection(), CLI_OPTION_END() }; //=========================================================================================================================== // RegisterRecord Command Options //=========================================================================================================================== static const char * gRegisterRecord_Name = NULL; static const char * gRegisterRecord_Type = NULL; static const char * gRegisterRecord_Data = NULL; static int gRegisterRecord_TTL = 0; static int gRegisterRecord_LifetimeMs = -1; static const char * gRegisterRecord_UpdateData = NULL; static int gRegisterRecord_UpdateDelayMs = 0; static int gRegisterRecord_UpdateTTL = 0; static const char * gRegisterRecord_TimeOfReceipt = NULL; static const char * gRegisterRecord_HostKeyHash = NULL; static CLIOption kRegisterRecordOpts[] = { InterfaceOption(), StringOption( 'n', "name", &gRegisterRecord_Name, "record name", "Fully qualified domain name of record.", true ), StringOption( 't', "type", &gRegisterRecord_Type, "record type", "Record type by name (e.g., TXT, PTR, A) or number.", true ), StringOption( 'd', "data", &gRegisterRecord_Data, "record data", "The record data. See " kRecordDataSection_Name " below.", false ), IntegerOption( 0 , "ttl", &gRegisterRecord_TTL, "seconds", "Time-to-live in seconds. Use '0' for default.", false ), CLI_OPTION_GROUP( "Attributes" ), StringOption( 0, "timestamp", &gRegisterRecord_TimeOfReceipt, "Unix time", "Time since epoch in seconds to indicate when the record registration request is received.", false ), StringOption( 0, "hostKeyHash", &gRegisterRecord_HostKeyHash, "32-bit hash", "Unique hostkey hash value.", false ), CLI_OPTION_GROUP( "Flags" ), DNSSDFlagsOption(), DNSSDFlagsOption_ForceMulticast(), DNSSDFlagsOption_IncludeAWDL(), DNSSDFlagsOption_KnownUnique(), DNSSDFlagsOption_Shared(), DNSSDFlagsOption_Unique(), CLI_OPTION_GROUP( "Operation" ), IntegerOption( 'l', "lifetime", &gRegisterRecord_LifetimeMs, "ms", "Lifetime of the service registration in milliseconds.", false ), CLI_OPTION_GROUP( "Options for updating the registered record with DNSServiceUpdateRecord()\n" ), StringOption( 0 , "updateData", &gRegisterRecord_UpdateData, "record data", "Record data for the record update.", false ), IntegerOption( 0 , "updateDelay", &gRegisterRecord_UpdateDelayMs, "ms", "Number of milliseconds after registration to wait before record update.", false ), IntegerOption( 0 , "updateTTL", &gRegisterRecord_UpdateTTL, "seconds", "Time-to-live of the updated record.", false ), RecordDataSection(), CLI_OPTION_END() }; //=========================================================================================================================== // Resolve Command Options //=========================================================================================================================== static char * gResolve_Name = NULL; static char * gResolve_Type = NULL; static char * gResolve_Domain = NULL; static int gResolve_TimeLimitSecs = 0; static CLIOption kResolveOpts[] = { InterfaceOption(), StringOption( 'n', "name", &gResolve_Name, "service name", "Name of the service instance to resolve.", true ), StringOption( 't', "type", &gResolve_Type, "service type", "Type of the service instance to resolve.", true ), StringOption( 'd', "domain", &gResolve_Domain, "domain", "Domain of the service instance to resolve.", true ), CLI_OPTION_GROUP( "Flags" ), DNSSDFlagsOption(), DNSSDFlagsOption_ForceMulticast(), DNSSDFlagsOption_IncludeAWDL(), DNSSDFlagsOption_ReturnIntermediates(), DNSSDFlagsOption_WakeOnResolve(), CLI_OPTION_GROUP( "Operation" ), ConnectionOptions(), IntegerOption( 'l', "timeLimit", &gResolve_TimeLimitSecs, "seconds", "Maximum duration of the resolve operation. Use '0' for no time limit.", false ), ConnectionSection(), CLI_OPTION_END() }; //=========================================================================================================================== // Reconfirm Command Options //=========================================================================================================================== static const char * gReconfirmRecord_Name = NULL; static const char * gReconfirmRecord_Type = NULL; static const char * gReconfirmRecord_Class = NULL; static const char * gReconfirmRecord_Data = NULL; static CLIOption kReconfirmOpts[] = { InterfaceOption(), StringOption( 'n', "name", &gReconfirmRecord_Name, "record name", "Full name of the record to reconfirm.", true ), StringOption( 't', "type", &gReconfirmRecord_Type, "record type", "Type of the record to reconfirm.", true ), StringOption( 'c', "class", &gReconfirmRecord_Class, "record class", "Class of the record to reconfirm. Default class is IN.", false ), StringOption( 'd', "data", &gReconfirmRecord_Data, "record data", "The record data. See " kRecordDataSection_Name " below.", false ), CLI_OPTION_GROUP( "Flags" ), DNSSDFlagsOption(), RecordDataSection(), CLI_OPTION_END() }; //=========================================================================================================================== // getaddrinfo-POSIX Command Options //=========================================================================================================================== static const char * gGAIPOSIX_HostName = NULL; static const char * gGAIPOSIX_ServName = NULL; static const char * gGAIPOSIX_Family = NULL; static int gGAIPOSIXFlag_AddrConfig = false; static int gGAIPOSIXFlag_All = false; static int gGAIPOSIXFlag_CanonName = false; static int gGAIPOSIXFlag_NumericHost = false; static int gGAIPOSIXFlag_NumericServ = false; static int gGAIPOSIXFlag_Passive = false; static int gGAIPOSIXFlag_V4Mapped = false; #if( defined( AI_V4MAPPED_CFG ) ) static int gGAIPOSIXFlag_V4MappedCFG = false; #endif #if( defined( AI_DEFAULT ) ) static int gGAIPOSIXFlag_Default = false; #endif #if( defined( AI_UNUSABLE ) ) static int gGAIPOSIXFlag_Unusable = false; #endif 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" ), 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" "address family is specified, then AF_UNSPEC is used.\n" "\n" ), BooleanOption( 0 , "flag-addrconfig", &gGAIPOSIXFlag_AddrConfig, "In hints ai_flags field, set AI_ADDRCONFIG." ), BooleanOption( 0 , "flag-all", &gGAIPOSIXFlag_All, "In hints ai_flags field, set AI_ALL." ), BooleanOption( 0 , "flag-canonname", &gGAIPOSIXFlag_CanonName, "In hints ai_flags field, set AI_CANONNAME." ), BooleanOption( 0 , "flag-numerichost", &gGAIPOSIXFlag_NumericHost, "In hints ai_flags field, set AI_NUMERICHOST." ), BooleanOption( 0 , "flag-numericserv", &gGAIPOSIXFlag_NumericServ, "In hints ai_flags field, set AI_NUMERICSERV." ), BooleanOption( 0 , "flag-passive", &gGAIPOSIXFlag_Passive, "In hints ai_flags field, set AI_PASSIVE." ), BooleanOption( 0 , "flag-v4mapped", &gGAIPOSIXFlag_V4Mapped, "In hints ai_flags field, set AI_V4MAPPED." ), #if( defined( AI_V4MAPPED_CFG ) ) BooleanOption( 0 , "flag-v4mappedcfg", &gGAIPOSIXFlag_V4MappedCFG, "In hints ai_flags field, set AI_V4MAPPED_CFG." ), #endif #if( defined( AI_DEFAULT ) ) BooleanOption( 0 , "flag-default", &gGAIPOSIXFlag_Default, "In hints ai_flags field, set AI_DEFAULT." ), #endif #if( defined( AI_UNUSABLE ) ) BooleanOption( 0 , "flag-unusable", &gGAIPOSIXFlag_Unusable, "In hints ai_flags field, set AI_UNUSABLE." ), #endif #if( MDNSRESPONDER_PROJECT ) FallbackDNSServiceGroup(), FallbackDNSServiceOption(), #endif CLI_SECTION( "Notes", "See getaddrinfo(3) man page for more details.\n" ), CLI_OPTION_END() }; //=========================================================================================================================== // ReverseLookup Command Options //=========================================================================================================================== static const char * gReverseLookup_IPAddr = NULL; static int gReverseLookup_OneShot = false; static int gReverseLookup_TimeLimitSecs = 0; static CLIOption kReverseLookupOpts[] = { InterfaceOption(), StringOption( 'a', "address", &gReverseLookup_IPAddr, "IP address", "IPv4 or IPv6 address for which to perform a reverse IP lookup.", true ), CLI_OPTION_GROUP( "Flags" ), DNSSDFlagsOption(), DNSSDFlagsOption_ForceMulticast(), DNSSDFlagsOption_ReturnIntermediates(), DNSSDFlagsOption_SuppressUnusable(), CLI_OPTION_GROUP( "Operation" ), ConnectionOptions(), BooleanOption( 'o', "oneshot", &gReverseLookup_OneShot, "Finish after first set of results." ), IntegerOption( 'l', "timeLimit", &gReverseLookup_TimeLimitSecs, "seconds", "Specifies the max duration of the query record operation. Use '0' for no time limit.", false ), ConnectionSection(), CLI_OPTION_END() }; //=========================================================================================================================== // PortMapping Command Options //=========================================================================================================================== static int gPortMapping_ProtocolTCP = false; static int gPortMapping_ProtocolUDP = false; static int gPortMapping_InternalPort = 0; static int gPortMapping_ExternalPort = 0; static int gPortMapping_TTL = 0; static CLIOption kPortMappingOpts[] = { InterfaceOption(), BooleanOption( 0, "tcp", &gPortMapping_ProtocolTCP, "Use kDNSServiceProtocol_TCP." ), BooleanOption( 0, "udp", &gPortMapping_ProtocolUDP, "Use kDNSServiceProtocol_UDP." ), IntegerOption( 0, "internalPort", &gPortMapping_InternalPort, "port number", "Internal port.", false ), IntegerOption( 0, "externalPort", &gPortMapping_ExternalPort, "port number", "Requested external port. Use '0' for any external port.", false ), IntegerOption( 0, "ttl", &gPortMapping_TTL, "seconds", "Requested TTL (renewal period) in seconds. Use '0' for a default value.", false ), CLI_OPTION_GROUP( "Flags" ), DNSSDFlagsOption(), CLI_OPTION_GROUP( "Operation" ), ConnectionOptions(), ConnectionSection(), CLI_OPTION_END() }; #if( TARGET_OS_DARWIN ) //=========================================================================================================================== // RegisterKA Command Options //=========================================================================================================================== static const char * gRegisterKA_LocalAddress = NULL; static const char * gRegisterKA_RemoteAddress = NULL; static int gRegisterKA_Timeout = 0; static CLIOption kRegisterKA_Opts[] = { DNSSDFlagsOption(), StringOption( 'l', "local", &gRegisterKA_LocalAddress, "IP addr+port", "TCP connection's local IPv4 or IPv6 address and port pair.", true ), StringOption( 'r', "remote", &gRegisterKA_RemoteAddress, "IP addr+port", "TCP connection's remote IPv4 or IPv6 address and port pair.", true ), IntegerOption( 't', "timeout", &gRegisterKA_Timeout, "timeout", "Keepalive record's timeout value, i.e., its 't=' value.", false ), CLI_OPTION_END() }; static void RegisterKACmd( void ); #endif //=========================================================================================================================== // BrowseAll Command Options //=========================================================================================================================== static const char * gBrowseAll_Domain = NULL; static const char ** gBrowseAll_ServiceTypes = NULL; static size_t gBrowseAll_ServiceTypesCount = 0; static int gBrowseAll_BrowseTimeSecs = 5; static int gBrowseAll_ConnectTimeout = 0; static int gBrowseAll_UseNewGAI = false; static int gBrowseAll_ValidateResults = false; 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 ), BooleanOption( 0 , "useNewGAI", &gBrowseAll_UseNewGAI, "Use dnssd_getaddrinfo_* instead of DNSServiceGetAddrInfo()." ), BooleanOption( 'v', "validate", &gBrowseAll_ValidateResults, "Validate results." ), CLI_OPTION_GROUP( "Flags" ), DNSSDFlagsOption_IncludeAWDL(), CLI_OPTION_GROUP( "Operation" ), 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() }; //=========================================================================================================================== // GetNameInfo Command Options //=========================================================================================================================== static void GetNameInfoCmd( void ); static char * gGetNameInfo_IPAddress = NULL; static int gGetNameInfoFlag_DGram = false; static int gGetNameInfoFlag_NameReqd = false; static int gGetNameInfoFlag_NoFQDN = false; static int gGetNameInfoFlag_NumericHost = false; static int gGetNameInfoFlag_NumericScope = false; static int gGetNameInfoFlag_NumericServ = false; static CLIOption kGetNameInfoOpts[] = { StringOption( 'a', "address", &gGetNameInfo_IPAddress, "IP address", "IPv4 or IPv6 address to use in sockaddr structure.", true ), CLI_OPTION_GROUP( "Flags" ), BooleanOption( 0 , "flag-dgram", &gGetNameInfoFlag_DGram, "Use NI_DGRAM flag." ), BooleanOption( 0 , "flag-namereqd", &gGetNameInfoFlag_NameReqd, "Use NI_NAMEREQD flag." ), BooleanOption( 0 , "flag-nofqdn", &gGetNameInfoFlag_NoFQDN, "Use NI_NOFQDN flag." ), BooleanOption( 0 , "flag-numerichost", &gGetNameInfoFlag_NumericHost, "Use NI_NUMERICHOST flag." ), BooleanOption( 0 , "flag-numericscope", &gGetNameInfoFlag_NumericScope, "Use NI_NUMERICSCOPE flag." ), BooleanOption( 0 , "flag-numericserv", &gGetNameInfoFlag_NumericServ, "Use NI_NUMERICSERV flag." ), CLI_SECTION( "Notes", "See getnameinfo(3) man page for more details.\n" ), CLI_OPTION_END() }; //=========================================================================================================================== // GetAddrInfoStress Command Options //=========================================================================================================================== static int gGAIStress_TestDurationSecs = 0; static int gGAIStress_ConnectionCount = 0; static int gGAIStress_DurationMinMs = 0; static int gGAIStress_DurationMaxMs = 0; static int gGAIStress_RequestCountMax = 0; static CLIOption kGetAddrInfoStressOpts[] = { InterfaceOption(), CLI_OPTION_GROUP( "Flags" ), DNSSDFlagsOption_ReturnIntermediates(), DNSSDFlagsOption_SuppressUnusable(), CLI_OPTION_GROUP( "Operation" ), IntegerOption( 0, "testDuration", &gGAIStress_TestDurationSecs, "seconds", "Stress test duration in seconds. Use '0' for forever.", false ), IntegerOption( 0, "connectionCount", &gGAIStress_ConnectionCount, "integer", "Number of simultaneous DNS-SD connections.", true ), IntegerOption( 0, "requestDurationMin", &gGAIStress_DurationMinMs, "ms", "Minimum duration of DNSServiceGetAddrInfo() request in milliseconds.", true ), IntegerOption( 0, "requestDurationMax", &gGAIStress_DurationMaxMs, "ms", "Maximum duration of DNSServiceGetAddrInfo() request in milliseconds.", true ), IntegerOption( 0, "consecutiveRequestMax", &gGAIStress_RequestCountMax, "integer", "Maximum number of requests on a connection before restarting it.", true ), CLI_OPTION_END() }; //=========================================================================================================================== // DNSQuery Command Options //=========================================================================================================================== static char * gDNSQuery_Name = NULL; static char * gDNSQuery_Type = "A"; static char * gDNSQuery_Server = NULL; static int gDNSQuery_TimeLimitSecs = 5; static int gDNSQuery_UseTCP = false; static int gDNSQuery_Flags = kDNSHeaderFlag_RecursionDesired; static int gDNSQuery_DNSSEC = false; static int gDNSQuery_CheckingDisabled = false; static int gDNSQuery_RawRData = false; static int gDNSQuery_Verbose = false; #if( TARGET_OS_DARWIN ) #define kDNSQueryServerOptionIsRequired false #else #define kDNSQueryServerOptionIsRequired true #endif static CLIOption kDNSQueryOpts[] = { StringOption( 'n', "name", &gDNSQuery_Name, "name", "Question name (QNAME) to put in DNS query message.", true ), StringOption( 't', "type", &gDNSQuery_Type, "type", "Question type (QTYPE) to put in DNS query message. (default: A)", false ), StringOptionEx( 's', "server", &gDNSQuery_Server, "IP address[+port]", "DNS server's IPv4 or IPv6 address and optional port.", kDNSQueryServerOptionIsRequired, "The following exemplify the notations that are supported:\n" "\n" " IPv4 address without port: 192.0.2.1\n" " IPv4 address with port 50001: 192.0.2.1:50001\n" " IPv6 address without port: 2001:db8::1\n" " IPv6 address with port 50001: [2001:db8::1]:50001\n" "\n" "If no port is specified, then the default DNS port of 53 is assumed.\n" "\n" ), IntegerOption( 'l', "timeLimit", &gDNSQuery_TimeLimitSecs, "seconds", "Specifies query time limit. Use '-1' for no limit and '0' to exit immediately after sending.", false ), BooleanOption( 0 , "tcp", &gDNSQuery_UseTCP, "Send the DNS query via TCP instead of UDP." ), IntegerOption( 'f', "flags", &gDNSQuery_Flags, "flags", "16-bit value for DNS header flags/codes field. (default: 0x0100 [Recursion Desired])", false ), BooleanOption( 0 , "dnssec", &gDNSQuery_DNSSEC, "Set the AD bit and include OPT record with DO extended flag bit set." ), BooleanOption( 0 , "checkingDisabled", &gDNSQuery_CheckingDisabled, "Set the Checking Disabled (CD) bit." ), BooleanOption( 0 , "raw", &gDNSQuery_RawRData, "Present record data as a hexdump." ), BooleanOption( 'v', "verbose", &gDNSQuery_Verbose, "Prints the DNS message to be sent to the server." ), CLI_OPTION_END() }; #if( DNSSDUTIL_INCLUDE_DNSCRYPT ) //=========================================================================================================================== // DNSCrypt Command Options //=========================================================================================================================== static char * gDNSCrypt_ProviderName = NULL; static char * gDNSCrypt_ProviderKey = NULL; static char * gDNSCrypt_Name = NULL; static char * gDNSCrypt_Type = NULL; static char * gDNSCrypt_Server = NULL; static int gDNSCrypt_TimeLimitSecs = 5; static int gDNSCrypt_RawRData = false; static int gDNSCrypt_Verbose = false; static CLIOption kDNSCryptOpts[] = { StringOption( 'p', "providerName", &gDNSCrypt_ProviderName, "name", "The DNSCrypt provider name.", true ), StringOption( 'k', "providerKey", &gDNSCrypt_ProviderKey, "hex string", "The DNSCrypt provider's public signing key.", true ), StringOption( 'n', "name", &gDNSCrypt_Name, "name", "Question name (QNAME) to put in DNS query message.", true ), StringOption( 't', "type", &gDNSCrypt_Type, "type", "Question type (QTYPE) to put in DNS query message.", true ), StringOption( 's', "server", &gDNSCrypt_Server, "IP address", "DNS server's IPv4 or IPv6 address.", true ), IntegerOption( 'l', "timeLimit", &gDNSCrypt_TimeLimitSecs, "seconds", "Specifies query time limit. Use '-1' for no time limit and '0' to exit immediately after sending.", false ), BooleanOption( 0 , "raw", &gDNSCrypt_RawRData, "Present record data as a hexdump." ), BooleanOption( 'v', "verbose", &gDNSCrypt_Verbose, "Prints the DNS message to be sent to the server." ), CLI_OPTION_END() }; #endif //=========================================================================================================================== // MDNSQuery Command Options //=========================================================================================================================== static char * gMDNSQuery_Name = NULL; static char * gMDNSQuery_Type = NULL; static int gMDNSQuery_SourcePort = 0; static int gMDNSQuery_IsQU = false; static int gMDNSQuery_RawRData = false; static int gMDNSQuery_UseIPv4 = false; static int gMDNSQuery_UseIPv6 = false; static int gMDNSQuery_AllResponses = false; static int gMDNSQuery_ReceiveSecs = 1; static CLIOption kMDNSQueryOpts[] = { StringOption( 'i', "interface", &gInterface, "name or index", "Network interface by name or index.", true ), StringOption( 'n', "name", &gMDNSQuery_Name, "name", "Question name (QNAME) to put in mDNS message.", true ), StringOption( 't', "type", &gMDNSQuery_Type, "type", "Question type (QTYPE) to put in mDNS message.", true ), IntegerOption( 'p', "sourcePort", &gMDNSQuery_SourcePort, "port number", "UDP source port to use when sending mDNS messages. Default is 5353 for QM questions.", false ), BooleanOption( 'u', "QU", &gMDNSQuery_IsQU, "Set the unicast-response bit, i.e., send a QU question." ), BooleanOption( 0 , "raw", &gMDNSQuery_RawRData, "Present record data as a hexdump." ), BooleanOption( 0 , "ipv4", &gMDNSQuery_UseIPv4, "Use IPv4." ), BooleanOption( 0 , "ipv6", &gMDNSQuery_UseIPv6, "Use IPv6." ), BooleanOption( 'a', "allResponses", &gMDNSQuery_AllResponses, "Print all received mDNS messages, not just those containing answers." ), IntegerOption( 'r', "receiveTime", &gMDNSQuery_ReceiveSecs, "seconds", "Amount of time to spend receiving messages after the query is sent. The default is one second. Use -1 for unlimited time.", false ), CLI_OPTION_END() }; //=========================================================================================================================== // 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 []\n" \ "\n" \ "The syntax for an action-string is\n" \ "\n" \ " ::= | \"-\" \n" \ " ::= [] \n" \ " ::= \"1\" | \"2\" | ... | \"10\"\n" \ " ::= \"n\" | \"r\" | \"u\" | \"m\" | \"p\"\n" \ "\n" \ "An expanded action-string is defined as\n" \ "\n" \ " ::= | \"-\" \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 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 \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 ; ... ; 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 ); #if( TARGET_OS_DARWIN ) //=========================================================================================================================== // PIDToUUID Command Options //=========================================================================================================================== static int gPIDToUUID_PID = 0; static CLIOption kPIDToUUIDOpts[] = { IntegerOption( 'p', "pid", &gPIDToUUID_PID, "PID", "Process ID.", true ), CLI_OPTION_END() }; #endif //=========================================================================================================================== // DNSServer Command Options //=========================================================================================================================== static const char 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 nine 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, the IPv6 label, Index labels, and SRV labels.\n" "\n" "Note: Sub-strings representing integers in domain name labels are in decimal notation and without leading zeros.\n"; static const char kDNSServerInfoText_NameExistence[] = "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" " 4. at most one TTL label; and\n" " 5. at most one IPv4 or IPv6 label.\n" " 6. at most one Index label.\n" "\n" "An SRV name is defined as a name with the following form:\n" "\n" " _._[.][.[.][.[.][...]]].d.test.\n" "\n" "See \"SRV Names\" for details.\n"; static const char kDNSServerInfoText_ResourceRecords[] = "Currently, the server only supports CNAME, A, AAAA, and SRV records.\n" "\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" "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:1::/120 block. Both of these address blocks are reserved for documentation. See\n" " and .\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 CNAME, A, and\n" "AAAA records with specific TTL values.\n"; static const char kDNSServerInfoText_AliasLabel[] = "Alias labels are of the form \"alias\" or \"alias-N\", where N is an integer in [2, 2^31 - 1].\n" "\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, 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 minus its first label.\n" "\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" "\n" " alias-4.count-5.d.test. 60 IN CNAME alias-3.count-5.d.test.\n" " alias-3.count-5.d.test. 60 IN CNAME alias-2.count-5.d.test.\n" " alias-2.count-5.d.test. 60 IN CNAME alias.count-5.d.test.\n" " alias.count-5.d.test. 60 IN CNAME count-5.d.test.\n"; static const char 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" "\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" " 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" " 2. The response will contain a CNAME record whose name is identical to QNAME, except that the first label\n" " is \"alias-ttl-T_N\", whose TTL is T_N, and whose RDATA is identical to QNAME stripped of its first\n" " label.\n" "\n" "Example. A response to a query with a QNAME of alias-ttl-20-40-80.count-5.d.test will contain the following\n" "CNAME records:\n" "\n" " alias-ttl-20-40-80.count-5.d.test. 20 IN CNAME alias-ttl-40-80.count-5.d.test.\n" " alias-ttl-40-80.count-5.d.test. 40 IN CNAME alias-ttl-80.count-5.d.test.\n" " alias-ttl-80.count-5.d.test. 80 IN CNAME count-5.d.test.\n"; static const char kDNSServerInfoText_CountLabel[] = "Count labels are of the form \"count-N_1\" or \"count-N_1-N_2\", where N_1 is an integer in [0, 255] and N_2 is\n" "an integer in [N_1, 255].\n" "\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 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" "Example. A response to an A record query with a QNAME of alias.count-3.d.test will contain the following A\n" "records:\n" "\n" " count-3.d.test. 60 IN A 203.0.113.1\n" " 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 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" "\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: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 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" "Address names that don't have a Count label are treated as though they contain a count label equal to\n" "count-1\".\n"; static const char kDNSServerInfoText_TagLabel[] = "Tag labels are labels prefixed with \"tag-\" and contain zero or more arbitrary octets after the prefix.\n" "\n" "This type of label exists to allow testers to \"uniquify\" domain names. Tag labels can also serve as padding\n" "to increase the sizes of domain names.\n"; static const char kDNSServerInfoText_TTLLabel[] = "TTL labels are of the form \"ttl-T\", where T is an integer in [0, 2^31 - 1].\n" "\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"; static const char kDNSServerInfoText_IPv4Label[] = "The IPv4 label is \"ipv4\". See \"Resource Records\" for the affect of this label.\n"; static const char kDNSServerInfoText_IPv6Label[] = "The IPv6 label is \"ipv6\". See \"Resource Records\" for the affect of this label.\n"; static const char kDNSServerInfoText_IndexLabel[] = "Index labels are of the form \"index-N\", where N is an integer in [1, 2^31 - 1].\n" "\n" "When the server runs in loopback-only mode, each of the server's addresses is assigned a sequential index value\n" "starting from 1. For example, if the server is running in loopback-only mode and listening exclusively on IPv6\n" "with two extra IPv6 addresses, then address ::1 would be assigned index 1, the first extra IPv6 address would be\n" "assigned index 2, and the second extra IPv6 address would be assigned index 3.\n" "\n" "If QNAME is an Address name and has an index label, then the query will be ignored unless the query was received\n" "on an address whose index value equals that of the index label. This is useful for simulating unresponsive servers.\n"; static const char 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 [.]., where 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"; static const char 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"; typedef enum { kDNSProtocol_Do53 = 0, kDNSProtocol_DoT = 1, kDNSProtocol_DoH = 2 } DNSProtocol; #define kDNSProtocolStr_Do53 "Do53" #define kDNSProtocolStr_DoT "DoT" #define kDNSProtocolStr_DoH "DoH" static int gDNSServer_LoopbackOnly = false; static int gDNSServer_Foreground = false; static int gDNSServer_ResponseDelayMs = 0; static int gDNSServer_DefaultTTL = 60; static int gDNSServer_Port = -1; static const char * gDNSServer_DomainOverride = NULL; static char ** gDNSServer_IgnoredQTypes = NULL; static size_t gDNSServer_IgnoredQTypesCount = 0; static int gDNSServer_ListenOnV4 = false; static int gDNSServer_ListenOnV6 = false; static int gDNSServer_BadUDPMode = false; static const char * gDNSServer_FollowPID = NULL; static int gDNSServer_ExtraV6Count = 0; static const char * gDNSServer_Protocol = kDNSProtocolStr_Do53; static int gDNSServer_RegisterWithSC = false; static int gDNSServer_MatchAllDomains = false; static CLIOption kDNSServerOpts[] = { BooleanOption( 'l', "loopback", &gDNSServer_LoopbackOnly, "Bind only to the loopback interface." ), 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 listening port. Use 0 for an ephemeral port. (default: Do53 → 53, DoT → 853, DoH → 443)", false ), StringOption( 0 , "domain", &gDNSServer_DomainOverride, "domain", "Use to override 'd.test.' as the server's domain.", false ), MultiStringOption( 'i', "ignoreQType", &gDNSServer_IgnoredQTypes, &gDNSServer_IgnoredQTypesCount, "qtype", "A QTYPE to ignore. This option can be specified more than once.", false ), BooleanOption( 0 , "ipv4", &gDNSServer_ListenOnV4, "Listen on IPv4. Will listen on both IPv4 and IPv6 if neither --ipv4 nor --ipv6 is used." ), BooleanOption( 0 , "ipv6", &gDNSServer_ListenOnV6, "Listen on IPv6. Will listen on both IPv4 and IPv6 if neither --ipv4 nor --ipv6 is used." ), #if( TARGET_OS_DARWIN ) 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." ), StringOptionEx( 'P', "protocol", &gDNSServer_Protocol, "protocol", "The DNS protocol to use. (default: Do53)", false, "\n" "Use '" kDNSProtocolStr_Do53 "' for DNS over UDP and TCP (Do53).\n" "Use '" kDNSProtocolStr_DoT "' for DNS over TLS (DoT).\n" "Use '" kDNSProtocolStr_DoH "' for DNS over HTTPS (DoH).\n" ), BooleanOption( 's', "registerSC", &gDNSServer_RegisterWithSC, "Register Do53 service with SystemConfiguration instead of mrc_dns_service_registration_*." ), BooleanOption( 0 , "default", &gDNSServer_MatchAllDomains, "If registering Do53 service with SystemConfiguration, include '.' as a match domain." ), #if( TARGET_OS_DARWIN ) CLI_OPTION_GROUP( "Loopback-Only Mode Options" ), IntegerOptionEx( 0 , "extraIPv6", &gDNSServer_ExtraV6Count, "count", "The number of extra IPv6 addresses to listen on. (default: 0)", false, "\n" "This option will add extra IPv6 addresses from the fded:f035:55e4::/64 address block to the loopback interface.\n" "The server will then bind to those addresses in addition to the standard loopback IP addresses, i.e., 127.0.0.1.\n" "and/or ::1, depending on the specified IP protocol options.\n" "\n" "This option is useful for setting up a DNS configuration with multiple server addresses, e.g., one for the\n" "primary server, one for the secondary server, etc. The Index label can then be used to simulate unresponsive\n" "servers.\n" "\n" "Note: This option is ignored unless the server is in loopback only mode and listening on IPv6.\n" "Note: This option currently requires root privileges.\n" ), #endif CLI_SECTION( "Intro", kDNSServerInfoText_Intro ), CLI_SECTION( "Name Existence", kDNSServerInfoText_NameExistence ), CLI_SECTION( "Resource Records", kDNSServerInfoText_ResourceRecords ), CLI_SECTION( "Alias Labels", kDNSServerInfoText_AliasLabel ), CLI_SECTION( "Alias-TTL Labels", kDNSServerInfoText_AliasTTLLabel ), CLI_SECTION( "Count Labels", kDNSServerInfoText_CountLabel ), CLI_SECTION( "Tag Labels", kDNSServerInfoText_TagLabel ), CLI_SECTION( "TTL Labels", kDNSServerInfoText_TTLLabel ), CLI_SECTION( "IPv4 Label", kDNSServerInfoText_IPv4Label ), CLI_SECTION( "IPv6 Label", kDNSServerInfoText_IPv6Label ), CLI_SECTION( "Index Labels", kDNSServerInfoText_IndexLabel ), CLI_SECTION( "SRV Names", kDNSServerInfoText_SRVNames ), CLI_SECTION( "Bad UDP Mode", kDNSServerInfoText_BadUDPMode ), CLI_OPTION_END() }; static void DNSServerCommand( void ); //=========================================================================================================================== // MDNSReplier Command Options //=========================================================================================================================== #define kMDNSReplierPortBase 50000 static const char 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"; static const char kMDNSReplierInfoText_Parameters[] = "There are five parameters that control the replier's set of authoritative records.\n" "\n" " 1. 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. 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., .local.,\n" " -1.local., ..., -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"; static const char kMDNSReplierInfoText_PTR[] = "The replier's authoritative PTR records have names of the form _t---._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---._tcp.local.\n" " TYPE: PTR\n" " CLASS: IN\n" " TTL: 4500\n" " RDATA: ._t---._tcp.local.\n" "\n" " 2. For each i in [2, N], there is one PTR record defined as\n" "\n" " NAME: _t---._tcp.local.\n" " TYPE: PTR\n" " CLASS: IN\n" " TTL: 4500\n" " RDATA: \" ()._t---._tcp.local.\"\n"; static const char kMDNSReplierInfoText_SRV[] = "The replier's authoritative SRV records have names of the form ._t---._tcp.local.,\n" "where L is an integer in [1, 65535], N is an integer in [1, N_max], and is or\n" "\" ()\", 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: ._t---._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: .local.\n" "\n" " 2. For each i in [2, N], there is one SRV record defined as:\n" "\n" " NAME: \" ()._t---._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: -.local.\n"; static const char kMDNSReplierInfoText_TXT[] = "The replier's authoritative TXT records have names of the form ._t---._tcp.local.,\n" "where L is an integer in [1, 65535], N is an integer in [1, N_max], and is or\n" "\" ()\", 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: ._t---._tcp.local.\n" " TYPE: TXT\n" " CLASS: IN\n" " TTL: 4500\n" " RDLENGTH: L\n" " RDATA: \n" "\n" " 2. For each i in [2, N], there is one TXT record:\n" "\n" " NAME: \" ()._t---._tcp.local.\"\n" " TYPE: TXT\n" " CLASS: IN\n" " TTL: 4500\n" " RDLENGTH: L\n" " RDATA: \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"; static const char kMDNSReplierInfoText_A[] = "The replier has exactly N_max ✕ N_a authoritative A records:\n" "\n" " For each i in [1, N_max], for each j in [1, N_a], an A record is defined as\n" "\n" " NAME: \".local.\" if i = 1, otherwise \"-.local.\"\n" " TYPE: A\n" " CLASS: IN\n" " TTL: 120\n" " RDLENGTH: 4\n" " RDATA: 0.<⌊i / 256⌋>..\n"; static const char kMDNSReplierInfoText_AAAA[] = "The replier has exactly N_max ✕ N_aaaa authoritative AAAA records:\n" "\n" " 1. For each j in [1, N_aaaa], a AAAA record is defined as\n" "\n" " NAME: .local.\n" " TYPE: AAAA\n" " CLASS: IN\n" " TTL: 120\n" " RDLENGTH: 16\n" " RDATA: fe80::1:\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: -.local.\n" " TYPE: AAAA\n" " CLASS: IN\n" " TTL: 120\n" " RDLENGTH: 16\n" " RDATA: 2001:db8:2:::\n"; static const char 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; #if( TARGET_OS_POSIX ) static const char * gMDNSReplier_FollowPID = NULL; #endif 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---._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 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_POSIX ) 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_SkipPathEvalulation = false; static int gGAIPerf_BadUDPMode = false; static int gGAIPerf_IterationCount = 100; static int gGAIPerf_IterationTimeLimitMs = 100; static const char * gGAIPerf_OutputFilePath = NULL; static const char * gGAIPerf_OutputFormat = kOutputFormatStr_JSON; static int gGAIPerf_OutputAppendNewline = false; static const char * gGAIPerf_Protocol = kDNSProtocolStr_Do53; static void GAIPerfCmd( void ); #define kGAIPerfSectionText_TestSuiteBasic \ "This test suite consists of the following three test cases:\n" \ "\n" \ "Test Case #1: Resolve a domain name with\n" \ "\n" \ " 2 CNAME records, 4 A records, and 4 AAAA records\n" \ "\n" \ "to its IPv4 and IPv6 addresses. Each iteration resolves a unique instance of such a domain name, which requires\n" \ "server queries.\n" \ "\n" \ "Test Case #2: Resolve a domain name with\n" \ "\n" \ " 2 CNAME records, 4 A records, and 4 AAAA records\n" \ "\n" \ "to its IPv4 and IPv6 addresses. A preliminary iteration resolves a unique instance of such a domain name, which\n" \ "requires server queries. Each subsequent iteration resolves the same domain name as the preliminary iteration,\n" \ "which should ideally require no additional server queries, i.e., the results should come from the cache.\n" \ "\n" \ "Unlike the preceding test case, this test case is concerned with DNSServiceGetAddrInfo() performance when the\n" \ "records of the domain name being resolved are already in the cache. Therefore, the time required to resolve the\n" \ "domain name in the preliminary iteration isn't counted in the performance stats.\n" \ "\n" \ "Test Case #3: Each iteration resolves localhost to its IPv4 and IPv6 addresses.\n" #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" \ "Test Case #N (where N is in [1, 32] and odd): Resolve a domain name with\n" \ "\n" \ " N_c CNAME records, N_a A records, and N_a AAAA records\n" \ "\n" \ "to its IPv4 and IPv6 addresses. Each iteration resolves a unique instance of such a domain name, which requires\n" \ "server queries.\n" \ "\n" \ "Test Case #N (where N is in [1, 32] and even): Resolve a domain name with\n" \ "\n" \ " N_c CNAME records, N_a A records, and N_a AAAA records\n" \ "\n" \ "to its IPv4 and IPv6 addresses. A preliminary iteration resolves a unique instance of such a domain name, which\n" \ "requires server queries. Each subsequent iteration resolves the same domain name as the preliminary iteration,\n" \ "which should ideally require no additional server queries, i.e., the results should come from the cache.\n" \ "\n" \ "Unlike the preceding test case, this test case is concerned with DNSServiceGetAddrInfo() performance when the\n" \ "records of the domain name being resolved are already in the cache. Therefore, the time required to resolve the\n" \ "domain name in the preliminary iteration isn't counted in the performance stats.\n" \ "\n" \ "N_c and N_a take on the following values, depending on the value of N:\n" \ "\n" \ " N_c is 0 if N is in [1, 8].\n" \ " N_c is 1 if N is in [9, 16].\n" \ " N_c is 2 if N is in [17, 24].\n" \ " N_c is 4 if N is in [25, 32].\n" \ "\n" \ " N_a is 1 if N mod 8 is 1 or 2.\n" \ " N_a is 2 if N mod 8 is 3 or 4.\n" \ " N_a is 4 if N mod 8 is 5 or 6.\n" \ " N_a is 8 if N mod 8 is 7 or 0.\n" \ "\n" \ "Finally,\n" \ "\n" \ "Test Case #33: Each iteration resolves localhost to its IPv4 and IPv6 addresses.\n" 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, '" 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 ), 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( 'i', "iterations", &gGAIPerf_IterationCount, "count", "The number of iterations per test case. (default: 100)", false ), IntegerOption( 'l', "timeLimit", &gGAIPerf_IterationTimeLimitMs, "ms", "Time limit for each DNSServiceGetAddrInfo() operation in milliseconds. (default: 100)", false ), IntegerOption( 0 , "callDelay", &gGAIPerf_CallDelayMs, "ms", "Time to wait before calling DNSServiceGetAddrInfo() in milliseconds. (default: 10)", false ), BooleanOption( 0 , "skipPathEval", &gGAIPerf_SkipPathEvalulation, "Use kDNSServiceFlagsPathEvaluationDone when calling DNSServiceGetAddrInfo()." ), 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." ), StringOptionEx( 'P', "protocol", &gGAIPerf_Protocol, "protocol", "The DNS protocol to use. (default: Do53)", false, "\n" "Use '" kDNSProtocolStr_Do53 "' for DNS over UDP and TCP (Do53).\n" "Use '" kDNSProtocolStr_DoT "' for DNS over TLS (DoT).\n" "Use '" kDNSProtocolStr_DoH "' for DNS over HTTPS (DoH).\n" ), 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 int gMDNSDiscoveryTest_UseNewGAI = false; static const char * gMDNSDiscoveryTest_HeapBytesLimit = NULL; 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 responses from each instance. (default: 0)", false ), BooleanOption( 0 , "ipv4", &gMDNSDiscoveryTest_UseIPv4, "Use IPv4." ), BooleanOption( 0 , "ipv6", &gMDNSDiscoveryTest_UseIPv6, "Use IPv6." ), BooleanOption( 0 , "useNewGAI", &gMDNSDiscoveryTest_UseNewGAI, "Use dnssd_getaddrinfo_* instead of DNSServiceGetAddrInfo()." ), StringOption( 'm', "memoryLimit", &gMDNSDiscoveryTest_HeapBytesLimit, "byte count", "If > 0, test fails if mDNSResponder's heap memory usage exceeds limit. (default: 0)", false ), 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 ), TestExitStatusSection(), CLI_OPTION_END() }; static void DotLocalTestCmd( void ); static const char * gDotLocalTest_Interface = NULL; static const char * gDotLocalTest_OutputFormat = kOutputFormatStr_JSON; 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., .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 ), 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 int gProbeConflictTest_UseIPv4 = false; static int gProbeConflictTest_UseIPv6 = false; static int gProbeConflictTest_RegisterOnAny = false; static int gProbeConflictTest_ExtraWaitMs = 0; static const char * gProbeConflictTest_OutputFormat = kOutputFormatStr_JSON; 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." ), BooleanOption( 0 , "ipv4", &gProbeConflictTest_UseIPv4, "Use IPv4 instead of IPv6. (Default behavior.)" ), BooleanOption( 0 , "ipv6", &gProbeConflictTest_UseIPv6, "Use IPv6 instead of IPv4." ), BooleanOption( 'r', "registerOnAny", &gProbeConflictTest_RegisterOnAny, "Register test service on kDNSServiceInterfaceIndexAny instead of specific interface." ), IntegerOptionEx( 0 , "extraWait", &gProbeConflictTest_ExtraWaitMs, "ms", "Extra time in milliseconds to wait after a probe conflict. (default: 0)", false, "\n" "After each probe conflict, there is a minimum amount of time that the test will wait to allow for probing and\n" "renames to take place. The minimum wait time was chosen for relatively tranquil environments.\n" "\n" "If the test environment is such that mDNSResponder is expected to be busier than usual, then use this option to\n" "allow extra time for renames, which may be delayed by events outside of mDNSResponder's control, such as network\n" "changes.\n" ), 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 ), TestExitStatusSection(), CLI_OPTION_END() }; static void RegistrationTestCmd( void ); static int gRegistrationTest_BATSEnvironment = false; static const char * gRegistrationTest_OutputFormat = kOutputFormatStr_JSON; static const char * gRegistrationTest_OutputFilePath = NULL; static CLIOption kRegistrationTestOpts[] = { CLI_OPTION_BOOLEAN( 0, "bats", &gRegistrationTest_BATSEnvironment, "Informs the test that it's running in a BATS environment.", "\n" "This option allows the test to take special measures while running in a BATS environment. Currently, this option\n" "only has an effect on watchOS. Because it has been observed that the Wi-Fi interface sometimes goes down during\n" "watchOS BATS testing, for watchOS, when a service is registered using kDNSServiceInterfaceIndexAny,\n" "\n" " 1. missing browse and query \"add\" results for Wi-Fi interfaces aren't enough for a subtest to fail; and\n" " 2. unexpected browse and query results for Wi-Fi interfaces are ignored.\n" ), CLI_OPTION_GROUP( "Results" ), FormatOption( 'f', "format", &gRegistrationTest_OutputFormat, "Specifies the test results output format. (default: " kOutputFormatStr_JSON ")", false ), StringOption( 'o', "output", &gRegistrationTest_OutputFilePath, "path", "Path of the file to write test results to instead of standard output (stdout).", false ), TestExitStatusSection(), CLI_OPTION_END() }; #if( MDNSRESPONDER_PROJECT ) static void FallbackTestCmd( void ); static int gFallbackTest_UseRefused = false; static const char * gFallbackTest_OutputFormat = kOutputFormatStr_JSON; static const char * gFallbackTest_OutputFilePath = NULL; static CLIOption kFallbackTestOpts[] = { BooleanOption( 0 , "useRefused", &gFallbackTest_UseRefused, "Have the server use the Refused RCODE in responses when a query is not allowed to be answered." ), CLI_OPTION_GROUP( "Results" ), FormatOption( 'f', "format", &gFallbackTest_OutputFormat, "Specifies the test results output format. (default: " kOutputFormatStr_JSON ")", false ), StringOption( 'o', "output", &gFallbackTest_OutputFilePath, "path", "Path of the file to write test results to instead of standard output (stdout).", false ), TestExitStatusSection(), CLI_OPTION_END() }; static void ExpensiveConstrainedTestCmd( void ); static const char * gExpensiveConstrainedTest_Interface = NULL; static const char * gExpensiveConstrainedTest_Name = NULL; static Boolean gExpensiveConstrainedTest_DenyExpensive = false; static Boolean gExpensiveConstrainedTest_DenyConstrained = false; static Boolean gExpensiveConstrainedTest_StartFromExpensive = false; static int gExpensiveConstrainedTest_ProtocolIPv4 = false; static int gExpensiveConstrainedTest_ProtocolIPv6 = false; static const char * gExpensiveConstrainedTest_OutputFormat = kOutputFormatStr_JSON; static const char * gExpensiveConstrainedTest_OutputFilePath = NULL; static CLIOption kExpensiveConstrainedTestOpts[] = { CLI_OPTION_GROUP( "Results" ), FormatOption( 'f', "format", &gExpensiveConstrainedTest_OutputFormat, "Specifies the test results output format. (default: " kOutputFormatStr_JSON ")", false ), StringOption( 'o', "output", &gExpensiveConstrainedTest_OutputFilePath, "path", "Path of the file to write test results to instead of standard output (stdout).", false ), TestExitStatusSection(), CLI_OPTION_END() }; static void DNSProxyTestCmd( void ); static const char * gDNSProxyTest_OutputFormat = kOutputFormatStr_JSON; static const char * gDNSProxyTest_OutputFilePath = NULL; static CLIOption kDNSProxyTestOpts[] = { CLI_OPTION_GROUP( "Results" ), FormatOption( 'f', "format", &gDNSProxyTest_OutputFormat, "Specifies the test results output format. (default: " kOutputFormatStr_JSON ")", false ), StringOption( 'o', "output", &gDNSProxyTest_OutputFilePath, "path", "Path of the file to write test results to instead of standard output (stdout).", false ), TestExitStatusSection(), CLI_OPTION_END() }; static void RCodeTestCmd( void ); static const char * gRCodeTest_OutputFormat = kOutputFormatStr_JSON; static const char * gRCodeTest_OutputFilePath = NULL; static CLIOption kRCodeTestOpts[] = { CLI_OPTION_GROUP( "Results" ), FormatOption( 'f', "format", &gRCodeTest_OutputFormat, "Specifies the test results output format. (default: " kOutputFormatStr_JSON ")", false ), StringOption( 'o', "output", &gRCodeTest_OutputFilePath, "path", "Path of the file to write test results to instead of standard output (stdout).", false ), TestExitStatusSection(), CLI_OPTION_END() }; static void DNSQueryTestCmd( void ); static const char * gDNSQueryTest_OutputFormat = kOutputFormatStr_JSON; static const char * gDNSQueryTest_OutputFilePath = NULL; static CLIOption kDNSQueryTestOpts[] = { CLI_OPTION_GROUP( "Results" ), FormatOption( 'f', "format", &gDNSQueryTest_OutputFormat, "Specifies the test results output format. (default: " kOutputFormatStr_JSON ")", false ), StringOption( 'o', "output", &gDNSQueryTest_OutputFilePath, "path", "Path of the file to write test results to instead of standard output (stdout).", false ), TestExitStatusSection(), CLI_OPTION_END() }; static void FastRecoveryTestCmd( void ); static const char * gFastRecoveryTest_OutputFormat = kOutputFormatStr_JSON; static const char * gFastRecoveryTest_OutputFilePath = NULL; static CLIOption kFastRecoveryTestOpts[] = { CLI_OPTION_GROUP( "Results" ), FormatOption( 'f', "format", &gFastRecoveryTest_OutputFormat, "Specifies the test results output format. (default: " kOutputFormatStr_JSON ")", false ), StringOption( 'o', "output", &gFastRecoveryTest_OutputFilePath, "path", "Path of the file to write test results to instead of standard output (stdout).", false ), TestExitStatusSection(), CLI_OPTION_END() }; static void XCTestCmd( void ); static const char * gXCTest_Classname = NULL; static CLIOption kXCTestOpts[] = { StringOption( 'c', "class", &gXCTest_Classname, "classname", "The classname of the XCTest to run (from /AppleInternal/XCTests/com.apple.mDNSResponder/Tests.xctest)", true ), CLI_OPTION_END() }; static void MultiConnectTestCmd( void ); static int gMultiConnectTest_ConnectionCount = 4; // default to 4 static CLIOption kMultiConnectTestOpts[] = { IntegerOption( 0, "connections", &gMultiConnectTest_ConnectionCount, "count", "Number of simultanious connections. (default: 4)", false ), CLI_OPTION_END() }; #endif // MDNSRESPONDER_PROJECT #if( TARGET_OS_DARWIN ) static void KeepAliveTestCmd( void ); static const char * gKeepAliveTest_OutputFormat = kOutputFormatStr_JSON; static const char * gKeepAliveTest_OutputFilePath = NULL; static CLIOption kKeepAliveTestOpts[] = { CLI_OPTION_GROUP( "Results" ), FormatOption( 'f', "format", &gKeepAliveTest_OutputFormat, "Specifies the test results output format. (default: " kOutputFormatStr_JSON ")", false ), StringOption( 'o', "output", &gKeepAliveTest_OutputFilePath, "path", "Path of the file to write test results to instead of standard output (stdout).", false ), TestExitStatusSection(), CLI_OPTION_END() }; #endif // TARGET_OS_DARWIN static void DNSSECTestCmd( void ); static const char * gDNSSECTest_TestCaseName = NULL; #if ( ENABLE_DNSSDUTIL_DNSSEC_TEST == 1 ) static const char * gDNSSECTest_OutputFormat = kOutputFormatStr_JSON; static const char * gDNSSECTest_OutputFilePath = NULL; #endif static CLIOption kDNSSECTestOpts[] = { StringOption( 'n', "testCaseName", &gDNSSECTest_TestCaseName, "Specifies the DNSSEC test that the user intends to run", "test name", true ), CLI_OPTION_GROUP( "Results" ), FormatOption( 'f', "format", &gExpensiveConstrainedTest_OutputFormat, "Specifies the test results output format. (default: " kOutputFormatStr_JSON ")", false ), StringOption( 'o', "output", &gExpensiveConstrainedTest_OutputFilePath, "path", "Path of the file to write test results to instead of standard output (stdout).", false ), TestExitStatusSection(), CLI_OPTION_END() }; static void OptimisticDNSTestCommand( void ); static int gOptimisticDNSTest_FullTest = false; static CLIOption kOptimisticDNSTestOpts[] = { BooleanOption( 'f', "full", &gOptimisticDNSTest_FullTest, "Proceed with the full version of the test, including subtests that require mDNSResponder to be killed." ), CLI_SECTION( "Exit Status", "This command exits with a status code of 0 if the test passed and a non-zero status code if it fails.\n" ), CLI_OPTION_END() }; static void RecordRegistrationTestCommand( void ); static int gRecordRegistrationTest_RRSetChangeIntervalMs = 0; static CLIOption kRecordRegistrationTestOpts[] = { IntegerOption( 'n', "interval", &gRecordRegistrationTest_RRSetChangeIntervalMs, "ms", "Interval between bulk RRSet changes in milliseconds. Use 0 for a default interval. (default: 0)", false ), CLI_SECTION( "Exit Status", "This command exits with a status code of 0 if the test passed and a non-zero status code if it fails.\n" ), CLI_OPTION_END() }; static void RecordCacheFlushTestCommand( void ); static CLIOption kRecordCacheFlushTestOpts[] = { CLI_SECTION( "Exit Status", "This command exits with a status code of 0 if the test passed and a non-zero status code if it fails.\n" ), CLI_OPTION_END() }; static void ResolverOverrideTestCommand( void ); static CLIOption kResolverOverrideTestOpts[] = { CLI_SECTION( "Exit Status", "This command exits with a status code of 0 if the test passed and a non-zero status code if it fails.\n" ), CLI_OPTION_END() }; static CLIOption kTestOpts[] = { 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 ), Command( "registration", RegistrationTestCmd, kRegistrationTestOpts, "Tests service registrations.", false ), #if( MDNSRESPONDER_PROJECT ) Command( "fallback", FallbackTestCmd, kFallbackTestOpts, "Tests DNS server fallback.", false ), Command( "expensive_constrained_updates", ExpensiveConstrainedTestCmd, kExpensiveConstrainedTestOpts, "Tests if the mDNSResponder can handle expensive and constrained property change correctly", false ), Command( "dnsproxy", DNSProxyTestCmd, kDNSProxyTestOpts, "Tests mDNSResponder's DNS proxy.", false ), Command( "rcodes", RCodeTestCmd, kRCodeTestOpts, "Tests handling of all DNS RCODEs.", false ), Command( "dnsquery", DNSQueryTestCmd, kDNSQueryTestOpts, "Tests mDNSResponder's DNS queries.", false ), Command( "fastrecovery", FastRecoveryTestCmd, kFastRecoveryTestOpts, "Tests mDNSResponder's fast querier recovery.", false ), Command( "xctest", XCTestCmd, kXCTestOpts, "Run a XCTest from /AppleInternal/XCTests/com.apple.mDNSResponder/Tests.xctest.", true ), Command( "multiconnect", MultiConnectTestCmd, kMultiConnectTestOpts, "Tests multiple simultanious connections.", false ), #endif #if( TARGET_OS_DARWIN ) Command( "keepalive", KeepAliveTestCmd, kKeepAliveTestOpts, "Tests keepalive record registrations.", false ), #endif Command( "dnssec", DNSSECTestCmd, kDNSSECTestOpts, "Tests mDNSResponder's DNSSEC validation", false), Command( "optimisticDNS", OptimisticDNSTestCommand, kOptimisticDNSTestOpts, "Tests mDNSResponder's Optimistic DNS functionality.", false ), Command( "record-registration", RecordRegistrationTestCommand, kRecordRegistrationTestOpts, "Tests the registration and deregistration of records.", false ), Command( "record-cache-flush", RecordCacheFlushTestCommand, kRecordCacheFlushTestOpts, "Tests the mrc_record_cache_flush SPI.", false ), Command( "resolver-override", ResolverOverrideTestCommand, kResolverOverrideTestOpts, "Tests use of DNSServiceQueryRecordWithAttribute() with a resolver override attribute.", false ), CLI_OPTION_END() }; //=========================================================================================================================== // SSDP Command Options //=========================================================================================================================== static int gSSDPDiscover_MX = 1; static const char * gSSDPDiscover_ST = "ssdp:all"; static int gSSDPDiscover_ReceiveSecs = 1; static int gSSDPDiscover_UseIPv4 = false; static int gSSDPDiscover_UseIPv6 = false; static int gSSDPDiscover_Verbose = false; static CLIOption kSSDPDiscoverOpts[] = { StringOption( 'i', "interface", &gInterface, "name or index", "Network interface by name or index.", true ), IntegerOption( 'm', "mx", &gSSDPDiscover_MX, "seconds", "MX value in search request, i.e., max response delay in seconds. (Default: 1 second)", false ), StringOption( 's', "st", &gSSDPDiscover_ST, "string", "ST value in search request, i.e., the search target. (Default: \"ssdp:all\")", false ), IntegerOption( 'r', "receiveTime", &gSSDPDiscover_ReceiveSecs, "seconds", "Amount of time to spend receiving responses. -1 means unlimited. (Default: 1 second)", false ), BooleanOption( 0 , "ipv4", &gSSDPDiscover_UseIPv4, "Use IPv4, i.e., multicast to 239.255.255.250:1900." ), BooleanOption( 0 , "ipv6", &gSSDPDiscover_UseIPv6, "Use IPv6, i.e., multicast to [ff02::c]:1900" ), BooleanOption( 'v', "verbose", &gSSDPDiscover_Verbose, "Prints the search request(s) that were sent." ), CLI_OPTION_END() }; static void SSDPDiscoverCmd( void ); static CLIOption kSSDPOpts[] = { Command( "discover", SSDPDiscoverCmd, kSSDPDiscoverOpts, "Crafts and multicasts an SSDP search message.", false ), CLI_OPTION_END() }; #if( TARGET_OS_DARWIN ) //=========================================================================================================================== // res_query Command Options //=========================================================================================================================== static void ResQueryCmd( void ); static const char * gResQuery_Name = NULL; static const char * gResQuery_Type = NULL; static const char * gResQuery_Class = NULL; static int gResQuery_UseLibInfo = false; static CLIOption kResQueryOpts[] = { StringOption( 'n', "name", &gResQuery_Name, "domain name", "Full domain name of record to query.", true ), StringOption( 't', "type", &gResQuery_Type, "record type", "Record type by name (e.g., TXT, SRV, etc.) or number.", true ), StringOption( 'c', "class", &gResQuery_Class, "record class", "Record class by name or number. Default class is IN.", false ), BooleanOption( 0 , "libinfo", &gResQuery_UseLibInfo, "Use res_query from libinfo instead of libresolv." ), CLI_OPTION_END() }; //=========================================================================================================================== // dns_query Command Options //=========================================================================================================================== static void ResolvDNSQueryCmd( void ); static const char * gResolvDNSQuery_Name = NULL; static const char * gResolvDNSQuery_Type = NULL; static const char * gResolvDNSQuery_Class = NULL; static const char * gResolvDNSQuery_Path = NULL; static CLIOption kResolvDNSQueryOpts[] = { StringOption( 'n', "name", &gResolvDNSQuery_Name, "domain name", "Full domain name of record to query.", true ), StringOption( 't', "type", &gResolvDNSQuery_Type, "record type", "Record type by name (e.g., TXT, SRV, etc.) or number.", true ), StringOption( 'c', "class", &gResolvDNSQuery_Class, "record class", "Record class by name or number. Default class is IN.", false ), StringOption( 'p', "path", &gResolvDNSQuery_Path, "file path", "The path argument to pass to dns_open() before calling dns_query(). Default value is NULL.", false ), CLI_OPTION_END() }; //=========================================================================================================================== // CFHost Command Options //=========================================================================================================================== static void CFHostCmd( void ); static const char * gCFHost_Name = NULL; static int gCFHost_WaitSecs = 0; static CLIOption kCFHostOpts[] = { StringOption( 'n', "name", &gCFHost_Name, "hostname", "Hostname to resolve.", true ), IntegerOption( 'w', "wait", &gCFHost_WaitSecs, "seconds", "Time in seconds to wait before a normal exit. (default: 0)", false ), CLI_OPTION_END() }; static CLIOption kLegacyOpts[] = { Command( "res_query", ResQueryCmd, kResQueryOpts, "Uses res_query() from either libresolv or libinfo to query for a record.", true ), Command( "dns_query", ResolvDNSQueryCmd, kResolvDNSQueryOpts, "Uses dns_query() from libresolv to query for a record.", true ), Command( "cfhost", CFHostCmd, kCFHostOpts, "Uses CFHost to resolve a hostname.", true ), CLI_OPTION_END() }; //=========================================================================================================================== // DNSConfigAdd Command Options //=========================================================================================================================== static void DNSConfigAddCmd( void ); static CFStringRef gDNSConfigAdd_ID = NULL; static char ** gDNSConfigAdd_IPAddrArray = NULL; static size_t gDNSConfigAdd_IPAddrCount = 0; static char ** gDNSConfigAdd_DomainArray = NULL; static size_t gDNSConfigAdd_DomainCount = 0; static const char * gDNSConfigAdd_Interface = NULL; static int gDNSConfigAdd_SearchOrder = -1; static CLIOption kDNSConfigAddOpts[] = { CFStringOption( 0 , "id", &gDNSConfigAdd_ID, "ID", "Arbitrary ID to use for resolver entry.", true ), MultiStringOption( 'a', "address", &gDNSConfigAdd_IPAddrArray, &gDNSConfigAdd_IPAddrCount, "IP address", "DNS server IP address(es). Can be specified more than once.", true ), MultiStringOption( 'd', "domain", &gDNSConfigAdd_DomainArray, &gDNSConfigAdd_DomainCount, "domain", "Specific domain(s) for the resolver entry. Can be specified more than once.", false ), StringOption( 'i', "interface", &gDNSConfigAdd_Interface, "interface name", "Specific interface for the resolver entry.", false ), IntegerOption( 'o', "searchOrder", &gDNSConfigAdd_SearchOrder, "integer", "Resolver entry's search order. Will only be set for values >= 0. (default: -1)", false ), CLI_SECTION( "Notes", "Run 'scutil -d -v --dns' to see the current DNS configuration. See scutil(8) man page for more details.\n" ), CLI_OPTION_END() }; //=========================================================================================================================== // DNSConfigRemove Command Options //=========================================================================================================================== static void DNSConfigRemoveCmd( void ); static CFStringRef gDNSConfigRemove_ID = NULL; static CLIOption kDNSConfigRemoveOpts[] = { CFStringOption( 0, "id", &gDNSConfigRemove_ID, "ID", "ID of resolver entry to remove.", true ), CLI_SECTION( "Notes", "Run 'scutil -d -v --dns' to see the current DNS configuration. See scutil(8) man page for more details.\n" ), CLI_OPTION_END() }; static CLIOption kDNSConfigOpts[] = { Command( "add", DNSConfigAddCmd, kDNSConfigAddOpts, "Add a supplemental resolver entry to the system's DNS configuration.", true ), Command( "remove", DNSConfigRemoveCmd, kDNSConfigRemoveOpts, "Remove a supplemental resolver entry from the system's DNS configuration.", true ), CLI_OPTION_END() }; //=========================================================================================================================== // XPCSend //=========================================================================================================================== static void XPCSendCommand( void ); static const char * gXPCSend_ServiceName = NULL; static const char * gXPCSend_MessageStr = NULL; static int gXPCSend_NoReply = false; static int gXPCSend_CancelDelaySecs = 0; static const char kXPCSendMessageSection_Name[] = "Message Argument"; static const char kXPCSendMessageSection_Text[] = "XPC messages are described as a string using the following syntax.\n" "\n" "With the exception of the top-most XPC message dictionary, dictionaries begin with a '{' and end with a '}'.\n" "Key-value pairs are of the form =, where is a string and is a value of any of the\n" "currently supported XPC types.\n" "\n" "Arrays begin with a '[' and end with a ']'.\n" "\n" "The following non-container XPC types are supported:\n" "\n" "Type Syntax Example\n" "bool bool: bool:true (or yes/y/on/1), bool:false (or no/n/off/0)\n" "data data: data:C0000201\n" "int64 (signed 64-bit integer) int: int:10, int:-1\n" "string string: string:example, string:escaped\\ white\\ space\n" "uint64 (unsigned 64-bit integer) uint: uint:1024 or uint:0x400\n" "UUID uuid: uuid:dab10183-84b5-4859-9de6-4bee287cfea3\n" "\n" "For convenience, the following type prefix abbreviations are accepted:\n" "\n" " bool: ↔ b:\n" " data: ↔ d:\n" " int: ↔ i:\n" " string: ↔ s:\n" " uint: ↔ u:\n" "\n" "Here are some message string examples:\n" "\n" " 1. 'cmd=s:add make=s:Apple model=s:Macintosh aliases=[s:Mac s:Macintosh\\ 128K]'\n" " 2. 'cmd=s:search features={portable=b:yes solar=b:no} priceMin=u:100 priceMax=u:200'\n"; static CLIOption kXPCSendOpts[] = { StringOption( 's', "service", &gXPCSend_ServiceName, "service name", "XPC service name.", true ), StringOption( 'm', "message", &gXPCSend_MessageStr, "message", "XPC message as a string.", false ), BooleanOption( 'n', "noReply", &gXPCSend_NoReply, "No reply is expected." ), IntegerOption( 'd', "delay", &gXPCSend_CancelDelaySecs, "seconds", "Time to delay the XPC connection's cancellation in seconds. (default: 0)", false ), CLI_SECTION( kXPCSendMessageSection_Name, kXPCSendMessageSection_Text ), CLI_OPTION_END() }; #endif // TARGET_OS_DARWIN #if( MDNSRESPONDER_PROJECT ) //=========================================================================================================================== // InterfaceMonitor Command Options //=========================================================================================================================== static void InterfaceMonitorCmd( void ); static CLIOption kInterfaceMonitorOpts[] = { StringOption( 'i', "interface", &gInterface, "name or index", "Network interface by name or index.", true ), CLI_OPTION_END() }; //=========================================================================================================================== // Querier Command Options //=========================================================================================================================== #define kMDNSResolverTypeStr_Normal "normal" #define kMDNSResolverTypeStr_TCPOnly "tcp" #define kMDNSResolverTypeStr_TLS "tls" #define kMDNSResolverTypeStr_HTTPS "https" static const char * gQuerier_Name = NULL; static const char * gQuerier_Type = "A"; static const char * gQuerier_Class = "IN"; static const char * gQuerier_Delegator = NULL; static int gQuerier_DNSSECOK = false; static int gQuerier_CheckingDisabled = false; static int gQuerier_SensitiveLogging = false; static const char * gQuerier_ResolverType = NULL; static char ** gQuerier_ServerAddrs = NULL; static size_t gQuerier_ServerAddrCount = 0; static const char * gQuerier_ProviderName = NULL; static const char * gQuerier_ConnectionHostname = NULL; static const char * gQuerier_URLPath = NULL; static const char * gQuerier_ODoHConfig = NULL; static const char * gQuerier_IdentityReference = NULL; static int gQuerier_NoConnectionReuse = false; static int gQuerier_SquashCNAMEs = false; static char ** gQuerier_Domains = NULL; static size_t gQuerier_DomainCount = 0; static const char * gQuerier_StartLeewayMs = NULL; static CLIOption kQuerierOpts[] = { StringOption( 'i', "interface", &gInterface, "name or index", "If specified, network traffic is scoped to this interface.", false ), StringOption( 'n', "name", &gQuerier_Name, "name", "Question name (QNAME).", true ), StringOption( 't', "type", &gQuerier_Type, "type", "Question type (QTYPE). (default: A)", false ), StringOption( 'c', "class", &gQuerier_Class, "class", "Question class (QCLASS). (default: IN)", false ), StringOption( 0 , "delegator", &gQuerier_Delegator, "PID|UUID", "Delegator's PID or UUID.", false ), BooleanOption( 0 , "dnssec", &gQuerier_DNSSECOK, "Have queries include an OPT record with the DNSSEC OK (DO) bit set." ), BooleanOption( 0 , "checkingDisabled", &gQuerier_CheckingDisabled, "Set the Checking Disabled (CD) bit in queries." ), BooleanOption( 0 , "sensitiveLogging", &gQuerier_SensitiveLogging, "Enable sensitive logging for the query." ), StringOption( 0 , "startLeeway", &gQuerier_StartLeewayMs, "ms", "Start time leeway in milliseconds. Negative values mean infinite leeway.", false ), CLI_OPTION_GROUP( "DNS Service Options" ), StringOptionEx( 'r', "resolverType", &gQuerier_ResolverType, "resolver type", "Specifies the type of resolver to use.", false, "\n" "Use '" kMDNSResolverTypeStr_Normal "' for DNS over UDP and TCP (Do53).\n" "Use '" kMDNSResolverTypeStr_TCPOnly "' for DNS over TCP.\n" "Use '" kMDNSResolverTypeStr_TLS "' for DNS over TLS (DoT).\n" "Use '" kMDNSResolverTypeStr_HTTPS "' for DNS over HTTPS (DoH or ODoH).\n" "\n" "If no resolver type is specified, an mdns_dns_service_manager will be used to determine which DNS service to use.\n" "How the mdns_dns_service_manager's services are populated depends on whether the server and domain options are\n" "specified. If either the server or domain, or both, options are specified, then an mdns_dns_service_definition\n" "will be used to define a Do53 DNS service using the interface, server, and domain options. If neither the server\n" "nor the domain options are specified, then the mdns_dns_service_manager will be populated with the system's DNS\n" "configuration. In both of these cases, the other resolver-specific options will be ignored.\n" "\n" ), MultiStringOptionEx( 's', "server", &gQuerier_ServerAddrs, &gQuerier_ServerAddrCount, "IP address", "Server's IPv4 or IPv6 address with optionally-specified port.", false, "\n" "Use this option one or more times to specify a DNS service's server(s) by IP address.\n" "\n" "If no server IP addresses are specified for DNS over TLS/HTTPS resolvers, then connections to the DNS service\n" "will use the specified provider name as the DNS service's hostname.\n" "\n" ), MultiStringOptionEx( 'd', "domain", &gQuerier_Domains, &gQuerier_DomainCount, "domain name", "DNS service's domains.", false, "\n" "Use this option one or more times to specify a DNS service's domains. This option is ignored if a resolver type\n" "is specified.\n" "\n" ), StringOption( 'p', "providerName", &gQuerier_ProviderName, "domain name", "Provider's domain name for DNS over TLS/HTTPS.", false ), StringOption( 0 , "connectionHostname", &gQuerier_ConnectionHostname, "hostname", "Overrides hostname used for transport layer connection for DNS over TLS/HTTPS.", false ), StringOption( 'q', "urlPath", &gQuerier_URLPath, "path", "URL path for DNS over HTTPS.", false ), StringOption( 'o', "odohConfig", &gQuerier_ODoHConfig, "odoh config", "Config for Oblivious DNS over HTTPS.", false ), StringOption( 0 , "identityReference", &gQuerier_IdentityReference, "hex string", "Persistent keychain reference for a client certificate.", false ), BooleanOption( 0 , "noConnectionReuse", &gQuerier_NoConnectionReuse, "Disable connection reuse." ), BooleanOption( 0 , "squashCNAMEs", &gQuerier_SquashCNAMEs, "Squash CNAME chains in responses." ), CLI_OPTION_END() }; static void QuerierCommand( void ); //=========================================================================================================================== // DNSProxy Command Options //=========================================================================================================================== static void DNSProxyCmd( void ); static char ** gDNSProxy_InputInterfaces = NULL; static size_t gDNSProxy_InputInterfaceCount = 0; static const char * gDNSProxy_OutputInterface = NULL; static const char * gDNSProxy_DNS64IPv6Prefix = NULL; static int gDNSProxy_ForceAAAASynthesis = false; static CLIOption kDNSProxyOpts[] = { MultiStringOption( 'i', "inputInterface", &gDNSProxy_InputInterfaces, &gDNSProxy_InputInterfaceCount, "name or index", "Interface to accept queries on. Can be specified more than once.", true ), StringOption( 'o', "outputInterface", &gDNSProxy_OutputInterface, "name or index", "Interface to forward queries over. Use '0' for primary interface. (default: 0)", false ), StringOption( 'p', "dns64Prefix", &gDNSProxy_DNS64IPv6Prefix, "IPv6 prefix", "IPv6 prefix to use for DNS64 AAAA record synthesis.", false ), BooleanOption( 'f', "forceAAAASynth", &gDNSProxy_ForceAAAASynthesis, "Force AAAA synthesis for DNS64." ), CLI_OPTION_END() }; //=========================================================================================================================== // GetAddrInfoNew Command Options //=========================================================================================================================== static const char * gGAINew_Hostname = NULL; static const char * gGAINew_DelegatorID = NULL; static const char * gGAINew_ServiceScheme = NULL; static const char * gGAINew_AccountID = NULL; static int gGAINew_ProtocolIPv4 = false; static int gGAINew_ProtocolIPv6 = false; static int gGAINew_ShowTracker = false; static int gGAINew_UseFailover = false; static int gGAINew_ProhibitEncryptedDNS = false; static int gGAINew_OneShot = false; static int gGAINew_TimeLimitSecs = 0; static const char * gGAINew_QoS = NULL; static const char * gGAINew_ResolverUUID = NULL; static int gGAINew_PrivateLogging = false; #define kQoSTypeStr_Unspecified "unspecified" #define kQoSTypeStr_Background "background" #define kQoSTypeStr_Utility "utility" #define kQoSTypeStr_Default "default" #define kQoSTypeStr_UserInitiated "userInitiated" #define kQoSTypeStr_UserInteractive "userInteractive" #define kQoSArgShortName "QoS class" static CLIOption kGetAddrInfoNewOpts[] = { InterfaceOption(), StringOption( 'n', "name", &gGAINew_Hostname, "domain name", "Hostname to resolve.", true ), StringOption( 'd', "delegate", &gGAINew_DelegatorID, "PID|UUID", "Delegator's PID or UUID. If PID p < 0, the audit token of PID |p| will be used.", false ), StringOption( 0, "accountID", &gGAINew_AccountID, "account ID", "Account ID string.", false ), StringOption( 0, "serviceScheme", &gGAINew_ServiceScheme, "scheme", "Service scheme such as '_443._https'.", false ), BooleanOption( 0 , "ipv4", &gGAINew_ProtocolIPv4, "Use kDNSServiceProtocol_IPv4." ), BooleanOption( 0 , "ipv6", &gGAINew_ProtocolIPv6, "Use kDNSServiceProtocol_IPv6." ), BooleanOption( 't', "showTracker", &gGAINew_ShowTracker, "Display tracker hostnames." ), BooleanOption( 0, "useFailover", &gGAINew_UseFailover, "Use DNS service failover if necessary and applicable." ), BooleanOption( 0, "prohibitEncryptedDNS", &gGAINew_ProhibitEncryptedDNS, "Prohibit use of encrypted DNS protocols such as DoT, DoH, ODoH, etc." ), StringOption( 0, "resolverUUID", &gGAINew_ResolverUUID, "UUID", "UUID of libnetwork DNS resolver configuration to use.", false ), BooleanOption( 0, "privateLogging", &gGAINew_PrivateLogging, "Use dnssd_log_privacy_level_private logging privacy level." ), CLI_OPTION_GROUP( "Flags" ), DNSSDFlagsOption(), DNSSDFlagsOption_AllowExpiredAnswers(), DNSSDFlagsOption_DenyCellular(), DNSSDFlagsOption_DenyConstrained(), DNSSDFlagsOption_DenyExpensive(), DNSSDFlagsOption_IncludeAWDL(), DNSSDFlagsOption_PathEvalDone(), DNSSDFlagsOption_ReturnIntermediates(), DNSSDFlagsOption_SuppressUnusable(), DNSSDFlagsOption_Timeout(), CLI_OPTION_GROUP( "Operation" ), ConnectionOptions(), BooleanOption( 'o', "oneshot", &gGAINew_OneShot, "Finish after first set of results." ), IntegerOption( 'l', "timeLimit", &gGAINew_TimeLimitSecs, "seconds", "Time limit for dnssd_getaddrinfo operation. Use '0' for no limit. (default: 0)", false ), StringOptionEx( 'q', "qos", &gGAINew_QoS, kQoSArgShortName, "Specifies the QoS of the queue used for the dnssd_getaddrinfo object.", false, "\n" "Use '" kQoSTypeStr_Unspecified "' for QOS_CLASS_UNSPECIFIED.\n" "Use '" kQoSTypeStr_Background "' for QOS_CLASS_BACKGROUND.\n" "Use '" kQoSTypeStr_Utility "' for QOS_CLASS_UTILITY.\n" "Use '" kQoSTypeStr_Default "' for QOS_CLASS_DEFAULT.\n" "Use '" kQoSTypeStr_UserInitiated "' for QOS_CLASS_USER_INITIATED.\n" "Use '" kQoSTypeStr_UserInteractive "' for QOS_CLASS_USER_INTERACTIVE.\n" "\n" ), CLI_OPTION_END() }; static void GetAddrInfoNewCommand( void ); //=========================================================================================================================== // TCPInfo Command Options //=========================================================================================================================== static const char * gTCPInfo_LocalAddrStr = NULL; static const char * gTCPInfo_RemoteAddrStr = NULL; static CLIOption kTCPInfoOpts[] = { StringOption( 'l', "local", &gTCPInfo_LocalAddrStr, "IP address+port", "TCP connection's local IPv4 or IPv6 address and port number.", true ), StringOption( 'r', "remote", &gTCPInfo_RemoteAddrStr, "IP address+port", "TCP connection's remote IPv4 or IPv6 address and port number.", true ), CLI_OPTION_END() }; static void TCPInfoCommand( void ); #endif // MDNSRESPONDER_PROJECT //=========================================================================================================================== // PF //=========================================================================================================================== static const char * gThreadPFNAT64_IPv6Prefix = NULL; static const char * gThreadPFNAT64_IPv4Address = NULL; static CLIOption kThreadPFNAT64Opts[] = { StringOption( 'p', "prefix", &gThreadPFNAT64_IPv6Prefix, "IPv6 prefix", "NAT64's IPv6 prefix.", true ), StringOption( 'a', "address", &gThreadPFNAT64_IPv4Address, "IPv4 address", "NAT64's IPv4 address.", true ), CLI_OPTION_END() }; static void ThreadPFNAT64Command( void ); static void ThreadPFDeleteCommand( void ); static CLIOption kPFOpts[] = { Command( "nat64", ThreadPFNAT64Command, kThreadPFNAT64Opts, "Set Thread border router NAT64 PF rules.", true ), Command( "delete", ThreadPFDeleteCommand, NULL, "Delete Thread border router PF rules.", true ), CLI_OPTION_END() }; //=========================================================================================================================== // IPv4 Forwarding //=========================================================================================================================== static void IPv4FwdEnableCommand( void ); static void IPv4FwdDisableCommand( void ); static CLIOption kIPv4FwdOpts[] = { Command( "enable", IPv4FwdEnableCommand, NULL, "Enable IPv4 forwarding between network interfaces.", true ), Command( "disable", IPv4FwdDisableCommand, NULL, "Disable IPv4 forwarding between network interfaces.", true ), CLI_OPTION_END() }; //=========================================================================================================================== // IPv6 Forwarding //=========================================================================================================================== static void IPv6FwdEnableCommand( void ); static void IPv6FwdDisableCommand( void ); static CLIOption kIPv6FwdOpts[] = { Command( "enable", IPv6FwdEnableCommand, NULL, "Enable IPv6 forwarding between network interfaces.", true ), Command( "disable", IPv6FwdDisableCommand, NULL, "Disable IPv6 forwarding between network interfaces.", true ), CLI_OPTION_END() }; //=========================================================================================================================== // Print //=========================================================================================================================== static void PrintCommand( void ); static CLIOption kPrintOpts[] = { CLI_OPTIONAL_ARGUMENT( "file", "Path to the file containing the DNS message.", "\n" "If this argument isn't specified or the argument is '-' (a single hyphen), then the DNS message is read from\n" "standard input.\n" ), CLI_OPTION_END() }; //=========================================================================================================================== // WiFi //=========================================================================================================================== static void WiFiOnCommand( void ); static void WiFiOffCommand( void ); static CLIOption kWiFiOpts[] = { Command( "on", WiFiOnCommand, NULL, "Turn Wi-Fi power on.", true ), Command( "off", WiFiOffCommand, NULL, "Turn Wi-Fi power off.", true ), CLI_OPTION_END() }; //=========================================================================================================================== // Discovery Proxy //=========================================================================================================================== static void DiscoveryProxyCommand( void ); static char ** gDiscoveryProxy_ServerAddrs = NULL; static size_t gDiscoveryProxy_ServerAddrCount = 0; static char ** gDiscoveryProxy_MatchDomains = NULL; static size_t gDiscoveryProxy_MatchDomainCount = 0; static char ** gDiscoveryProxy_Certificates = NULL; static size_t gDiscoveryProxy_CertificateCount = 0; static CLIOption kDiscoveryProxyOpts[] = { InterfaceOption(), MultiStringOption( 's', "server", &gDiscoveryProxy_ServerAddrs, &gDiscoveryProxy_ServerAddrCount, "IP address", "Server's IPv4 or IPv6 address with optionally-specified port. Can be specified more than once.", true ), MultiStringOption( 'd', "domain", &gDiscoveryProxy_MatchDomains, &gDiscoveryProxy_MatchDomainCount, "domain name", "Domain to match. Can be specified more than once.", true ), MultiStringOption( 'c', "certificate", &gDiscoveryProxy_Certificates, &gDiscoveryProxy_CertificateCount, "hex string", "Server certificate. Can be specified more than once.", true ), CLI_OPTION_END() }; //=========================================================================================================================== // Command Table //=========================================================================================================================== static OSStatus VersionOptionCallback( CLIOption *inOption, const char *inArg, int inUnset ); static void BrowseCmd( void ); static void GetAddrInfoCmd( void ); static void QueryRecordCmd( void ); static void RegisterCmd( void ); static void RegisterRecordCmd( void ); static void ResolveCmd( void ); static void ReconfirmCmd( void ); static void GetAddrInfoPOSIXCmd( void ); static void ReverseLookupCmd( void ); static void PortMappingCmd( void ); static void BrowseAllCmd( void ); static void GetAddrInfoStressCmd( void ); static void DNSQueryCmd( void ); #if( DNSSDUTIL_INCLUDE_DNSCRYPT ) static void DNSCryptCmd( void ); #endif static void MDNSQueryCmd( void ); #if( TARGET_OS_DARWIN ) static void PIDToUUIDCmd( void ); #endif static void DNSProxyStateCmd( void ); static void CachedLocalRecordsCommand( void ); static void DaemonVersionCmd( void ); static CLIOption kGlobalOpts[] = { CLI_OPTION_CALLBACK_EX( 'V', "version", VersionOptionCallback, NULL, NULL, kCLIOptionFlags_NoArgument | kCLIOptionFlags_GlobalOnly, "Displays the version of this tool.", NULL ), CLI_OPTION_HELP(), // Common commands. Command( "browse", BrowseCmd, kBrowseOpts, "Uses DNSServiceBrowse() to browse for one or more service types.", false ), Command( "getAddrInfo", GetAddrInfoCmd, kGetAddrInfoOpts, "Uses DNSServiceGetAddrInfo() to resolve a hostname to IP addresses.", false ), Command( "queryRecord", QueryRecordCmd, kQueryRecordOpts, "Uses DNSServiceQueryRecord() to query for an arbitrary DNS record.", false ), Command( "register", RegisterCmd, kRegisterOpts, "Uses DNSServiceRegister() to register a service.", false ), Command( "registerRecord", RegisterRecordCmd, kRegisterRecordOpts, "Uses DNSServiceRegisterRecord() to register a record.", false ), Command( "resolve", ResolveCmd, kResolveOpts, "Uses DNSServiceResolve() to resolve a service.", false ), Command( "reconfirm", ReconfirmCmd, kReconfirmOpts, "Uses DNSServiceReconfirmRecord() to reconfirm a record.", false ), Command( "getaddrinfo-posix", GetAddrInfoPOSIXCmd, kGetAddrInfoPOSIXOpts, "Uses getaddrinfo() to resolve a hostname to IP addresses.", false ), Command( "reverseLookup", ReverseLookupCmd, kReverseLookupOpts, "Uses DNSServiceQueryRecord() to perform a reverse IP address lookup.", false ), Command( "portMapping", PortMappingCmd, kPortMappingOpts, "Uses DNSServiceNATPortMappingCreate() to create a port mapping.", false ), #if( TARGET_OS_DARWIN ) Command( "registerKA", RegisterKACmd, kRegisterKA_Opts, "Uses DNSServiceSleepKeepalive_sockaddr() to register a keep alive record.", false ), #endif Command( "browseAll", BrowseAllCmd, kBrowseAllOpts, "Browse and resolve all (or specific) services and, optionally, attempt connections.", false ), // Uncommon commands. Command( "getnameinfo", GetNameInfoCmd, kGetNameInfoOpts, "Calls getnameinfo() and prints results.", true ), Command( "getAddrInfoStress", GetAddrInfoStressCmd, kGetAddrInfoStressOpts, "Runs DNSServiceGetAddrInfo() stress testing.", true ), Command( "DNSQuery", DNSQueryCmd, kDNSQueryOpts, "Crafts and sends a DNS query.", true ), #if( DNSSDUTIL_INCLUDE_DNSCRYPT ) Command( "DNSCrypt", DNSCryptCmd, kDNSCryptOpts, "Crafts and sends a DNSCrypt query.", true ), #endif Command( "mdnsquery", MDNSQueryCmd, kMDNSQueryOpts, "Crafts and sends an mDNS query over the specified interface.", true ), Command( "mdnscollider", MDNSColliderCmd, kMDNSColliderOpts, "Creates record name collision scenarios.", true ), #if( TARGET_OS_DARWIN ) Command( "pid2uuid", PIDToUUIDCmd, kPIDToUUIDOpts, "Prints the UUID of a process.", true ), #endif Command( "server", DNSServerCommand, 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, "Simple Service Discovery Protocol (SSDP).", true ), #if( TARGET_OS_DARWIN ) Command( "legacy", NULL, kLegacyOpts, "Legacy DNS API.", true ), Command( "dnsconfig", NULL, kDNSConfigOpts, "Add/remove a supplemental resolver entry to/from the system's DNS configuration.", true ), Command( "xpcsend", XPCSendCommand, kXPCSendOpts, "Sends a message to an XPC service.", true ), #endif #if( MDNSRESPONDER_PROJECT ) Command( "interfaceMonitor", InterfaceMonitorCmd, kInterfaceMonitorOpts, "Instantiates an mdns_interface_monitor.", true ), Command( "querier", QuerierCommand, kQuerierOpts, "Sends a DNS query using mdns_querier.", true ), Command( "dnsproxy", DNSProxyCmd, kDNSProxyOpts, "Enables mDNSResponder's DNS proxy.", true ), Command( "dnsproxy-state", DNSProxyStateCmd, NULL, "Gets mDNSResponder's DNS proxy state dump.", true ), Command( "getaddrinfo-new", GetAddrInfoNewCommand, kGetAddrInfoNewOpts, "Uses dnssd_getaddrinfo to resolve a hostname to IP addresses.", false ), Command( "tcpinfo", TCPInfoCommand, kTCPInfoOpts, "Uses mdns_tcpinfo_* to get TCP info.", true ), #endif Command( "pf", NULL, kPFOpts, "Packet filter commands.", true ), Command( "ipv4fwd", NULL, kIPv4FwdOpts, "IPv4 forwarding commands.", true ), Command( "ipv6fwd", NULL, kIPv6FwdOpts, "IPv6 forwarding commands.", true ), Command( "print", PrintCommand, kPrintOpts, "Reads a DNS message in wire format and writes it to stdout in a human-readable form.", true ), Command( "wifi", NULL, kWiFiOpts, "Wi-Fi commands.", true ), Command( "discovery-proxy", DiscoveryProxyCommand, kDiscoveryProxyOpts, "Enables mDNSResponder's discovery proxy.", true ), Command( "cached-local-records", CachedLocalRecordsCommand, NULL, "Uses mrc_cached_local_records_inquiry to inquire about cached .local records.", true ), Command( "daemonVersion", DaemonVersionCmd, NULL, "Prints the version of the DNS-SD daemon.", true ), CLI_COMMAND_HELP(), CLI_OPTION_END() }; //=========================================================================================================================== // Helper Prototypes //=========================================================================================================================== #define kExitReason_OneShotDone "one-shot done" #define kExitReason_ReceivedResponse "received response" #define kExitReason_SIGINT "interrupt signal" #define kExitReason_Timeout "timeout" #define kExitReason_TimeLimit "time limit" static void Exit( void *inContext ) ATTRIBUTE_NORETURN; static DNSServiceFlags GetDNSSDFlagsFromOpts( void ); typedef enum { kConnectionType_None = 0, kConnectionType_Normal = 1, kConnectionType_DelegatePID = 2, kConnectionType_DelegateUUID = 3 } ConnectionType; typedef struct { ConnectionType type; union { int32_t pid; uint8_t uuid[ 16 ]; } delegate; } ConnectionDesc; static OSStatus CreateConnectionFromArgString( const char * inString, dispatch_queue_t inQueue, DNSServiceRef * outSDRef, ConnectionDesc * outDesc ); static OSStatus InterfaceIndexFromArgString( const char *inString, uint32_t *outIndex ); static OSStatus RecordDataFromArgString( const char *inString, uint8_t **outDataPtr, size_t *outDataLen ); static OSStatus RecordTypeFromArgString( const char *inString, uint16_t *outValue ); static OSStatus RecordClassFromArgString( const char *inString, uint16_t *outValue ); static OSStatus SockAddrFromArgString( const char *inString, const char *inArgName, sockaddr_ip *outSA ); #define kInterfaceNameBufLen ( Max( IF_NAMESIZE, 16 ) + 1 ) static char * InterfaceIndexToName( uint32_t inIfIndex, char inNameBuf[ kInterfaceNameBufLen ] ); static const char * RecordTypeToString( int inValue ); #if( MDNSRESPONDER_PROJECT ) static const char * RecordClassToString( int inValue ); #endif static OSStatus WriteDNSQueryMessage( uint8_t inMsg[ kDNSQueryMessageMaxLen ], uint16_t inMsgID, uint16_t inFlags, const char * inQName, uint16_t inQType, uint16_t inQClass, size_t * outMsgLen ); // Dispatch helpers typedef void ( *DispatchHandler )( void *inContext ); static OSStatus DispatchSignalSourceCreate( int inSignal, dispatch_queue_t inQueue, DispatchHandler inEventHandler, void * inContext, dispatch_source_t * outSource ); static OSStatus DispatchSocketSourceCreate( SocketRef inSock, dispatch_source_type_t inType, dispatch_queue_t inQueue, DispatchHandler inEventHandler, DispatchHandler inCancelHandler, void * inContext, dispatch_source_t * outSource ); #define DispatchReadSourceCreate( SOCK, QUEUE, EVENT_HANDLER, CANCEL_HANDLER, CONTEXT, OUT_SOURCE ) \ DispatchSocketSourceCreate( SOCK, DISPATCH_SOURCE_TYPE_READ, QUEUE, EVENT_HANDLER, CANCEL_HANDLER, CONTEXT, OUT_SOURCE ) #define DispatchWriteSourceCreate( SOCK, QUEUE, EVENT_HANDLER, CANCEL_HANDLER, CONTEXT, OUT_SOURCE ) \ DispatchSocketSourceCreate( SOCK, DISPATCH_SOURCE_TYPE_WRITE, QUEUE, EVENT_HANDLER, CANCEL_HANDLER, CONTEXT, OUT_SOURCE ) static OSStatus DispatchTimerCreate( dispatch_time_t inStart, uint64_t inIntervalNs, uint64_t inLeewayNs, dispatch_queue_t inQueue, DispatchHandler inEventHandler, DispatchHandler inCancelHandler, void * inContext, dispatch_source_t * outTimer ); #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 ) #if( TARGET_OS_DARWIN ) static OSStatus DispatchProcessMonitorCreate( pid_t inPID, unsigned long inFlags, dispatch_queue_t inQueue, DispatchHandler inEventHandler, DispatchHandler inCancelHandler, void * inContext, dispatch_source_t * outMonitor ); #endif static const char * ServiceTypeDescription( const char *inName ); typedef void ( *SocketContextFinalizer_f )( void *inUserCtx ); typedef struct { SocketRef sock; // Socket. int32_t refCount; // Reference count. void * userContext; // User's context. SocketContextFinalizer_f userFinalizer; // User's finalizer. } SocketContext; static SocketContext * SocketContextCreate( SocketRef inSock, void *inUserContext, OSStatus *outError ); static SocketContext * SocketContextCreateEx( SocketRef inSock, void * inUserContext, SocketContextFinalizer_f inUserFinalizer, OSStatus * outError ); static SocketContext * SocketContextRetain( SocketContext *inContext ); static void SocketContextRelease( SocketContext *inContext ); static void SocketContextCancelHandler( void *inContext ); static void SocketContextFinalizerCF( void *inUserCtx ); #define ForgetSocketContext( X ) ForgetCustom( X, SocketContextRelease ) static OSStatus StringToInt32( const char *inString, int32_t *outValue ); static OSStatus StringToUInt32( const char *inString, uint32_t *outValue ); #if( TARGET_OS_DARWIN ) static int64_t _StringToInt64( const char *inString, OSStatus *outError ); static uint64_t _StringToUInt64( const char *inString, OSStatus *outError ); static pid_t _StringToPID( const char *inString, OSStatus *outError ); static OSStatus _ParseEscapedString( const char * inSrc, const char * inEnd, const char * inDelimiters, char * inBufPtr, size_t inBufLen, size_t * outCopiedLen, size_t * outActualLen, const char ** outPtr ); static OSStatus _ParseEscapedStringWithCopy( const char * inSrc, const char * inEnd, const char * inDelimiters, char * inBufPtr, size_t inBufLen, const char ** outString, char ** outMemory, const char ** outPtr ); #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 ); #if( TARGET_OS_DARWIN ) static OSStatus GetDefaultDNSServer( sockaddr_ip *outAddr ); #endif static OSStatus _ServerSocketOpenEx2( int inFamily, int inType, int inProtocol, const void * inAddr, int inPort, int * outPort, int inRcvBufSize, Boolean inNoPortReuse, SocketRef * outSock ); static const struct sockaddr * GetMDNSMulticastAddrV4( void ); static const struct sockaddr * GetMDNSMulticastAddrV6( void ); 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 ); #if( TARGET_OS_POSIX ) static OSStatus _SpawnCommand( pid_t * outPID, const char * inStdOutRedirect, const char * inStdErrRedirect, const char * inFormat, ... ); #endif static OSStatus OutputFormatFromArgString( const char *inArgString, OutputFormatType *outFormat ); static OSStatus OutputPropertyList( CFPropertyListRef inPList, OutputFormatType inType, const char *inOutputFilePath ); 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 * _NanoTime64ToTimestamp( NanoTime64 inTime, char *inBuf, size_t inMaxLen ); typedef struct MDNSInterfaceItem MDNSInterfaceItem; struct MDNSInterfaceItem { MDNSInterfaceItem * next; char * ifName; uint32_t ifIndex; Boolean hasIPv4; Boolean hasIPv6; Boolean isAWDL; Boolean isWiFi; }; typedef enum { kMDNSInterfaceSubset_All = 0, // All mDNS-capable interfaces. kMDNSInterfaceSubset_AWDL = 1, // All mDNS-capable AWDL interfaces. kMDNSInterfaceSubset_NonAWDL = 2 // All mDNS-capable non-AWDL iterfaces. } MDNSInterfaceSubset; static OSStatus _MDNSInterfaceListCreate( MDNSInterfaceSubset inSubset, size_t inItemSize, MDNSInterfaceItem **outList ); static void _MDNSInterfaceListFree( MDNSInterfaceItem *inList ); #define _MDNSInterfaceListForget( X ) ForgetCustom( X, _MDNSInterfaceListFree ) static OSStatus _MDNSInterfaceGetAny( MDNSInterfaceSubset inSubset, char inNameBuf[ IF_NAMESIZE + 1 ], uint32_t *outIndex ); static OSStatus _SetComputerName( CFStringRef inComputerName, CFStringEncoding inEncoding ); static OSStatus _SetComputerNameWithUTF8CString( const char *inComputerName ); static OSStatus _SetLocalHostName( CFStringRef inLocalHostName ); static OSStatus _SetLocalHostNameWithUTF8CString( const char *inLocalHostName ); #if( TARGET_OS_DARWIN ) static OSStatus _InterfaceIPv6AddressAdd( const char *inIfName, uint8_t inAddr[ STATIC_PARAM 16 ], int inMaskBitLen ); static OSStatus _InterfaceIPv6AddressRemove( const char *inIfName, const uint8_t inAddr[ STATIC_PARAM 16 ] ); #endif static int64_t _TicksDiff( uint64_t inT1, uint64_t inT2 ); static void _SockAddrInitIPv4( struct sockaddr_in *inSA, uint32_t inIPv4, uint16_t inPort ); static void _SockAddrInitIPv6( struct sockaddr_in6 * inSA, const uint8_t inIPv6[ STATIC_PARAM 16 ], uint32_t inScope, uint16_t inPort ); #define kIP6ArpaDomainStr "ip6.arpa." #define kReverseIPv6DomainNameBufLen ( ( 4 * 16 ) + sizeof_string( kIP6ArpaDomainStr ) + 1 ) static void _WriteReverseIPv6DomainNameString( const uint8_t inIPv6Addr[ STATIC_PARAM 16 ], char outBuffer[ STATIC_PARAM kReverseIPv6DomainNameBufLen ] ); #define kInAddrArpaDomainStr "in-addr.arpa." #define kReverseIPv4DomainNameBufLen ( ( 4 * 4 ) + sizeof_string( kInAddrArpaDomainStr ) + 1 ) static void _WriteReverseIPv4DomainNameString( uint32_t inIPv4Addr, char outBuffer[ STATIC_PARAM kReverseIPv4DomainNameBufLen ] ); #if( MDNSRESPONDER_PROJECT ) static OSStatus _SetDefaultFallbackDNSService( const char *inFallbackDNSServiceStr ); #endif static OSStatus _StringToIPv4Address( const char * inStr, StringToIPAddressFlags inFlags, uint32_t * outIP, int * outPort, uint32_t * outSubnet, uint32_t * outRouter, const char ** outStr ); static OSStatus _StringToIPv6Address( const char * inStr, StringToIPAddressFlags inFlags, uint8_t outIPv6[ 16 ], uint32_t * outScope, int * outPort, int * outPrefix, const char ** outStr ); static Boolean _ParseQuotedEscapedString( const char * inSrc, const char * inEnd, const char * inDelimiters, char * inBuf, size_t inMaxLen, size_t * outCopiedLen, size_t * outTotalLen, const char ** outSrc ); static void * _memdup( const void *inPtr, size_t inLen ); static int _memicmp( const void *inP1, const void *inP2, size_t inLen ); static uint32_t _FNV1( const void *inData, size_t inSize ); static OSStatus _UInt32FromArgString( const char *inArgStr, const char *inArgName, uint32_t *outValue ); static char * _UnixTimeToDateAndTimeString( int64_t inTimeSecs, char *inBufPtr, size_t inBufLen ); static char * _DNSSDSourceVersionToCString( uint32_t inVersion, char *inBufPtr, size_t inBufLen ); static Boolean _StdOutIsTTY( void ); #if( TARGET_OS_IOS ) static Boolean _StdErrIsTTY( void ); #endif static void _PrintValidatedToStdOut( const char *inPrefix, Boolean inValidated, const char *inSuffix ); static Boolean _DNSProtocolIsSecure( DNSProtocol inProtocol ); static const char * _DNSProtocolToString( DNSProtocol inProtocol ); #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 ); #define MDNSColliderForget( X ) ForgetCustomEx( X, MDNSColliderStop, CFRelease ) //=========================================================================================================================== // 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. Boolean validated; // True if IP address is validated. }; 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 ServiceBrowserSetUseNewGAI( ServiceBrowserRef inBrowser, Boolean inUseNewGAI ); static void ServiceBrowserSetValidateResults( ServiceBrowserRef inBrowser, Boolean inValidateResults ); 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 ) //=========================================================================================================================== // DNSServer //=========================================================================================================================== typedef struct DNSServerPrivate * DNSServerRef; typedef void ( *DNSServerStartHandler_f )( uint16_t inActualPort, void *inCtx ); typedef void ( *DNSServerStopHandler_f )( OSStatus inError, void *inCtx ); static CFTypeID DNSServerGetTypeID( void ); static OSStatus _DNSServerCreate( dispatch_queue_t inQueue, DNSServerStartHandler_f inStartHandler, DNSServerStopHandler_f inStopHandler, void * inUserContext, unsigned int inResponseDelayMs, uint32_t inDefaultTTL, const sockaddr_ip * inServerArray, size_t inServerCount, const char * inDomain, Boolean inBadUDPMode, DNSServerRef * outServer ); static OSStatus _DNSServerSetIgnoredQType( DNSServerRef inServer, int inQType ); static void _DNSServerSetPort( DNSServerRef inServer, uint16_t inPort ); static void _DNSServerStart( DNSServerRef inServer ); static void _DNSServerStop( DNSServerRef inServer ); //=========================================================================================================================== // main //=========================================================================================================================== #define _PRINTF_EXTENSION_HANDLER_DECLARE( NAME ) \ static int \ _PrintFExtensionHandler_ ## NAME ( \ PrintFContext * inContext, \ PrintFFormat * inFormat, \ PrintFVAList * inArgs, \ void * inUserContext ) _PRINTF_EXTENSION_HANDLER_DECLARE( Timestamp ); _PRINTF_EXTENSION_HANDLER_DECLARE( DNSMessage ); _PRINTF_EXTENSION_HANDLER_DECLARE( RawDNSMessage ); _PRINTF_EXTENSION_HANDLER_DECLARE( CallbackFlags ); _PRINTF_EXTENSION_HANDLER_DECLARE( DNSRecordData ); _PRINTF_EXTENSION_HANDLER_DECLARE( DomainName ); #ifdef FUZZING #define main main_dnssdutil #endif int main( int argc, const char **argv ) { OSStatus err; // Route DebugServices logging output to stderr. dlog_control( "DebugServices:output=file;stderr" ); PrintFRegisterExtension( "du:time", _PrintFExtensionHandler_Timestamp, NULL ); PrintFRegisterExtension( "du:dnsmsg", _PrintFExtensionHandler_DNSMessage, NULL ); PrintFRegisterExtension( "du:rdnsmsg", _PrintFExtensionHandler_RawDNSMessage, NULL ); PrintFRegisterExtension( "du:cbflags", _PrintFExtensionHandler_CallbackFlags, NULL ); PrintFRegisterExtension( "du:rdata", _PrintFExtensionHandler_DNSRecordData, NULL ); PrintFRegisterExtension( "du:dname", _PrintFExtensionHandler_DomainName, NULL ); CLIInit( argc, argv ); err = CLIParse( kGlobalOpts, kCLIFlags_None ); if( err ) gExitCode = 1; return( gExitCode ); } //=========================================================================================================================== // VersionOptionCallback //=========================================================================================================================== static OSStatus VersionOptionCallback( CLIOption *inOption, const char *inArg, int inUnset ) { const char * srcVers; #if( MDNSRESPONDER_PROJECT ) char srcStr[ 16 ]; #endif Unused( inOption ); Unused( inArg ); Unused( inUnset ); #if( MDNSRESPONDER_PROJECT ) srcVers = _DNSSDSourceVersionToCString( _DNS_SD_H, srcStr, sizeof( srcStr ) ); #else srcVers = DNSSDUTIL_SOURCE_VERSION; #endif FPrintF( stdout, "%s version %v (%s)\n", gProgramName, kDNSSDUtilNumVersion, srcVers ); return( kEndingErr ); } //=========================================================================================================================== // BrowseCmd //=========================================================================================================================== typedef struct BrowseResolveOp BrowseResolveOp; struct BrowseResolveOp { BrowseResolveOp * next; // Next resolve operation in list. DNSServiceRef sdRef; // sdRef of the DNSServiceResolve or DNSServiceQueryRecord operation. char * fullName; // Full name of the service to resolve. uint32_t interfaceIndex; // Interface index of the DNSServiceResolve or DNSServiceQueryRecord operation. }; typedef struct { DNSServiceRef mainRef; // Main sdRef for shared connection. DNSServiceRef * opRefs; // Array of sdRefs for individual Browse operarions. size_t opRefsCount; // Count of array of sdRefs for non-shared connections. const char * domain; // Domain for DNSServiceBrowse operation(s). DNSServiceFlags flags; // Flags for DNSServiceBrowse operation(s). char ** serviceTypes; // Array of service types to browse for. size_t serviceTypesCount; // Count of array of service types to browse for. int timeLimitSecs; // Time limit of DNSServiceBrowse operation in seconds. BrowseResolveOp * resolveList; // List of resolve and/or TXT record query operations. uint32_t ifIndex; // Interface index of DNSServiceBrowse operation(s). useconds_t resolveDelayUs; // Amount of time to wait before a resolve or TXT query in microseconds. Boolean printedHeader; // True if results header has been printed. Boolean doResolve; // True if service instances are to be resolved. Boolean doResolveTXTOnly; // True if TXT records of service instances are to be queried. Boolean validateResults; // True if signed results are requested and validated. } BrowseContext; #define _DNSServiceAttrForget( X ) ForgetCustom( X, DNSServiceAttributeDeallocate ) static void BrowsePrintPrologue( const BrowseContext *inContext ); static void BrowseContextFree( BrowseContext *inContext ); static OSStatus BrowseResolveOpCreate( const char *inFullName, uint32_t inInterfaceIndex, BrowseResolveOp **outOp ); static void BrowseResolveOpFree( BrowseResolveOp *inOp ); static void DNSSD_API BrowseCallback( DNSServiceRef inSDRef, DNSServiceFlags inFlags, uint32_t inInterfaceIndex, DNSServiceErrorType inError, const char * inName, const char * inRegType, const char * inDomain, void * inContext ); static void DNSSD_API BrowseResolveCallback( DNSServiceRef inSDRef, DNSServiceFlags inFlags, uint32_t inInterfaceIndex, DNSServiceErrorType inError, const char * inFullName, const char * inHostname, uint16_t inPort, uint16_t inTXTLen, const unsigned char * inTXTPtr, void * inContext ); static void DNSSD_API BrowseQueryRecordCallback( DNSServiceRef inSDRef, DNSServiceFlags inFlags, uint32_t inInterfaceIndex, DNSServiceErrorType inError, const char * inFullName, uint16_t inType, uint16_t inClass, uint16_t inRDataLen, const void * inRDataPtr, uint32_t inTTL, void * inContext ); ulog_define_ex( kDNSSDUtilIdentifier, Browse, kLogLevelTrace, kLogFlags_None, "Browse", NULL ); #define bc_ulog( LEVEL, ... ) ulog( &log_category_from_name( Browse ), (LEVEL), __VA_ARGS__ ) static void BrowseCmd( void ) { OSStatus err; size_t i; BrowseContext * context = NULL; dispatch_source_t signalSource = NULL; int useMainConnection; // Set up SIGINT handler. signal( SIGINT, SIG_IGN ); err = DispatchSignalSourceCreate( SIGINT, dispatch_get_main_queue(), Exit, kExitReason_SIGINT, &signalSource ); require_noerr( err, exit ); dispatch_resume( signalSource ); // Create context. context = (BrowseContext *) calloc( 1, sizeof( *context ) ); require_action( context, exit, err = kNoMemoryErr ); context->opRefs = (DNSServiceRef *) calloc( gBrowse_ServiceTypesCount, sizeof( DNSServiceRef ) ); require_action( context->opRefs, exit, err = kNoMemoryErr ); context->opRefsCount = gBrowse_ServiceTypesCount; // Check command parameters. if( gBrowse_TimeLimitSecs < 0 ) { FPrintF( stderr, "Invalid time limit: %d seconds.\n", gBrowse_TimeLimitSecs ); err = kParamErr; goto exit; } // Create main connection. if( gConnectionOpt ) { err = CreateConnectionFromArgString( gConnectionOpt, dispatch_get_main_queue(), &context->mainRef, NULL ); require_noerr_quiet( err, exit ); useMainConnection = true; } else { useMainConnection = false; } // Get flags. context->flags = GetDNSSDFlagsFromOpts(); if( useMainConnection ) context->flags |= kDNSServiceFlagsShareConnection; // Get interface. err = InterfaceIndexFromArgString( gInterface, &context->ifIndex ); require_noerr_quiet( err, exit ); // Set remaining parameters. context->serviceTypes = gBrowse_ServiceTypes; context->serviceTypesCount = gBrowse_ServiceTypesCount; context->domain = gBrowse_Domain; context->doResolve = gBrowse_DoResolve ? true : false; context->timeLimitSecs = gBrowse_TimeLimitSecs; context->doResolveTXTOnly = gBrowse_QueryTXT ? true : false; context->validateResults = gBrowse_ValidateResults ? true : false; if( gBrowse_ResolveDelayMs > 0 ) { const int maxDelayMs = ( (useconds_t) -1 ) / kMicrosecondsPerMillisecond; err = CheckIntegerArgument( gBrowse_ResolveDelayMs, "resolveDelay", INT_MIN, maxDelayMs ); require_noerr_quiet( err, exit ); context->resolveDelayUs = (useconds_t)( gBrowse_ResolveDelayMs * kMicrosecondsPerMillisecond ); } #if( TARGET_OS_IOS ) // Check for potential issues. if( context->validateResults && context->doResolve && os_feature_enabled( mDNSResponder, revoke_media_sessions ) ) { FPrintF( stderr, "%s" "Warning: --validate and --resolve are not compatible when media revocation is enabled.\n" " Use 'ffctl mDNSResponder/revoke_media_sessions=off' to disable.\n" "%s", _StdErrIsTTY() ? kANSIRed : "", _StdErrIsTTY() ? kANSINormal : "" ); } #endif // Print prologue. BrowsePrintPrologue( context ); // Start operation(s). for( i = 0; i < context->serviceTypesCount; ++i ) { DNSServiceRef sdRef; sdRef = useMainConnection ? context->mainRef : kBadDNSServiceRef; if( context->validateResults ) { err = DNSServiceBrowseEx( &sdRef, context->flags, context->ifIndex, context->serviceTypes[ i ], context->domain, &kDNSServiceAttrValidationRequired, BrowseCallback, context ); require_noerr( err, exit ); } else { err = DNSServiceBrowse( &sdRef, context->flags, context->ifIndex, context->serviceTypes[ i ], context->domain, BrowseCallback, context ); require_noerr( err, exit ); } context->opRefs[ i ] = sdRef; if( !useMainConnection ) { err = DNSServiceSetDispatchQueue( context->opRefs[ i ], dispatch_get_main_queue() ); require_noerr( err, exit ); } } // Set time limit. if( context->timeLimitSecs > 0 ) { dispatch_after_f( dispatch_time_seconds( context->timeLimitSecs ), dispatch_get_main_queue(), kExitReason_TimeLimit, Exit ); } dispatch_main(); exit: dispatch_source_forget( &signalSource ); if( context ) BrowseContextFree( context ); if( err ) exit( 1 ); } //=========================================================================================================================== // BrowsePrintPrologue //=========================================================================================================================== static void BrowsePrintPrologue( const BrowseContext *inContext ) { const int timeLimitSecs = inContext->timeLimitSecs; const char * const * ptr = (const char **) inContext->serviceTypes; const char * const * const end = (const char **) inContext->serviceTypes + inContext->serviceTypesCount; char ifName[ kInterfaceNameBufLen ]; InterfaceIndexToName( inContext->ifIndex, ifName ); FPrintF( stdout, "Flags: %#{flags}\n", inContext->flags, kDNSServiceFlagsDescriptors ); FPrintF( stdout, "Interface: %d (%s)\n", (int32_t) inContext->ifIndex, ifName ); FPrintF( stdout, "Service types: %s", *ptr++ ); while( ptr < end ) FPrintF( stdout, ", %s", *ptr++ ); FPrintF( stdout, "\n" ); FPrintF( stdout, "Domain: %s\n", inContext->domain ? inContext->domain : " (default domains)" ); FPrintF( stdout, "Time limit: " ); if( timeLimitSecs > 0 ) FPrintF( stdout, "%d second%?c\n", timeLimitSecs, timeLimitSecs != 1, 's' ); else FPrintF( stdout, "∞\n" ); FPrintF( stdout, "Start time: %{du:time}\n", NULL ); FPrintF( stdout, "---\n" ); } //=========================================================================================================================== // BrowseContextFree //=========================================================================================================================== static void BrowseContextFree( BrowseContext *inContext ) { size_t i; for( i = 0; i < inContext->opRefsCount; ++i ) { DNSServiceForget( &inContext->opRefs[ i ] ); } if( inContext->serviceTypes ) { StringArray_Free( inContext->serviceTypes, inContext->serviceTypesCount ); inContext->serviceTypes = NULL; inContext->serviceTypesCount = 0; } DNSServiceForget( &inContext->mainRef ); free( inContext ); } //=========================================================================================================================== // BrowseResolveOpCreate //=========================================================================================================================== static OSStatus BrowseResolveOpCreate( const char *inFullName, uint32_t inInterfaceIndex, BrowseResolveOp **outOp ) { OSStatus err; BrowseResolveOp * resolveOp; resolveOp = (BrowseResolveOp *) calloc( 1, sizeof( *resolveOp ) ); require_action( resolveOp, exit, err = kNoMemoryErr ); resolveOp->fullName = strdup( inFullName ); require_action( resolveOp->fullName, exit, err = kNoMemoryErr ); resolveOp->interfaceIndex = inInterfaceIndex; *outOp = resolveOp; resolveOp = NULL; err = kNoErr; exit: if( resolveOp ) BrowseResolveOpFree( resolveOp ); return( err ); } //=========================================================================================================================== // BrowseResolveOpFree //=========================================================================================================================== static void BrowseResolveOpFree( BrowseResolveOp *inOp ) { DNSServiceForget( &inOp->sdRef ); ForgetMem( &inOp->fullName ); free( inOp ); } //=========================================================================================================================== // BrowseCallback //=========================================================================================================================== static void DNSSD_API BrowseCallback( DNSServiceRef inSDRef, DNSServiceFlags inFlags, uint32_t inInterfaceIndex, DNSServiceErrorType inError, const char * inName, const char * inRegType, const char * inDomain, void * inContext ) { BrowseContext * const context = (BrowseContext *) inContext; OSStatus err; BrowseResolveOp * newOp = NULL; BrowseResolveOp ** p; mdns_signed_browse_result_t signedResult = NULL; DNSServiceAttributeRef attr = NULL; struct timeval now; Boolean browseOnly, resultValidated; char fullName[ kDNSServiceMaxDomainName ]; gettimeofday( &now, NULL ); err = inError; require_noerr( err, exit ); if( !context->printedHeader ) { FPrintF( stdout, "%-26s %-17s IF %-20s %-20s Instance Name\n", "Timestamp", "Flags", "Domain", "Service Type" ); context->printedHeader = true; } browseOnly = !context->doResolve && !context->doResolveTXTOnly; resultValidated = false; if( context->validateResults ) { const uint8_t * dataPtr; size_t dataLen; dataPtr = DNSServiceGetValidationData( inSDRef, &dataLen ); bc_ulog( kLogLevelTrace, "Got %zu bytes of validation data for TXT query result\n", dataLen ); if( dataPtr ) { if( browseOnly ) { signedResult = mdns_signed_browse_result_create_from_data( dataPtr, dataLen, &err ); bc_ulog( kLogLevelTrace, "Signed browse result -- %@", signedResult ); if( signedResult ) { uint8_t instanceName[ kDomainNameLengthMax ]; err = DNSServiceConstructFullName( fullName, inName, inRegType, inDomain ); require_noerr( err, exit ); err = DomainNameFromString( instanceName, fullName, NULL ); require_noerr( err, exit ); if( mdns_signed_browse_result_contains( signedResult, instanceName, inInterfaceIndex ) ) { resultValidated = true; } else { bc_ulog( kLogLevelError, "Signed browse result doesn't contain instance %s interface %d\n", fullName, inInterfaceIndex ); } mdns_forget( &signedResult ); } else { bc_ulog( kLogLevelError, "mdns_signed_browse_result_create_from_data() failed: %#m\n", err ); } } else { attr = DNSServiceAttributeCreate(); require( attr, exit ); err = DNSServiceAttrSetValidationData( attr, dataPtr, dataLen ); require_noerr( err, exit ); } } } FPrintF( stdout, "%{du:time} %{du:cbflags} %2d %-20s %-20s %s", &now, inFlags, (int32_t) inInterfaceIndex, inDomain, inRegType, inName ); if( browseOnly && context->validateResults ) _PrintValidatedToStdOut( " (", resultValidated, ")" ); FPrintF( stdout, "\n" ); if( browseOnly ) goto exit; err = DNSServiceConstructFullName( fullName, inName, inRegType, inDomain ); require_noerr( err, exit ); if( inFlags & kDNSServiceFlagsAdd ) { DNSServiceRef sdRef; DNSServiceFlags flags; err = BrowseResolveOpCreate( fullName, inInterfaceIndex, &newOp ); require_noerr( err, exit ); if( context->mainRef ) { sdRef = context->mainRef; flags = kDNSServiceFlagsShareConnection; } else { flags = 0; } if( context->resolveDelayUs > 0 ) usleep( context->resolveDelayUs ); if( context->doResolve ) { if( attr ) { err = DNSServiceResolveEx( &sdRef, flags, inInterfaceIndex, inName, inRegType, inDomain, attr, BrowseResolveCallback, context ); require_noerr( err, exit ); } else { err = DNSServiceResolve( &sdRef, flags, inInterfaceIndex, inName, inRegType, inDomain, BrowseResolveCallback, context ); require_noerr( err, exit ); } } else { if( attr ) { err = DNSServiceQueryRecordWithAttribute( &sdRef, flags, inInterfaceIndex, fullName, kDNSServiceType_TXT, kDNSServiceClass_IN, attr, BrowseQueryRecordCallback, context ); require_noerr( err, exit ); } else { err = DNSServiceQueryRecord( &sdRef, flags, inInterfaceIndex, fullName, kDNSServiceType_TXT, kDNSServiceClass_IN, BrowseQueryRecordCallback, context ); require_noerr( err, exit ); } } newOp->sdRef = sdRef; if( !context->mainRef ) { err = DNSServiceSetDispatchQueue( newOp->sdRef, dispatch_get_main_queue() ); require_noerr( err, exit ); } for( p = &context->resolveList; *p; p = &( *p )->next ) {} *p = newOp; newOp = NULL; } else { BrowseResolveOp * resolveOp; for( p = &context->resolveList; ( resolveOp = *p ) != NULL; p = &resolveOp->next ) { if( ( resolveOp->interfaceIndex == inInterfaceIndex ) && ( strcasecmp( resolveOp->fullName, fullName ) == 0 ) ) { break; } } if( resolveOp ) { *p = resolveOp->next; BrowseResolveOpFree( resolveOp ); } } exit: mdns_forget( &signedResult ); _DNSServiceAttrForget( &attr ); if( newOp ) BrowseResolveOpFree( newOp ); if( err ) exit( 1 ); } //=========================================================================================================================== // BrowseQueryRecordCallback //=========================================================================================================================== static void DNSSD_API BrowseQueryRecordCallback( DNSServiceRef inSDRef, DNSServiceFlags inFlags, uint32_t inInterfaceIndex, DNSServiceErrorType inError, const char * inFullName, uint16_t inType, uint16_t inClass, uint16_t inRDataLen, const void * inRDataPtr, uint32_t inTTL, void * inContext ) { OSStatus err; BrowseContext * const context = (BrowseContext *) inContext; struct timeval now; Boolean txtValidated = false; Unused( inClass ); Unused( inTTL ); gettimeofday( &now, NULL ); err = inError; require_noerr( err, exit ); require_action( inType == kDNSServiceType_TXT, exit, err = kTypeErr ); if( context->validateResults ) { const uint8_t * dataPtr; size_t dataLen; dataPtr = DNSServiceGetValidationData( inSDRef, &dataLen ); bc_ulog( kLogLevelTrace, "Got %zu bytes of validation data for TXT query result\n", dataLen ); if( dataPtr ) { mdns_signed_browse_result_t signedResult; signedResult = mdns_signed_browse_result_create_from_data( dataPtr, dataLen, &err ); bc_ulog( kLogLevelTrace, "Signed browse result -- %@", signedResult ); if( signedResult ) { if( mdns_signed_browse_result_covers_txt_rdata( signedResult, inRDataPtr, inRDataLen ) ) { txtValidated = true; } else { bc_ulog( kLogLevelError, "Signed resolve result doesn't cover TXT record data\n" ); } mdns_forget( &signedResult ); } else { bc_ulog( kLogLevelError, "mdns_signed_resolve_result_create_from_data() failed: %#m\n", err ); } } } FPrintF( stdout, "%{du:time} %s %s TXT on interface %d\n TXT: %#{txt}", &now, DNSServiceFlagsToAddRmvStr( inFlags ), inFullName, (int32_t) inInterfaceIndex, inRDataPtr, (size_t) inRDataLen ); if( context->validateResults ) _PrintValidatedToStdOut( " (", txtValidated, ")" ); FPrintF( stdout, "\n" ); exit: if( err ) exit( 1 ); } //=========================================================================================================================== // BrowseResolveCallback //=========================================================================================================================== static void DNSSD_API BrowseResolveCallback( DNSServiceRef inSDRef, DNSServiceFlags inFlags, uint32_t inInterfaceIndex, DNSServiceErrorType inError, const char * inFullName, const char * inHostname, uint16_t inPort, uint16_t inTXTLen, const unsigned char * inTXTPtr, void * inContext ) { BrowseContext * const context = (BrowseContext *) inContext; struct timeval now; char errorStr[ 64 ]; Boolean txtValidated = false; Unused( inFlags ); gettimeofday( &now, NULL ); if( inError ) SNPrintF( errorStr, sizeof( errorStr ), " error %#m", inError ); if( context->validateResults ) { OSStatus err; const uint8_t * dataPtr; size_t dataLen; dataPtr = DNSServiceGetValidationData( inSDRef, &dataLen ); bc_ulog( kLogLevelTrace, "Got %zu bytes of validation data for resolve result\n", dataLen ); if( dataPtr ) { mdns_signed_resolve_result_t signedResult; signedResult = mdns_signed_resolve_result_create_from_data( dataPtr, dataLen, &err ); bc_ulog( kLogLevelTrace, "Signed resolve result -- %@", signedResult ); if( signedResult ) { if( mdns_signed_resolve_result_covers_txt_rdata( signedResult, inTXTPtr, inTXTLen ) ) { txtValidated = true; } else { bc_ulog( kLogLevelError, "Signed resolve result doesn't cover TXT record data\n" ); } mdns_forget( &signedResult ); } else { bc_ulog( kLogLevelError, "mdns_signed_resolve_result_create_from_data() failed: %#m\n", err ); } } } FPrintF( stdout, "%{du:time} %s can be reached at %s:%u (interface %d)%?s\n", &now, inFullName, inHostname, ntohs( inPort ), (int32_t) inInterfaceIndex, inError, errorStr ); if( inTXTLen == 1 ) { FPrintF( stdout, " TXT record: %#H", inTXTPtr, (int) inTXTLen, INT_MAX ); } else { FPrintF( stdout, " TXT record: %#{txt}", inTXTPtr, (size_t) inTXTLen ); } if( context->validateResults ) _PrintValidatedToStdOut( " (", txtValidated, ")" ); FPrintF( stdout, "\n" ); } //=========================================================================================================================== // GetAddrInfoCmd //=========================================================================================================================== typedef struct { DNSServiceRef mainRef; // Main sdRef for shared connection. DNSServiceRef opRef; // sdRef for the DNSServiceGetAddrInfo operation. const char * name; // Hostname to resolve. DNSServiceFlags flags; // Flags argument for DNSServiceGetAddrInfo(). DNSServiceProtocol protocols; // Protocols argument for DNSServiceGetAddrInfo(). uint32_t ifIndex; // Interface index argument for DNSServiceGetAddrInfo(). int timeLimitSecs; // Time limit for the DNSServiceGetAddrInfo() operation in seconds. Boolean printedHeader; // True if the results header has been printed. Boolean oneShotMode; // True if command is done after the first set of results (one-shot mode). Boolean needIPv4; // True if in one-shot mode and an IPv4 result is needed. Boolean needIPv6; // True if in one-shot mode and an IPv6 result is needed. } GetAddrInfoContext; static void GetAddrInfoPrintPrologue( const GetAddrInfoContext *inContext ); static void GetAddrInfoContextFree( GetAddrInfoContext *inContext ); static void DNSSD_API GetAddrInfoCallback( DNSServiceRef inSDRef, DNSServiceFlags inFlags, uint32_t inInterfaceIndex, DNSServiceErrorType inError, const char * inHostname, const struct sockaddr * inSockAddr, uint32_t inTTL, void * inContext ); static void GetAddrInfoCmd( void ) { OSStatus err; DNSServiceRef sdRef; GetAddrInfoContext * context = NULL; dispatch_source_t signalSource = NULL; int useMainConnection; // Set up SIGINT handler. signal( SIGINT, SIG_IGN ); err = DispatchSignalSourceCreate( SIGINT, dispatch_get_main_queue(), Exit, kExitReason_SIGINT, &signalSource ); require_noerr( err, exit ); dispatch_resume( signalSource ); // Check command parameters. if( gGetAddrInfo_TimeLimitSecs < 0 ) { FPrintF( stderr, "Invalid time limit: %d s.\n", gGetAddrInfo_TimeLimitSecs ); err = kParamErr; goto exit; } #if( MDNSRESPONDER_PROJECT ) if( gFallbackDNSService ) { err = _SetDefaultFallbackDNSService( gFallbackDNSService ); require_noerr_quiet( err, exit ); } #endif // Create context. context = (GetAddrInfoContext *) calloc( 1, sizeof( *context ) ); require_action( context, exit, err = kNoMemoryErr ); // Create main connection. if( gConnectionOpt ) { err = CreateConnectionFromArgString( gConnectionOpt, dispatch_get_main_queue(), &context->mainRef, NULL ); require_noerr_quiet( err, exit ); useMainConnection = true; } else { useMainConnection = false; } // Get flags. context->flags = GetDNSSDFlagsFromOpts(); if( useMainConnection ) context->flags |= kDNSServiceFlagsShareConnection; // Get interface. err = InterfaceIndexFromArgString( gInterface, &context->ifIndex ); require_noerr_quiet( err, exit ); // Set remaining parameters. context->name = gGetAddrInfo_Name; context->timeLimitSecs = gGetAddrInfo_TimeLimitSecs; if( gGetAddrInfo_ProtocolIPv4 ) context->protocols |= kDNSServiceProtocol_IPv4; if( gGetAddrInfo_ProtocolIPv6 ) context->protocols |= kDNSServiceProtocol_IPv6; if( gGetAddrInfo_OneShot ) { context->oneShotMode = true; context->needIPv4 = ( gGetAddrInfo_ProtocolIPv4 || !gGetAddrInfo_ProtocolIPv6 ) ? true : false; context->needIPv6 = ( gGetAddrInfo_ProtocolIPv6 || !gGetAddrInfo_ProtocolIPv4 ) ? true : false; } // Print prologue. GetAddrInfoPrintPrologue( context ); // Start operation. sdRef = useMainConnection ? context->mainRef : kBadDNSServiceRef; err = DNSServiceGetAddrInfo( &sdRef, context->flags, context->ifIndex, context->protocols, context->name, GetAddrInfoCallback, context ); require_noerr( err, exit ); context->opRef = sdRef; if( !useMainConnection ) { err = DNSServiceSetDispatchQueue( context->opRef, dispatch_get_main_queue() ); require_noerr( err, exit ); } // Set time limit. if( context->timeLimitSecs > 0 ) { dispatch_after_f( dispatch_time_seconds( context->timeLimitSecs ), dispatch_get_main_queue(), kExitReason_TimeLimit, Exit ); } dispatch_main(); exit: dispatch_source_forget( &signalSource ); if( context ) GetAddrInfoContextFree( context ); if( err ) exit( 1 ); } //=========================================================================================================================== // GetAddrInfoPrintPrologue //=========================================================================================================================== static void GetAddrInfoPrintPrologue( const GetAddrInfoContext *inContext ) { const int timeLimitSecs = inContext->timeLimitSecs; char ifName[ kInterfaceNameBufLen ]; InterfaceIndexToName( inContext->ifIndex, ifName ); FPrintF( stdout, "Flags: %#{flags}\n", inContext->flags, kDNSServiceFlagsDescriptors ); FPrintF( stdout, "Interface: %d (%s)\n", (int32_t) inContext->ifIndex, ifName ); FPrintF( stdout, "Protocols: %#{flags}\n", inContext->protocols, kDNSServiceProtocolDescriptors ); FPrintF( stdout, "Name: %s\n", inContext->name ); FPrintF( stdout, "Mode: %s\n", inContext->oneShotMode ? "one-shot" : "continuous" ); FPrintF( stdout, "Time limit: " ); if( timeLimitSecs > 0 ) FPrintF( stdout, "%d second%?c\n", timeLimitSecs, timeLimitSecs != 1, 's' ); else FPrintF( stdout, "∞\n" ); FPrintF( stdout, "Start time: %{du:time}\n", NULL ); FPrintF( stdout, "---\n" ); } //=========================================================================================================================== // GetAddrInfoContextFree //=========================================================================================================================== static void GetAddrInfoContextFree( GetAddrInfoContext *inContext ) { DNSServiceForget( &inContext->opRef ); DNSServiceForget( &inContext->mainRef ); free( inContext ); } //=========================================================================================================================== // GetAddrInfoCallback //=========================================================================================================================== static void DNSSD_API GetAddrInfoCallback( DNSServiceRef inSDRef, DNSServiceFlags inFlags, uint32_t inInterfaceIndex, DNSServiceErrorType inError, const char * inHostname, const struct sockaddr * inSockAddr, uint32_t inTTL, void * inContext ) { GetAddrInfoContext * const context = (GetAddrInfoContext *) inContext; struct timeval now; OSStatus err; const char * addrStr; char addrStrBuf[ kSockAddrStringMaxSize ]; Unused( inSDRef ); gettimeofday( &now, NULL ); switch( inError ) { case kDNSServiceErr_NoError: case kDNSServiceErr_NoSuchRecord: err = kNoErr; break; case kDNSServiceErr_Timeout: Exit( kExitReason_Timeout ); default: err = inError; goto exit; } if( ( inSockAddr->sa_family != AF_INET ) && ( inSockAddr->sa_family != AF_INET6 ) ) { dlogassert( "Unexpected address family: %d", inSockAddr->sa_family ); err = kTypeErr; goto exit; } if( !inError ) { err = SockAddrToString( inSockAddr, kSockAddrStringFlagsNone, addrStrBuf ); require_noerr( err, exit ); addrStr = addrStrBuf; } else { addrStr = ( inSockAddr->sa_family == AF_INET ) ? kNoSuchRecordAStr : kNoSuchRecordAAAAStr; } if( !context->printedHeader ) { FPrintF( stdout, "%-26s %-17s IF %-30s %-34s %6s\n", "Timestamp", "Flags", "Hostname", "Address", "TTL" ); context->printedHeader = true; } FPrintF( stdout, "%{du:time} %{du:cbflags} %2d %-30s %-34s %6u\n", &now, inFlags, (int32_t) inInterfaceIndex, inHostname, addrStr, inTTL ); if( context->oneShotMode ) { if( inFlags & kDNSServiceFlagsAdd ) { if( inSockAddr->sa_family == AF_INET ) context->needIPv4 = false; else context->needIPv6 = false; } if( !( inFlags & kDNSServiceFlagsMoreComing ) && !context->needIPv4 && !context->needIPv6 ) { Exit( kExitReason_OneShotDone ); } } exit: if( err ) exit( 1 ); } //=========================================================================================================================== // QueryRecordCmd //=========================================================================================================================== typedef struct { DNSServiceRef mainRef; // Main sdRef for shared connection. DNSServiceRef opRef; // sdRef for the DNSServiceQueryRecord operation. const char * recordName; // Resource record name argument for DNSServiceQueryRecord(). uuid_t * resolverOverride; // UUID of libnetwork DNS resolver configuration to use for resolver override. DNSServiceFlags flags; // Flags argument for DNSServiceQueryRecord(). uint32_t ifIndex; // Interface index argument for DNSServiceQueryRecord(). int timeLimitSecs; // Time limit for the DNSServiceQueryRecord() operation in seconds. uint16_t recordType; // Resource record type argument for DNSServiceQueryRecord(). Boolean useAAAAFallback; // True if query for AAAA should fallback to A if AAAA doesn't exist. Boolean useFailover; // True if DNS service failover should be used if necessary and applicable. Boolean printedHeader; // True if the results header was printed. Boolean oneShotMode; // True if command is done after the first set of results (one-shot mode). Boolean gotRecord; // True if in one-shot mode and received at least one record of the desired type. Boolean printRawRData; // True if RDATA results are not to be formatted when printed. } QueryRecordContext; static void QueryRecordPrintPrologue( const QueryRecordContext *inContext ); static void QueryRecordContextFree( QueryRecordContext *inContext ); static void DNSSD_API QueryRecordCallback( DNSServiceRef inSDRef, DNSServiceFlags inFlags, uint32_t inInterfaceIndex, DNSServiceErrorType inError, const char * inFullName, uint16_t inType, uint16_t inClass, uint16_t inRDataLen, const void * inRDataPtr, uint32_t inTTL, void * inContext ); static void QueryRecordCmd( void ) { OSStatus err; DNSServiceRef sdRef; DNSServiceAttributeRef attr = NULL; QueryRecordContext * context = NULL; dispatch_source_t signalSource = NULL; int useMainConnection; // Set up SIGINT handler. signal( SIGINT, SIG_IGN ); err = DispatchSignalSourceCreate( SIGINT, dispatch_get_main_queue(), Exit, kExitReason_SIGINT, &signalSource ); require_noerr( err, exit ); dispatch_resume( signalSource ); // Create context. context = (QueryRecordContext *) calloc( 1, sizeof( *context ) ); require_action( context, exit, err = kNoMemoryErr ); // Check command parameters. if( gQueryRecord_TimeLimitSecs < 0 ) { FPrintF( stderr, "Invalid time limit: %d seconds.\n", gQueryRecord_TimeLimitSecs ); err = kParamErr; goto exit; } #if( MDNSRESPONDER_PROJECT ) if( gFallbackDNSService ) { err = _SetDefaultFallbackDNSService( gFallbackDNSService ); require_noerr_quiet( err, exit ); } #endif // Create main connection. if( gConnectionOpt ) { err = CreateConnectionFromArgString( gConnectionOpt, dispatch_get_main_queue(), &context->mainRef, NULL ); require_noerr_quiet( err, exit ); useMainConnection = true; } else { useMainConnection = false; } // Get flags. context->flags = GetDNSSDFlagsFromOpts(); if( useMainConnection ) context->flags |= kDNSServiceFlagsShareConnection; // Get interface. err = InterfaceIndexFromArgString( gInterface, &context->ifIndex ); require_noerr_quiet( err, exit ); // Get record type. err = RecordTypeFromArgString( gQueryRecord_Type, &context->recordType ); require_noerr( err, exit ); // Get resolver override UUID. if( gQueryRecord_ResolverOverride ) { uuid_t uuid; err = uuid_parse( gQueryRecord_ResolverOverride, uuid ); if( err ) { FPrintF( stderr, "Invalid resolver UUID: %s\n", gQueryRecord_ResolverOverride ); err = kParamErr; goto exit; } context->resolverOverride = (uuid_t *) _memdup( uuid, sizeof( uuid ) ); require_action( context->resolverOverride, exit, err = kNoMemoryErr ); } // Set remaining parameters. context->recordName = gQueryRecord_Name; context->timeLimitSecs = gQueryRecord_TimeLimitSecs; context->useAAAAFallback = gQueryRecord_AAAAFallback ? true : false; context->useFailover = gQueryRecord_UseFailover ? true : false; context->oneShotMode = gQueryRecord_OneShot ? true : false; context->printRawRData = gQueryRecord_RawRData ? true : false; // Print prologue. QueryRecordPrintPrologue( context ); // Start operation. if( context->useAAAAFallback || context->useFailover || context->resolverOverride ) { attr = DNSServiceAttributeCreate(); require_action( attr, exit, err = kNoResourcesErr ); if( context->useAAAAFallback ) { err = DNSServiceAttributeSetAAAAPolicy( attr, kDNSServiceAAAAPolicyFallback ); require_noerr( err, exit ); } if( context->useFailover ) { err = DNSServiceAttrSetFailoverPolicy( attr, kDNSServiceFailoverPolicyAllow ); require_noerr( err, exit ); } if( context->resolverOverride ) { if( __builtin_available( macOS 15.0, iOS 18.0, tvOS 18.0, watchOS 11.0, * ) ) { err = DNSServiceAttributeSetResolverOverride( attr, *context->resolverOverride ); require_noerr( err, exit ); } else { FPrintF( stderr, "DNSServiceAttributeSetResolverOverride() is not available on this OS build." ); err = kUnsupportedErr; goto exit; } } } sdRef = useMainConnection ? context->mainRef : kBadDNSServiceRef; if( attr ) { err = DNSServiceQueryRecordWithAttribute( &sdRef, context->flags, context->ifIndex, context->recordName, context->recordType, kDNSServiceClass_IN, attr, QueryRecordCallback, context ); require_noerr( err, exit ); _DNSServiceAttrForget( &attr ); } else { err = DNSServiceQueryRecord( &sdRef, context->flags, context->ifIndex, context->recordName, context->recordType, kDNSServiceClass_IN, QueryRecordCallback, context ); require_noerr( err, exit ); } context->opRef = sdRef; if( !useMainConnection ) { err = DNSServiceSetDispatchQueue( context->opRef, dispatch_get_main_queue() ); require_noerr( err, exit ); } // Set time limit. if( context->timeLimitSecs > 0 ) { dispatch_after_f( dispatch_time_seconds( context->timeLimitSecs ), dispatch_get_main_queue(), kExitReason_TimeLimit, Exit ); } dispatch_main(); exit: _DNSServiceAttrForget( &attr ); dispatch_source_forget( &signalSource ); if( context ) QueryRecordContextFree( context ); if( err ) exit( 1 ); } //=========================================================================================================================== // QueryRecordContextFree //=========================================================================================================================== static void QueryRecordContextFree( QueryRecordContext *inContext ) { DNSServiceForget( &inContext->opRef ); DNSServiceForget( &inContext->mainRef ); ForgetMem( &inContext->resolverOverride ); free( inContext ); } //=========================================================================================================================== // QueryRecordPrintPrologue //=========================================================================================================================== static void QueryRecordPrintPrologue( const QueryRecordContext *inContext ) { const int timeLimitSecs = inContext->timeLimitSecs; char ifName[ kInterfaceNameBufLen ]; InterfaceIndexToName( inContext->ifIndex, ifName ); const char *resolverOverrideStr = "n/a"; uuid_string_t resolverOverrideUUIDStr; if( inContext->resolverOverride ) { uuid_unparse_upper( *inContext->resolverOverride, resolverOverrideUUIDStr ); resolverOverrideStr = resolverOverrideUUIDStr; } FPrintF( stdout, "Flags: %#{flags}\n", inContext->flags, kDNSServiceFlagsDescriptors ); FPrintF( stdout, "Interface: %d (%s)\n", (int32_t) inContext->ifIndex, ifName ); FPrintF( stdout, "Name: %s\n", inContext->recordName ); FPrintF( stdout, "Type: %s (%u)\n", RecordTypeToString( inContext->recordType ), inContext->recordType ); FPrintF( stdout, "AAAA Fallback: %s\n", YesNoStr( inContext->useAAAAFallback ) ); FPrintF( stdout, "Resolver Override: %s\n", resolverOverrideStr ); FPrintF( stdout, "Mode: %s\n", inContext->oneShotMode ? "one-shot" : "continuous" ); FPrintF( stdout, "Time limit: " ); if( timeLimitSecs > 0 ) FPrintF( stdout, "%d second%?c\n", timeLimitSecs, timeLimitSecs != 1, 's' ); else FPrintF( stdout, "∞\n" ); FPrintF( stdout, "Start time: %{du:time}\n", NULL ); FPrintF( stdout, "---\n" ); } //=========================================================================================================================== // QueryRecordCallback //=========================================================================================================================== static void DNSSD_API QueryRecordCallback( DNSServiceRef inSDRef, DNSServiceFlags inFlags, uint32_t inInterfaceIndex, DNSServiceErrorType inError, const char * inFullName, uint16_t inType, uint16_t inClass, uint16_t inRDataLen, const void * inRDataPtr, uint32_t inTTL, void * inContext ) { QueryRecordContext * const context = (QueryRecordContext *) inContext; struct timeval now; OSStatus err; char * rdataStrMem = NULL; const char * rdataStr; Unused( inSDRef ); gettimeofday( &now, NULL ); switch( inError ) { case kDNSServiceErr_NoError: if( !context->printRawRData ) DNSRecordDataToString( inRDataPtr, inRDataLen, inType, &rdataStrMem ); if( !rdataStrMem ) { ASPrintF( &rdataStrMem, "%#H", inRDataPtr, (int) inRDataLen, INT_MAX ); require_action( rdataStrMem, exit, err = kNoMemoryErr ); } rdataStr = rdataStrMem; break; case kDNSServiceErr_NoSuchRecord: rdataStr = kNoSuchRecordStr; break; case kDNSServiceErr_NoSuchName: rdataStr = kNoSuchNameStr; break; case kDNSServiceErr_Timeout: Exit( kExitReason_Timeout ); default: err = inError; goto exit; } if( !context->printedHeader ) { FPrintF( stdout, "%-26s %-17s IF %-32s %-5s %-5s %6s RData\n", "Timestamp", "Flags", "Name", "Type", "Class", "TTL" ); context->printedHeader = true; } FPrintF( stdout, "%{du:time} %{du:cbflags} %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 ); if( context->oneShotMode ) { if( ( inFlags & kDNSServiceFlagsAdd ) && ( ( context->recordType == kDNSServiceType_ANY ) || ( context->recordType == inType ) ) ) { context->gotRecord = true; } if( !( inFlags & kDNSServiceFlagsMoreComing ) && context->gotRecord ) Exit( kExitReason_OneShotDone ); } err = kNoErr; exit: ForgetMem( &rdataStrMem ); if( err ) ErrQuit( 1, "error: %#m\n", err ); } //=========================================================================================================================== // RegisterCmd //=========================================================================================================================== typedef struct RecordUpdate RecordUpdate; struct RecordUpdate { RecordUpdate * next; // Next record update in list. uint8_t * dataPtr; // Record data. size_t dataLen; // Record data length. uint32_t ttl; // Record TTL value. uint32_t delayMs; // Update delay in milliseconds. }; typedef struct { DNSRecordRef recordRef; // Reference returned by DNSServiceAddRecord(). uint8_t * dataPtr; // Record data. size_t dataLen; // Record data length. uint32_t ttl; // Record TTL value. uint16_t type; // Record type. } ExtraRecord; typedef struct { DNSServiceRef opRef; // sdRef for DNSServiceRegister operation. const char * name; // Service name argument for DNSServiceRegister(). const char * type; // Service type argument for DNSServiceRegister(). const char * domain; // Domain in which advertise the service. uint8_t * txtPtr; // Service TXT record data. (malloc'd) size_t txtLen; // Service TXT record data len. ExtraRecord * extraRecords; // Array of extra records to add to registered service. size_t extraRecordsCount; // Number of extra records. RecordUpdate * txtUpdates; // List of TXT record updates. (malloc'd) dispatch_source_t updateTimer; // Timer for the next delayed TXT record update. dispatch_source_t lifetimeTimer; // Timer for the registration's lifetime. DNSServiceFlags flags; // Flags argument for DNSServiceRegister(). uint32_t ifIndex; // Interface index argument for DNSServiceRegister(). int lifetimeMs; // Lifetime of the record registration in milliseconds. uint32_t timestamp; // Timestamp in seconds since epoch time to indicate when the service is registered. uint32_t hostKeyHash; // Host key hash value. uint16_t port; // Service instance's port number. Boolean setTimestamp; // True if the timestamp attribute needs to be set. Boolean setHostKeyHash; // True if the host key hash attribute needs to be set. Boolean printedHeader; // True if results header was printed. Boolean didRegister; // True if service was registered. } RegisterContext; static void RegisterPrintPrologue( const RegisterContext *inContext ); static void RegisterContextFree( RegisterContext *inContext ); static void DNSSD_API RegisterCallback( DNSServiceRef inSDRef, DNSServiceFlags inFlags, DNSServiceErrorType inError, const char * inName, const char * inType, const char * inDomain, void * inContext ); static OSStatus _RegisterHandleTXTRecordUpdates( RegisterContext *inContext ); static void _RegisterUpdateTimerHandler( void *inContext ); static void _RecordUpdateFree( RecordUpdate *inUpdate ); #define _RecordUpdateForget( X ) ForgetCustom( X, _RecordUpdateFree ) static void RegisterCmd( void ) { OSStatus err; RegisterContext * context = NULL; dispatch_source_t signalSource = NULL; DNSServiceAttributeRef attr = NULL; // Set up SIGINT handler. signal( SIGINT, SIG_IGN ); err = DispatchSignalSourceCreate( SIGINT, dispatch_get_main_queue(), Exit, kExitReason_SIGINT, &signalSource ); require_noerr( err, exit ); dispatch_resume( signalSource ); // Create context. context = (RegisterContext *) calloc( 1, sizeof( *context ) ); require_action( context, exit, err = kNoMemoryErr ); // Check command parameters. err = CheckIntegerArgument( gRegister_Port, "port", 0, UINT16_MAX ); require_noerr_quiet( err, exit ); require_action_quiet( ( gAddRecord_TypesCount == gAddRecord_DataCount ) && ( ( gAddRecord_TTLsCount == 0 ) || ( gAddRecord_TTLsCount == gAddRecord_TypesCount ) ), exit, FPrintF( stderr, "There are missing additional record parameters.\n" ); err = kParamErr ); require_action_quiet( ( gUpdateRecord_DataCount == gUpdateRecord_DelayCount ) && ( ( gUpdateRecord_TTLCount == 0 ) || ( gUpdateRecord_TTLCount == gUpdateRecord_DataCount ) ), exit, FPrintF( stderr, "There are missing update record parameters.\n" ); err = kParamErr ); // Get flags. context->flags = GetDNSSDFlagsFromOpts(); // Get interface index. err = InterfaceIndexFromArgString( gInterface, &context->ifIndex ); require_noerr_quiet( err, exit ); // Get TXT record data. if( gRegister_TXT ) { err = RecordDataFromArgString( gRegister_TXT, &context->txtPtr, &context->txtLen ); require_noerr_quiet( err, exit ); } // Set remaining parameters. context->name = gRegister_Name; context->type = gRegister_Type; context->domain = gRegister_Domain; context->port = (uint16_t) gRegister_Port; context->lifetimeMs = gRegister_LifetimeMs; if ( gRegister_TimeOfReceipt ) { err = _UInt32FromArgString( gRegister_TimeOfReceipt, "timestamp", &context->timestamp ); require_noerr_quiet( err, exit ); context->setTimestamp = true; } if( gRegister_HostKeyHash ) { err = _UInt32FromArgString( gRegister_HostKeyHash, "hostKeyHash", &context->hostKeyHash ); require_noerr_quiet( err, exit ); context->setHostKeyHash = true; } if( gAddRecord_TypesCount > 0 ) { size_t i; context->extraRecords = (ExtraRecord *) calloc( gAddRecord_TypesCount, sizeof( *context->extraRecords ) ); require_action( context->extraRecords, exit, err = kNoMemoryErr ); context->extraRecordsCount = gAddRecord_TypesCount; for( i = 0; i < gAddRecord_TypesCount; ++i ) { ExtraRecord * const extraRecord = &context->extraRecords[ i ]; err = RecordTypeFromArgString( gAddRecord_Types[ i ], &extraRecord->type ); require_noerr( err, exit ); err = RecordDataFromArgString( gAddRecord_Data[ i ], &extraRecord->dataPtr, &extraRecord->dataLen ); require_noerr_quiet( err, exit ); if( gAddRecord_TTLsCount > 0 ) { err = StringToUInt32( gAddRecord_TTLs[ i ], &extraRecord->ttl ); require_noerr_action_quiet( err, exit, FPrintF( stderr, "Invalid TTL value: '%s'\n", gAddRecord_TTLs[ i ] ); err = kParamErr ); } else { extraRecord->ttl = 0; } } } if( gUpdateRecord_DataCount > 0 ) { size_t i; RecordUpdate ** ptr = &context->txtUpdates; for( i = 0; i < gUpdateRecord_DataCount; ++i ) { RecordUpdate * update; update = (RecordUpdate *) calloc( 1, sizeof( *update ) ); require_action( update, exit, err = kNoMemoryErr ); *ptr = update; ptr = &update->next; err = RecordDataFromArgString( gUpdateRecord_Datas[ i ], &update->dataPtr, &update->dataLen ); require_noerr_quiet( err, exit ); err = StringToUInt32( gUpdateRecord_DelaysMs[ i ], &update->delayMs ); require_noerr_action_quiet( err, exit, FPrintF( stderr, "Invalid delay value: '%s'\n", gUpdateRecord_DelaysMs[ i ] ); err = kParamErr ); if( gUpdateRecord_TTLCount > 0 ) { err = StringToUInt32( gUpdateRecord_TTLs[ i ], &update->ttl ); require_noerr_action_quiet( err, exit, FPrintF( stderr, "Invalid TTL value: '%s'\n", gUpdateRecord_TTLs[ i ] ); err = kParamErr ); } else { update->ttl = 0; } } } // Print prologue. RegisterPrintPrologue( context ); // Start operation. if( context->setTimestamp || context->setHostKeyHash ) { attr = DNSServiceAttributeCreate(); require_action( attr, exit, err = kNoResourcesErr ); if( context->setTimestamp ) { err = DNSServiceAttributeSetTimestamp( attr, context->timestamp ); require_noerr( err, exit ); } if( context->setHostKeyHash ) { if( __builtin_available( macOS 15.0, iOS 18.0, tvOS 18.0, watchOS 11.0, * ) ) { err = DNSServiceAttributeSetHostKeyHash( attr, context->hostKeyHash ); require_noerr( err, exit ); } else { FPrintF( stderr, "DNSServiceAttributeSetHostKeyHash() is not available on this OS build." ); err = kUnsupportedErr; goto exit; } } } if( attr ) { err = DNSServiceRegisterWithAttribute( &context->opRef, context->flags, context->ifIndex, context->name, context->type, context->domain, NULL, htons( context->port ), (uint16_t) context->txtLen, context->txtPtr, attr, RegisterCallback, context ); require_noerr( err, exit ); _DNSServiceAttrForget( &attr ); } else { err = DNSServiceRegister( &context->opRef, context->flags, context->ifIndex, context->name, context->type, context->domain, NULL, htons( context->port ), (uint16_t) context->txtLen, context->txtPtr, RegisterCallback, context ); ForgetMem( &context->txtPtr ); require_noerr( err, exit ); } err = DNSServiceSetDispatchQueue( context->opRef, dispatch_get_main_queue() ); require_noerr( err, exit ); dispatch_main(); exit: _DNSServiceAttrForget( &attr ); dispatch_source_forget( &signalSource ); if( context ) RegisterContextFree( context ); if( err ) exit( 1 ); } //=========================================================================================================================== // RegisterPrintPrologue //=========================================================================================================================== static void RegisterPrintPrologue( const RegisterContext *inContext ) { char ifName[ kInterfaceNameBufLen ]; const char * const defaultTTLStr = " (system will use a default value.)"; Boolean printedRecords; InterfaceIndexToName( inContext->ifIndex, ifName ); FPrintF( stdout, "Flags: %#{flags}\n", inContext->flags, kDNSServiceFlagsDescriptors ); FPrintF( stdout, "Interface: %d (%s)\n", (int32_t) inContext->ifIndex, ifName ); FPrintF( stdout, "Name: %s\n", inContext->name ? inContext->name : "" ); FPrintF( stdout, "Type: %s\n", inContext->type ); FPrintF( stdout, "Domain: %s\n", inContext->domain ? inContext->domain : " (default domains)" ); FPrintF( stdout, "Port: %u\n", inContext->port ); FPrintF( stdout, "TXT data: %#{txt}\n", inContext->txtPtr, inContext->txtLen ); if( inContext->setTimestamp ) { const char * dateTimeStr; char dateTimeBuf[ 32 ]; FPrintF( stdout, "Timestamp: %u", inContext->timestamp ); dateTimeStr = _UnixTimeToDateAndTimeString( inContext->timestamp, dateTimeBuf, sizeof( dateTimeBuf ) ); if( dateTimeStr ) FPrintF( stdout, " (%s)", dateTimeStr ); FPrintF( stdout, "\n" ); } if( inContext->setHostKeyHash ) { FPrintF( stdout, "Host Key Hash: 0x%08X (%u)\n", inContext->hostKeyHash, inContext->hostKeyHash ); } if( inContext->lifetimeMs < 0 ) FPrintF( stdout, "Lifetime: ∞ ms\n" ); else FPrintF( stdout, "Lifetime: %d ms\n", inContext->lifetimeMs ); printedRecords = false; if( inContext->extraRecordsCount > 0 ) { size_t i; for( i = 0; i < inContext->extraRecordsCount; ++i ) { const ExtraRecord * record = &inContext->extraRecords[ i ]; FPrintF( stdout, "\nExtra record #%zu:\n", i + 1 ); FPrintF( stdout, " Type: %s (%u)\n", RecordTypeToString( record->type ), record->type ); FPrintF( stdout, " RData: %{du:rdata}\n", record->type, record->dataPtr, (unsigned int) record->dataLen ); FPrintF( stdout, " TTL: %u%?s\n", record->ttl, record->ttl == 0, defaultTTLStr ); } printedRecords = true; } if( inContext->txtUpdates ) { const RecordUpdate * update; size_t count = 0; for( update = inContext->txtUpdates; update; update = update->next ) { ++count; FPrintF( stdout, "\nTXT record update #%zu:\n", count ); FPrintF( stdout, " Delay: %u ms\n", ( update->delayMs > 0 ) ? update->delayMs : 0 ); FPrintF( stdout, " TXT data: %#{txt}\n", update->dataPtr, update->dataLen ); FPrintF( stdout, " TTL: %u%?s\n", update->ttl, update->ttl == 0, defaultTTLStr ); } printedRecords = true; } FPrintF( stdout, "%sStart time: %{du:time}\n", printedRecords ? "\n" : "", NULL ); FPrintF( stdout, "---\n" ); } //=========================================================================================================================== // RegisterContextFree //=========================================================================================================================== static void RegisterContextFree( RegisterContext *inContext ) { ExtraRecord * record; const ExtraRecord * const end = inContext->extraRecords + inContext->extraRecordsCount; DNSServiceForget( &inContext->opRef ); ForgetMem( &inContext->txtPtr ); for( record = inContext->extraRecords; record < end; ++record ) { check( !record->recordRef ); ForgetMem( &record->dataPtr ); } ForgetMem( &inContext->extraRecords ); while( inContext->txtUpdates ) { RecordUpdate * update = inContext->txtUpdates; inContext->txtUpdates = update->next; _RecordUpdateForget( &update ); } dispatch_source_forget( &inContext->updateTimer ); dispatch_source_forget( &inContext->lifetimeTimer ); free( inContext ); } //=========================================================================================================================== // RegisterCallback //=========================================================================================================================== static void DNSSD_API RegisterCallback( DNSServiceRef inSDRef, DNSServiceFlags inFlags, DNSServiceErrorType inError, const char * inName, const char * inType, const char * inDomain, void * inContext ) { RegisterContext * const context = (RegisterContext *) inContext; OSStatus err; struct timeval now; Unused( inSDRef ); gettimeofday( &now, NULL ); if( !context->printedHeader ) { FPrintF( stdout, "%-26s %-17s Service\n", "Timestamp", "Flags" ); context->printedHeader = true; } FPrintF( stdout, "%{du:time} %{du:cbflags} %s.%s%s %?#m\n", &now, inFlags, inName, inType, inDomain, inError, inError ); require_noerr_action_quiet( inError, exit, err = inError ); if( !context->didRegister && ( inFlags & kDNSServiceFlagsAdd ) ) { context->didRegister = true; if( context->extraRecordsCount > 0 ) { ExtraRecord * record; const ExtraRecord * const end = context->extraRecords + context->extraRecordsCount; for( record = context->extraRecords; record < end; ++record ) { err = DNSServiceAddRecord( context->opRef, &record->recordRef, 0, record->type, (uint16_t) record->dataLen, record->dataPtr, record->ttl ); require_noerr( err, exit ); } } if( context->lifetimeMs == 0 ) { Exit( kExitReason_TimeLimit ); } else if( context->lifetimeMs > 0 ) { check( !context->lifetimeTimer ); err = DispatchTimerOneShotCreate( dispatch_time_milliseconds( context->lifetimeMs ), 0, dispatch_get_main_queue(), Exit, kExitReason_TimeLimit, &context->lifetimeTimer ); require_noerr( err, exit ); dispatch_activate( context->lifetimeTimer ); } err = _RegisterHandleTXTRecordUpdates( context ); require_noerr( err, exit ); } err = kNoErr; exit: if( err ) exit( 1 ); } //=========================================================================================================================== // _RegisterHandleTXTRecordUpdates //=========================================================================================================================== static OSStatus _RegisterHandleTXTRecordUpdates( RegisterContext *inContext ) { OSStatus err; while( inContext->txtUpdates ) { RecordUpdate * update = inContext->txtUpdates; if( update->delayMs > 0 ) { check( !inContext->updateTimer ); err = DispatchTimerOneShotCreate( dispatch_time_milliseconds( update->delayMs ), 0, dispatch_get_main_queue(), _RegisterUpdateTimerHandler, inContext, &inContext->updateTimer ); require_noerr( err, exit ); dispatch_activate( inContext->updateTimer ); break; } err = DNSServiceUpdateRecord( inContext->opRef, NULL, 0, (uint16_t) update->dataLen, update->dataPtr, update->ttl ); require_noerr( err, exit ); inContext->txtUpdates = update->next; _RecordUpdateForget( &update ); } err = kNoErr; exit: return( err ); } //=========================================================================================================================== // _RegisterUpdateTimerHandler //=========================================================================================================================== static void _RegisterUpdateTimerHandler( void *inContext ) { OSStatus err; RegisterContext * const context = (RegisterContext *) inContext; RecordUpdate * const update = context->txtUpdates; dispatch_source_forget( &context->updateTimer ); if( update ) update->delayMs = 0; err = _RegisterHandleTXTRecordUpdates( context ); require_noerr( err, exit ); exit: if( err ) ErrQuit( 1, "error: %#m\n", err ); } //=========================================================================================================================== // _RecordUpdateFree //=========================================================================================================================== static void _RecordUpdateFree( RecordUpdate *inUpdate ) { inUpdate->next = NULL; ForgetMem( &inUpdate->dataPtr ); free( inUpdate ); } //=========================================================================================================================== // RegisterRecordCmd //=========================================================================================================================== typedef struct { DNSServiceRef conRef; // sdRef to be initialized by DNSServiceCreateConnection(). DNSRecordRef recordRef; // Registered record reference. const char * recordName; // Name of resource record. uint8_t * dataPtr; // Pointer to resource record data. size_t dataLen; // Length of resource record data. uint32_t ttl; // TTL value of resource record in seconds. uint32_t ifIndex; // Interface index argument for DNSServiceRegisterRecord(). DNSServiceFlags flags; // Flags argument for DNSServiceRegisterRecord(). int lifetimeMs; // Lifetime of the record registration in milliseconds. uint16_t recordType; // Resource record type. uint8_t * updateDataPtr; // Pointer to data for record update. (malloc'd) size_t updateDataLen; // Length of data for record update. uint32_t updateTTL; // TTL for updated record. int updateDelayMs; // Post-registration record update delay in milliseconds. uint32_t timestamp; // Timestamp in seconds since epoch time to indicate when the record is registered. uint32_t hostKeyHash; // Host key hash value. Boolean setTimestamp; // True if the timestamp attribute needs to be set. Boolean setHostKeyHash; // True if the host key hash attribute needs to be set. Boolean didRegister; // True if the record was registered. } RegisterRecordContext; static void RegisterRecordPrintPrologue( const RegisterRecordContext *inContext ); static void RegisterRecordContextFree( RegisterRecordContext *inContext ); static void DNSSD_API RegisterRecordCallback( DNSServiceRef inSDRef, DNSRecordRef inRecordRef, DNSServiceFlags inFlags, DNSServiceErrorType inError, void * inContext ); static void RegisterRecordUpdate( void *inContext ); static void RegisterRecordCmd( void ) { OSStatus err; RegisterRecordContext * context = NULL; dispatch_source_t signalSource = NULL; DNSServiceAttributeRef attr = NULL; // Set up SIGINT handler. signal( SIGINT, SIG_IGN ); err = DispatchSignalSourceCreate( SIGINT, dispatch_get_main_queue(), Exit, kExitReason_SIGINT, &signalSource ); require_noerr( err, exit ); dispatch_resume( signalSource ); // Create context. context = (RegisterRecordContext *) calloc( 1, sizeof( *context ) ); require_action( context, exit, err = kNoMemoryErr ); // Create connection. err = CreateConnectionFromArgString( gConnectionOpt, dispatch_get_main_queue(), &context->conRef, NULL ); require_noerr_quiet( err, exit ); // Get flags. context->flags = GetDNSSDFlagsFromOpts(); // Get interface. err = InterfaceIndexFromArgString( gInterface, &context->ifIndex ); require_noerr_quiet( err, exit ); // Get record type. err = RecordTypeFromArgString( gRegisterRecord_Type, &context->recordType ); require_noerr( err, exit ); // Get record data. if( gRegisterRecord_Data ) { err = RecordDataFromArgString( gRegisterRecord_Data, &context->dataPtr, &context->dataLen ); require_noerr_quiet( err, exit ); } // Set remaining parameters. context->recordName = gRegisterRecord_Name; context->ttl = (uint32_t) gRegisterRecord_TTL; context->lifetimeMs = gRegisterRecord_LifetimeMs; if( gRegisterRecord_TimeOfReceipt ) { err = _UInt32FromArgString( gRegisterRecord_TimeOfReceipt, "timestamp", &context->timestamp ); require_noerr_quiet( err, exit ); context->setTimestamp = true; } if( gRegisterRecord_HostKeyHash ) { err = _UInt32FromArgString( gRegisterRecord_HostKeyHash, "hostKeyHash", &context->hostKeyHash ); require_noerr_quiet( err, exit ); context->setHostKeyHash = true; } // Get update data. if( gRegisterRecord_UpdateData ) { err = RecordDataFromArgString( gRegisterRecord_UpdateData, &context->updateDataPtr, &context->updateDataLen ); require_noerr_quiet( err, exit ); context->updateTTL = (uint32_t) gRegisterRecord_UpdateTTL; context->updateDelayMs = gRegisterRecord_UpdateDelayMs; } // Print prologue. RegisterRecordPrintPrologue( context ); // Start operation. // Only call DNSServiceAttributeSetTimestamp when the option is set. if( context->setTimestamp || context->setHostKeyHash ) { attr = DNSServiceAttributeCreate(); require_action( attr, exit, err = kNoResourcesErr ); if( context->setTimestamp ) { err = DNSServiceAttributeSetTimestamp( attr, context->timestamp ); require_noerr( err, exit ); } if( context->setHostKeyHash ) { if( __builtin_available( macOS 15.0, iOS 18.0, tvOS 18.0, watchOS 11.0, * ) ) { err = DNSServiceAttributeSetHostKeyHash( attr, context->hostKeyHash ); require_noerr( err, exit ); } else { FPrintF( stderr, "DNSServiceAttributeSetHostKeyHash() is not available on this OS build." ); err = kUnsupportedErr; goto exit; } } } if( attr ) { err = DNSServiceRegisterRecordWithAttribute( context->conRef, &context->recordRef, context->flags, context->ifIndex, context->recordName, context->recordType, kDNSServiceClass_IN, (uint16_t) context->dataLen, context->dataPtr, context->ttl, attr, RegisterRecordCallback, context ); require_noerr( err, exit ); _DNSServiceAttrForget( &attr ); } else { err = DNSServiceRegisterRecord( context->conRef, &context->recordRef, context->flags, context->ifIndex, context->recordName, context->recordType, kDNSServiceClass_IN, (uint16_t) context->dataLen, context->dataPtr, context->ttl, RegisterRecordCallback, context ); if( err ) { FPrintF( stderr, "DNSServiceRegisterRecord() returned %#m\n", err ); goto exit; } } dispatch_main(); exit: _DNSServiceAttrForget( &attr ); dispatch_source_forget( &signalSource ); if( context ) RegisterRecordContextFree( context ); if( err ) exit( 1 ); } //=========================================================================================================================== // RegisterRecordPrintPrologue //=========================================================================================================================== static void RegisterRecordPrintPrologue( const RegisterRecordContext *inContext ) { int infinite; char ifName[ kInterfaceNameBufLen ]; InterfaceIndexToName( inContext->ifIndex, ifName ); FPrintF( stdout, "Flags: %#{flags}\n", inContext->flags, kDNSServiceFlagsDescriptors ); FPrintF( stdout, "Interface: %d (%s)\n", (int32_t) inContext->ifIndex, ifName ); FPrintF( stdout, "Name: %s\n", inContext->recordName ); FPrintF( stdout, "Type: %s (%u)\n", RecordTypeToString( inContext->recordType ), inContext->recordType ); FPrintF( stdout, "TTL: %u\n", inContext->ttl ); FPrintF( stdout, "Data: %#H\n", inContext->dataPtr, (int) inContext->dataLen, INT_MAX ); if( inContext->setTimestamp ) { const char * dateTimeStr; char dateTimeBuf[ 32 ]; FPrintF( stdout, "Timestamp: %u", inContext->timestamp ); dateTimeStr = _UnixTimeToDateAndTimeString( inContext->timestamp, dateTimeBuf, sizeof( dateTimeBuf ) ); if( dateTimeStr ) FPrintF( stdout, " (%s)", dateTimeStr ); FPrintF( stdout, "\n" ); } if( inContext->setHostKeyHash ) { FPrintF( stdout, "Host Key Hash: 0x%08X (%u)\n", inContext->hostKeyHash, inContext->hostKeyHash ); } infinite = ( inContext->lifetimeMs < 0 ) ? true : false; FPrintF( stdout, "Lifetime: %?s%?d ms\n", infinite, "∞", !infinite, inContext->lifetimeMs ); if( inContext->updateDataPtr ) { FPrintF( stdout, "\nUpdate record:\n" ); FPrintF( stdout, " Delay: %d ms\n", ( inContext->updateDelayMs >= 0 ) ? inContext->updateDelayMs : 0 ); FPrintF( stdout, " TTL: %u%?s\n", inContext->updateTTL, inContext->updateTTL == 0, " (system will use a default value.)" ); FPrintF( stdout, " RData: %#H\n", inContext->updateDataPtr, (int) inContext->updateDataLen, INT_MAX ); } FPrintF( stdout, "Start time: %{du:time}\n", NULL ); FPrintF( stdout, "---\n" ); } //=========================================================================================================================== // RegisterRecordContextFree //=========================================================================================================================== static void RegisterRecordContextFree( RegisterRecordContext *inContext ) { DNSServiceForget( &inContext->conRef ); ForgetMem( &inContext->dataPtr ); ForgetMem( &inContext->updateDataPtr ); free( inContext ); } //=========================================================================================================================== // RegisterRecordCallback //=========================================================================================================================== static void RegisterRecordCallback( DNSServiceRef inSDRef, DNSRecordRef inRecordRef, DNSServiceFlags inFlags, DNSServiceErrorType inError, void * inContext ) { RegisterRecordContext * context = (RegisterRecordContext *) inContext; struct timeval now; Unused( inSDRef ); Unused( inRecordRef ); Unused( inFlags ); Unused( context ); gettimeofday( &now, NULL ); FPrintF( stdout, "%{du:time} Record registration result (error %#m)\n", &now, inError ); if( !context->didRegister && !inError ) { context->didRegister = true; if( context->updateDataPtr ) { if( context->updateDelayMs > 0 ) { dispatch_after_f( dispatch_time_milliseconds( context->updateDelayMs ), dispatch_get_main_queue(), context, RegisterRecordUpdate ); } else { RegisterRecordUpdate( context ); } } if( context->lifetimeMs == 0 ) { Exit( kExitReason_TimeLimit ); } else if( context->lifetimeMs > 0 ) { dispatch_after_f( dispatch_time_milliseconds( context->lifetimeMs ), dispatch_get_main_queue(), kExitReason_TimeLimit, Exit ); } } } //=========================================================================================================================== // RegisterRecordUpdate //=========================================================================================================================== static void RegisterRecordUpdate( void *inContext ) { OSStatus err; RegisterRecordContext * const context = (RegisterRecordContext *) inContext; err = DNSServiceUpdateRecord( context->conRef, context->recordRef, 0, (uint16_t) context->updateDataLen, context->updateDataPtr, context->updateTTL ); require_noerr( err, exit ); exit: if( err ) exit( 1 ); } //=========================================================================================================================== // ResolveCmd //=========================================================================================================================== typedef struct { DNSServiceRef mainRef; // Main sdRef for shared connections. DNSServiceRef opRef; // sdRef for the DNSServiceResolve operation. DNSServiceFlags flags; // Flags argument for DNSServiceResolve(). const char * name; // Service name argument for DNSServiceResolve(). const char * type; // Service type argument for DNSServiceResolve(). const char * domain; // Domain argument for DNSServiceResolve(). uint32_t ifIndex; // Interface index argument for DNSServiceResolve(). int timeLimitSecs; // Time limit for the DNSServiceResolve operation in seconds. } ResolveContext; static void ResolvePrintPrologue( const ResolveContext *inContext ); static void ResolveContextFree( ResolveContext *inContext ); static void DNSSD_API ResolveCallback( DNSServiceRef inSDRef, DNSServiceFlags inFlags, uint32_t inInterfaceIndex, DNSServiceErrorType inError, const char * inFullName, const char * inHostname, uint16_t inPort, uint16_t inTXTLen, const unsigned char * inTXTPtr, void * inContext ); static void ResolveCmd( void ) { OSStatus err; DNSServiceRef sdRef; ResolveContext * context = NULL; dispatch_source_t signalSource = NULL; int useMainConnection; // Set up SIGINT handler. signal( SIGINT, SIG_IGN ); err = DispatchSignalSourceCreate( SIGINT, dispatch_get_main_queue(), Exit, kExitReason_SIGINT, &signalSource ); require_noerr( err, exit ); dispatch_resume( signalSource ); // Create context. context = (ResolveContext *) calloc( 1, sizeof( *context ) ); require_action( context, exit, err = kNoMemoryErr ); // Check command parameters. if( gResolve_TimeLimitSecs < 0 ) { FPrintF( stderr, "Invalid time limit: %d seconds.\n", gResolve_TimeLimitSecs ); err = kParamErr; goto exit; } // Create main connection. if( gConnectionOpt ) { err = CreateConnectionFromArgString( gConnectionOpt, dispatch_get_main_queue(), &context->mainRef, NULL ); require_noerr_quiet( err, exit ); useMainConnection = true; } else { useMainConnection = false; } // Get flags. context->flags = GetDNSSDFlagsFromOpts(); if( useMainConnection ) context->flags |= kDNSServiceFlagsShareConnection; // Get interface index. err = InterfaceIndexFromArgString( gInterface, &context->ifIndex ); require_noerr_quiet( err, exit ); // Set remaining parameters. context->name = gResolve_Name; context->type = gResolve_Type; context->domain = gResolve_Domain; context->timeLimitSecs = gResolve_TimeLimitSecs; // Print prologue. ResolvePrintPrologue( context ); // Start operation. sdRef = useMainConnection ? context->mainRef : kBadDNSServiceRef; err = DNSServiceResolve( &sdRef, context->flags, context->ifIndex, context->name, context->type, context->domain, ResolveCallback, NULL ); require_noerr( err, exit ); context->opRef = sdRef; if( !useMainConnection ) { err = DNSServiceSetDispatchQueue( context->opRef, dispatch_get_main_queue() ); require_noerr( err, exit ); } // Set time limit. if( context->timeLimitSecs > 0 ) { dispatch_after_f( dispatch_time_seconds( context->timeLimitSecs ), dispatch_get_main_queue(), kExitReason_TimeLimit, Exit ); } dispatch_main(); exit: dispatch_source_forget( &signalSource ); if( context ) ResolveContextFree( context ); if( err ) exit( 1 ); } //=========================================================================================================================== // ReconfirmCmd //=========================================================================================================================== static void ReconfirmCmd( void ) { OSStatus err; uint8_t * rdataPtr = NULL; size_t rdataLen = 0; DNSServiceFlags flags; uint32_t ifIndex; uint16_t type, class; char ifName[ kInterfaceNameBufLen ]; // Get flags. flags = GetDNSSDFlagsFromOpts(); // Get interface index. err = InterfaceIndexFromArgString( gInterface, &ifIndex ); require_noerr_quiet( err, exit ); // Get record type. err = RecordTypeFromArgString( gReconfirmRecord_Type, &type ); require_noerr( err, exit ); // Get record data. if( gReconfirmRecord_Data ) { err = RecordDataFromArgString( gReconfirmRecord_Data, &rdataPtr, &rdataLen ); require_noerr_quiet( err, exit ); } // Get record class. if( gReconfirmRecord_Class ) { err = RecordClassFromArgString( gReconfirmRecord_Class, &class ); require_noerr( err, exit ); } else { class = kDNSServiceClass_IN; } // Print prologue. FPrintF( stdout, "Flags: %#{flags}\n", flags, kDNSServiceFlagsDescriptors ); FPrintF( stdout, "Interface: %d (%s)\n", (int32_t) ifIndex, InterfaceIndexToName( ifIndex, ifName ) ); FPrintF( stdout, "Name: %s\n", gReconfirmRecord_Name ); FPrintF( stdout, "Type: %s (%u)\n", RecordTypeToString( type ), type ); FPrintF( stdout, "Class: %s (%u)\n", ( class == kDNSServiceClass_IN ) ? "IN" : "???", class ); FPrintF( stdout, "Data: %#H\n", rdataPtr, (int) rdataLen, INT_MAX ); FPrintF( stdout, "---\n" ); err = DNSServiceReconfirmRecord( flags, ifIndex, gReconfirmRecord_Name, type, class, (uint16_t) rdataLen, rdataPtr ); FPrintF( stdout, "Error: %#m\n", err ); exit: FreeNullSafe( rdataPtr ); if( err ) exit( 1 ); } //=========================================================================================================================== // ResolvePrintPrologue //=========================================================================================================================== static void ResolvePrintPrologue( const ResolveContext *inContext ) { const int timeLimitSecs = inContext->timeLimitSecs; char ifName[ kInterfaceNameBufLen ]; InterfaceIndexToName( inContext->ifIndex, ifName ); FPrintF( stdout, "Flags: %#{flags}\n", inContext->flags, kDNSServiceFlagsDescriptors ); FPrintF( stdout, "Interface: %d (%s)\n", (int32_t) inContext->ifIndex, ifName ); FPrintF( stdout, "Name: %s\n", inContext->name ); FPrintF( stdout, "Type: %s\n", inContext->type ); FPrintF( stdout, "Domain: %s\n", inContext->domain ); FPrintF( stdout, "Time limit: " ); if( timeLimitSecs > 0 ) FPrintF( stdout, "%d second%?c\n", timeLimitSecs, timeLimitSecs != 1, 's' ); else FPrintF( stdout, "∞\n" ); FPrintF( stdout, "Start time: %{du:time}\n", NULL ); FPrintF( stdout, "---\n" ); } //=========================================================================================================================== // ResolveContextFree //=========================================================================================================================== static void ResolveContextFree( ResolveContext *inContext ) { DNSServiceForget( &inContext->opRef ); DNSServiceForget( &inContext->mainRef ); free( inContext ); } //=========================================================================================================================== // ResolveCallback //=========================================================================================================================== static void DNSSD_API ResolveCallback( DNSServiceRef inSDRef, DNSServiceFlags inFlags, uint32_t inInterfaceIndex, DNSServiceErrorType inError, const char * inFullName, const char * inHostname, uint16_t inPort, uint16_t inTXTLen, const unsigned char * inTXTPtr, void * inContext ) { struct timeval now; char errorStr[ 64 ]; Unused( inSDRef ); Unused( inFlags ); Unused( inContext ); gettimeofday( &now, NULL ); if( inError ) SNPrintF( errorStr, sizeof( errorStr ), " error %#m", inError ); FPrintF( stdout, "%{du:time}: %s can be reached at %s:%u (interface %d)%?s\n", &now, inFullName, inHostname, ntohs( inPort ), (int32_t) inInterfaceIndex, inError, errorStr ); if( inTXTLen == 1 ) { FPrintF( stdout, " TXT record: %#H\n", inTXTPtr, (int) inTXTLen, INT_MAX ); } else { FPrintF( stdout, " TXT record: %#{txt}\n", inTXTPtr, (size_t) inTXTLen ); } } //=========================================================================================================================== // GetAddrInfoPOSIXCmd //=========================================================================================================================== #define AddressFamilyStr( X ) ( \ ( (X) == AF_INET ) ? "inet" : \ ( (X) == AF_INET6 ) ? "inet6" : \ ( (X) == AF_UNSPEC ) ? "unspec" : \ "???" ) typedef struct { unsigned int flag; const char * str; } FlagStringPair; #define CaseFlagStringify( X ) { (X), # X } const FlagStringPair kGAIPOSIXFlagStringPairs[] = { #if( defined( AI_UNUSABLE ) ) CaseFlagStringify( AI_UNUSABLE ), #endif CaseFlagStringify( AI_NUMERICSERV ), CaseFlagStringify( AI_V4MAPPED ), CaseFlagStringify( AI_ADDRCONFIG ), #if( defined( AI_V4MAPPED_CFG ) ) CaseFlagStringify( AI_V4MAPPED_CFG ), #endif CaseFlagStringify( AI_ALL ), CaseFlagStringify( AI_NUMERICHOST ), CaseFlagStringify( AI_CANONNAME ), CaseFlagStringify( AI_PASSIVE ), { 0, NULL } }; static void GetAddrInfoPOSIXCmd( void ) { OSStatus err; struct addrinfo hints; struct timeval now; const struct addrinfo * addrInfo; struct addrinfo * addrInfoList = NULL; const FlagStringPair * pair; memset( &hints, 0, sizeof( hints ) ); hints.ai_socktype = SOCK_STREAM; // Set hints address family. if( !gGAIPOSIX_Family ) hints.ai_family = AF_UNSPEC; else if( strcasecmp( gGAIPOSIX_Family, "inet" ) == 0 ) hints.ai_family = AF_INET; else if( strcasecmp( gGAIPOSIX_Family, "inet6" ) == 0 ) hints.ai_family = AF_INET6; else if( strcasecmp( gGAIPOSIX_Family, "unspec" ) == 0 ) hints.ai_family = AF_UNSPEC; else { FPrintF( stderr, "Invalid address family: %s.\n", gGAIPOSIX_Family ); err = kParamErr; goto exit; } // Set hints flags. if( gGAIPOSIXFlag_AddrConfig ) hints.ai_flags |= AI_ADDRCONFIG; if( gGAIPOSIXFlag_All ) hints.ai_flags |= AI_ALL; if( gGAIPOSIXFlag_CanonName ) hints.ai_flags |= AI_CANONNAME; if( gGAIPOSIXFlag_NumericHost ) hints.ai_flags |= AI_NUMERICHOST; if( gGAIPOSIXFlag_NumericServ ) hints.ai_flags |= AI_NUMERICSERV; if( gGAIPOSIXFlag_Passive ) hints.ai_flags |= AI_PASSIVE; if( gGAIPOSIXFlag_V4Mapped ) hints.ai_flags |= AI_V4MAPPED; #if( defined( AI_V4MAPPED_CFG ) ) if( gGAIPOSIXFlag_V4MappedCFG ) hints.ai_flags |= AI_V4MAPPED_CFG; #endif #if( defined( AI_DEFAULT ) ) if( gGAIPOSIXFlag_Default ) hints.ai_flags |= AI_DEFAULT; #endif #if( defined( AI_UNUSABLE ) ) if( gGAIPOSIXFlag_Unusable ) hints.ai_flags |= AI_UNUSABLE; #endif #if( MDNSRESPONDER_PROJECT ) if( gFallbackDNSService ) { err = _SetDefaultFallbackDNSService( gFallbackDNSService ); require_noerr_quiet( err, exit ); } #endif // Print prologue. FPrintF( stdout, "Hostname: %s\n", gGAIPOSIX_HostName ); FPrintF( stdout, "Servname: %s\n", gGAIPOSIX_ServName ); FPrintF( stdout, "Address family: %s\n", AddressFamilyStr( hints.ai_family ) ); FPrintF( stdout, "Flags: 0x%X < ", hints.ai_flags ); for( pair = kGAIPOSIXFlagStringPairs; pair->str != NULL; ++pair ) { if( ( (unsigned int) hints.ai_flags ) & pair->flag ) FPrintF( stdout, "%s ", pair->str ); } FPrintF( stdout, ">\n" ); FPrintF( stdout, "Start time: %{du:time}\n", NULL ); FPrintF( stdout, "---\n" ); // Call getaddrinfo(). err = getaddrinfo( gGAIPOSIX_HostName, gGAIPOSIX_ServName, &hints, &addrInfoList ); gettimeofday( &now, NULL ); if( err ) { FPrintF( stderr, "Error %d: %s.\n", err, gai_strerror( err ) ); } else { int addrCount = 0; for( addrInfo = addrInfoList; addrInfo; addrInfo = addrInfo->ai_next ) { ++addrCount; } FPrintF( stdout, "Addresses (%d total):\n", addrCount ); for( addrInfo = addrInfoList; addrInfo; addrInfo = addrInfo->ai_next ) { FPrintF( stdout, "%##a\n", addrInfo->ai_addr ); } } FPrintF( stdout, "---\n" ); FPrintF( stdout, "End time: %{du:time}\n", &now ); exit: if( addrInfoList ) freeaddrinfo( addrInfoList ); if( err ) exit( 1 ); } //=========================================================================================================================== // ReverseLookupCmd //=========================================================================================================================== static void ReverseLookupCmd( void ) { OSStatus err; QueryRecordContext * context = NULL; DNSServiceRef sdRef; dispatch_source_t signalSource = NULL; uint32_t ipv4Addr; uint8_t ipv6Addr[ 16 ]; char recordName[ kReverseIPv6DomainNameBufLen ]; int useMainConnection; const char * endPtr; // Set up SIGINT handler. signal( SIGINT, SIG_IGN ); err = DispatchSignalSourceCreate( SIGINT, dispatch_get_main_queue(), Exit, kExitReason_SIGINT, &signalSource ); require_noerr( err, exit ); dispatch_resume( signalSource ); // Create context. context = (QueryRecordContext *) calloc( 1, sizeof( *context ) ); require_action( context, exit, err = kNoMemoryErr ); // Check command parameters. if( gReverseLookup_TimeLimitSecs < 0 ) { FPrintF( stderr, "Invalid time limit: %d s.\n", gReverseLookup_TimeLimitSecs ); err = kParamErr; goto exit; } // Create main connection. if( gConnectionOpt ) { err = CreateConnectionFromArgString( gConnectionOpt, dispatch_get_main_queue(), &context->mainRef, NULL ); require_noerr_quiet( err, exit ); useMainConnection = true; } else { useMainConnection = false; } // Get flags. context->flags = GetDNSSDFlagsFromOpts(); if( useMainConnection ) context->flags |= kDNSServiceFlagsShareConnection; // Get interface index. err = InterfaceIndexFromArgString( gInterface, &context->ifIndex ); require_noerr_quiet( err, exit ); // Create reverse lookup record name. err = _StringToIPv4Address( gReverseLookup_IPAddr, kStringToIPAddressFlagsNoPort | kStringToIPAddressFlagsNoPrefix, &ipv4Addr, NULL, NULL, NULL, &endPtr ); if( err || ( *endPtr != '\0' ) ) { err = _StringToIPv6Address( gReverseLookup_IPAddr, kStringToIPAddressFlagsNoPort | kStringToIPAddressFlagsNoPrefix | kStringToIPAddressFlagsNoScope, ipv6Addr, NULL, NULL, NULL, &endPtr ); if( err || ( *endPtr != '\0' ) ) { FPrintF( stderr, "Invalid IP address: \"%s\".\n", gReverseLookup_IPAddr ); err = kParamErr; goto exit; } _WriteReverseIPv6DomainNameString( ipv6Addr, recordName ); } else { _WriteReverseIPv4DomainNameString( ipv4Addr, recordName ); } // Set remaining parameters. context->recordName = recordName; context->recordType = kDNSServiceType_PTR; context->timeLimitSecs = gReverseLookup_TimeLimitSecs; context->oneShotMode = gReverseLookup_OneShot ? true : false; // Print prologue. QueryRecordPrintPrologue( context ); // Start operation. sdRef = useMainConnection ? context->mainRef : kBadDNSServiceRef; err = DNSServiceQueryRecord( &sdRef, context->flags, context->ifIndex, context->recordName, context->recordType, kDNSServiceClass_IN, QueryRecordCallback, context ); require_noerr( err, exit ); context->opRef = sdRef; if( !useMainConnection ) { err = DNSServiceSetDispatchQueue( context->opRef, dispatch_get_main_queue() ); require_noerr( err, exit ); } // Set time limit. if( context->timeLimitSecs > 0 ) { dispatch_after_f( dispatch_time_seconds( context->timeLimitSecs ), dispatch_get_main_queue(), kExitReason_TimeLimit, Exit ); } dispatch_main(); exit: dispatch_source_forget( &signalSource ); if( context ) QueryRecordContextFree( context ); if( err ) exit( 1 ); } //=========================================================================================================================== // PortMappingCmd //=========================================================================================================================== typedef struct { DNSServiceRef mainRef; // Main sdRef for shared connection. DNSServiceRef opRef; // sdRef for the DNSServiceNATPortMappingCreate operation. DNSServiceFlags flags; // Flags for DNSServiceNATPortMappingCreate operation. uint32_t ifIndex; // Interface index argument for DNSServiceNATPortMappingCreate operation. DNSServiceProtocol protocols; // Protocols argument for DNSServiceNATPortMappingCreate operation. uint32_t ttl; // TTL argument for DNSServiceNATPortMappingCreate operation. uint16_t internalPort; // Internal port argument for DNSServiceNATPortMappingCreate operation. uint16_t externalPort; // External port argument for DNSServiceNATPortMappingCreate operation. Boolean printedHeader; // True if results header was printed. } PortMappingContext; static void PortMappingPrintPrologue( const PortMappingContext *inContext ); static void PortMappingContextFree( PortMappingContext *inContext ); static void DNSSD_API PortMappingCallback( DNSServiceRef inSDRef, DNSServiceFlags inFlags, uint32_t inInterfaceIndex, DNSServiceErrorType inError, uint32_t inExternalIPv4Address, DNSServiceProtocol inProtocol, uint16_t inInternalPort, uint16_t inExternalPort, uint32_t inTTL, void * inContext ); static void PortMappingCmd( void ) { OSStatus err; PortMappingContext * context = NULL; DNSServiceRef sdRef; dispatch_source_t signalSource = NULL; int useMainConnection; // Set up SIGINT handler. signal( SIGINT, SIG_IGN ); err = DispatchSignalSourceCreate( SIGINT, dispatch_get_main_queue(), Exit, kExitReason_SIGINT, &signalSource ); require_noerr( err, exit ); dispatch_resume( signalSource ); // Create context. context = (PortMappingContext *) calloc( 1, sizeof( *context ) ); require_action( context, exit, err = kNoMemoryErr ); // Check command parameters. if( ( gPortMapping_InternalPort < 0 ) || ( gPortMapping_InternalPort > UINT16_MAX ) ) { FPrintF( stderr, "Internal port number %d is out-of-range.\n", gPortMapping_InternalPort ); err = kParamErr; goto exit; } if( ( gPortMapping_ExternalPort < 0 ) || ( gPortMapping_ExternalPort > UINT16_MAX ) ) { FPrintF( stderr, "External port number %d is out-of-range.\n", gPortMapping_ExternalPort ); err = kParamErr; goto exit; } // Create main connection. if( gConnectionOpt ) { err = CreateConnectionFromArgString( gConnectionOpt, dispatch_get_main_queue(), &context->mainRef, NULL ); require_noerr_quiet( err, exit ); useMainConnection = true; } else { useMainConnection = false; } // Get flags. context->flags = GetDNSSDFlagsFromOpts(); if( useMainConnection ) context->flags |= kDNSServiceFlagsShareConnection; // Get interface index. err = InterfaceIndexFromArgString( gInterface, &context->ifIndex ); require_noerr_quiet( err, exit ); // Set remaining parameters. if( gPortMapping_ProtocolTCP ) context->protocols |= kDNSServiceProtocol_TCP; if( gPortMapping_ProtocolUDP ) context->protocols |= kDNSServiceProtocol_UDP; context->ttl = (uint32_t) gPortMapping_TTL; context->internalPort = (uint16_t) gPortMapping_InternalPort; context->externalPort = (uint16_t) gPortMapping_ExternalPort; // Print prologue. PortMappingPrintPrologue( context ); // Start operation. sdRef = useMainConnection ? context->mainRef : kBadDNSServiceRef; err = DNSServiceNATPortMappingCreate( &sdRef, context->flags, context->ifIndex, context->protocols, htons( context->internalPort ), htons( context->externalPort ), context->ttl, PortMappingCallback, context ); require_noerr( err, exit ); context->opRef = sdRef; if( !useMainConnection ) { err = DNSServiceSetDispatchQueue( context->opRef, dispatch_get_main_queue() ); require_noerr( err, exit ); } dispatch_main(); exit: dispatch_source_forget( &signalSource ); if( context ) PortMappingContextFree( context ); if( err ) exit( 1 ); } //=========================================================================================================================== // PortMappingPrintPrologue //=========================================================================================================================== static void PortMappingPrintPrologue( const PortMappingContext *inContext ) { char ifName[ kInterfaceNameBufLen ]; InterfaceIndexToName( inContext->ifIndex, ifName ); FPrintF( stdout, "Flags: %#{flags}\n", inContext->flags, kDNSServiceFlagsDescriptors ); FPrintF( stdout, "Interface: %d (%s)\n", (int32_t) inContext->ifIndex, ifName ); FPrintF( stdout, "Protocols: %#{flags}\n", inContext->protocols, kDNSServiceProtocolDescriptors ); FPrintF( stdout, "Internal Port: %u\n", inContext->internalPort ); FPrintF( stdout, "External Port: %u\n", inContext->externalPort ); FPrintF( stdout, "TTL: %u%?s\n", inContext->ttl, !inContext->ttl, " (system will use a default value.)" ); FPrintF( stdout, "Start time: %{du:time}\n", NULL ); FPrintF( stdout, "---\n" ); } //=========================================================================================================================== // PortMappingContextFree //=========================================================================================================================== static void PortMappingContextFree( PortMappingContext *inContext ) { DNSServiceForget( &inContext->opRef ); DNSServiceForget( &inContext->mainRef ); free( inContext ); } //=========================================================================================================================== // PortMappingCallback //=========================================================================================================================== static void DNSSD_API PortMappingCallback( DNSServiceRef inSDRef, DNSServiceFlags inFlags, uint32_t inInterfaceIndex, DNSServiceErrorType inError, uint32_t inExternalIPv4Address, DNSServiceProtocol inProtocol, uint16_t inInternalPort, uint16_t inExternalPort, uint32_t inTTL, void * inContext ) { PortMappingContext * const context = (PortMappingContext *) inContext; struct timeval now; char errorStr[ 128 ]; Unused( inSDRef ); Unused( inFlags ); gettimeofday( &now, NULL ); if( inError ) SNPrintF( errorStr, sizeof( errorStr ), " (error: %#m)", inError ); if( !context->printedHeader ) { FPrintF( stdout, "%-26s IF %7s %15s %7s %6s Protocol\n", "Timestamp", "IntPort", "ExtAddr", "ExtPort", "TTL" ); context->printedHeader = true; } FPrintF( stdout, "%{du:time} %2u %7u %15.4a %7u %6u %#{flags}%?s\n", &now, inInterfaceIndex, ntohs( inInternalPort), &inExternalIPv4Address, ntohs( inExternalPort ), inTTL, inProtocol, kDNSServiceProtocolDescriptors, inError, errorStr ); } #if( TARGET_OS_DARWIN ) //=========================================================================================================================== // RegisterKACmd //=========================================================================================================================== typedef struct { dispatch_queue_t queue; // Serial queue for command's events. dispatch_semaphore_t doneSem; // Semaphore to signal when underlying command operation is done. sockaddr_ip local; // Connection's local IP address and port. sockaddr_ip remote; // Connection's remote IP address and port. DNSServiceFlags flags; // Flags to pass to DNSServiceSleepKeepalive_sockaddr(). unsigned int timeout; // Timeout to pass to DNSServiceSleepKeepalive_sockaddr(). DNSServiceRef keepalive; // DNSServiceSleepKeepalive_sockaddr operation. dispatch_source_t sourceSigInt; // Dispatch source for SIGINT. dispatch_source_t sourceSigTerm; // Dispatch source for SIGTERM. OSStatus error; // Command's error. } RegisterKACmdContext; static void _RegisterKACmdFree( RegisterKACmdContext *inCmd ); static void _RegisterKACmdStart( void *inContext ); static void RegisterKACmd( void ) { OSStatus err; RegisterKACmdContext * cmd = NULL; cmd = (RegisterKACmdContext *) calloc( 1, sizeof( *cmd ) ); require_action( cmd, exit, err = kNoMemoryErr ); err = SockAddrFromArgString( gRegisterKA_LocalAddress, "local IP address", &cmd->local ); require_noerr_quiet( err, exit ); err = SockAddrFromArgString( gRegisterKA_RemoteAddress, "remote IP address", &cmd->remote ); require_noerr_quiet( err, exit ); err = CheckIntegerArgument( gRegisterKA_Timeout, "timeout", 0, INT_MAX ); require_noerr_quiet( err, exit ); cmd->flags = GetDNSSDFlagsFromOpts(); cmd->timeout = (unsigned int) gRegisterKA_Timeout; // Start command. cmd->queue = dispatch_queue_create( "com.apple.dnssdutil.registerka-command", DISPATCH_QUEUE_SERIAL ); require_action( cmd->queue, exit, err = kNoResourcesErr ); cmd->doneSem = dispatch_semaphore_create( 0 ); require_action( cmd->doneSem, exit, err = kNoResourcesErr ); dispatch_async_f( cmd->queue, cmd, _RegisterKACmdStart ); dispatch_semaphore_wait( cmd->doneSem, DISPATCH_TIME_FOREVER ); if( cmd->error ) err = cmd->error; FPrintF( stdout, "---\n" ); FPrintF( stdout, "End time: %{du:time}\n", NULL ); exit: if( cmd ) _RegisterKACmdFree( cmd ); gExitCode = err ? 1 : 0; } //=========================================================================================================================== static void _RegisterKACmdFree( RegisterKACmdContext *inCmd ) { check( !inCmd->keepalive ); check( !inCmd->sourceSigInt ); check( !inCmd->sourceSigTerm ); dispatch_forget( &inCmd->queue ); dispatch_forget( &inCmd->doneSem ); free( inCmd ); } //=========================================================================================================================== static void _RegisterKACmdStop( RegisterKACmdContext *inCmd, OSStatus inError ); static void _RegisterKACmdSignalHandler( void *inContext ); static void DNSSD_API _RegisterKACmdKeepaliveCallback( DNSServiceRef inSDRef, DNSServiceErrorType inError, void *inCtx ); static void _RegisterKACmdStart( void *inContext ) { OSStatus err; RegisterKACmdContext * const cmd = (RegisterKACmdContext *) inContext; signal( SIGINT, SIG_IGN ); err = DispatchSignalSourceCreate( SIGINT, cmd->queue, _RegisterKACmdSignalHandler, cmd, &cmd->sourceSigInt ); require_noerr( err, exit ); dispatch_resume( cmd->sourceSigInt ); signal( SIGTERM, SIG_IGN ); err = DispatchSignalSourceCreate( SIGTERM, cmd->queue, _RegisterKACmdSignalHandler, cmd, &cmd->sourceSigTerm ); require_noerr( err, exit ); dispatch_resume( cmd->sourceSigTerm ); FPrintF( stdout, "Flags: %#{flags}\n", cmd->flags, kDNSServiceFlagsDescriptors ); FPrintF( stdout, "Local: %##a\n", &cmd->local.sa ); FPrintF( stdout, "Remote: %##a\n", &cmd->remote.sa ); FPrintF( stdout, "Timeout: %u\n", cmd->timeout ); FPrintF( stdout, "Start time: %{du:time}\n", NULL ); FPrintF( stdout, "---\n" ); err = DNSServiceSleepKeepalive_sockaddr( &cmd->keepalive, cmd->flags, &cmd->local.sa, &cmd->remote.sa, cmd->timeout, _RegisterKACmdKeepaliveCallback, cmd ); require_noerr( err, exit ); err = DNSServiceSetDispatchQueue( cmd->keepalive, cmd->queue ); require_noerr( err, exit ); exit: if( err ) _RegisterKACmdStop( cmd, err ); } //=========================================================================================================================== static void _RegisterKACmdStop( RegisterKACmdContext *inCmd, OSStatus inError ) { if( !inCmd->error ) inCmd->error = inError; DNSServiceForget( &inCmd->keepalive ); dispatch_source_forget( &inCmd->sourceSigInt ); dispatch_source_forget( &inCmd->sourceSigTerm ); dispatch_semaphore_signal( inCmd->doneSem ); } //=========================================================================================================================== static void _RegisterKACmdSignalHandler( void *inContext ) { RegisterKACmdContext * const cmd = (RegisterKACmdContext *) inContext; _RegisterKACmdStop( cmd, kNoErr ); } //=========================================================================================================================== static void DNSSD_API _RegisterKACmdKeepaliveCallback( DNSServiceRef inSDRef, DNSServiceErrorType inError, void *inCtx ) { RegisterKACmdContext * const cmd = (RegisterKACmdContext *) inCtx; Unused( inSDRef ); FPrintF( stdout, "%{du:time} Record registration result: %#m\n", NULL, inError ); if( !cmd->error ) cmd->error = inError; } #endif // TARGET_OS_DARWIN //=========================================================================================================================== // BrowseAllCmd //=========================================================================================================================== typedef struct BrowseAllConnection BrowseAllConnection; typedef struct { 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. Boolean validateResults; // Validate results. } BrowseAllContext; struct BrowseAllConnection { BrowseAllConnection * next; // Next connection object in list. sockaddr_ip sip; // IPv4 or IPv6 address to connect to. uint16_t port; // TCP port to connect to. AsyncConnectionRef asyncCnx; // AsyncConnection object to handle the actual connection. OSStatus status; // Status of connection. NoErr means connection succeeded. CFTimeInterval connectTimeSecs; // Time it took to connect in seconds. int32_t refCount; // This object's reference count. BrowseAllContext * context; // Back pointer to parent context. }; static void _BrowseAllContextFree( BrowseAllContext *inContext ); static void _BrowseAllServiceBrowserCallback( ServiceBrowserResults *inResults, OSStatus inError, void *inContext ); static OSStatus _BrowseAllConnectionCreate( const struct sockaddr * inSockAddr, uint16_t inPort, BrowseAllContext * inContext, 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 ); 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 parameters. if( gBrowseAll_BrowseTimeSecs <= 0 ) { FPrintF( stdout, "Invalid browse time: %d seconds.\n", gBrowseAll_BrowseTimeSecs ); err = kParamErr; goto exit; } context = (BrowseAllContext *) calloc( 1, sizeof( *context ) ); require_action( context, exit, err = kNoMemoryErr ); context->connectionTimeoutSecs = gBrowseAll_ConnectTimeout; context->validateResults = gBrowseAll_ValidateResults ? true : false; #if( TARGET_OS_POSIX ) // Increase the open file descriptor limit for connection sockets. if( context->connectionTimeoutSecs > 0 ) { struct rlimit fdLimits; err = getrlimit( RLIMIT_NOFILE, &fdLimits ); err = map_global_noerr_errno( err ); require_noerr( err, exit ); if( fdLimits.rlim_cur < 4096 ) { fdLimits.rlim_cur = 4096; err = setrlimit( RLIMIT_NOFILE, &fdLimits ); err = map_global_noerr_errno( err ); require_noerr( err, exit ); } } #endif // Get interface index. err = InterfaceIndexFromArgString( gInterface, &ifIndex ); require_noerr_quiet( err, exit ); #if( TARGET_OS_IOS ) // Check for potential issues. if( context->validateResults && os_feature_enabled( mDNSResponder, revoke_media_sessions ) ) { FPrintF( stderr, "%s" "Warning: --validate may not work as expected when media revocation is enabled.\n" " Use 'ffctl mDNSResponder/revoke_media_sessions=off' to disable.\n" "%s", _StdErrIsTTY() ? kANSIRed : "", _StdErrIsTTY() ? kANSINormal : "" ); } #endif // Print prologue. FPrintF( stdout, "Interface: %d (%s)\n", (int32_t) ifIndex, InterfaceIndexToName( ifIndex, ifName ) ); FPrintF( stdout, "Service types: "); if( gBrowseAll_ServiceTypesCount > 0 ) { FPrintF( stdout, "%s", gBrowseAll_ServiceTypes[ 0 ] ); for( i = 1; i < gBrowseAll_ServiceTypesCount; ++i ) { FPrintF( stdout, ", %s", gBrowseAll_ServiceTypes[ i ] ); } FPrintF( stdout, "\n" ); } else { 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", YesNoStr( gDNSSDFlag_IncludeAWDL ) ); FPrintF( stdout, "New GAI: %s\n", YesNoStr( gBrowseAll_UseNewGAI ) ); FPrintF( stdout, "Validate results: %s\n", YesNoStr( context->validateResults ) ); FPrintF( stdout, "Start time: %{du:time}\n", NULL ); FPrintF( stdout, "---\n" ); err = ServiceBrowserCreate( dispatch_get_main_queue(), ifIndex, gBrowseAll_Domain, (unsigned int) gBrowseAll_BrowseTimeSecs, gDNSSDFlag_IncludeAWDL ? true : false, &context->browser ); require_noerr( err, exit ); ServiceBrowserSetUseNewGAI( context->browser, gBrowseAll_UseNewGAI ? true : false ); ServiceBrowserSetValidateResults( context->browser, context->validateResults ); 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 ) _BrowseAllContextFree( context ); if( err ) exit( 1 ); } //=========================================================================================================================== // _BrowseAllContextFree //=========================================================================================================================== static void _BrowseAllContextFree( BrowseAllContext *inContext ) { check( !inContext->browser ); check( !inContext->connectionTimer ); check( !inContext->connectionList ); ForgetServiceBrowserResults( &inContext->results ); free( inContext ); } //=========================================================================================================================== // _BrowseAllServiceBrowserCallback //=========================================================================================================================== #define kDiscardProtocolPort 9 static void _BrowseAllServiceBrowserCallback( ServiceBrowserResults *inResults, OSStatus inError, void *inContext ) { OSStatus err; BrowseAllContext * const context = (BrowseAllContext *) inContext; SBRDomain * domain; SBRServiceType * type; SBRServiceInstance * instance; SBRIPAddress * ipaddr; Unused( inError ); require_action( inResults, exit, err = kUnexpectedErr ); check( !context->results ); context->results = inResults; ServiceBrowserResultsRetain( context->results ); check( context->connectionPendingCount == 0 ); if( context->connectionTimeoutSecs > 0 ) { BrowseAllConnection * connection; BrowseAllConnection ** connectionPtr = &context->connectionList; char destination[ kSockAddrStringMaxSize ]; for( domain = context->results->domainList; domain; domain = domain->next ) { for( type = domain->typeList; type; type = type->next ) { if( !_IsServiceTypeTCP( type->name ) ) continue; for( instance = type->instanceList; instance; instance = instance->next ) { if( instance->port == kDiscardProtocolPort ) continue; for( ipaddr = instance->ipaddrList; ipaddr; ipaddr = ipaddr->next ) { err = _BrowseAllConnectionCreate( &ipaddr->sip.sa, instance->port, context, &connection ); require_noerr( err, exit ); *connectionPtr = connection; connectionPtr = &connection->next; err = SockAddrToString( &ipaddr->sip, kSockAddrStringFlagsNoPort, destination ); check_noerr( err ); if( !err ) { err = AsyncConnection_Connect( &connection->asyncCnx, destination, -instance->port, kAsyncConnectionFlag_P2P, kAsyncConnectionNoTimeout, kSocketBufferSize_DontSet, kSocketBufferSize_DontSet, _BrowseAllConnectionProgress, connection, _BrowseAllConnectionHandler, connection, dispatch_get_main_queue() ); check_noerr( err ); } if( !err ) { _BrowseAllConnectionRetain( connection ); connection->status = kInProgressErr; ++context->connectionPendingCount; } else { connection->status = err; } } } } } } if( context->connectionPendingCount > 0 ) { check( !context->connectionTimer ); err = DispatchTimerCreate( dispatch_time_seconds( context->connectionTimeoutSecs ), DISPATCH_TIME_FOREVER, 100 * kNanosecondsPerMillisecond, NULL, _BrowseAllExit, NULL, context, &context->connectionTimer ); require_noerr( err, exit ); dispatch_resume( context->connectionTimer ); } else { dispatch_async_f( dispatch_get_main_queue(), context, _BrowseAllExit ); } err = kNoErr; exit: ForgetCF( &context->browser ); if( err ) exit( 1 ); } //=========================================================================================================================== // _BrowseAllConnectionCreate //=========================================================================================================================== static OSStatus _BrowseAllConnectionCreate( const struct sockaddr * inSockAddr, uint16_t inPort, BrowseAllContext * inContext, BrowseAllConnection ** outConnection ) { OSStatus err; BrowseAllConnection * obj; obj = (BrowseAllConnection *) calloc( 1, sizeof( *obj ) ); require_action( obj, exit, err = kNoMemoryErr ); obj->refCount = 1; SockAddrCopy( inSockAddr, &obj->sip ); obj->port = inPort; obj->context = inContext; *outConnection = obj; err = kNoErr; exit: return( err ); } //=========================================================================================================================== // _BrowseAllConnectionRetain //=========================================================================================================================== static void _BrowseAllConnectionRetain( BrowseAllConnection *inConnection ) { ++inConnection->refCount; } //=========================================================================================================================== // _BrowseAllConnectionRelease //=========================================================================================================================== static void _BrowseAllConnectionRelease( BrowseAllConnection *inConnection ) { if( --inConnection->refCount == 0 ) free( inConnection ); } //=========================================================================================================================== // _BrowseAllConnectionProgress //=========================================================================================================================== static void _BrowseAllConnectionProgress( int inPhase, const void *inDetails, void *inArg ) { BrowseAllConnection * const connection = (BrowseAllConnection *) inArg; if( inPhase == kAsyncConnectionPhase_Connected ) { const AsyncConnectedInfo * const info = (AsyncConnectedInfo *) inDetails; connection->connectTimeSecs = info->connectSecs; } } //=========================================================================================================================== // _BrowseAllConnectionHandler //=========================================================================================================================== static void _BrowseAllConnectionHandler( SocketRef inSock, OSStatus inError, void *inArg ) { BrowseAllConnection * const connection = (BrowseAllConnection *) inArg; BrowseAllContext * const context = connection->context; connection->status = inError; ForgetSocket( &inSock ); if( context ) { check( context->connectionPendingCount > 0 ); if( ( --context->connectionPendingCount == 0 ) && context->connectionTimer ) { dispatch_source_forget( &context->connectionTimer ); dispatch_async_f( dispatch_get_main_queue(), context, _BrowseAllExit ); } } _BrowseAllConnectionRelease( connection ); } //=========================================================================================================================== // _BrowseAllExit //=========================================================================================================================== #define Indent( X ) ( (X) * 4 ), "" static void _BrowseAllExit( void *inContext ) { BrowseAllContext * const context = (BrowseAllContext *) inContext; SBRDomain * domain; SBRServiceType * type; SBRServiceInstance * instance; SBRIPAddress * ipaddr; char textBuf[ 512 ]; size_t serviceInstanceCount = 0; size_t serviceResolveCount = 0; uint64_t totalDiscoveryTimeUs = 0; uint64_t totalResolveTimeUs = 0; dispatch_source_forget( &context->connectionTimer ); 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 * description; const Boolean serviceTypeIsTCP = _IsServiceTypeTCP( 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( instance = type->instanceList; instance; instance = instance->next ) { char * dst = textBuf; char * const lim = &textBuf[ countof( textBuf ) ]; char ifname[ IF_NAMESIZE + 1 ]; ++serviceInstanceCount; totalDiscoveryTimeUs += instance->discoverTimeUs; 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 ) ) { NetTransportType netType; 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 ) { ++serviceResolveCount; totalResolveTimeUs += instance->resolveTimeUs; 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; const char * const colorEnd = _StdOutIsTTY() ? kANSINormal : ""; FPrintF( stdout, "%*s" "%-##47a %4llu.%03llu ms", Indent( 4 ), &ipaddr->sip.sa, ipaddr->resolveTimeUs / 1000, ipaddr->resolveTimeUs % 1000 ); conn = NULL; if( serviceTypeIsTCP && ( instance->port != kDiscardProtocolPort ) ) { for( connPtr = &context->connectionList; ( conn = *connPtr ) != NULL; connPtr = &conn->next ) { if( ( conn->port == instance->port ) && ( SockAddrCompareAddr( &conn->sip, &ipaddr->sip ) == 0 ) ) break; } if( conn ) { if( conn->status == kInProgressErr ) conn->status = kTimeoutErr; *connPtr = conn->next; conn->context = NULL; AsyncConnection_Forget( &conn->asyncCnx ); } } FPrintF( stdout, " ("); if( context->validateResults ) _PrintValidatedToStdOut( "", ipaddr->validated , ", " ); if( conn ) { if( conn->status == kNoErr ) { FPrintF( stdout, "%sconnected%s in %.3f ms", _StdOutIsTTY() ? kANSIGreen : "", colorEnd, conn->connectTimeSecs * 1000 ); } else { FPrintF( stdout, "%scould not connect%s: %m", _StdOutIsTTY() ? kANSIRed : "", colorEnd, conn->status ); } _BrowseAllConnectionRelease( conn ); } else { FPrintF( stdout, "no connection attempted" ); } 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" ); } } _BrowseAllContextFree( context ); // Additional Stats FPrintF( stdout, "---\n" ); FPrintF( stdout, "Service instance count: %zu\n", serviceInstanceCount ); FPrintF( stdout, "Service resolve count: %zu\n", serviceResolveCount ); if( serviceInstanceCount > 0 ) { const uint64_t averageDiscoveryTimeUs = totalDiscoveryTimeUs / serviceInstanceCount; FPrintF( stdout, "Average discovery time: %llu.%03llu ms\n", averageDiscoveryTimeUs / 1000, averageDiscoveryTimeUs % 1000 ); } if( serviceResolveCount > 0 ) { const uint64_t averageResolveTimeUs = totalResolveTimeUs / serviceResolveCount; FPrintF( stdout, "Average resolve time: %llu.%03llu ms\n", averageResolveTimeUs / 1000, averageResolveTimeUs % 1000 ); } Exit( NULL ); } //=========================================================================================================================== // _IsServiceTypeTCP //=========================================================================================================================== static Boolean _IsServiceTypeTCP( const char *inServiceType ) { OSStatus err; const uint8_t * secondLabel; uint8_t name[ kDomainNameLengthMax ]; err = DomainNameFromString( name, inServiceType, NULL ); if( !err ) { secondLabel = DomainNameGetNextLabel( name ); if( secondLabel && DomainNameEqual( secondLabel, (const uint8_t *) "\x04" "_tcp" ) ) return( true ); } return( false ); } //=========================================================================================================================== // GetNameInfoCmd //=========================================================================================================================== const FlagStringPair kGetNameInfoFlagStringPairs[] = { CaseFlagStringify( NI_NUMERICSCOPE ), CaseFlagStringify( NI_DGRAM ), CaseFlagStringify( NI_NUMERICSERV ), CaseFlagStringify( NI_NAMEREQD ), CaseFlagStringify( NI_NUMERICHOST ), CaseFlagStringify( NI_NOFQDN ), { 0, NULL } }; static void GetNameInfoCmd( void ) { OSStatus err; sockaddr_ip sip; size_t sockAddrLen; unsigned int flags; const FlagStringPair * pair; struct timeval now; char host[ NI_MAXHOST ]; char serv[ NI_MAXSERV ]; err = StringToSockAddr( gGetNameInfo_IPAddress, &sip, sizeof( sip ), &sockAddrLen ); check_noerr( err ); if( err ) { FPrintF( stderr, "Failed to convert \"%s\" to a sockaddr.\n", gGetNameInfo_IPAddress ); goto exit; } flags = 0; if( gGetNameInfoFlag_DGram ) flags |= NI_DGRAM; if( gGetNameInfoFlag_NameReqd ) flags |= NI_NAMEREQD; if( gGetNameInfoFlag_NoFQDN ) flags |= NI_NOFQDN; if( gGetNameInfoFlag_NumericHost ) flags |= NI_NUMERICHOST; if( gGetNameInfoFlag_NumericScope ) flags |= NI_NUMERICSCOPE; if( gGetNameInfoFlag_NumericServ ) flags |= NI_NUMERICSERV; // Print prologue. FPrintF( stdout, "SockAddr: %##a\n", &sip.sa ); FPrintF( stdout, "Flags: 0x%X < ", flags ); for( pair = kGetNameInfoFlagStringPairs; pair->str != NULL; ++pair ) { if( flags & pair->flag ) FPrintF( stdout, "%s ", pair->str ); } FPrintF( stdout, ">\n" ); FPrintF( stdout, "Start time: %{du:time}\n", NULL ); FPrintF( stdout, "---\n" ); // Call getnameinfo(). err = getnameinfo( &sip.sa, (socklen_t) sockAddrLen, host, (socklen_t) sizeof( host ), serv, (socklen_t) sizeof( serv ), (int) flags ); gettimeofday( &now, NULL ); if( err ) { FPrintF( stderr, "Error %d: %s.\n", err, gai_strerror( err ) ); } else { FPrintF( stdout, "host: %s\n", host ); FPrintF( stdout, "serv: %s\n", serv ); } FPrintF( stdout, "---\n" ); FPrintF( stdout, "End time: %{du:time}\n", &now ); exit: gExitCode = err ? 1 : 0; } //=========================================================================================================================== // GetAddrInfoStressCmd //=========================================================================================================================== typedef struct { DNSServiceRef mainRef; DNSServiceRef sdRef; DNSServiceFlags flags; unsigned int interfaceIndex; unsigned int connectionNumber; unsigned int requestCount; unsigned int requestCountMax; unsigned int requestCountLimit; unsigned int durationMinMs; unsigned int durationMaxMs; } GAIStressContext; static void GetAddrInfoStressEvent( void *inContext ); static void DNSSD_API GetAddrInfoStressCallback( DNSServiceRef inSDRef, DNSServiceFlags inFlags, uint32_t inInterfaceIndex, DNSServiceErrorType inError, const char * inHostname, const struct sockaddr * inSockAddr, uint32_t inTTL, void * inContext ); static void GetAddrInfoStressCmd( void ) { OSStatus err; GAIStressContext * context = NULL; int i; DNSServiceFlags flags; uint32_t ifIndex; char ifName[ kInterfaceNameBufLen ]; if( gGAIStress_TestDurationSecs < 0 ) { FPrintF( stdout, "Invalid test duration: %d s.\n", gGAIStress_TestDurationSecs ); err = kParamErr; goto exit; } if( gGAIStress_ConnectionCount <= 0 ) { FPrintF( stdout, "Invalid simultaneous connection count: %d.\n", gGAIStress_ConnectionCount ); err = kParamErr; goto exit; } if( gGAIStress_DurationMinMs <= 0 ) { FPrintF( stdout, "Invalid minimum DNSServiceGetAddrInfo() duration: %d ms.\n", gGAIStress_DurationMinMs ); err = kParamErr; goto exit; } if( gGAIStress_DurationMaxMs <= 0 ) { FPrintF( stdout, "Invalid maximum DNSServiceGetAddrInfo() duration: %d ms.\n", gGAIStress_DurationMaxMs ); err = kParamErr; goto exit; } if( gGAIStress_DurationMinMs > gGAIStress_DurationMaxMs ) { FPrintF( stdout, "Invalid minimum and maximum DNSServiceGetAddrInfo() durations: %d ms and %d ms.\n", gGAIStress_DurationMinMs, gGAIStress_DurationMaxMs ); err = kParamErr; goto exit; } if( gGAIStress_RequestCountMax <= 0 ) { FPrintF( stdout, "Invalid maximum request count: %d.\n", gGAIStress_RequestCountMax ); err = kParamErr; goto exit; } // Set flags. flags = GetDNSSDFlagsFromOpts(); // Set interface index. err = InterfaceIndexFromArgString( gInterface, &ifIndex ); require_noerr_quiet( err, exit ); for( i = 0; i < gGAIStress_ConnectionCount; ++i ) { context = (GAIStressContext *) calloc( 1, sizeof( *context ) ); require_action( context, exit, err = kNoMemoryErr ); context->flags = flags; context->interfaceIndex = ifIndex; context->connectionNumber = (unsigned int)( i + 1 ); context->requestCountMax = (unsigned int) gGAIStress_RequestCountMax; context->durationMinMs = (unsigned int) gGAIStress_DurationMinMs; context->durationMaxMs = (unsigned int) gGAIStress_DurationMaxMs; dispatch_async_f( dispatch_get_main_queue(), context, GetAddrInfoStressEvent ); context = NULL; } if( gGAIStress_TestDurationSecs > 0 ) { dispatch_after_f( dispatch_time_seconds( gGAIStress_TestDurationSecs ), dispatch_get_main_queue(), NULL, Exit ); } FPrintF( stdout, "Flags: %#{flags}\n", flags, kDNSServiceFlagsDescriptors ); FPrintF( stdout, "Interface: %d (%s)\n", (int32_t) ifIndex, InterfaceIndexToName( ifIndex, ifName ) ); FPrintF( stdout, "Test duration: " ); if( gGAIStress_TestDurationSecs == 0 ) { FPrintF( stdout, "∞\n" ); } else { FPrintF( stdout, "%d s\n", gGAIStress_TestDurationSecs ); } FPrintF( stdout, "Connection count: %d\n", gGAIStress_ConnectionCount ); FPrintF( stdout, "Request duration min: %d ms\n", gGAIStress_DurationMinMs ); FPrintF( stdout, "Request duration max: %d ms\n", gGAIStress_DurationMaxMs ); FPrintF( stdout, "Request count max: %d\n", gGAIStress_RequestCountMax ); FPrintF( stdout, "Start time: %{du:time}\n", NULL); FPrintF( stdout, "---\n" ); dispatch_main(); exit: FreeNullSafe( context ); if( err ) exit( 1 ); } //=========================================================================================================================== // GetAddrInfoStressEvent //=========================================================================================================================== #define kStressRandStrLen 5 #define kLowercaseAlphaCharSet "abcdefghijklmnopqrstuvwxyz" static void GetAddrInfoStressEvent( void *inContext ) { GAIStressContext * const context = (GAIStressContext *) inContext; OSStatus err; DNSServiceRef sdRef; unsigned int nextMs; char randomStr[ kStressRandStrLen + 1 ]; char hostname[ kStressRandStrLen + 4 + 1 ]; Boolean isConnectionNew = false; static Boolean printedHeader = false; if( !context->mainRef || ( context->requestCount >= context->requestCountLimit ) ) { DNSServiceForget( &context->mainRef ); context->sdRef = NULL; context->requestCount = 0; context->requestCountLimit = RandomRange( 1, context->requestCountMax ); err = DNSServiceCreateConnection( &context->mainRef ); require_noerr( err, exit ); err = DNSServiceSetDispatchQueue( context->mainRef, dispatch_get_main_queue() ); require_noerr( err, exit ); isConnectionNew = true; } RandomString( kLowercaseAlphaCharSet, sizeof_string( kLowercaseAlphaCharSet ), 2, kStressRandStrLen, randomStr ); SNPrintF( hostname, sizeof( hostname ), "%s.com", randomStr ); nextMs = RandomRange( context->durationMinMs, context->durationMaxMs ); if( !printedHeader ) { FPrintF( stdout, "%-26s Conn Hostname Dur (ms)\n", "Timestamp" ); printedHeader = true; } FPrintF( stdout, "%{du:time} %3u%c %9s %8u\n", NULL, context->connectionNumber, isConnectionNew ? '*': ' ', hostname, nextMs ); DNSServiceForget( &context->sdRef ); sdRef = context->mainRef; err = DNSServiceGetAddrInfo( &sdRef, context->flags | kDNSServiceFlagsShareConnection, context->interfaceIndex, kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6, hostname, GetAddrInfoStressCallback, NULL ); require_noerr( err, exit ); context->sdRef = sdRef; context->requestCount++; dispatch_after_f( dispatch_time_milliseconds( nextMs ), dispatch_get_main_queue(), context, GetAddrInfoStressEvent ); exit: if( err ) exit( 1 ); } //=========================================================================================================================== // GetAddrInfoStressCallback //=========================================================================================================================== static void DNSSD_API GetAddrInfoStressCallback( DNSServiceRef inSDRef, DNSServiceFlags inFlags, uint32_t inInterfaceIndex, DNSServiceErrorType inError, const char * inHostname, const struct sockaddr * inSockAddr, uint32_t inTTL, void * inContext ) { Unused( inSDRef ); Unused( inFlags ); Unused( inInterfaceIndex ); Unused( inError ); Unused( inHostname ); Unused( inSockAddr ); Unused( inTTL ); Unused( inContext ); } //=========================================================================================================================== // DNSQueryCmd //=========================================================================================================================== typedef struct { sockaddr_ip serverAddr; uint64_t sendTicks; uint8_t * msgPtr; size_t msgLen; size_t msgOffset; const char * name; dispatch_source_t readSource; SocketRef sock; int timeLimitSecs; uint16_t queryID; uint16_t type; Boolean haveTCPLen; Boolean useTCP; Boolean printRawRData; // True if RDATA results are not to be formatted. uint8_t msgBuf[ 512 ]; } DNSQueryContext; static void DNSQueryPrintPrologue( const DNSQueryContext *inContext ); static void DNSQueryReadHandler( void *inContext ); static void DNSQueryCancelHandler( void *inContext ); static void DNSQueryCmd( void ) { OSStatus err; DNSQueryContext * context = NULL; uint8_t * msgPtr; size_t msgLen, sendLen; // Check command parameters. if( gDNSQuery_TimeLimitSecs < -1 ) { FPrintF( stdout, "Invalid time limit: %d seconds.\n", gDNSQuery_TimeLimitSecs ); err = kParamErr; goto exit; } if( ( gDNSQuery_Flags < INT16_MIN ) || ( gDNSQuery_Flags > UINT16_MAX ) ) { FPrintF( stdout, "DNS flags-and-codes value is out of the unsigned 16-bit range: 0x%08X.\n", gDNSQuery_Flags ); err = kParamErr; goto exit; } // Create context. context = (DNSQueryContext *) calloc( 1, sizeof( *context ) ); require_action( context, exit, err = kNoMemoryErr ); context->name = gDNSQuery_Name; context->sock = kInvalidSocketRef; context->timeLimitSecs = gDNSQuery_TimeLimitSecs; context->queryID = (uint16_t) Random32(); context->useTCP = gDNSQuery_UseTCP ? true : false; context->printRawRData = gDNSQuery_RawRData ? true : false; #if( TARGET_OS_DARWIN ) if( gDNSQuery_Server ) #endif { err = StringToSockAddr( gDNSQuery_Server, &context->serverAddr, sizeof( context->serverAddr ), NULL ); require_noerr( err, exit ); } #if( TARGET_OS_DARWIN ) else { err = GetDefaultDNSServer( &context->serverAddr ); require_noerr( err, exit ); } #endif if( SockAddrGetPort( &context->serverAddr ) == 0 ) SockAddrSetPort( &context->serverAddr, kDNSPort_Do53 ); err = RecordTypeFromArgString( gDNSQuery_Type, &context->type ); require_noerr( err, exit ); // Write query message. check_compile_time_code( sizeof( context->msgBuf ) >= ( 2 + kDNSQueryMessageMaxLen + sizeof( dns_fixed_fields_opt ) ) ); msgPtr = context->useTCP ? &context->msgBuf[ 2 ] : context->msgBuf; err = WriteDNSQueryMessage( msgPtr, context->queryID, (uint16_t) gDNSQuery_Flags, context->name, context->type, kDNSServiceClass_IN, &msgLen ); require_noerr( err, exit ); check( msgLen <= UINT16_MAX ); if( gDNSQuery_DNSSEC ) { DNSHeader * const hdr = (DNSHeader *) msgPtr; dns_fixed_fields_opt * const opt = (dns_fixed_fields_opt *) &msgPtr[ msgLen ]; unsigned int flags; memset( opt, 0, sizeof( *opt ) ); dns_fixed_fields_opt_set_type( opt, kDNSServiceType_OPT ); dns_fixed_fields_opt_set_udp_payload_size( opt, 512 ); dns_fixed_fields_opt_set_extended_flags( opt, kDNSExtendedFlag_DNSSECOK ); flags = DNSHeaderGetFlags( hdr ) | kDNSHeaderFlag_AuthenticData; DNSHeaderSetFlags( hdr, flags ); DNSHeaderSetAdditionalCount( hdr, 1 ); msgLen += sizeof( dns_fixed_fields_opt ); } if( gDNSQuery_CheckingDisabled ) { DNSHeader * const hdr = (DNSHeader *) msgPtr; unsigned int flags; flags = DNSHeaderGetFlags( hdr ) | kDNSHeaderFlag_CheckingDisabled; DNSHeaderSetFlags( hdr, flags ); } if( context->useTCP ) { WriteBig16Typed( context->msgBuf, (uint16_t) msgLen ); sendLen = 2 + msgLen; } else { sendLen = msgLen; } DNSQueryPrintPrologue( context ); if( gDNSQuery_Verbose ) { FPrintF( stdout, "DNS message to send:\n\n%{du:dnsmsg}\n", msgPtr, msgLen ); FPrintF( stdout, "---\n" ); } if( context->useTCP ) { // Create TCP socket. context->sock = socket( context->serverAddr.sa.sa_family, SOCK_STREAM, IPPROTO_TCP ); err = map_socket_creation_errno( context->sock ); require_noerr( err, exit ); err = SocketConnect( context->sock, &context->serverAddr, 5 ); require_noerr( err, exit ); } else { // Create UDP socket. err = UDPClientSocketOpen( AF_UNSPEC, &context->serverAddr, 0, -1, NULL, &context->sock ); require_noerr( err, exit ); } context->sendTicks = UpTicks(); err = SocketWriteAll( context->sock, context->msgBuf, sendLen, 5 ); require_noerr( err, exit ); if( context->timeLimitSecs == 0 ) goto exit; err = DispatchReadSourceCreate( context->sock, NULL, DNSQueryReadHandler, DNSQueryCancelHandler, context, &context->readSource ); require_noerr( err, exit ); dispatch_resume( context->readSource ); if( context->timeLimitSecs > 0 ) { dispatch_after_f( dispatch_time_seconds( context->timeLimitSecs ), dispatch_get_main_queue(), kExitReason_Timeout, Exit ); } dispatch_main(); exit: if( context ) { dispatch_source_forget( &context->readSource ); ForgetSocket( &context->sock ); free( context ); } if( err ) exit( 1 ); } //=========================================================================================================================== // DNSQueryPrintPrologue //=========================================================================================================================== static void DNSQueryPrintPrologue( const DNSQueryContext *inContext ) { const int timeLimitSecs = inContext->timeLimitSecs; FPrintF( stdout, "Name: %s\n", inContext->name ); FPrintF( stdout, "Type: %s (%u)\n", RecordTypeToString( inContext->type ), inContext->type ); FPrintF( stdout, "Server: %##a\n", &inContext->serverAddr ); FPrintF( stdout, "Transport: %s\n", inContext->useTCP ? "TCP" : "UDP" ); FPrintF( stdout, "Time limit: " ); if( timeLimitSecs >= 0 ) FPrintF( stdout, "%d second%?c\n", timeLimitSecs, timeLimitSecs != 1, 's' ); else FPrintF( stdout, "∞\n" ); FPrintF( stdout, "Start time: %{du:time}\n", NULL ); FPrintF( stdout, "---\n" ); } //=========================================================================================================================== // DNSQueryReadHandler //=========================================================================================================================== static void DNSQueryReadHandler( void *inContext ) { OSStatus err; struct timeval now; const uint64_t nowTicks = UpTicks(); DNSQueryContext * const context = (DNSQueryContext *) inContext; gettimeofday( &now, NULL ); if( context->useTCP ) { if( !context->haveTCPLen ) { err = SocketReadData( context->sock, &context->msgBuf, 2, &context->msgOffset ); if( err == EWOULDBLOCK ) { err = kNoErr; goto exit; } require_noerr( err, exit ); context->msgOffset = 0; context->msgLen = ReadBig16( context->msgBuf ); context->haveTCPLen = true; if( context->msgLen <= sizeof( context->msgBuf ) ) { context->msgPtr = context->msgBuf; } else { context->msgPtr = (uint8_t *) malloc( context->msgLen ); require_action( context->msgPtr, exit, err = kNoMemoryErr ); } } err = SocketReadData( context->sock, context->msgPtr, context->msgLen, &context->msgOffset ); if( err == EWOULDBLOCK ) { err = kNoErr; goto exit; } require_noerr( err, exit ); context->msgOffset = 0; context->haveTCPLen = false; } else { sockaddr_ip fromAddr; context->msgPtr = context->msgBuf; err = SocketRecvFrom( context->sock, context->msgPtr, sizeof( context->msgBuf ), &context->msgLen, &fromAddr, sizeof( fromAddr ), NULL, NULL, NULL, NULL ); require_noerr( err, exit ); check( SockAddrCompareAddr( &fromAddr, &context->serverAddr ) == 0 ); } FPrintF( stdout, "Receive time: %{du:time}\n", &now ); FPrintF( stdout, "Source: %##a\n", &context->serverAddr ); FPrintF( stdout, "Message size: %zu\n", context->msgLen ); FPrintF( stdout, "RTT: %llu ms\n\n", UpTicksToMilliseconds( nowTicks - context->sendTicks ) ); if( context->printRawRData ) FPrintF( stdout, "%{du:rdnsmsg}\n", context->msgPtr, context->msgLen ); else FPrintF( stdout, "%{du:dnsmsg}\n", context->msgPtr, context->msgLen ); if( ( context->msgLen >= kDNSHeaderLength ) && ( DNSHeaderGetID( (DNSHeader *) context->msgPtr ) == context->queryID ) ) { Exit( kExitReason_ReceivedResponse ); } exit: if( err ) dispatch_source_forget( &context->readSource ); } //=========================================================================================================================== // DNSQueryCancelHandler //=========================================================================================================================== static void DNSQueryCancelHandler( void *inContext ) { DNSQueryContext * const context = (DNSQueryContext *) inContext; check( !context->readSource ); ForgetSocket( &context->sock ); if( context->msgPtr != context->msgBuf ) ForgetMem( &context->msgPtr ); free( context ); dispatch_async_f( dispatch_get_main_queue(), NULL, Exit ); } #if( DNSSDUTIL_INCLUDE_DNSCRYPT ) //=========================================================================================================================== // DNSCryptCmd //=========================================================================================================================== #define kDNSCryptPort 443 #define kDNSCryptMinPadLength 8 #define kDNSCryptMaxPadLength 256 #define kDNSCryptBlockSize 64 #define kDNSCryptCertMinimumLength 124 #define kDNSCryptClientMagicLength 8 #define kDNSCryptResolverMagicLength 8 #define kDNSCryptHalfNonceLength 12 #define kDNSCryptCertMagicLength 4 check_compile_time( ( kDNSCryptHalfNonceLength * 2 ) == crypto_box_NONCEBYTES ); static const uint8_t kDNSCryptCertMagic[ kDNSCryptCertMagicLength ] = { 'D', 'N', 'S', 'C' }; static const uint8_t kDNSCryptResolverMagic[ kDNSCryptResolverMagicLength ] = { 0x72, 0x36, 0x66, 0x6e, 0x76, 0x57, 0x6a, 0x38 }; typedef struct { uint8_t certMagic[ kDNSCryptCertMagicLength ]; uint8_t esVersion[ 2 ]; uint8_t minorVersion[ 2 ]; uint8_t signature[ crypto_sign_BYTES ]; uint8_t publicKey[ crypto_box_PUBLICKEYBYTES ]; uint8_t clientMagic[ kDNSCryptClientMagicLength ]; uint8_t serial[ 4 ]; uint8_t startTime[ 4 ]; uint8_t endTime[ 4 ]; uint8_t extensions[ 1 ]; // Variably-sized extension data. } DNSCryptCert; check_compile_time( offsetof( DNSCryptCert, extensions ) == kDNSCryptCertMinimumLength ); typedef struct { uint8_t clientMagic[ kDNSCryptClientMagicLength ]; uint8_t clientPublicKey[ crypto_box_PUBLICKEYBYTES ]; uint8_t clientNonce[ kDNSCryptHalfNonceLength ]; uint8_t poly1305MAC[ 16 ]; } DNSCryptQueryHeader; check_compile_time( sizeof( DNSCryptQueryHeader ) == 68 ); check_compile_time( sizeof( DNSCryptQueryHeader ) >= crypto_box_ZEROBYTES ); check_compile_time( ( sizeof( DNSCryptQueryHeader ) - crypto_box_ZEROBYTES + crypto_box_BOXZEROBYTES ) == offsetof( DNSCryptQueryHeader, poly1305MAC ) ); typedef struct { uint8_t resolverMagic[ kDNSCryptResolverMagicLength ]; uint8_t clientNonce[ kDNSCryptHalfNonceLength ]; uint8_t resolverNonce[ kDNSCryptHalfNonceLength ]; uint8_t poly1305MAC[ 16 ]; } DNSCryptResponseHeader; check_compile_time( sizeof( DNSCryptResponseHeader ) == 48 ); check_compile_time( offsetof( DNSCryptResponseHeader, poly1305MAC ) >= crypto_box_BOXZEROBYTES ); check_compile_time( ( offsetof( DNSCryptResponseHeader, poly1305MAC ) - crypto_box_BOXZEROBYTES + crypto_box_ZEROBYTES ) == sizeof( DNSCryptResponseHeader ) ); typedef struct { sockaddr_ip serverAddr; uint64_t sendTicks; const char * providerName; const char * qname; const uint8_t * certPtr; size_t certLen; dispatch_source_t readSource; size_t msgLen; int timeLimitSecs; uint16_t queryID; uint16_t qtype; Boolean printRawRData; uint8_t serverPublicSignKey[ crypto_sign_PUBLICKEYBYTES ]; uint8_t serverPublicKey[ crypto_box_PUBLICKEYBYTES ]; uint8_t clientPublicKey[ crypto_box_PUBLICKEYBYTES ]; uint8_t clientSecretKey[ crypto_box_SECRETKEYBYTES ]; uint8_t clientMagic[ kDNSCryptClientMagicLength ]; uint8_t clientNonce[ kDNSCryptHalfNonceLength ]; uint8_t nmKey[ crypto_box_BEFORENMBYTES ]; uint8_t msgBuf[ 512 ]; } DNSCryptContext; static void DNSCryptReceiveCertHandler( void *inContext ); static void DNSCryptReceiveResponseHandler( void *inContext ); static void DNSCryptProceed( void *inContext ); static OSStatus DNSCryptProcessCert( DNSCryptContext *inContext ); static OSStatus DNSCryptBuildQuery( DNSCryptContext *inContext ); static OSStatus DNSCryptSendQuery( DNSCryptContext *inContext ); static void DNSCryptPrintCertificate( const DNSCryptCert *inCert, size_t inLen ); static void DNSCryptCmd( void ) { OSStatus err; DNSCryptContext * context = NULL; size_t writtenBytes; size_t totalBytes; SocketContext * sockCtx; SocketRef sock = kInvalidSocketRef; const char * ptr; // Check command parameters. if( gDNSCrypt_TimeLimitSecs < -1 ) { FPrintF( stdout, "Invalid time limit: %d seconds.\n", gDNSCrypt_TimeLimitSecs ); err = kParamErr; goto exit; } // Create context. context = (DNSCryptContext *) calloc( 1, sizeof( *context ) ); require_action( context, exit, err = kNoMemoryErr ); context->providerName = gDNSCrypt_ProviderName; context->qname = gDNSCrypt_Name; context->timeLimitSecs = gDNSCrypt_TimeLimitSecs; context->printRawRData = gDNSCrypt_RawRData ? true : false; err = crypto_box_keypair( context->clientPublicKey, context->clientSecretKey ); require_noerr( err, exit ); err = HexToData( gDNSCrypt_ProviderKey, kSizeCString, kHexToData_DefaultFlags, context->serverPublicSignKey, sizeof( context->serverPublicSignKey ), &writtenBytes, &totalBytes, &ptr ); if( err || ( *ptr != '\0' ) ) { FPrintF( stderr, "Failed to parse public signing key hex string (%s).\n", gDNSCrypt_ProviderKey ); goto exit; } else if( totalBytes != sizeof( context->serverPublicSignKey ) ) { FPrintF( stderr, "Public signing key contains incorrect number of hex bytes (%zu != %zu)\n", totalBytes, sizeof( context->serverPublicSignKey ) ); err = kSizeErr; goto exit; } check( writtenBytes == totalBytes ); err = StringToSockAddr( gDNSCrypt_Server, &context->serverAddr, sizeof( context->serverAddr ), NULL ); require_noerr( err, exit ); if( SockAddrGetPort( &context->serverAddr ) == 0 ) SockAddrSetPort( &context->serverAddr, kDNSCryptPort ); err = RecordTypeFromArgString( gDNSCrypt_Type, &context->qtype ); require_noerr( err, exit ); // Write query message. context->queryID = (uint16_t) Random32(); err = WriteDNSQueryMessage( context->msgBuf, context->queryID, kDNSHeaderFlag_RecursionDesired, context->providerName, kDNSServiceType_TXT, kDNSServiceClass_IN, &context->msgLen ); require_noerr( err, exit ); // Create UDP socket. err = UDPClientSocketOpen( AF_UNSPEC, &context->serverAddr, 0, -1, NULL, &sock ); require_noerr( err, exit ); // Send DNS query. context->sendTicks = UpTicks(); err = SocketWriteAll( sock, context->msgBuf, context->msgLen, 5 ); require_noerr( err, exit ); sockCtx = SocketContextCreate( sock, context, &err ); require_noerr( err, exit ); sock = kInvalidSocketRef; err = DispatchReadSourceCreate( sockCtx->sock, NULL, DNSCryptReceiveCertHandler, SocketContextCancelHandler, sockCtx, &context->readSource ); if( err ) ForgetSocketContext( &sockCtx ); require_noerr( err, exit ); dispatch_resume( context->readSource ); if( context->timeLimitSecs > 0 ) { dispatch_after_f( dispatch_time_seconds( context->timeLimitSecs ), dispatch_get_main_queue(), kExitReason_Timeout, Exit ); } dispatch_main(); exit: if( context ) free( context ); ForgetSocket( &sock ); if( err ) exit( 1 ); } //=========================================================================================================================== // DNSCryptReceiveCertHandler //=========================================================================================================================== static void DNSCryptReceiveCertHandler( void *inContext ) { OSStatus err; struct timeval now; const uint64_t nowTicks = UpTicks(); SocketContext * const sockCtx = (SocketContext *) inContext; DNSCryptContext * const context = (DNSCryptContext *) sockCtx->userContext; const DNSHeader * hdr; sockaddr_ip fromAddr; const uint8_t * ptr; const uint8_t * txtPtr; size_t txtLen; unsigned int answerCount, i; uint8_t targetName[ kDomainNameLengthMax ]; gettimeofday( &now, NULL ); dispatch_source_forget( &context->readSource ); err = SocketRecvFrom( sockCtx->sock, context->msgBuf, sizeof( context->msgBuf ), &context->msgLen, &fromAddr, sizeof( fromAddr ), NULL, NULL, NULL, NULL ); require_noerr( err, exit ); check( SockAddrCompareAddr( &fromAddr, &context->serverAddr ) == 0 ); FPrintF( stdout, "Receive time: %{du:time}\n", &now ); FPrintF( stdout, "Source: %##a\n", &context->serverAddr ); FPrintF( stdout, "Message size: %zu\n", context->msgLen ); FPrintF( stdout, "RTT: %llu ms\n\n", UpTicksToMilliseconds( nowTicks - context->sendTicks ) ); if( context->printRawRData ) FPrintF( stdout, "%{du:rdnsmsg}\n", context->msgBuf, context->msgLen ); else FPrintF( stdout, "%{du:dnsmsg}\n", context->msgBuf, context->msgLen ); require_action_quiet( context->msgLen >= kDNSHeaderLength, exit, err = kSizeErr ); hdr = (DNSHeader *) context->msgBuf; require_action_quiet( DNSHeaderGetID( hdr ) == context->queryID, exit, err = kMismatchErr ); err = DNSMessageGetAnswerSection( context->msgBuf, context->msgLen, &ptr ); require_noerr( err, exit ); err = DomainNameFromString( targetName, context->providerName, NULL ); require_noerr( err, exit ); answerCount = DNSHeaderGetAnswerCount( hdr ); for( i = 0; i < answerCount; ++i ) { uint16_t type; uint16_t class; uint8_t name[ kDomainNameLengthMax ]; err = DNSMessageExtractRecord( context->msgBuf, context->msgLen, ptr, name, &type, &class, NULL, &txtPtr, &txtLen, &ptr ); require_noerr( err, exit ); if( ( type == kDNSServiceType_TXT ) && ( class == kDNSServiceClass_IN ) && DomainNameEqual( name, targetName ) ) { break; } } if( txtLen < ( 1 + kDNSCryptCertMinimumLength ) ) { FPrintF( stderr, "TXT record length is too short (%u < %u)\n", txtLen, kDNSCryptCertMinimumLength + 1 ); err = kSizeErr; goto exit; } if( txtPtr[ 0 ] < kDNSCryptCertMinimumLength ) { FPrintF( stderr, "TXT record value length is too short (%u < %u)\n", txtPtr[ 0 ], kDNSCryptCertMinimumLength ); err = kSizeErr; goto exit; } context->certLen = txtPtr[ 0 ]; context->certPtr = &txtPtr[ 1 ]; dispatch_async_f( dispatch_get_main_queue(), context, DNSCryptProceed ); exit: if( err ) Exit( NULL ); } //=========================================================================================================================== // DNSCryptReceiveResponseHandler //=========================================================================================================================== static void DNSCryptReceiveResponseHandler( void *inContext ) { OSStatus err; struct timeval now; const uint64_t nowTicks = UpTicks(); SocketContext * const sockCtx = (SocketContext *) inContext; DNSCryptContext * const context = (DNSCryptContext *) sockCtx->userContext; sockaddr_ip fromAddr; DNSCryptResponseHeader * hdr; const uint8_t * end; uint8_t * ciphertext; uint8_t * plaintext; const uint8_t * response; uint8_t nonce[ crypto_box_NONCEBYTES ]; gettimeofday( &now, NULL ); dispatch_source_forget( &context->readSource ); err = SocketRecvFrom( sockCtx->sock, context->msgBuf, sizeof( context->msgBuf ), &context->msgLen, &fromAddr, sizeof( fromAddr ), NULL, NULL, NULL, NULL ); require_noerr( err, exit ); check( SockAddrCompareAddr( &fromAddr, &context->serverAddr ) == 0 ); FPrintF( stdout, "Receive time: %{du:time}\n", &now ); FPrintF( stdout, "Source: %##a\n", &context->serverAddr ); FPrintF( stdout, "Message size: %zu\n", context->msgLen ); FPrintF( stdout, "RTT: %llu ms\n\n", UpTicksToMilliseconds( nowTicks - context->sendTicks ) ); if( context->msgLen < sizeof( DNSCryptResponseHeader ) ) { FPrintF( stderr, "DNSCrypt response is too short.\n" ); err = kSizeErr; goto exit; } hdr = (DNSCryptResponseHeader *) context->msgBuf; if( memcmp( hdr->resolverMagic, kDNSCryptResolverMagic, kDNSCryptResolverMagicLength ) != 0 ) { FPrintF( stderr, "DNSCrypt response resolver magic %#H != %#H\n", hdr->resolverMagic, kDNSCryptResolverMagicLength, INT_MAX, kDNSCryptResolverMagic, kDNSCryptResolverMagicLength, INT_MAX ); err = kValueErr; goto exit; } if( memcmp( hdr->clientNonce, context->clientNonce, kDNSCryptHalfNonceLength ) != 0 ) { FPrintF( stderr, "DNSCrypt response client nonce mismatch.\n" ); err = kValueErr; goto exit; } memcpy( nonce, hdr->clientNonce, crypto_box_NONCEBYTES ); ciphertext = hdr->poly1305MAC - crypto_box_BOXZEROBYTES; memset( ciphertext, 0, crypto_box_BOXZEROBYTES ); plaintext = (uint8_t *)( hdr + 1 ) - crypto_box_ZEROBYTES; check( plaintext == ciphertext ); end = context->msgBuf + context->msgLen; err = crypto_box_open_afternm( plaintext, ciphertext, (size_t)( end - ciphertext ), nonce, context->nmKey ); require_noerr( err, exit ); response = plaintext + crypto_box_ZEROBYTES; if( context->printRawRData ) FPrintF( stdout, "%{du:rdnsmsg}\n", response, (size_t)( end - response ) ); else FPrintF( stdout, "%{du:dnsmsg}\n", response, (size_t)( end - response ) ); Exit( kExitReason_ReceivedResponse ); exit: if( err ) Exit( NULL ); } //=========================================================================================================================== // DNSCryptProceed //=========================================================================================================================== static void DNSCryptProceed( void *inContext ) { OSStatus err; DNSCryptContext * const context = (DNSCryptContext *) inContext; err = DNSCryptProcessCert( context ); require_noerr_quiet( err, exit ); err = DNSCryptBuildQuery( context ); require_noerr_quiet( err, exit ); err = DNSCryptSendQuery( context ); require_noerr_quiet( err, exit ); exit: if( err ) Exit( NULL ); } //=========================================================================================================================== // DNSCryptProcessCert //=========================================================================================================================== static OSStatus DNSCryptProcessCert( DNSCryptContext *inContext ) { OSStatus err; const DNSCryptCert * const cert = (DNSCryptCert *) inContext->certPtr; const uint8_t * const certEnd = inContext->certPtr + inContext->certLen; struct timeval now; time_t startTimeSecs, endTimeSecs; size_t signedLen; uint8_t * tempBuf; unsigned long long tempLen; DNSCryptPrintCertificate( cert, inContext->certLen ); if( memcmp( cert->certMagic, kDNSCryptCertMagic, kDNSCryptCertMagicLength ) != 0 ) { FPrintF( stderr, "DNSCrypt certificate magic %#H != %#H\n", cert->certMagic, kDNSCryptCertMagicLength, INT_MAX, kDNSCryptCertMagic, kDNSCryptCertMagicLength, INT_MAX ); err = kValueErr; goto exit; } startTimeSecs = (time_t) ReadBig32( cert->startTime ); endTimeSecs = (time_t) ReadBig32( cert->endTime ); gettimeofday( &now, NULL ); if( now.tv_sec < startTimeSecs ) { FPrintF( stderr, "DNSCrypt certificate start time is in the future.\n" ); err = kDateErr; goto exit; } if( now.tv_sec >= endTimeSecs ) { FPrintF( stderr, "DNSCrypt certificate has expired.\n" ); err = kDateErr; goto exit; } signedLen = (size_t)( certEnd - cert->signature ); tempBuf = (uint8_t *) malloc( signedLen ); require_action( tempBuf, exit, err = kNoMemoryErr ); err = crypto_sign_open( tempBuf, &tempLen, cert->signature, signedLen, inContext->serverPublicSignKey ); free( tempBuf ); if( err ) { FPrintF( stderr, "DNSCrypt certificate failed verification.\n" ); err = kAuthenticationErr; goto exit; } memcpy( inContext->serverPublicKey, cert->publicKey, crypto_box_PUBLICKEYBYTES ); memcpy( inContext->clientMagic, cert->clientMagic, kDNSCryptClientMagicLength ); err = crypto_box_beforenm( inContext->nmKey, inContext->serverPublicKey, inContext->clientSecretKey ); require_noerr( err, exit ); inContext->certPtr = NULL; inContext->certLen = 0; inContext->msgLen = 0; exit: return( err ); } //=========================================================================================================================== // DNSCryptBuildQuery //=========================================================================================================================== static OSStatus DNSCryptPadQuery( uint8_t *inMsgPtr, size_t inMsgLen, size_t inMaxLen, size_t *outPaddedLen ); static OSStatus DNSCryptBuildQuery( DNSCryptContext *inContext ) { OSStatus err; DNSCryptQueryHeader * const hdr = (DNSCryptQueryHeader *) inContext->msgBuf; uint8_t * const queryPtr = (uint8_t *)( hdr + 1 ); size_t queryLen; size_t paddedQueryLen; const uint8_t * const msgLimit = inContext->msgBuf + sizeof( inContext->msgBuf ); const uint8_t * padLimit; uint8_t nonce[ crypto_box_NONCEBYTES ]; check_compile_time_code( sizeof( inContext->msgBuf ) >= ( sizeof( DNSCryptQueryHeader ) + kDNSQueryMessageMaxLen ) ); inContext->queryID = (uint16_t) Random32(); err = WriteDNSQueryMessage( queryPtr, inContext->queryID, kDNSHeaderFlag_RecursionDesired, inContext->qname, inContext->qtype, kDNSServiceClass_IN, &queryLen ); require_noerr( err, exit ); padLimit = &queryPtr[ queryLen + kDNSCryptMaxPadLength ]; if( padLimit > msgLimit ) padLimit = msgLimit; err = DNSCryptPadQuery( queryPtr, queryLen, (size_t)( padLimit - queryPtr ), &paddedQueryLen ); require_noerr( err, exit ); memset( queryPtr - crypto_box_ZEROBYTES, 0, crypto_box_ZEROBYTES ); RandomBytes( inContext->clientNonce, kDNSCryptHalfNonceLength ); memcpy( nonce, inContext->clientNonce, kDNSCryptHalfNonceLength ); memset( &nonce[ kDNSCryptHalfNonceLength ], 0, kDNSCryptHalfNonceLength ); err = crypto_box_afternm( queryPtr - crypto_box_ZEROBYTES, queryPtr - crypto_box_ZEROBYTES, paddedQueryLen + crypto_box_ZEROBYTES, nonce, inContext->nmKey ); require_noerr( err, exit ); memcpy( hdr->clientMagic, inContext->clientMagic, kDNSCryptClientMagicLength ); memcpy( hdr->clientPublicKey, inContext->clientPublicKey, crypto_box_PUBLICKEYBYTES ); memcpy( hdr->clientNonce, nonce, kDNSCryptHalfNonceLength ); inContext->msgLen = (size_t)( &queryPtr[ paddedQueryLen ] - inContext->msgBuf ); exit: return( err ); } static OSStatus DNSCryptPadQuery( uint8_t *inMsgPtr, size_t inMsgLen, size_t inMaxLen, size_t *outPaddedLen ) { OSStatus err; size_t paddedLen; require_action_quiet( ( inMsgLen + kDNSCryptMinPadLength ) <= inMaxLen, exit, err = kSizeErr ); paddedLen = inMsgLen + kDNSCryptMinPadLength + arc4random_uniform( (uint32_t)( inMaxLen - ( inMsgLen + kDNSCryptMinPadLength ) + 1 ) ); paddedLen += ( kDNSCryptBlockSize - ( paddedLen % kDNSCryptBlockSize ) ); if( paddedLen > inMaxLen ) paddedLen = inMaxLen; inMsgPtr[ inMsgLen ] = 0x80; memset( &inMsgPtr[ inMsgLen + 1 ], 0, paddedLen - ( inMsgLen + 1 ) ); if( outPaddedLen ) *outPaddedLen = paddedLen; err = kNoErr; exit: return( err ); } //=========================================================================================================================== // DNSCryptSendQuery //=========================================================================================================================== static OSStatus DNSCryptSendQuery( DNSCryptContext *inContext ) { OSStatus err; SocketContext * sockCtx; SocketRef sock = kInvalidSocketRef; check( inContext->msgLen > 0 ); check( !inContext->readSource ); err = UDPClientSocketOpen( AF_UNSPEC, &inContext->serverAddr, 0, -1, NULL, &sock ); require_noerr( err, exit ); inContext->sendTicks = UpTicks(); err = SocketWriteAll( sock, inContext->msgBuf, inContext->msgLen, 5 ); require_noerr( err, exit ); sockCtx = SocketContextCreate( sock, inContext, &err ); require_noerr( err, exit ); sock = kInvalidSocketRef; err = DispatchReadSourceCreate( sockCtx->sock, NULL, DNSCryptReceiveResponseHandler, SocketContextCancelHandler, sockCtx, &inContext->readSource ); if( err ) ForgetSocketContext( &sockCtx ); require_noerr( err, exit ); dispatch_resume( inContext->readSource ); exit: ForgetSocket( &sock ); return( err ); } //=========================================================================================================================== // DNSCryptPrintCertificate //=========================================================================================================================== #define kCertTimeStrBufLen 32 static char * CertTimeStr( time_t inTime, char inBuffer[ kCertTimeStrBufLen ] ); static void DNSCryptPrintCertificate( const DNSCryptCert *inCert, size_t inLen ) { time_t startTime, endTime; int extLen; char timeBuf[ kCertTimeStrBufLen ]; check( inLen >= kDNSCryptCertMinimumLength ); startTime = (time_t) ReadBig32( inCert->startTime ); endTime = (time_t) ReadBig32( inCert->endTime ); FPrintF( stdout, "DNSCrypt certificate (%zu bytes):\n", inLen ); FPrintF( stdout, "Cert Magic: %#H\n", inCert->certMagic, kDNSCryptCertMagicLength, INT_MAX ); FPrintF( stdout, "ES Version: %u\n", ReadBig16( inCert->esVersion ) ); FPrintF( stdout, "Minor Version: %u\n", ReadBig16( inCert->minorVersion ) ); FPrintF( stdout, "Signature: %H\n", inCert->signature, crypto_sign_BYTES / 2, INT_MAX ); FPrintF( stdout, " %H\n", &inCert->signature[ crypto_sign_BYTES / 2 ], crypto_sign_BYTES / 2, INT_MAX ); FPrintF( stdout, "Public Key: %H\n", inCert->publicKey, sizeof( inCert->publicKey ), INT_MAX ); FPrintF( stdout, "Client Magic: %H\n", inCert->clientMagic, kDNSCryptClientMagicLength, INT_MAX ); FPrintF( stdout, "Serial: %u\n", ReadBig32( inCert->serial ) ); FPrintF( stdout, "Start Time: %u (%s)\n", (uint32_t) startTime, CertTimeStr( startTime, timeBuf ) ); FPrintF( stdout, "End Time: %u (%s)\n", (uint32_t) endTime, CertTimeStr( endTime, timeBuf ) ); if( inLen > kDNSCryptCertMinimumLength ) { extLen = (int)( inLen - kDNSCryptCertMinimumLength ); FPrintF( stdout, "Extensions: %.1H\n", inCert->extensions, extLen, extLen ); } FPrintF( stdout, "\n" ); } static char * CertTimeStr( time_t inTime, char inBuffer[ kCertTimeStrBufLen ] ) { struct tm * tm; tm = localtime( &inTime ); if( !tm ) { dlogassert( "localtime() returned a NULL pointer.\n" ); *inBuffer = '\0'; } else { strftime( inBuffer, kCertTimeStrBufLen, "%a %b %d %H:%M:%S %Z %Y", tm ); } return( inBuffer ); } #endif // DNSSDUTIL_INCLUDE_DNSCRYPT //=========================================================================================================================== // MDNSQueryCmd //=========================================================================================================================== typedef struct { const char * qnameStr; // Name (QNAME) of the record being queried as a C string. dispatch_source_t readSourceV4; // Read dispatch source for IPv4 socket. dispatch_source_t readSourceV6; // Read dispatch source for IPv6 socket. int localPort; // The port number to which the sockets are bound. int receiveSecs; // After send, the amount of time to spend receiving. uint32_t ifIndex; // Index of the interface over which to send the query. uint16_t qtype; // The type (QTYPE) of the record being queried. Boolean isQU; // True if the query is QU, i.e., requests unicast responses. Boolean allResponses; // True if all mDNS messages received should be printed. Boolean printRawRData; // True if RDATA should be printed as hexdumps. Boolean useIPv4; // True if the query should be sent via IPv4 multicast. Boolean useIPv6; // True if the query should be sent via IPv6 multicast. char ifName[ IF_NAMESIZE + 1 ]; // Name of the interface over which to send the query. uint8_t qname[ kDomainNameLengthMax ]; // Buffer to hold the QNAME in DNS label format. uint8_t msgBuf[ kMDNSMessageSizeMax ]; // mDNS message buffer. } MDNSQueryContext; static void MDNSQueryPrintPrologue( const MDNSQueryContext *inContext ); static void MDNSQueryReadHandler( void *inContext ); static void MDNSQueryCmd( void ) { OSStatus err; MDNSQueryContext * context; SocketRef sockV4 = kInvalidSocketRef; SocketRef sockV6 = kInvalidSocketRef; ssize_t n; const char * ifname; size_t msgLen; unsigned int sendCount; // Check command parameters. if( gMDNSQuery_ReceiveSecs < -1 ) { FPrintF( stdout, "Invalid receive time value: %d seconds.\n", gMDNSQuery_ReceiveSecs ); err = kParamErr; goto exit; } context = (MDNSQueryContext *) calloc( 1, sizeof( *context ) ); require_action( context, exit, err = kNoMemoryErr ); context->qnameStr = gMDNSQuery_Name; context->receiveSecs = gMDNSQuery_ReceiveSecs; context->isQU = gMDNSQuery_IsQU ? true : false; context->allResponses = gMDNSQuery_AllResponses ? true : false; context->printRawRData = gMDNSQuery_RawRData ? true : false; context->useIPv4 = ( gMDNSQuery_UseIPv4 || !gMDNSQuery_UseIPv6 ) ? true : false; context->useIPv6 = ( gMDNSQuery_UseIPv6 || !gMDNSQuery_UseIPv4 ) ? true : false; err = InterfaceIndexFromArgString( gInterface, &context->ifIndex ); require_noerr_quiet( err, exit ); ifname = if_indextoname( context->ifIndex, context->ifName ); require_action( ifname, exit, err = kNameErr ); err = RecordTypeFromArgString( gMDNSQuery_Type, &context->qtype ); require_noerr( err, exit ); // Set up IPv4 socket. if( context->useIPv4 ) { err = CreateMulticastSocket( GetMDNSMulticastAddrV4(), gMDNSQuery_SourcePort ? gMDNSQuery_SourcePort : ( context->isQU ? context->localPort : kMDNSPort ), ifname, context->ifIndex, !context->isQU, &context->localPort, &sockV4 ); require_noerr( err, exit ); } // Set up IPv6 socket. if( context->useIPv6 ) { err = CreateMulticastSocket( GetMDNSMulticastAddrV6(), gMDNSQuery_SourcePort ? gMDNSQuery_SourcePort : ( context->isQU ? context->localPort : kMDNSPort ), ifname, context->ifIndex, !context->isQU, &context->localPort, &sockV6 ); require_noerr( err, exit ); } // Craft mDNS query message. check_compile_time_code( sizeof( context->msgBuf ) >= kDNSQueryMessageMaxLen ); err = WriteDNSQueryMessage( context->msgBuf, kDefaultMDNSMessageID, kDefaultMDNSQueryFlags, context->qnameStr, context->qtype, context->isQU ? ( kDNSServiceClass_IN | kMDNSClassUnicastResponseBit ) : kDNSServiceClass_IN, &msgLen ); require_noerr( err, exit ); // Print prologue. MDNSQueryPrintPrologue( context ); // Send mDNS query message. sendCount = 0; if( IsValidSocket( sockV4 ) ) { const struct sockaddr * const mcastAddr4 = GetMDNSMulticastAddrV4(); n = sendto( sockV4, context->msgBuf, msgLen, 0, mcastAddr4, SockAddrGetSize( mcastAddr4 ) ); err = map_socket_value_errno( sockV4, n == (ssize_t) msgLen, n ); if( err ) { FPrintF( stderr, "*** Failed to send query on IPv4 socket with error %#m\n", err ); ForgetSocket( &sockV4 ); } else { ++sendCount; } } if( IsValidSocket( sockV6 ) ) { const struct sockaddr * const mcastAddr6 = GetMDNSMulticastAddrV6(); n = sendto( sockV6, context->msgBuf, msgLen, 0, mcastAddr6, SockAddrGetSize( mcastAddr6 ) ); err = map_socket_value_errno( sockV6, n == (ssize_t) msgLen, n ); if( err ) { FPrintF( stderr, "*** Failed to send query on IPv6 socket with error %#m\n", err ); ForgetSocket( &sockV6 ); } else { ++sendCount; } } require_action_quiet( sendCount > 0, exit, err = kUnexpectedErr ); // If there's no wait period after the send, then exit. if( context->receiveSecs == 0 ) goto exit; // Create dispatch read sources for socket(s). if( IsValidSocket( sockV4 ) ) { SocketContext * sockCtx; sockCtx = SocketContextCreate( sockV4, context, &err ); require_noerr( err, exit ); sockV4 = kInvalidSocketRef; err = DispatchReadSourceCreate( sockCtx->sock, NULL, MDNSQueryReadHandler, SocketContextCancelHandler, sockCtx, &context->readSourceV4 ); if( err ) ForgetSocketContext( &sockCtx ); require_noerr( err, exit ); dispatch_resume( context->readSourceV4 ); } if( IsValidSocket( sockV6 ) ) { SocketContext * sockCtx; sockCtx = SocketContextCreate( sockV6, context, &err ); require_noerr( err, exit ); sockV6 = kInvalidSocketRef; err = DispatchReadSourceCreate( sockCtx->sock, NULL, MDNSQueryReadHandler, SocketContextCancelHandler, sockCtx, &context->readSourceV6 ); if( err ) ForgetSocketContext( &sockCtx ); require_noerr( err, exit ); dispatch_resume( context->readSourceV6 ); } if( context->receiveSecs > 0 ) { dispatch_after_f( dispatch_time_seconds( context->receiveSecs ), dispatch_get_main_queue(), kExitReason_Timeout, Exit ); } dispatch_main(); exit: ForgetSocket( &sockV4 ); ForgetSocket( &sockV6 ); exit( 1 ); } //=========================================================================================================================== // 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 //=========================================================================================================================== static void MDNSQueryPrintPrologue( const MDNSQueryContext *inContext ) { const int receiveSecs = inContext->receiveSecs; FPrintF( stdout, "Interface: %d (%s)\n", (int32_t) inContext->ifIndex, inContext->ifName ); FPrintF( stdout, "Name: %s\n", inContext->qnameStr ); FPrintF( stdout, "Type: %s (%u)\n", RecordTypeToString( inContext->qtype ), inContext->qtype ); FPrintF( stdout, "Class: IN (%s)\n", inContext->isQU ? "QU" : "QM" ); FPrintF( stdout, "Local port: %d\n", inContext->localPort ); FPrintF( stdout, "IP protocols: %?s%?s%?s\n", inContext->useIPv4, "IPv4", ( inContext->useIPv4 && inContext->useIPv6 ), ", ", inContext->useIPv6, "IPv6" ); FPrintF( stdout, "Receive duration: " ); if( receiveSecs >= 0 ) FPrintF( stdout, "%d second%?c\n", receiveSecs, receiveSecs != 1, 's' ); else FPrintF( stdout, "∞\n" ); FPrintF( stdout, "Start time: %{du:time}\n", NULL ); } //=========================================================================================================================== // MDNSQueryReadHandler //=========================================================================================================================== static void MDNSQueryReadHandler( void *inContext ) { OSStatus err; struct timeval now; SocketContext * const sockCtx = (SocketContext *) inContext; MDNSQueryContext * const context = (MDNSQueryContext *) sockCtx->userContext; size_t msgLen; sockaddr_ip fromAddr; Boolean foundAnswer = false; gettimeofday( &now, NULL ); err = SocketRecvFrom( sockCtx->sock, context->msgBuf, sizeof( context->msgBuf ), &msgLen, &fromAddr, sizeof( fromAddr ), NULL, NULL, NULL, NULL ); require_noerr( err, exit ); if( !context->allResponses && ( msgLen >= kDNSHeaderLength ) ) { const uint8_t * ptr; const DNSHeader * const hdr = (DNSHeader *) context->msgBuf; unsigned int rrCount, i; uint16_t type, class; uint8_t name[ kDomainNameLengthMax ]; err = DNSMessageGetAnswerSection( context->msgBuf, msgLen, &ptr ); require_noerr( err, exit ); if( context->qname[ 0 ] == 0 ) { err = DomainNameAppendString( context->qname, context->qnameStr, NULL ); require_noerr( err, exit ); } rrCount = DNSHeaderGetAnswerCount( hdr ) + DNSHeaderGetAuthorityCount( hdr ) + DNSHeaderGetAdditionalCount( hdr ); for( i = 0; i < rrCount; ++i ) { err = DNSMessageExtractRecord( context->msgBuf, msgLen, ptr, name, &type, &class, NULL, NULL, NULL, &ptr ); require_noerr( err, exit ); if( ( ( context->qtype == kDNSServiceType_ANY ) || ( type == context->qtype ) ) && DomainNameEqual( name, context->qname ) ) { foundAnswer = true; break; } } } if( context->allResponses || foundAnswer ) { FPrintF( stdout, "---\n" ); FPrintF( stdout, "Receive time: %{du:time}\n", &now ); FPrintF( stdout, "Source: %##a\n", &fromAddr ); FPrintF( stdout, "Message size: %zu\n\n", msgLen ); if( context->printRawRData ) FPrintF( stdout, "%#{du:rdnsmsg}\n", context->msgBuf, msgLen ); else FPrintF( stdout, "%#{du:dnsmsg}\n", context->msgBuf, msgLen ); } exit: if( err ) exit( 1 ); } #if( TARGET_OS_DARWIN ) //=========================================================================================================================== // PIDToUUIDCmd //=========================================================================================================================== static void PIDToUUIDCmd( void ) { OSStatus err; uuid_t uuid; err = mdns_system_pid_to_uuid( gPIDToUUID_PID, uuid ); require_noerr_quiet( err, exit ); FPrintF( stdout, "%#U\n", uuid ); exit: gExitCode = err ? 1 : 0; } #endif //=========================================================================================================================== // DNSServerCommand //=========================================================================================================================== typedef struct { DNSServerRef server; // Reference to the DNS server. dispatch_queue_t queue; // Serial queue for server. sockaddr_ip * addrArray; // Server's addresses. size_t addrCount; // Count of server's addresses. dispatch_source_t sourceSigInt; // Dispatch source for SIGINT. dispatch_source_t sourceSigTerm; // Dispatch source for SIGTERM. const char * domainOverride; // If non-NULL, server is to use this domain instead of d.test. dispatch_group_t group; // Dispatch group to signal when command is done. dispatch_source_t processMonitor; // Process monitor source for process being followed, if any. nw_resolver_config_t secureDNSConfig; // Resolver configuration for DNS over TLS (DoT). mdns_network_policy_t domainPolicy; // Networking policy for matching domains to DoT service. SecIdentityRef secIdentity; // Security identity associated with self-signed DoT certificate. nw_listener_t tlsListener; // TLS listener. mrc_dns_service_registration_t registration; // DNS service registration for Do53. DNSProtocol protocol; // DNS protocol to use, e.g., Do53, DoT, or DoH. pid_t followPID; // PID of process being followed, if any. If it exits, we exit. int32_t refCount; // Object's reference count. OSStatus error; // Error encounted while running server. uint16_t portRequested; // The port that was requested by the user. Boolean loopbackOnly; // True if the server should be bound to the loopback interface. Boolean addedResolver; // True if a resolver entry was added to the system DNS settings. Boolean registerWithSC; // True if Do53 is to be registered with SystemConfiguration. Boolean matchAllDomains; // Include '.' as match domain if registering Do53 with SC. [1] Boolean stopped; // True if the command has stopped. } DNSServerCmd; // Notes: // 1. If registering a Do53 service with SystemConfiguration, include '.' (root domain) as a low-priority match domain. // This is useful if a test scenario where a default DNS service needs to be available for domain names for which the // DNS server is not authoritative. Note that this means that the server may end up getting queries for domain names // that it doesn't recognize. // // For example, suppose that a test involves iterating search domains. Currently, a negative response from a server // is required to iterate to the next search domain in the search domain list. If a test device happens to not be // connected to any network, then it won't have a DHCP-assigned DNS service to act as a default DNS service that // could provide a potentially negative response. // // If the test DNS server includes '.' as a low-priority match domain, then it can act as a default DNS service of // last resort. configd usually sets up a default DNS service from DHCP with '.' as its match domain with an order // value of 0, which gives that DNS service the highest level of priority when comparing it to a DNS service that // has '.' as a match domain, but with a greater order value (lower order value means higher priority). static DNSServerCmd * _DNSServerCmdCreate( OSStatus *outError ); static void _DNSServerCmdRetain( DNSServerCmd *inCmd ); static void _DNSServerCmdRelease( DNSServerCmd *inCmd ); static void _DNSServerCmdStart( void *inCtx ); static void _DNSServerCmdStop( DNSServerCmd *inCmd, OSStatus inError ); static OSStatus _DNSServerCmdAddExtraLoopbackAddrs( sockaddr_ip *inAddrArray, size_t inAddrCount ); static void _DNSServerCmdServerStartHandler( uint16_t inDNSServerPort, void *inCtx ); static void _DNSServerCmdServerStopHandler( OSStatus inError, void *inCtx ); static void _DNSServerCmdSIGINTHandler( void *inCtx ); static void _DNSServerCmdSIGTERMHandler( void *inCtx ); static void _DNSServerCmdShutdown( DNSServerCmd *inCtx, int inSignal ); static void _DNSServerCmdFollowedProcessHandler( void *inCtx ); static OSStatus _DNSServerCmdModifySystemSettings( DNSServerCmd *inCmd, uint16_t inListeningPort ); static OSStatus _DNSServerCmdUndoSystemSettings( DNSServerCmd *inCmd ); static SecIdentityRef _DNSServerCmdCreateSecIdentity( OSStatus *outError ); static OSStatus _DNSServerCmdSetUpCertificate( SecIdentityRef inIdentity ); static OSStatus _DNSServerCmdCleanUpCertificates( void ); static nw_listener_t _DNSServerCmdCreateTLSListener( sec_identity_t inIdentity, uint16_t inDesiredPort, Boolean inUseHTTPS, OSStatus * outError ); static OSStatus _DNSServerCmdHandleNewTLSConnection( DNSServerCmd * inCmd, nw_connection_t inConnection, uint16_t inTLSListeningPort, uint16_t inDNSServerPort ); ulog_define_ex( kDNSSDUtilIdentifier, DNSServer, kLogLevelInfo, kLogFlags_None, "DNSServer", NULL ); #define ds_ulog( LEVEL, ... ) ulog( &log_category_from_name( DNSServer ), (LEVEL), __VA_ARGS__ ) static void DNSServerCommand( void ) { OSStatus err; DNSServerCmd * cmd = NULL; sockaddr_ip * sip; size_t addrCount, extraLoopbackV6Count; uint16_t port; Boolean listenOnV4, listenOnV6; // Check command arguments. 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 ); cmd = _DNSServerCmdCreate( &err ); require_noerr( err, exit ); cmd->followPID = -1; cmd->domainOverride = gDNSServer_DomainOverride; cmd->protocol = (DNSProtocol) CLIArgToValue( "protocol", gDNSServer_Protocol, &err, kDNSProtocolStr_Do53, (int) kDNSProtocol_Do53, kDNSProtocolStr_DoT, (int) kDNSProtocol_DoT, kDNSProtocolStr_DoH, (int) kDNSProtocol_DoH, NULL ); require_noerr_quiet( err, exit ); if( gDNSServer_Port == -1 ) { switch( cmd->protocol ) { case kDNSProtocol_Do53: port = kDNSPort_Do53; break; case kDNSProtocol_DoT: port = kDNSPort_DoT; break; case kDNSProtocol_DoH: port = kDNSPort_DoH; break; } } else { err = CheckIntegerArgument( gDNSServer_Port, "port number", 0, UINT16_MAX ); require_noerr_quiet( err, exit ); port = (uint16_t) gDNSServer_Port; } cmd->portRequested = port; listenOnV4 = gDNSServer_ListenOnV4 || !gDNSServer_ListenOnV6; listenOnV6 = gDNSServer_ListenOnV6 || !gDNSServer_ListenOnV4; if( gDNSServer_LoopbackOnly || ( cmd->protocol == kDNSProtocol_DoT ) || ( cmd->protocol == kDNSProtocol_DoH ) ) { cmd->loopbackOnly = true; } if( cmd->loopbackOnly && listenOnV6 ) { err = CheckIntegerArgument( gDNSServer_ExtraV6Count, "extra IPv6", 0, 100 ); require_noerr_quiet( err, exit ); extraLoopbackV6Count = (size_t) gDNSServer_ExtraV6Count; if( extraLoopbackV6Count > 0 ) { err = CheckRootUser(); require_noerr_quiet( err, exit ); } } else { extraLoopbackV6Count = 0; } if( gDNSServer_FollowPID ) { cmd->followPID = _StringToPID( gDNSServer_FollowPID, &err ); if( err || ( cmd->followPID < 0 ) ) { FPrintF( stderr, "error: Invalid follow PID: %s\n", gDNSServer_FollowPID ); err = kParamErr; goto exit; } } cmd->registerWithSC = gDNSServer_RegisterWithSC ? true : false; cmd->matchAllDomains = gDNSServer_MatchAllDomains ? true : false; // Set up IP addresses. if( listenOnV4 ) ++cmd->addrCount; if( listenOnV6 ) ++cmd->addrCount; cmd->addrCount += extraLoopbackV6Count; check( cmd->addrCount > 0 ); cmd->addrArray = (sockaddr_ip *) calloc( cmd->addrCount, sizeof( *cmd->addrArray ) ); require_action( cmd->addrArray, exit, err = kNoMemoryErr ); addrCount = 0; if( listenOnV4 ) { sip = &cmd->addrArray[ addrCount++ ]; _SockAddrInitIPv4( &sip->v4, cmd->loopbackOnly ? INADDR_LOOPBACK : INADDR_ANY, 0 ); } if( listenOnV6 ) { const struct in6_addr * const addr = cmd->loopbackOnly ? &in6addr_loopback : &in6addr_any; sip = &cmd->addrArray[ addrCount++ ]; _SockAddrInitIPv6( &sip->v6, addr->s6_addr, 0, 0 ); } if( extraLoopbackV6Count > 0 ) { err = _DNSServerCmdAddExtraLoopbackAddrs( &cmd->addrArray[ addrCount ], extraLoopbackV6Count ); require_noerr( err, exit ); addrCount += extraLoopbackV6Count; } check( addrCount == cmd->addrCount ); // Start command. cmd->queue = dispatch_queue_create( "com.apple.dnssdutil.server-command", DISPATCH_QUEUE_SERIAL ); require_action( cmd->queue, exit, err = kNoResourcesErr ); dispatch_group_enter( cmd->group ); // Enter for the duration of _DNSServerCmdStart(). dispatch_async_f( cmd->queue, cmd, _DNSServerCmdStart ); dispatch_group_wait( cmd->group, DISPATCH_TIME_FOREVER ); exit: if( err ) FPrintF( stderr, "Failed to start DNS server: %#m\n", err ); if( cmd ) _DNSServerCmdRelease( cmd ); gExitCode = err ? 1 : 0; } //=========================================================================================================================== static DNSServerCmd * _DNSServerCmdCreate( OSStatus * const outError ) { OSStatus err; DNSServerCmd * obj; DNSServerCmd * cmd = NULL; obj = (DNSServerCmd *) calloc( 1, sizeof( *obj ) ); require_action( obj, exit, err = kNoMemoryErr ); obj->refCount = 1; obj->group = dispatch_group_create(); require_action( obj->group, exit, err = kNoResourcesErr ); cmd = obj; obj = NULL; err = kNoErr; exit: if( obj ) _DNSServerCmdRelease( obj ); if( outError ) *outError = err; return( cmd ); } //=========================================================================================================================== static void _DNSServerCmdRetain( DNSServerCmd * const inCmd ) { atomic_add_32( &inCmd->refCount, 1 ); } //=========================================================================================================================== static void _DNSServerCmdRelease( DNSServerCmd * const inCmd ) { if( atomic_add_and_fetch_32( &inCmd->refCount, -1 ) == 0 ) { size_t i; check( !inCmd->server ); check( !inCmd->sourceSigInt ); check( !inCmd->sourceSigTerm ); check( !inCmd->processMonitor ); check( !inCmd->secureDNSConfig ); check( !inCmd->domainPolicy ); check( !inCmd->tlsListener ); for( i = 0; i < inCmd->addrCount; ++i ) { OSStatus err; const struct sockaddr_in6 * const sin6 = &inCmd->addrArray[ i ].v6; int cmp; if( sin6->sin6_family != AF_INET6 ) continue; cmp = memcmp( sin6->sin6_addr.s6_addr, kExtraLoopbackIPv6Prefix, sizeof( kExtraLoopbackIPv6Prefix ) ); if( cmp != 0 ) continue; err = _InterfaceIPv6AddressRemove( "lo0", sin6->sin6_addr.s6_addr ); check_noerr( err ); } dispatch_forget( &inCmd->queue ); dispatch_forget( &inCmd->group ); ForgetMem( &inCmd->addrArray ); CFForget( &inCmd->secIdentity ); free( inCmd ); } } //=========================================================================================================================== static void _DNSServerCmdStart( void *inCtx ) { OSStatus err; DNSServerCmd * const cmd = (DNSServerCmd *) inCtx; size_t i; signal( SIGINT, SIG_IGN ); err = DispatchSignalSourceCreate( SIGINT, cmd->queue, _DNSServerCmdSIGINTHandler, cmd, &cmd->sourceSigInt ); require_noerr( err, exit ); dispatch_resume( cmd->sourceSigInt ); signal( SIGTERM, SIG_IGN ); err = DispatchSignalSourceCreate( SIGTERM, cmd->queue, _DNSServerCmdSIGTERMHandler, cmd, &cmd->sourceSigTerm ); require_noerr( err, exit ); dispatch_resume( cmd->sourceSigTerm ); if( cmd->followPID >= 0 ) { err = DispatchProcessMonitorCreate( cmd->followPID, DISPATCH_PROC_EXIT, cmd->queue, _DNSServerCmdFollowedProcessHandler, NULL, cmd, &cmd->processMonitor ); require_noerr( err, exit ); dispatch_resume( cmd->processMonitor ); } err = _DNSServerCreate( cmd->queue, _DNSServerCmdServerStartHandler, _DNSServerCmdServerStopHandler, cmd, (unsigned int) gDNSServer_ResponseDelayMs, (uint32_t) gDNSServer_DefaultTTL, cmd->addrArray, cmd->addrCount, cmd->domainOverride, gDNSServer_BadUDPMode ? true : false, &cmd->server ); require_noerr( err, exit ); _DNSServerCmdRetain( cmd ); dispatch_group_enter( cmd->group ); // Enter for the DNS server's lifetime. _DNSServerSetPort( cmd->server, _DNSProtocolIsSecure( cmd->protocol ) ? 0 : cmd->portRequested ); for( i = 0; i < gDNSServer_IgnoredQTypesCount; ++i ) { uint16_t qtype; err = RecordTypeFromArgString( gDNSServer_IgnoredQTypes[ i ], &qtype ); require_noerr( err, exit ); err = _DNSServerSetIgnoredQType( cmd->server, qtype ); require_noerr( err, exit ); } _DNSServerStart( cmd->server ); exit: if( err ) _DNSServerCmdStop( cmd, err ); dispatch_group_leave( cmd->group ); // Leave for _DNSServerCmdStart(). } //=========================================================================================================================== static void _DNSServerCmdStop( DNSServerCmd *inCmd, OSStatus inError ) { if( !inCmd->stopped ) { inCmd->stopped = true; if( !inCmd->error ) inCmd->error = inError; if( inCmd->server ) { _DNSServerStop( inCmd->server ); CFForget( &inCmd->server ); } dispatch_source_forget( &inCmd->sourceSigInt ); dispatch_source_forget( &inCmd->sourceSigTerm ); dispatch_source_forget( &inCmd->processMonitor ); } } //=========================================================================================================================== static OSStatus _DNSServerCmdAddExtraLoopbackAddrs( sockaddr_ip *inAddrArray, size_t inAddrCount ) { OSStatus err; uint8_t addrV6[ 16 ]; size_t i; check_compile_time_code( sizeof( kExtraLoopbackIPv6Prefix ) == 8 ); memcpy( addrV6, kExtraLoopbackIPv6Prefix, 8 ); // 64-bit prefix RandomBytes( &addrV6[ 8 ], 4 ); // 32-bit random WriteBig32Typed( &addrV6[ 12 ], 2 ); // 16-bit base offset starting at 2 for( i = 0; i < inAddrCount; ++i ) { struct sockaddr_in6 * const sin6 = &inAddrArray[ i ].v6; err = _InterfaceIPv6AddressAdd( "lo0", addrV6, kExtraLoopbackIPv6PrefixBitLen ); require_noerr( err, exit ); _SockAddrInitIPv6( sin6, addrV6, 0, 0 ); BigEndianIntegerIncrement( addrV6, sizeof( addrV6 ) ); } err = kNoErr; exit: return( err ); } //=========================================================================================================================== static void _DNSServerCmdServerStartHandler( const uint16_t inDNSServerPort, void * const inCtx ) { OSStatus err; DNSServerCmd * const cmd = (DNSServerCmd *) inCtx; sec_identity_t secIdentity = NULL; if( cmd->loopbackOnly ) { if( _DNSProtocolIsSecure( cmd->protocol ) ) { Boolean useHTTPS; err = _DNSServerCmdCleanUpCertificates(); require_noerr( err, exit ); check( !cmd->secIdentity ); cmd->secIdentity = _DNSServerCmdCreateSecIdentity( &err ); require_noerr( err, exit ); err = _DNSServerCmdSetUpCertificate( cmd->secIdentity ); require_noerr( err, exit ); secIdentity = sec_identity_create( cmd->secIdentity ); require_action( secIdentity, exit, err = kNoResourcesErr ); useHTTPS = ( cmd->protocol == kDNSProtocol_DoH ); cmd->tlsListener = _DNSServerCmdCreateTLSListener( secIdentity, cmd->portRequested, useHTTPS, &err ); require_noerr( err, exit ); _DNSServerCmdRetain( cmd ); dispatch_group_enter( cmd->group ); // Enter for the TLS listener's lifetime. nw_listener_set_state_changed_handler( cmd->tlsListener, ^( const nw_listener_state_t inState, const nw_error_t inError ) { if( cmd->tlsListener ) { switch( inState ) { case nw_listener_state_ready: { const uint16_t listeningPort = nw_listener_get_port( cmd->tlsListener ); ds_ulog( kLogLevelInfo, "Listening for TLS connections on port %u\n", listeningPort ); const OSStatus settingsErr = _DNSServerCmdModifySystemSettings( cmd, listeningPort ); if( settingsErr ) { ds_ulog( kLogLevelError, "Failed to modify system settings: %#m\n", settingsErr ); _DNSServerCmdStop( cmd, settingsErr ); } break; } case nw_listener_state_failed: { OSStatus listenerErr; ds_ulog( kLogLevelError, "TLS listener failed: %@\n", inError ); listenerErr = nw_error_get_error_code( inError ); if( !listenerErr ) listenerErr = kUnknownErr; _DNSServerCmdStop( cmd, listenerErr ); break; } case nw_listener_state_invalid: case nw_listener_state_waiting: case nw_listener_state_cancelled: break; } } if( inState == nw_listener_state_cancelled ) { dispatch_group_leave( cmd->group ); // Leave for the TLS listener. _DNSServerCmdRelease( cmd ); } } ); nw_listener_set_new_connection_handler( cmd->tlsListener, ^( const nw_connection_t inConnection ) { if( cmd->tlsListener ) { const uint16_t listeningPort = nw_listener_get_port( cmd->tlsListener ); const OSStatus connectionErr = _DNSServerCmdHandleNewTLSConnection( cmd, inConnection, listeningPort, inDNSServerPort ); if( connectionErr ) _DNSServerCmdStop( cmd, connectionErr ); } } ); nw_listener_set_queue( cmd->tlsListener, cmd->queue ); nw_listener_start( cmd->tlsListener ); } else { err = _DNSServerCmdModifySystemSettings( cmd, inDNSServerPort ); require_noerr_action( err, exit, ds_ulog( kLogLevelError, "Failed to modify system settings: %#m\n", err ) ); } } err = kNoErr; exit: if( secIdentity ) sec_release( secIdentity ); if( err ) _DNSServerCmdStop( cmd, err ); } //=========================================================================================================================== static void _DNSServerCmdServerStopHandler( OSStatus inError, void *inCtx ) { OSStatus err; DNSServerCmd * const cmd = (DNSServerCmd *) inCtx; if( inError ) { ds_ulog( kLogLevelError, "The server stopped unexpectedly with error: %#m.\n", inError ); if( !cmd->error ) cmd->error = inError; } err = _DNSServerCmdUndoSystemSettings( cmd ); if( err ) { ds_ulog( kLogLevelError, "Failed to undo system settings: %#m\n", err ); if( !cmd->error ) cmd->error = err; } if( _DNSProtocolIsSecure( cmd->protocol ) ) { err = _DNSServerCmdCleanUpCertificates(); if( !cmd->error ) cmd->error = err; } if( cmd->tlsListener ) { nw_listener_cancel( cmd->tlsListener ); nw_forget( &cmd->tlsListener ); } _DNSServerCmdStop( cmd, cmd->error ); dispatch_group_leave( cmd->group ); // Leave for the DNS server. _DNSServerCmdRelease( cmd ); } //=========================================================================================================================== static void _DNSServerCmdSIGINTHandler( void *inCtx ) { _DNSServerCmdShutdown( (DNSServerCmd *) inCtx, SIGINT ); } //=========================================================================================================================== static void _DNSServerCmdSIGTERMHandler( void *inCtx ) { _DNSServerCmdShutdown( (DNSServerCmd *) inCtx, SIGTERM ); } //=========================================================================================================================== static void _DNSServerCmdShutdown( DNSServerCmd *inCmd, int inSignal ) { dispatch_source_forget( &inCmd->sourceSigInt ); dispatch_source_forget( &inCmd->sourceSigTerm ); dispatch_source_forget( &inCmd->processMonitor ); if( inSignal == 0 ) { ds_ulog( kLogLevelNotice, "Exiting: followed process (%lld) exited\n", (int64_t) inCmd->followPID ); } else { const char * sigName; switch( inSignal ) { case SIGINT: sigName = "SIGINT"; break; case SIGTERM: sigName = "SIGTERM"; break; default: sigName = "???"; break; } ds_ulog( kLogLevelNotice, "Exiting: received signal %d (%s)\n", inSignal, sigName ); } _DNSServerCmdStop( inCmd, kNoErr ); } //=========================================================================================================================== static void _DNSServerCmdFollowedProcessHandler( void *inCtx ) { DNSServerCmd * const cmd = (DNSServerCmd *) inCtx; if( dispatch_source_get_data( cmd->processMonitor ) & DISPATCH_PROC_EXIT ) _DNSServerCmdShutdown( cmd, 0 ); } //=========================================================================================================================== #define kDNSServerHostname "dns.apple.test" #define kDNSServerDoHURLPath "/dns-query" #define kDNSServerServiceID CFSTR( "com.apple.dnssdutil.server" ) static OSStatus _DNSServerCmdModifySystemSettings( DNSServerCmd * const inCmd, const uint16_t inListeningPort ) { OSStatus err; mdns_dns_configurator_t configurator = NULL; mdns_dns_service_definition_t definition = NULL; nw_resolver_config_t secureDNSConfig = NULL; nw_endpoint_t endpoint = NULL; const char * primaryDomainStr; CFArrayRef domains = NULL; size_t i; char dnssecDomainStr[ kDNSServiceMaxDomainName ]; require_action_quiet( inCmd->addrCount > 0, exit, err = kCountErr ); err = DomainNameToString( kDNSServerDomain_DNSSEC, NULL, dnssecDomainStr, NULL ); require_noerr( err, exit ); primaryDomainStr = inCmd->domainOverride ? inCmd->domainOverride : "d.test."; const char * const domainStrings[] = { primaryDomainStr, dnssecDomainStr, kDNSServerReverseIPv4DomainStr, kDNSServerReverseIPv6DomainStr }; switch( inCmd->protocol ) { case kDNSProtocol_DoT: case kDNSProtocol_DoH: { // Try to clean up a stale Do53 resolver entry for a previous server from the system's DNS configuration in case // one happens to still be present. Such an entry may interfere with the system settings for the DoT server if // they're for the same domain, i.e., queries may be incorrectly sent to the Do53 server which no longer exists. Boolean usingDefaultPort; mdns_dns_configurator_deregister_configuration( kDNSServerServiceID, CFSTR( kDNSSDUtilIdentifier ) ); if( inCmd->protocol == kDNSProtocol_DoT ) { usingDefaultPort = ( inListeningPort == kDNSPort_DoT ); const uint16_t endpointPort = usingDefaultPort ? 0 : inListeningPort; endpoint = nw_endpoint_create_host_with_numeric_port( kDNSServerHostname, endpointPort ); require_action( endpoint, exit, err = kUnknownErr ); secureDNSConfig = nw_resolver_config_create_tls( endpoint ); nw_forget( &endpoint ); require_action( secureDNSConfig, exit, err = kUnknownErr ); } else { char *templateURL = NULL; usingDefaultPort = ( inListeningPort == kDNSPort_DoH ); if( usingDefaultPort ) { ASPrintF( &templateURL, "https://%s%s", kDNSServerHostname, kDNSServerDoHURLPath ); require_action( templateURL, exit, err = kNoMemoryErr ); } else { ASPrintF( &templateURL, "https://%s:%u%s", kDNSServerHostname, inListeningPort, kDNSServerDoHURLPath ); require_action( templateURL, exit, err = kNoMemoryErr ); } endpoint = nw_endpoint_create_url( templateURL ); ForgetMem( &templateURL ); require_action( endpoint, exit, err = kUnknownErr ); secureDNSConfig = nw_resolver_config_create_https( endpoint ); nw_forget( &endpoint ); require_action( secureDNSConfig, exit, err = kUnknownErr ); } domains = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks ); require_action( domains, exit, err = kNoResourcesErr ); for( i = 0; i < countof( domainStrings ); ++i ) { const char * const domainStr = domainStrings[ i ]; size_t len; char * domainStrDup; len = strlen( domainStr ); if( len <= 0 ) continue; // NECP doesn't properly handle trailing root dots at the end of FQDNs, so remove them if present. if( domainStr[ len - 1 ] == '.' ) --len; domainStrDup = NULL; ASPrintF( &domainStrDup, "%.*s", (int) len, domainStr ); require_action( domainStrDup, exit, err = kNoMemoryErr ); nw_resolver_config_add_match_domain( secureDNSConfig, domainStrDup ); err = CFPropertyListAppendFormatted( NULL, domains, "%s", domainStrDup ); ForgetMem( &domainStrDup ); require_noerr( err, exit ); } for( i = 0; i < inCmd->addrCount; ++i ) { sockaddr_ip serverAddr; SockAddrCopy( &inCmd->addrArray[ i ], &serverAddr ); SockAddrSetPort( &serverAddr.sa, usingDefaultPort ? 0 : inListeningPort ); char serverAddrStr[ kSockAddrStringMaxSize ]; err = SockAddrToString( &serverAddr.sa, kSockAddrStringFlagsNone, serverAddrStr ); require_noerr( err, exit ); nw_resolver_config_add_name_server( secureDNSConfig, serverAddrStr ); } nw_resolver_config_set_interface_name( secureDNSConfig, "lo0" ); const bool ok = nw_resolver_config_publish( secureDNSConfig ); require_action( ok, exit, err = kUnknownErr ); if( domains ) { uuid_t resolverConfigID; uuid_clear( resolverConfigID ); nw_resolver_config_get_identifier( secureDNSConfig, resolverConfigID ); check( !inCmd->domainPolicy ); inCmd->domainPolicy = mdns_system_add_net_agent_match_domains( resolverConfigID, domains ); } inCmd->secureDNSConfig = secureDNSConfig; secureDNSConfig = NULL; break; } case kDNSProtocol_Do53: { if( inCmd->registerWithSC ) { configurator = mdns_dns_configurator_create_with_cfstring_id( kDNSServerServiceID, &err ); require_noerr( err, exit ); err = mdns_dns_configurator_add_domain( configurator, primaryDomainStr, 0 ); require_noerr( err, exit ); err = mdns_dns_configurator_add_domain( configurator, dnssecDomainStr, 0 ); require_noerr( err, exit ); err = mdns_dns_configurator_add_domain( configurator, kDNSServerReverseIPv4DomainStr, 0 ); require_noerr( err, exit ); err = mdns_dns_configurator_add_domain( configurator, kDNSServerReverseIPv6DomainStr, 0 ); require_noerr( err, exit ); if( inCmd->matchAllDomains ) { // Use the highest possible order value to make the root match domain as low-priority as possible. err = mdns_dns_configurator_add_domain( configurator, ".", UINT32_MAX ); require_noerr( err, exit ); } for( i = 0; i < inCmd->addrCount; ++i ) { const sockaddr_ip * const sip = &inCmd->addrArray[ i ]; char addrStr[ kSockAddrStringMaxSize ]; err = SockAddrToString( &sip->sa, kSockAddrStringFlagsNoPort, addrStr ); require_noerr( err, exit ); err = mdns_dns_configurator_add_server_address_string( configurator, addrStr ); require_noerr( err, exit ); } err = mdns_dns_configurator_set_port( configurator, inListeningPort ); require_noerr( err, exit ); err = mdns_dns_configurator_set_interface( configurator, "lo0" ); require_noerr( err, exit ); err = mdns_dns_configurator_register( configurator, CFSTR( kDNSSDUtilIdentifier ) ); require_noerr( err, exit ); inCmd->addedResolver = true; } else { definition = mdns_dns_service_definition_create(); require_action( definition, exit, err = kNoResourcesErr ); for( i = 0; i < countof( domainStrings ); ++i ) { const char * const domainStr = domainStrings[ i ]; mdns_domain_name_t domain = mdns_domain_name_create( domainStr, mdns_domain_name_create_opts_none, &err ); require_noerr_action( err, exit, ds_ulog( kLogLevelError, "error: Failed to create domain name for '%s': %#m\n", domainStr, err ) ); mdns_dns_service_definition_add_domain( definition, domain ); mdns_forget( &domain ); } for( i = 0; i < inCmd->addrCount; ++i ) { sockaddr_ip sip = inCmd->addrArray[ i ]; SockAddrSetPort( &sip, inListeningPort ); char addrStr[ kSockAddrStringMaxSize ]; err = SockAddrToString( &sip.sa, kSockAddrStringFlagsNone, addrStr ); require_noerr( err, exit ); mdns_address_t serverAddr = mdns_address_create_from_ip_address_string( addrStr ); require_action( serverAddr, exit, err = kNoResourcesErr; ds_ulog( kLogLevelError, "error: Failed to create address for '%s'\n", addrStr ) ); err = mdns_dns_service_definition_append_server_address( definition, serverAddr ); mdns_forget( &serverAddr ); require_noerr( err, exit ); } const uint32_t ifIndex = if_nametoindex( "lo0" ); err = map_global_value_errno( ifIndex != 0, ifIndex ); require_noerr_action_quiet( err, exit, ds_ulog( kLogLevelError, "Failed to get interface index for lo0: %#m", err ) ); mdns_dns_service_definition_set_interface_index( definition, ifIndex, false ); inCmd->registration = mrc_dns_service_registration_create( definition ); mdns_forget( &definition ); require_action_quiet( inCmd->registration, exit, err = kNoResourcesErr ); mrc_dns_service_registration_set_queue( inCmd->registration, inCmd->queue ); mrc_dns_service_registration_set_event_handler( inCmd->registration, ^( const mrc_dns_service_registration_event_t inEvent, const OSStatus inError ) { switch( inEvent ) { case mrc_dns_service_registration_event_started: ds_ulog( kLogLevelInfo, "DNS service registration started\n" ); break; case mrc_dns_service_registration_event_interruption: ds_ulog( kLogLevelInfo, "DNS service registration interrupted\n" ); break; case mrc_dns_service_registration_event_invalidation: if( inError ) { ds_ulog( kLogLevelError, "DNS service registration invalidated with error: %#m\n", inError ); } else { ds_ulog( kLogLevelInfo, "DNS service registration gracefully invalidated\n" ); } break; case mrc_dns_service_registration_event_connection_error: ds_ulog( kLogLevelFault, "DNS service registration invalid event with error: %#m\n", inError ); break; } } ); mrc_dns_service_registration_activate( inCmd->registration ); } break; } default: err = kInternalErr; goto exit; } exit: mdns_forget( &configurator ); mdns_forget( &definition ); nw_forget( &secureDNSConfig ); nw_forget( &endpoint ); CFForget( &domains ); return( err ); } //=========================================================================================================================== static OSStatus _DNSServerCmdUndoSystemSettings( DNSServerCmd * const inCmd ) { OSStatus err; if( inCmd->secureDNSConfig ) { nw_resolver_config_unpublish( inCmd->secureDNSConfig ); nw_forget( &inCmd->secureDNSConfig ); } if( inCmd->domainPolicy ) { mdns_system_remove_network_policy( inCmd->domainPolicy ); inCmd->domainPolicy = NULL; } if( inCmd->addedResolver ) { err = mdns_dns_configurator_deregister_configuration( kDNSServerServiceID, CFSTR( kDNSSDUtilIdentifier ) ); require_noerr( err, exit ); inCmd->addedResolver = false; } mrc_dns_service_registration_forget( &inCmd->registration ); err = kNoErr; exit: return( err ); } //=========================================================================================================================== static SecIdentityRef _DNSServerCmdCreateSecIdentity( OSStatus * const outError ) { SecIdentityRef identity = NULL; CFArrayRef certSubject = NULL; CFDictionaryRef csrParams = NULL; OSStatus err = CFPropertyListCreateFormatted( kCFAllocatorDefault, &certSubject, "[" "[[%O%O]]" // kSecOidCommonName "[[%O%O]]" // kSecOidOrganizationalUnit "[[%O%O]]" // kSecOidOrganization "]", kSecOidCommonName, CFSTR( kDNSServerHostname ), kSecOidOrganizationalUnit, CFSTR( "Networking" ), kSecOidOrganization, CFSTR( "Apple Inc." ) ); require_noerr( err, exit ); err = CFPropertyListCreateFormatted( kCFAllocatorDefault, &csrParams, "{" "%kO=" // kSecSubjectAltName "{" "%kO=%O" // kSecSubjectAltNameDNSName "}" "%kO=%lli" // kSecCertificateLifetime "}", kSecSubjectAltName, kSecSubjectAltNameDNSName, CFSTR( kDNSServerHostname ), kSecCertificateLifetime, (int64_t)( 24 * kSecondsPerHour ) ); require_noerr( err, exit ); identity = mdns_security_create_self_signed_certificate( certSubject, csrParams, kSecAttrKeyTypeECSECPrimeRandom, 256, &err ); require_noerr( err, exit ); exit: CFForget( &certSubject ); CFForget( &csrParams ); if( outError ) *outError = err; return( identity ); } //=========================================================================================================================== static OSStatus _DNSServerCmdSetUpCertificate( const SecIdentityRef inIdentity ) { SecCertificateRef cert = NULL; SecPolicyRef policySSL = NULL; CFDictionaryRef trustSettings = NULL; OSStatus err = SecIdentityCopyCertificate( inIdentity, &cert ); require_noerr( err, exit ); err = KeychainAddFormatted( NULL, "{" "%kO=%O" // kSecClass "%kO=%O" // kSecValueRef "}", kSecClass, kSecClassCertificate, kSecValueRef, cert ); require_noerr( err, exit ); policySSL = SecPolicyCreateSSL( true, NULL ); require_action( policySSL, exit, err = kNoResourcesErr ); err = CFPropertyListCreateFormatted( kCFAllocatorDefault, &trustSettings, "{" "%kO=%O" // kSecTrustSettingsPolicy "%kO=%lli" // kSecTrustSettingsResult "}", kSecTrustSettingsPolicy, policySSL, kSecTrustSettingsResult, (int64_t) kSecTrustSettingsResultTrustRoot ); require_noerr( err, exit ); err = mdns_security_set_per_user_certificate_trust_settings( cert, trustSettings ); require_noerr( err, exit ); exit: CFForget( &cert ); CFForget( &policySSL ); CFForget( &trustSettings ); return( err ); } //=========================================================================================================================== static OSStatus _DNSServerCmdCleanUpCertificates( void ) { OSStatus err; CFMutableDictionaryRef certQuery = NULL; CFTypeRef certResults = NULL; err = CFPropertyListCreateFormatted( kCFAllocatorDefault, &certQuery, "{" "%kO=%O" // kSecClass "%kO=%O" // kSecAttrLabel "%kO=%O" // kSecMatchLimit "%kO=%O" // kSecReturnAttributes "%kO=%O" // kSecReturnRef "}", kSecClass, kSecClassCertificate, kSecAttrLabel, CFSTR( kDNSServerHostname ), kSecMatchLimit, kSecMatchLimitAll, kSecReturnAttributes, kCFBooleanTrue, kSecReturnRef, kCFBooleanTrue ); require_noerr( err, exit ); err = SecItemCopyMatching( certQuery, &certResults ); if( err == errSecItemNotFound ) { err = kNoErr; goto exit; } require_noerr( err, exit ); if( certResults ) { const CFArrayApplierBlock applierBlock = ^( const void * const inValue ) { const CFDictionaryRef item = CFGetCFDictionary( inValue, NULL ); require_return( item ); const SecCertificateRef itemCert = (SecCertificateRef) CFDictionaryGetValue( item, kSecValueRef ); require_return( itemCert ); OSStatus localErr = mdns_security_remove_per_user_certificate_trust_settings( itemCert ); require_noerr_fatal( localErr, "mdns_security_remove_per_user_certificate_trust_settings() failed: %#m", localErr ); }; const CFArrayRef certResultsArray = CFGetCFArray( certResults, NULL ); if( certResultsArray ) { CFArrayApplyBlock( certResultsArray, CFRangeMake( 0, CFArrayGetCount( certResultsArray ) ), applierBlock ); } else { applierBlock( certResults ); } err = KeychainDeleteFormatted( "{" "%kO=%O" // kSecClass "%kO=%O" // kSecAttrLabel "}", kSecClass, kSecClassCertificate, kSecAttrLabel, CFSTR( kDNSServerHostname ) ); require_noerr( err, exit ); } exit: CFForget( &certQuery ); CFForget( &certResults ); return( err ); } //=========================================================================================================================== static nw_listener_t _DNSServerCmdCreateTLSListener( const sec_identity_t inIdentity, const uint16_t inDesiredPort, const Boolean inUseHTTPS, OSStatus * const outError ) { OSStatus err; nw_parameters_t params = NULL; nw_protocol_options_t options = NULL; nw_protocol_stack_t stack = NULL; nw_interface_t interface = NULL; nw_listener_t listener = NULL; __block bool tlsWasConfigured; tlsWasConfigured = false; const nw_parameters_configure_protocol_block_t configureTLS = ^( const nw_protocol_options_t inOptions ) { sec_protocol_options_t tlsOptions; tlsOptions = nw_tls_copy_sec_protocol_options( inOptions ); require_return( tlsOptions ); sec_protocol_options_set_local_identity( tlsOptions, inIdentity ); sec_protocol_options_append_tls_ciphersuite_group( tlsOptions, tls_ciphersuite_group_default ); if( inUseHTTPS ) { sec_protocol_options_add_tls_application_protocol( tlsOptions, "h2" ); } sec_release( tlsOptions ); tlsWasConfigured = true; }; params = nw_parameters_create_secure_tcp( configureTLS, NW_PARAMETERS_DEFAULT_CONFIGURATION ); require_action( params, exit, err = kNoResourcesErr ); require_action( tlsWasConfigured, exit, err = kSecurityErr ); if( inUseHTTPS ) { nw_parameters_set_attach_protocol_listener( params, true ); options = nw_http2_create_options(); require_action( options, exit, err = kNoResourcesErr ); stack = nw_parameters_copy_default_protocol_stack( params ); require_action( stack, exit, err = kNoResourcesErr ); nw_protocol_stack_prepend_application_protocol( stack, options ); } interface = nw_interface_create_with_name( "lo0" ); require_action( interface, exit, err = kNoResourcesErr ); nw_parameters_require_interface( params, interface ); nw_parameters_set_server_mode( params, true ); nw_parameters_set_reuse_local_address( params, true ); if( inDesiredPort > 0 ) { char portStr[ 16 ]; SNPrintF( portStr, sizeof( portStr ), "%u", inDesiredPort ); listener = nw_listener_create_with_port( portStr, params ); require_action( listener, exit, err = kNoResourcesErr ); } else { listener = nw_listener_create( params ); require_action( listener, exit, err = kNoResourcesErr ); } err = kNoErr; exit: nw_forget( ¶ms ); nw_forget( &options ); nw_forget( &stack ); nw_forget( &interface ); if( outError ) *outError = err; return( listener ); } //=========================================================================================================================== static OSStatus _DNSServerCmdHandleNewTLSConnection( DNSServerCmd * const inCmd, const nw_connection_t inConnection, const uint16_t inTLSListeningPort, const uint16_t inDNSServerPort ) { OSStatus err; nw_connection_t clientConnection; nw_parameters_t params; nw_endpoint_t localEndpoint = NULL; const struct sockaddr * localAddr; sockaddr_ip serverAddr; mdns_dns_relay_t relay = NULL; clientConnection = inConnection; params = nw_connection_copy_parameters( clientConnection ); require_action( params, exit, err = kNoResourcesErr ); localEndpoint = nw_parameters_copy_local_endpoint( params ); require_action( localEndpoint, exit, err = kNoResourcesErr ); localAddr = nw_endpoint_get_address( localEndpoint ); require_action( localAddr, exit, err = kUnexpectedErr ); memset( &serverAddr, 0, sizeof( serverAddr ) ); switch( localAddr->sa_family ) { case AF_INET: case AF_INET6: SockAddrCopy( localAddr, &serverAddr.sa ); SockAddrSetPort( &serverAddr.sa, inDNSServerPort ); break; default: err = kAddressErr; goto exit; } if( inCmd->protocol == kDNSProtocol_DoH ) { mdns_doh_relay_t relayDoH; relayDoH = mdns_doh_relay_create( NULL ); require_action( relayDoH, exit, err = kNoResourcesErr ); relay = mdns_dns_relay_upcast( relayDoH ); err = mdns_doh_relay_set_request_uri_path( relayDoH, kDNSServerDoHURLPath ); require_noerr( err, exit ); err = mdns_doh_relay_set_host_and_port( relayDoH, kDNSServerHostname, inTLSListeningPort ); require_noerr( err, exit ); } else { mdns_dot_relay_t relayDoT; relayDoT = mdns_dot_relay_create( NULL ); require_action( relayDoT, exit, err = kNoResourcesErr ); relay = mdns_dns_relay_upcast( relayDoT ); } mdns_dns_relay_set_client_connection( relay, clientConnection ); clientConnection = NULL; mdns_dns_relay_set_server_address( relay, &serverAddr ); mdns_dns_relay_activate( relay ); err = kNoErr; exit: nw_forget( ¶ms ); nw_forget( &localEndpoint ); mdns_forget( &relay ); if( clientConnection ) nw_connection_cancel( clientConnection ); return( err ); } //=========================================================================================================================== typedef struct DNSServerConnectionPrivate * DNSServerConnectionRef; typedef struct DNSServerDelayedResponse DNSServerDelayedResponse; struct DNSServerDelayedResponse { DNSServerDelayedResponse * next; // Next delayed response in list. sockaddr_ip client; // Destination address. uint64_t dueTicks; // Time, in ticks, when send is due. uint8_t * msgPtr; // Response message pointer. size_t msgLen; // Response message length. size_t index; // Address index. SocketRef sock; // Socket to use for send. }; struct DNSServerPrivate { CFRuntimeBase base; // CF object base. uint8_t * domain; // Parent domain of server's resource records. (malloc'd) dispatch_queue_t queue; // Queue for DNS server's events. sockaddr_ip * addrArray; // Array of addresses to listen on. size_t addrCount; // Number of addresses to listen on. dispatch_source_t * readSourceArrayUDP; // Array of read sources for UDP sockets. dispatch_source_t * readSourceArrayTCP; // Array of read sources for TCP listening sockets. DNSServerConnectionRef connectionList; // List of TCP connections. dispatch_source_t connectionTimer; // Timer for idle connections. DNSServerStartHandler_f startHandler; // User's activation handler. DNSServerStopHandler_f stopHandler; // User's invalidation handler. void * userContext; // User's handler context. DNSServerDelayedResponse * responseList; // List of delayed UDP responses. dispatch_source_t responseTimer; // Timer for when to send next delayed response. int * ignoredQTypes; // Array of QTYPEs to ignore. size_t ignoredQTypeCount; // Number of QTYPEs to ignore. unsigned int responseDelayMs; // Response delay in milliseconds. uint32_t defaultTTL; // Default TTL for resource records. uint32_t serial; // Serial number for SOA record. OSStatus stopErr; // The error, if any, that caused the server to stop. uint16_t portRequested; // The port that was requested by the user. Boolean started; // True if the server was started. Boolean stopped; // True if the server was stopped. Boolean badUDPMode; // True if the server runs in Bad UDP mode. Boolean suspended; // True if the server is suspended (intentionally not responding). }; static void _DNSServerUDPReadHandler( void *inContext ); static OSStatus _DNSServerScheduleDelayedResponse( DNSServerRef inServer, SocketRef inSock, const struct sockaddr * inDestAddr, uint8_t * inMsgPtr, size_t inMsgLen, size_t inIndex ); static void _DNSServerDelayedResponseFree( DNSServerDelayedResponse *inResponse ); static void _DNSServerDelayedResponseListFree( DNSServerDelayedResponse *inList ); static void _DNSServerTCPAcceptHandler( void *inContext ); static void _DNSServerConnectionTimerHandler( void *inContext ); static void _DNSServerResetConnectionTimerMs( DNSServerRef me, uint64_t inTimeoutMs ); static OSStatus _DNSServerAnswerQuery( DNSServerRef inServer, const uint8_t * inMsgPtr, size_t inMsgLen, size_t inIndex, Boolean inForTCP, uint8_t ** outResponsePtr, size_t * outResponseLen ); #define _DNSServerAnswerQueryForUDP( SERVER, QUERY_PTR, QUERY_LEN, INDEX, RESPONSE_PTR, RESPONSE_LEN ) \ _DNSServerAnswerQuery( SERVER, QUERY_PTR, QUERY_LEN, INDEX, false, RESPONSE_PTR, RESPONSE_LEN ) #define _DNSServerAnswerQueryForTCP( SERVER, QUERY_PTR, QUERY_LEN, INDEX, RESPONSE_PTR, RESPONSE_LEN ) \ _DNSServerAnswerQuery( SERVER, QUERY_PTR, QUERY_LEN, INDEX, true, RESPONSE_PTR, RESPONSE_LEN ) CF_CLASS_DEFINE( DNSServer ); struct DNSServerConnectionPrivate { CFRuntimeBase base; // CF object base. DNSServerConnectionRef next; // Next connection in list. DNSServerRef server; // Back pointer to server object. sockaddr_ip local; // TCP connection's local address. sockaddr_ip remote; // TCP connection's remote address. size_t index; // Sever address index. uint64_t expirationTicks; // Expiration time in ticks. Renewed upon receiving a complete query. dispatch_source_t readSource; // Dispatch read source for TCP connection. dispatch_source_t writeSource; // Dispatch write source for TCP connection. size_t offset; // Offset into receive buffer. void * msgPtr; // Pointer to dynamically allocated message buffer. size_t msgLen; // Length of message buffer. iovec_t iov[ 2 ]; // IO vector for writing response message. iovec_t * iovPtr; // Vector pointer for SocketWriteData(). int iovCount; // Vector count for SocketWriteData(). Boolean readSuspended; // True if the read source is currently suspended. Boolean writeSuspended; // True if the write source is currently suspended. Boolean haveLen; // True if currently receiving message instead of message length. uint8_t lenBuf[ 2 ]; // Buffer for two-octet message length field. }; static CFTypeID DNSServerConnectionGetTypeID( void ); static OSStatus _DNSServerConnectionCreate( DNSServerRef inServer, const struct sockaddr * inLocal, const struct sockaddr * inRemote, size_t inIndex, DNSServerConnectionRef * outCnx ); static OSStatus _DNSServerConnectionStart( DNSServerConnectionRef inCnx, SocketRef inSock ); static void _DNSServerConnectionStop( DNSServerConnectionRef inCnx, Boolean inRemoveFromList ); static void _DNSServerConnectionReadHandler( void *inContext ); static void _DNSServerConnectionWriteHandler( void *inContext ); static void _DNSServerConnectionRenewExpiration( DNSServerConnectionRef inCnx ); CF_CLASS_DEFINE( DNSServerConnection ); static OSStatus _DNSServerCreate( dispatch_queue_t inQueue, DNSServerStartHandler_f inStartHandler, DNSServerStopHandler_f inStopHandler, void * inUserContext, unsigned int inResponseDelayMs, uint32_t inDefaultTTL, const sockaddr_ip * inAddrArray, size_t inAddrCount, 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->startHandler = inStartHandler; obj->stopHandler = inStopHandler; obj->userContext = inUserContext; obj->responseDelayMs = inResponseDelayMs; obj->defaultTTL = inDefaultTTL; obj->badUDPMode = inBadUDPMode; obj->addrCount = inAddrCount; obj->addrArray = (sockaddr_ip *) _memdup( inAddrArray, obj->addrCount * sizeof( *obj->addrArray ) ); require_action( obj->addrArray, exit, err = kNoMemoryErr ); obj->readSourceArrayUDP = (dispatch_source_t *) calloc( obj->addrCount, sizeof( *obj->readSourceArrayUDP ) ); require_action( obj->readSourceArrayUDP, exit, err = kNoMemoryErr ); obj->readSourceArrayTCP = (dispatch_source_t *) calloc( obj->addrCount, sizeof( *obj->readSourceArrayTCP ) ); require_action( obj->readSourceArrayTCP, exit, err = kNoMemoryErr ); if( inDomain ) { err = StringToDomainName( inDomain, &obj->domain, NULL ); require_noerr_quiet( err, exit ); } else { err = DomainNameDup( kDNSServerDomain_Default, &obj->domain, NULL ); require_noerr_quiet( err, exit ); } *outServer = obj; obj = NULL; err = kNoErr; exit: CFReleaseNullSafe( obj ); return( err ); } //=========================================================================================================================== static void _DNSServerFinalize( CFTypeRef inObj ) { DNSServerRef const me = (DNSServerRef) inObj; size_t i; check( !me->responseTimer ); check( !me->connectionList ); check( !me->connectionTimer ); ForgetMem( &me->addrArray ); if( me->readSourceArrayUDP ) { for( i = 0; i < me->addrCount; ++i ) check( !me->readSourceArrayUDP[ i ] ); ForgetMem( &me->readSourceArrayUDP ); } if( me->readSourceArrayTCP ) { for( i = 0; i < me->addrCount; ++i ) check( !me->readSourceArrayTCP[ i ] ); ForgetMem( &me->readSourceArrayTCP ); } ForgetMem( &me->domain ); dispatch_forget( &me->queue ); ForgetMem( &me->ignoredQTypes ); } //=========================================================================================================================== static OSStatus _DNSServerSetIgnoredQType( DNSServerRef me, int inQType ) { size_t newCount; int * mem; newCount = me->ignoredQTypeCount + 1; require_return_value( newCount <= SIZE_MAX / sizeof( int ), kSizeErr ); mem = realloc( me->ignoredQTypes, newCount * sizeof( int ) ); require_return_value( mem, kNoMemoryErr ); me->ignoredQTypes = mem; me->ignoredQTypes[ me->ignoredQTypeCount++ ] = inQType; return( kNoErr ); } //=========================================================================================================================== static void _DNSServerSetPort( const DNSServerRef me, const uint16_t inPort ) { me->portRequested = inPort; } //=========================================================================================================================== static void _DNSServerStartOnQueue( void *inContext ); static void _DNSServerStartInternal( DNSServerRef inServer ); static OSStatus _DNSServerSetUpSockets( DNSServerRef inServer, uint16_t *outActualPort ); static void _DNSServerStopInternal( void *inContext, OSStatus inError ); static SocketContext * _DNSServerSocketContextCreate( SocketRef inSock, DNSServerRef inServer, size_t inIndex, OSStatus * outError ); static void _DNSServerStart( DNSServerRef me ) { CFRetain( me ); dispatch_async_f( me->queue, me, _DNSServerStartOnQueue ); } static void _DNSServerStartOnQueue( void *inContext ) { const DNSServerRef me = (DNSServerRef) inContext; _DNSServerStartInternal( me ); CFRelease( me ); } static void _DNSServerStartInternal( DNSServerRef me ) { OSStatus err; struct timeval now; SocketRef sock = kInvalidSocketRef; SocketContext * sockCtx = NULL; int year, month, day; uint16_t actualPort; require_action_quiet( !me->started && !me->stopped, exit, err = kNoErr ); me->started = true; CFRetain( me ); err = _DNSServerSetUpSockets( me, &actualPort ); require_noerr( err, exit ); if( me->startHandler ) me->startHandler( actualPort, me->userContext ); // Create the serial number for the server's SOA record in the YYYMMDDnn convention recommended by // 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 ); err = kNoErr; exit: ForgetSocket( &sock ); if( sockCtx ) SocketContextRelease( sockCtx ); if( err ) _DNSServerStopInternal( me, err ); } typedef struct { SocketRef sockUDP; SocketRef sockTCP; } _DNSServerSocketPair; #define kDNSServerMaxBindTryCount 10 static OSStatus _DNSServerSetUpSockets( const DNSServerRef me, uint16_t * const outActualPort ) { OSStatus err; SocketContext * sockCtx = NULL; _DNSServerSocketPair * sockPairs = NULL; _DNSServerSocketPair * sockPairsHeap = NULL; _DNSServerSocketPair sockPairsStack[ 16 ]; size_t i; const size_t addrCount = me->addrCount; // Don't use me->addrCount to avoid false analyzer warning. int portActual = 0; int tryCount, tryCountMax; require_action_quiet( addrCount > 0, exit, err = kNoErr ); if( me->addrCount > countof( sockPairsStack ) ) { sockPairsHeap = (_DNSServerSocketPair *) calloc( me->addrCount, sizeof( *sockPairsHeap ) ); require_action( sockPairsHeap, exit, err = kNoMemoryErr ); sockPairs = sockPairsHeap; } else { sockPairs = sockPairsStack; } for( i = 0; i < addrCount; ++i ) { sockPairs[ i ].sockUDP = kInvalidSocketRef; sockPairs[ i ].sockTCP = kInvalidSocketRef; } // Create server sockets. err = kNoErr; tryCountMax = ( me->portRequested == 0 ) ? kDNSServerMaxBindTryCount : 1; for( tryCount = 0; tryCount < tryCountMax; ++tryCount ) { int portDefault = 0; portActual = 0; for( i = 0; i < addrCount; ++i ) { sockaddr_ip * const sip = &me->addrArray[ i ]; _DNSServerSocketPair * const pair = &sockPairs[ i ]; const void * address; SocketRef sock; int port; sockaddr_ip tmpSA; switch( sip->sa.sa_family ) { case AF_INET: address = &sip->v4.sin_addr.s_addr; break; case AF_INET6: address = sip->v6.sin6_addr.s6_addr; break; default: ds_ulog( kLogLevelError, "Unhandled address family %d", sip->sa.sa_family ); err = kTypeErr; goto exit; } // Create UDP socket. // If the user's requested port 0, then the user wants any available ephemeral port. The actual port number // that was used will be stored in portActual and used for the remaining addresses that don't specify a // non-zero port. port = ( me->portRequested == 0 ) ? portDefault : me->portRequested; err = _ServerSocketOpenEx2( sip->sa.sa_family, SOCK_DGRAM, IPPROTO_UDP, address, port, &portActual, kSocketBufferSize_DontSet, true, &sock ); if( err == EADDRINUSE ) { SockAddrCopy( sip, &tmpSA ); SockAddrSetPort( &tmpSA, port ); ds_ulog( kLogLevelError, "IP address %##a is already in use for UDP\n", &tmpSA ); break; } require_noerr( err, exit ); check( ( me->portRequested == 0 ) || ( portActual == me->portRequested ) ); ForgetSocket( &pair->sockUDP ); pair->sockUDP = sock; sock = kInvalidSocketRef; if( portDefault == 0 ) portDefault = portActual; // Create TCP socket. err = _ServerSocketOpenEx2( sip->sa.sa_family, SOCK_STREAM, IPPROTO_TCP, address, portActual, NULL, kSocketBufferSize_DontSet, false, &sock ); if( err == EADDRINUSE ) { SockAddrCopy( sip, &tmpSA ); SockAddrSetPort( &tmpSA, portActual ); ds_ulog( kLogLevelError, "IP address %##a is already in use for TCP\n", &tmpSA ); break; } require_noerr( err, exit ); ForgetSocket( &pair->sockTCP ); pair->sockTCP = sock; sock = kInvalidSocketRef; SockAddrSetPort( sip, portActual ); } if( !err ) break; } require_noerr( err, exit ); // Create read sources for server sockets. for( i = 0; i < addrCount; ++i ) { const sockaddr_ip * const sip = &me->addrArray[ i ]; dispatch_source_t * const readSourceUDPPtr = &me->readSourceArrayUDP[ i ]; dispatch_source_t * const readSourceTCPPtr = &me->readSourceArrayTCP[ i ]; _DNSServerSocketPair * const pair = &sockPairs[ i ]; // Create read source for UDP socket. check( IsValidSocket( pair->sockUDP ) ); sockCtx = _DNSServerSocketContextCreate( pair->sockUDP, me, i, &err ); require_noerr( err, exit ); pair->sockUDP = kInvalidSocketRef; err = DispatchReadSourceCreate( sockCtx->sock, me->queue, _DNSServerUDPReadHandler, SocketContextCancelHandler, sockCtx, readSourceUDPPtr ); require_noerr( err, exit ); dispatch_resume( *readSourceUDPPtr ); sockCtx = NULL; // Create read source for TCP socket. check( IsValidSocket( pair->sockTCP ) ); sockCtx = _DNSServerSocketContextCreate( pair->sockTCP, me, i, &err ); require_noerr( err, exit ); pair->sockTCP = kInvalidSocketRef; err = DispatchReadSourceCreate( sockCtx->sock, me->queue, _DNSServerTCPAcceptHandler, SocketContextCancelHandler, sockCtx, readSourceTCPPtr ); require_noerr( err, exit ); dispatch_resume( *readSourceTCPPtr ); sockCtx = NULL; ds_ulog( kLogLevelInfo, "Server is listening on %##a\n", sip ); } exit: if( sockPairs ) { for( i = 0; i < addrCount; ++i ) { ForgetSocket( &sockPairs[ i ].sockUDP ); ForgetSocket( &sockPairs[ i ].sockTCP ); } } FreeNullSafe( sockPairsHeap ); if( sockCtx ) SocketContextRelease( sockCtx ); if( outActualPort ) *outActualPort = (uint16_t) portActual; return( err ); } //=========================================================================================================================== typedef struct { DNSServerRef server; size_t index; } DNSServerContext; static void _DNSServerContextFree( DNSServerContext *inCtx ); static void _DNSServerSocketContextFinalizer( void *inCtx ); static SocketContext * _DNSServerSocketContextCreate( SocketRef inSock, DNSServerRef inServer, size_t inIndex, OSStatus * outError ) { OSStatus err; SocketContext * sockCtx = NULL; DNSServerContext * ctx; ctx = (DNSServerContext *) calloc( 1, sizeof( *ctx ) ); require_action( ctx, exit, err = kNoMemoryErr ); ctx->index = inIndex; ctx->server = inServer; CFRetain( ctx->server ); sockCtx = SocketContextCreateEx( inSock, ctx, _DNSServerSocketContextFinalizer, &err ); require_noerr( err, exit ); ctx = NULL; exit: if( outError ) *outError = err; if( ctx ) _DNSServerContextFree( ctx ); return( sockCtx ); } static void _DNSServerSocketContextFinalizer( void *inCtx ) { _DNSServerContextFree( (DNSServerContext *) inCtx ); } static void _DNSServerContextFree( DNSServerContext *inCtx ) { ForgetCF( &inCtx->server ); free( inCtx ); } //=========================================================================================================================== static void _DNSServerStopOnQueue( void *inContext ); static void _DNSServerStop2( void *inContext ); static void _DNSServerStop( DNSServerRef me ) { CFRetain( me ); dispatch_async_f( me->queue, me, _DNSServerStopOnQueue ); } static void _DNSServerStopOnQueue( void *inContext ) { DNSServerRef const me = (DNSServerRef) inContext; _DNSServerStopInternal( me, kNoErr ); CFRelease( me ); } static void _DNSServerStopInternal( void *inContext, OSStatus inError ) { DNSServerRef const me = (DNSServerRef) inContext; DNSServerConnectionRef cnx; size_t i; require_quiet( !me->stopped, exit ); me->stopped = true; me->stopErr = inError; if( me->responseList ) { _DNSServerDelayedResponseListFree( me->responseList ); me->responseList = NULL; } dispatch_source_forget( &me->responseTimer ); for( i = 0; i < me->addrCount; ++i ) { dispatch_source_forget( &me->readSourceArrayUDP[ i ] ); dispatch_source_forget( &me->readSourceArrayTCP[ i ] ); } while( ( cnx = me->connectionList ) != NULL ) { me->connectionList = cnx->next; _DNSServerConnectionStop( cnx, false ); cnx->next = NULL; CFRelease( cnx ); } dispatch_source_forget( &me->connectionTimer ); CFRetain( me ); dispatch_async_f( me->queue, me, _DNSServerStop2 ); if( me->started ) CFRelease( me ); exit: return; } static void _DNSServerStop2( void *inContext ) { DNSServerRef const me = (DNSServerRef) inContext; if( me->stopHandler ) me->stopHandler( me->stopErr, me->userContext ); CFRelease( me ); } //=========================================================================================================================== static void _DNSServerUDPReadHandler( void *inContext ) { OSStatus err; SocketContext * const sockCtx = (SocketContext *) inContext; const DNSServerContext * const ctx = (DNSServerContext *) sockCtx->userContext; const DNSServerRef me = ctx->server; ssize_t n; sockaddr_ip client; socklen_t clientLen; uint8_t * respPtr = NULL; // malloc'd size_t respLen; uint8_t msg[ 512 ]; // Receive message. clientLen = (socklen_t) sizeof( client ); n = recvfrom( sockCtx->sock, (char *) msg, sizeof( msg ), 0, &client.sa, &clientLen ); err = map_socket_value_errno( sockCtx->sock, n >= 0, n ); require_noerr( err, exit ); if( n < kDNSHeaderLength ) { ds_ulog( kLogLevelInfo, "UDP: Received %zd bytes from %##a to %##a: Message is too small (< %d bytes)\n", n, &client, &me->addrArray[ ctx->index ], kDNSHeaderLength ); goto exit; } ds_ulog( kLogLevelInfo, "UDP: Received %zd bytes from %##a to %##a -- %.1{du:dnsmsg}\n", n, &client, &me->addrArray[ ctx->index ], msg, (size_t) n ); // Create response. err = _DNSServerAnswerQueryForUDP( me, msg, (size_t) n, ctx->index + 1, &respPtr, &respLen ); if( err == kSkipErr ) ds_ulog( kLogLevelInfo, "UDP: Ignoring query\n" ); require_noerr_quiet( err, exit ); if( me->responseDelayMs > 0 ) // Defer response. { err = _DNSServerScheduleDelayedResponse( me, sockCtx->sock, &client.sa, respPtr, respLen, ctx->index ); require_noerr( err, exit ); respPtr = NULL; } else // Send response. { ds_ulog( kLogLevelInfo, "UDP: Sending %zu byte response from %##a to %##a -- %.1{du:dnsmsg}\n", respLen, &me->addrArray[ ctx->index ], &client, respPtr, respLen ); n = sendto( sockCtx->sock, (char *) respPtr, respLen, 0, &client.sa, clientLen ); err = map_socket_value_errno( sockCtx->sock, n == (ssize_t) respLen, n ); require_noerr( err, exit ); } exit: FreeNullSafe( respPtr ); } //=========================================================================================================================== static void _DNSServerSendDelayedResponses( void *inContext ); static OSStatus _DNSServerScheduleDelayedResponse( DNSServerRef me, SocketRef inSock, const struct sockaddr * inDestAddr, uint8_t * inMsgPtr, size_t inMsgLen, size_t inIndex ) { OSStatus err; DNSServerDelayedResponse * resp; DNSServerDelayedResponse * newResp; DNSServerDelayedResponse ** ptr; uint64_t dueTicks; dueTicks = UpTicks() + MillisecondsToUpTicks( me->responseDelayMs ); newResp = (DNSServerDelayedResponse *) calloc( 1, sizeof( *newResp ) ); require_action( newResp, exit, err = kNoMemoryErr ); newResp->dueTicks = dueTicks; newResp->msgPtr = inMsgPtr; newResp->msgLen = inMsgLen; newResp->index = inIndex; newResp->sock = inSock; SockAddrCopy( inDestAddr, &newResp->client ); if( !me->responseList || ( _TicksDiff( dueTicks, me->responseList->dueTicks ) < 0 ) ) { dispatch_source_forget( &me->responseTimer ); err = DispatchTimerOneShotCreate( dispatch_time_milliseconds( me->responseDelayMs ), 0, me->queue, _DNSServerSendDelayedResponses, me, &me->responseTimer ); require_noerr( err, exit ); dispatch_resume( me->responseTimer ); } for( ptr = &me->responseList; ( resp = *ptr ) != NULL; ptr = &resp->next ) { if( _TicksDiff( newResp->dueTicks, resp->dueTicks ) < 0 ) break; } newResp->next = resp; *ptr = newResp; newResp = NULL; err = kNoErr; exit: if( newResp ) _DNSServerDelayedResponseFree( newResp ); return( err ); } static void _DNSServerSendDelayedResponses( void *inContext ) { OSStatus err; const DNSServerRef me = (DNSServerRef) inContext; DNSServerDelayedResponse * resp; DNSServerDelayedResponse * freeList; int64_t deltaTicks; dispatch_source_forget( &me->responseTimer ); deltaTicks = -1; freeList = NULL; while( ( resp = me->responseList ) != NULL ) { ssize_t n; uint64_t nowTicks = UpTicks(); deltaTicks = _TicksDiff( resp->dueTicks, nowTicks ); if( deltaTicks > 0 ) break; me->responseList = resp->next; ds_ulog( kLogLevelInfo, "UDP: Sending %zu byte delayed response from %##a to %##a -- %.1{du:dnsmsg}\n", resp->msgLen, &me->addrArray[ resp->index ], &resp->client, resp->msgPtr, resp->msgLen ); n = sendto( resp->sock, (char *) resp->msgPtr, resp->msgLen, 0, &resp->client.sa, SockAddrGetSize( &resp->client ) ); err = map_socket_value_errno( resp->sock, n == (ssize_t) resp->msgLen, n ); check_noerr( err ); resp->next = freeList; freeList = resp; } if( deltaTicks > 0 ) { uint64_t deltaNs; deltaNs = UpTicksToNanoseconds( (uint64_t) deltaTicks ); if( deltaNs > INT64_MAX ) deltaNs = INT64_MAX; err = DispatchTimerOneShotCreate( dispatch_time( DISPATCH_TIME_NOW, (int64_t) deltaNs ), 0, me->queue, _DNSServerSendDelayedResponses, me, &me->responseTimer ); require_noerr( err, exit ); dispatch_resume( me->responseTimer ); } exit: if( freeList ) _DNSServerDelayedResponseListFree( freeList ); } //=========================================================================================================================== static void _DNSServerDelayedResponseFree( DNSServerDelayedResponse *inResp ) { ForgetMem( &inResp->msgPtr ); inResp->sock = kInvalidSocketRef; free( inResp ); } //=========================================================================================================================== static void _DNSServerDelayedResponseListFree( DNSServerDelayedResponse *inList ) { DNSServerDelayedResponse * resp; while( ( resp = inList ) != NULL ) { inList = resp->next; _DNSServerDelayedResponseFree( resp ); } } //=========================================================================================================================== #define kDNSServerConnectionExpirationTimeSecs 5 #define kDNSServerConnectionExpirationTimeMs ( kDNSServerConnectionExpirationTimeSecs * kMillisecondsPerSecond ) static void _DNSServerTCPAcceptHandler( void *inContext ) { OSStatus err; SocketContext * const sockCtx = (SocketContext *) inContext; const DNSServerContext * const ctx = (DNSServerContext *) sockCtx->userContext; const DNSServerRef me = ctx->server; DNSServerConnectionRef cnx = NULL; sockaddr_ip remote, local; socklen_t len; SocketRef sock; len = (socklen_t) sizeof( remote ); sock = accept( sockCtx->sock, &remote.sa, &len ); err = map_socket_creation_errno( sock ); require_noerr( err, exit ); len = (socklen_t) sizeof( local ); err = getsockname( sock, &local.sa, &len ); if( unlikely( err ) ) SockAddrCopy( &me->addrArray[ ctx->index ], &local ); err = _DNSServerConnectionCreate( me, &local.sa, &remote.sa, ctx->index, &cnx ); require_noerr_quiet( err, exit ); err = _DNSServerConnectionStart( cnx, sock ); require_noerr( err, exit ); sock = kInvalidSocketRef; if( !me->connectionList ) _DNSServerResetConnectionTimerMs( me, kDNSServerConnectionExpirationTimeMs ); cnx->next = me->connectionList; me->connectionList = cnx; cnx = NULL; exit: ForgetSocket( &sock ); if( cnx ) { _DNSServerConnectionStop( cnx, true ); CFRelease( cnx ); } } //=========================================================================================================================== static void _DNSServerConnectionTimerHandler( void *inContext ) { const DNSServerRef me = (DNSServerRef) inContext; DNSServerConnectionRef cnx; DNSServerConnectionRef * ptr; uint64_t nowTicks; int64_t delta, deltaMin; nowTicks = UpTicks(); deltaMin = INT64_MAX; ptr = &me->connectionList; while( ( cnx = *ptr ) != NULL ) { delta = _TicksDiff( cnx->expirationTicks, nowTicks ); if( delta <= 0 ) { ds_ulog( kLogLevelInfo, "Timing out TCP connection: %##a <-> %##a\n", &cnx->local, &cnx->remote ); *ptr = cnx->next; cnx->next = NULL; _DNSServerConnectionStop( cnx, false ); CFRelease( cnx ); } else { if( delta < deltaMin ) deltaMin = delta; ptr = &cnx->next; } } if( me->connectionList ) { const uint64_t timeMs = UpTicksToMilliseconds( (uint64_t) deltaMin ); check( timeMs <= kDNSServerConnectionExpirationTimeMs ); _DNSServerResetConnectionTimerMs( me, timeMs + 1 ); } } //=========================================================================================================================== static void _DNSServerResetConnectionTimerMs( DNSServerRef me, uint64_t inTimeoutMs ) { OSStatus err; dispatch_source_forget( &me->connectionTimer ); if( inTimeoutMs == 0 ) inTimeoutMs = 1; err = DispatchTimerOneShotCreate( dispatch_time_milliseconds( inTimeoutMs ), UINT64_C( 10 ) * kNanosecondsPerMillisecond, me->queue, _DNSServerConnectionTimerHandler, me, &me->connectionTimer ); if( likely( !err ) ) dispatch_resume( me->connectionTimer ); else ds_ulog( kLogLevelError, "Failed to create connection timer: %#m\n", err ); } //=========================================================================================================================== static OSStatus _DNSServerInitializeResponseMessage( DataBuffer * inDB, uint16_t inID, uint16_t inFlags, const uint8_t * inQName, uint16_t inQType, uint16_t inQClass ); static OSStatus _DNSServerAnswerQueryDynamically( DNSServerRef inServer, const uint8_t * inQName, int inQType, int inQClass, size_t inIndex, size_t inTruncateLen, Boolean inDNSSEC, Boolean inUseBadAddrs, DataBuffer * inDB ); static OSStatus _DNSServerAnswerQuery( DNSServerRef me, const uint8_t * const inMsgPtr, const size_t inMsgLen, const size_t inIndex, const Boolean inForTCP, uint8_t ** const outResponsePtr, size_t * const outResponseLen ) { OSStatus err; DataBuffer db; const uint8_t * qptr; const DNSHeader * hdr; const uint8_t * qOptPtr; size_t qOptLen, truncateLen; unsigned int qflags, rcode; uint16_t msgID, qtype, qclass, rflags; uint8_t qname[ kDomainNameLengthMax ]; uint8_t dbBuf[ 512 ]; Boolean dnssecOK, usePadding, useBadAddrs; DataBuffer_Init( &db, dbBuf, sizeof( dbBuf ), kDNSMaxTCPMessageSize ); require_action_quiet( inMsgLen >= kDNSHeaderLength, exit, err = kUnderrunErr ); hdr = (const DNSHeader *) inMsgPtr; qflags = DNSHeaderGetFlags( hdr ); // Minimal checking of the query message's header. require_action_quiet( !( qflags & kDNSHeaderFlag_Response ), exit, err = kRequestErr ); require_action_quiet( DNSFlagsGetOpCode( qflags ) == kDNSOpCode_Query, exit, err = kRequestErr ); require_action_quiet( DNSHeaderGetQuestionCount( hdr ) == 1, exit, err = kRequestErr ); qptr = (const uint8_t *) &hdr[ 1 ]; err = DNSMessageExtractQuestion( inMsgPtr, inMsgLen, qptr, qname, &qtype, &qclass, NULL ); require_noerr( err, exit ); // Check if this query should be ignored because of its QTYPE. if( qclass == kDNSClassType_IN ) { size_t i; for( i = 0; i < me->ignoredQTypeCount; ++i ) { if( qtype == me->ignoredQTypes[ i ] ) { err = kSkipErr; goto exit; } } } // Set up response flags. rflags = kDNSHeaderFlag_Response; if( qflags & kDNSHeaderFlag_RecursionDesired ) rflags |= kDNSHeaderFlag_RecursionDesired; DNSFlagsSetOpCode( rflags, kDNSOpCode_Query ); // Get OPT record, if any. dnssecOK = false; usePadding = false; err = DNSMessageGetOptRecord( inMsgPtr, inMsgLen, &qOptPtr, &qOptLen ); require_noerr_action_quiet( err, done, rcode = kDNSRCode_FormErr ); // Create a tentative response message. msgID = DNSHeaderGetID( hdr ); if( me->badUDPMode && !inForTCP ) ++msgID; err = _DNSServerInitializeResponseMessage( &db, msgID, rflags, qname, qtype, qclass ); require_noerr_action( err, done, rcode = kDNSRCode_ServFail ); // Complete the response message. if( qOptPtr ) { const dns_fixed_fields_opt * const opt = (const dns_fixed_fields_opt *) qOptPtr; unsigned int version; version = dns_fixed_fields_opt_get_version( opt ); require_action_quiet( version == 0, done, rcode = kDNSRCode_BADVERS ); if( dns_fixed_fields_opt_get_extended_flags( opt ) & kDNSExtendedFlag_DNSSECOK ) dnssecOK = true; // If the query includes EDNS(0) padding, then so must the response according to // . if( inForTCP ) { const uint8_t * ptr; const uint8_t * const optEnd = &qOptPtr[ qOptLen ]; ptr = (const uint8_t *) &opt[ 1 ]; while( ptr < optEnd ) { const dns_fixed_fields_option * optionFields; unsigned int optionCode, optionLen; require_action_quiet( (size_t)( optEnd - ptr ) >= sizeof( *optionFields ), done, rcode = kDNSRCode_FormErr ); optionFields = (const dns_fixed_fields_option *) ptr; optionCode = dns_fixed_fields_option_get_code( optionFields ); optionLen = dns_fixed_fields_option_get_length( optionFields ); ptr = (const uint8_t *) &optionFields[ 1 ]; require_action_quiet( (size_t)( optEnd - ptr ) >= optionLen, done, rcode = kDNSRCode_FormErr ); ptr += optionLen; if( optionCode == kDNSEDNS0OptionCode_Padding ) { usePadding = true; break; } } truncateLen = 0; } else { // For UDP, the response's length limit for truncation should account for the inclusion of an OPT record. check_compile_time_code( sizeof( *opt ) < kDNSMaxUDPMessageSize ); truncateLen = kDNSMaxUDPMessageSize - sizeof( *opt ); } } else { truncateLen = inForTCP ? 0 : kDNSMaxUDPMessageSize; } useBadAddrs = me->badUDPMode && !inForTCP; err = _DNSServerAnswerQueryDynamically( me, qname, qtype, qclass, inIndex, truncateLen, dnssecOK, useBadAddrs, &db ); if( err == kSkipErr ) goto exit; require_noerr_action( err, done, rcode = kDNSRCode_ServFail ); rcode = kDNSRCode_NoError; done: // Create an error response if necessary. if( rcode != kDNSRCode_NoError ) { const unsigned int rcode4BitMax = 15; // 2^4 - 1 const unsigned int rcode12BitMax = 4095; // 2^12 - 1 // One of the following must be true. // 1. The RCODE value must completely fit in the response header's 4-bit RCODE field. // 2. The response will include an OPT record and the RCODE value can be represented as an unsigned 12-bit // integer (upper eight bits go in the OPT record's EXTENDED-RCODE field). require_fatal( ( rcode <= rcode4BitMax ) || ( ( rcode <= rcode12BitMax ) && qOptPtr ), "Invalid RCODE value %u for response with%s OPT record", rcode, qOptPtr ? "" : "out" ); DNSFlagsSetRCode( rflags, rcode & 0x0FU ); err = _DNSServerInitializeResponseMessage( &db, DNSHeaderGetID( hdr ), rflags, qname, qtype, qclass ); require_noerr( err, exit ); } // Include an OPT record in the response if the query included one. // See . if( qOptPtr ) { DNSHeader * rhdr; uint16_t additionalCount; dns_fixed_fields_opt optFields; size_t rOptOffset; memset( &optFields, 0, sizeof( optFields ) ); dns_fixed_fields_opt_set_type( &optFields, kDNSRecordType_OPT ); dns_fixed_fields_opt_set_udp_payload_size( &optFields, kDNSMaxUDPMessageSize ); dns_fixed_fields_opt_set_extended_rcode( &optFields, ( rcode >> 4 ) & 0xFFU ); if( dnssecOK ) dns_fixed_fields_opt_set_extended_flags( &optFields, kDNSExtendedFlag_DNSSECOK ); rOptOffset = DataBuffer_GetLen( &db ); err = DataBuffer_Append( &db, &optFields, sizeof( optFields ) ); require_noerr( err, exit ); // Pad responses using the block-length padding strategy using a 468-octet block length as recommended by // . if( usePadding ) { dns_fixed_fields_opt * rOptPtr; dns_fixed_fields_option padOption; size_t curLen, newLen, rdLen; uint16_t padLen; curLen = DataBuffer_GetLen( &db ); newLen = RoundUp( curLen + sizeof( padOption ), 468 ); require_action( newLen > curLen, exit, err = kSizeErr ); rdLen = newLen - curLen; require_action( rdLen <= UINT16_MAX, exit, err = kSizeErr ); memset( &padOption, 0, sizeof( padOption ) ); dns_fixed_fields_option_set_code( &padOption, kDNSEDNS0OptionCode_Padding ); padLen = (uint16_t)( rdLen - sizeof( padOption ) ); dns_fixed_fields_option_set_length( &padOption, padLen ); err = DataBuffer_Append( &db, &padOption, sizeof( padOption ) ); require_noerr( err, exit ); if( padLen > 0 ) { err = DataBuffer_AppendF( &db, "%{fill}", 0, padLen ); require_noerr( err, exit ); } check( DataBuffer_GetLen( &db ) == newLen ); rOptPtr = (dns_fixed_fields_opt *)( DataBuffer_GetPtr( &db ) + rOptOffset ); dns_fixed_fields_opt_set_rdlen( rOptPtr, (uint16_t) rdLen ); } check( DataBuffer_GetLen( &db ) >= sizeof( *rhdr ) ); rhdr = (DNSHeader *) DataBuffer_GetPtr( &db ); additionalCount = DNSHeaderGetAdditionalCount( rhdr ) + 1; DNSHeaderSetAdditionalCount( rhdr, additionalCount ); } err = DataBuffer_Detach( &db, outResponsePtr, outResponseLen ); require_noerr( err, exit ); exit: DataBuffer_Free( &db ); return( err ); } //=========================================================================================================================== static OSStatus _DNSServerInitializeResponseMessage( DataBuffer * inDB, uint16_t inID, uint16_t inFlags, const uint8_t * inQName, uint16_t inQType, uint16_t inQClass ) { OSStatus err; DNSHeader header; DataBuffer_Reset( inDB ); memset( &header, 0, sizeof( header ) ); DNSHeaderSetID( &header, inID ); DNSHeaderSetFlags( &header, inFlags ); DNSHeaderSetQuestionCount( &header, 1 ); err = DataBuffer_Append( inDB, &header, sizeof( header ) ); require_noerr( err, exit ); err = _DataBuffer_AppendDNSQuestion( inDB, inQName, DomainNameLength( inQName ), inQType, inQClass ); require_noerr( err, exit ); exit: return( err ); } //=========================================================================================================================== // DNS Server QNAME Labels #define kLabel_IPv4 "ipv4" #define kLabel_IPv6 "ipv6" #define kLabelPrefix_Alias "alias" #define kLabelPrefix_AliasTTL "alias-ttl" #define kLabelPrefix_Count "count-" #define kLabelPrefix_Index "index-" #define kLabelPrefix_RCode "rcode-" #define kLabelPrefix_SRV "srv-" #define kLabelPrefix_Tag "tag-" #define kLabelPrefix_TTL "ttl-" // Experimental Labels #define kLabel_CommandSuspend "command-suspend" // Causes the DNS server to suspend query responses. #define kLabel_CommandResume "command-resume" // Causes the DNS server to resume query responses. #define kLabelPrefix_Offset "offset-" // Specifies an address offset for A and AAAA record addresses. #define kLabelPrefix_PDelay "pdelay-" // Specifies a simulated processing delay in milliseconds. #define kLabelPrefix_Zone "z-" // format: z-- 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; typedef uint32_t DNSNameFlags; #define kDNSNameFlag_HasA ( 1U << 0 ) #define kDNSNameFlag_HasAAAA ( 1U << 1 ) #define kDNSNameFlag_HasSOA ( 1U << 2 ) #define kDNSNameFlag_HasSRV ( 1U << 3 ) #define kDNSNameFlag_HasPTRv4 ( 1U << 4 ) #define kDNSNameFlag_HasPTRv6 ( 1U << 5 ) #define kDNSNameFlag_HasRRSIG ( 1U << 6 ) #define kDNSNameFlag_HasDNSKEY ( 1U << 7 ) #define kDNSNameFlag_HasDS ( 1U << 8 ) #define kAliasTTLCountMax ( ( kDomainLabelLengthMax - sizeof_string( kLabelPrefix_AliasTTL ) ) / 2 ) #define kParsedSRVCountMax ( kDomainNameLengthMax / ( 1 + sizeof_string( kLabelPrefix_SRV ) + 5 ) ) typedef enum { kDNSServerAction_None = 0, kDNSServerAction_Suspend = 1, kDNSServerAction_Resume = 2 } DNSServerAction; static Boolean _DNSServerParseHostName( DNSServerRef inServer, const uint8_t * inQName, uint32_t * outAliasCount, uint32_t outAliasTTLs[ kAliasTTLCountMax ], uint32_t * outAliasTTLCount, uint32_t * outCount, uint32_t * outRandCount, uint32_t * outIndex, int * outRCode, uint32_t * outTTL, uint32_t * outOffset, uint32_t * outProcDelayMs, DNSNameFlags * outFlags, const uint8_t ** outZone, const uint8_t ** outZoneParent, DNSKeyInfoRef * outZSK, DNSKeyInfoRef * outKSK, DNSKeyInfoRef * outParentZSK, DNSServerAction * outAction ); static Boolean _DNSServerParseSRVName( DNSServerRef inServer, const uint8_t * inName, const uint8_t ** outDomainPtr, size_t * outDomainLen, ParsedSRV outSRVArray[ kParsedSRVCountMax ], size_t * outSRVCount ); static Boolean _DNSServerParseReverseIPv4Name( DNSServerRef me, const uint8_t *inQName, unsigned int *outHostID ); static Boolean _DNSServerParseReverseIPv6Name( DNSServerRef me, const uint8_t *inQName, unsigned int *outHostID ); #if( DEBUG ) static void _DNSServerSigCheck( const uint8_t * inOwner, int inTypeCovered, const void * inMsgPtr, size_t inMsgLen, const uint8_t * inSignaturePtr, const size_t inSignatureLen, DNSKeyInfoRef inKeyInfo ); #endif typedef enum { kQueryStatus_Null = 0, kQueryStatus_OK = 1, kQueryStatus_Truncated = 2, kQueryStatus_NotImplemented = 3, kQueryStatus_Refused = 4 } QueryStatus; static OSStatus _DNSServerAnswerQueryDynamically( DNSServerRef me, const uint8_t * const inQName, const int inQType, const int inQClass, const size_t inIndex, const size_t inTruncateLen, const Boolean inDNSSEC, const Boolean inUseBadAddrs, DataBuffer * const inDB ) { OSStatus err; uint32_t aliasCount = 0; uint32_t aliasTTLs[ kAliasTTLCountMax ]; uint32_t aliasTTLCount = 0; uint32_t addrCount = 0; uint32_t randCount = 0; uint32_t index = 0; int rcodeOverride = -1; uint32_t ttl = 0; uint32_t offset = 0; uint32_t procDelayMs = 0; DNSNameFlags nameFlags = 0; const uint8_t * zone = NULL; const uint8_t * zoneParent = NULL; DNSKeyInfoRef zsk = NULL; DNSKeyInfoRef ksk = NULL; DNSKeyInfoRef zskParent = NULL; DNSServerAction action = kDNSServerAction_None; const uint8_t * srvDomainPtr = NULL; size_t srvDomainLen = 0; ParsedSRV srvArray[ kParsedSRVCountMax ]; size_t srvCount = 0; unsigned int hostID = 0; struct timeval now; DNSHeader * hdr; unsigned int flags; int rcode; QueryStatus status; unsigned int answerCount = 0; unsigned int additionalCount = 0; Boolean nameExists = false; uint8_t * qnameLower = NULL; size_t qnameLowerLen; const uint8_t * ownerLower; size_t ownerLowerLen; DataBuffer * sigMsg = NULL; DataBuffer sigDB; uint8_t sigBuf[ 256 ]; uint8_t nameCPtr[ 2 ]; uint64_t startTicks; Boolean wasSuspended; startTicks = UpTicks(); require_action_quiet( inQClass == kDNSServiceClass_IN, done, status = kQueryStatus_NotImplemented ); wasSuspended = me->suspended; nameExists = _DNSServerParseHostName( me, inQName, &aliasCount, aliasTTLs, &aliasTTLCount, &addrCount, &randCount, &index, &rcodeOverride, &ttl, &offset, &procDelayMs, &nameFlags, &zone, &zoneParent, &zsk, &ksk, &zskParent, &action ); if( nameExists ) { check( !( ( aliasCount > 0 ) && ( aliasTTLCount > 0 ) ) ); check( ( randCount == 0 ) || ( ( randCount >= addrCount ) && ( randCount <= 255 ) ) ); check( rcodeOverride <= 15 ); check( !( nameFlags & kDNSNameFlag_HasRRSIG ) || ( zsk && ksk && zskParent ) ); switch( action ) { case kDNSServerAction_Suspend: me->suspended = true; break; case kDNSServerAction_Resume: me->suspended = false; break; case kDNSServerAction_None: break; } if( aliasTTLCount > 0 ) aliasCount = (uint32_t) aliasTTLCount; if( index != 0 ) { if( index == inIndex ) { rcodeOverride = -1; } else if( rcodeOverride < 0 ) { err = kSkipErr; goto exit; } else { addrCount = 0; } } } // If the server is in suspended mode, but didn't transition into suspended mode just now, then skip this query. if( me->suspended && wasSuspended ) { err = kSkipErr; goto exit; } if( !nameExists ) { if( ( nameExists = _DNSServerParseSRVName( me, inQName, &srvDomainPtr, &srvDomainLen, srvArray, &srvCount ) ) ) { nameFlags = kDNSNameFlag_HasSRV; } else if( ( nameExists = _DNSServerParseReverseIPv4Name( me, inQName, &hostID ) ) ) { check( ( hostID >= 1 ) && ( hostID <= 255 ) ); nameFlags = kDNSNameFlag_HasPTRv4; } else if( ( nameExists = _DNSServerParseReverseIPv6Name( me, inQName, &hostID ) ) ) { check( ( hostID >= 1 ) && ( hostID <= 255 ) ); nameFlags = kDNSNameFlag_HasPTRv6; } } require_action_quiet( nameExists, done, status = kQueryStatus_OK ); err = DomainNameDupLower( inQName, &qnameLower, &qnameLowerLen ); require_noerr( err, exit ); gettimeofday( &now, NULL ); if( aliasCount > 0 ) { size_t nameOffset, rdataLabelLen; const uint8_t * parentLower; size_t parentLowerLen = 0; uint32_t i; dns_fixed_fields_record recFields; uint8_t rdataLabel[ 1 + kDomainLabelLengthMax ]; uint8_t parentCPtr[ 2 ]; Boolean needSig; // If aliasCount is non-zero, then the first label of QNAME is either "alias" or "alias-". parentCPtr is a // name compression pointer to the second label of QNAME, i.e., the parent domain name of QNAME. It's used for // the RDATA of CNAME records whose canonical name ends with the superdomain name. It may also be used to // construct CNAME record names when the offset to the previous CNAME's RDATA doesn't fit in a compression // pointer. DNSMessageWriteLabelPointer( parentCPtr, kDNSHeaderLength + ( 1 + inQName[ 0 ] ) ); rdataLabel[ 0 ] = 0; rdataLabelLen = 1; nameOffset = kDNSHeaderLength; // The name of the first CNAME record is equal to QNAME. needSig = ( inDNSSEC && ( nameFlags & kDNSNameFlag_HasRRSIG ) ) ? true : false; if( needSig ) { parentLower = DomainNameGetNextLabel( qnameLower ); parentLowerLen = DomainNameLength( parentLower ); } for( i = aliasCount; i >= 1; --i ) { size_t nameLabelLen, nameLen, rdataLen, recordLen; uint32_t aliasTTL; uint8_t nameLabel[ 1 + kDomainLabelLengthMax ]; Boolean useNamePtr; const Boolean useAliasTTLs = ( aliasTTLCount > 0 ) ? true : false; memcpy( nameLabel, rdataLabel, rdataLabelLen ); nameLabelLen = rdataLabelLen; if( nameOffset <= kDNSCompressionOffsetMax ) { DNSMessageWriteLabelPointer( nameCPtr, nameOffset ); nameLen = sizeof( nameCPtr ); useNamePtr = true; } else { nameLen = nameLabelLen + sizeof( parentCPtr ); useNamePtr = false; } if( i > 1 ) { // There's at least one alias/CNAME left. uint8_t * dst = &rdataLabel[ 1 ]; const uint8_t * lim = &rdataLabel[ countof( rdataLabel ) ]; size_t maxLen; int n; maxLen = (size_t)( lim - dst ); if( useAliasTTLs ) { uint32_t j; n = MemPrintF( dst, maxLen, "%s", kLabelPrefix_AliasTTL ); require_fatal( ( n > 0 ) && ( ( (size_t) n ) <= maxLen ), "Failed to print AliasTTL label" ); dst += n; for( j = aliasCount - ( i - 1 ); j < aliasCount; ++j ) { maxLen = (size_t)( lim - dst ); n = MemPrintF( dst, maxLen, "-%u", aliasTTLs[ j ] ); require_fatal( ( n > 0 ) && ( ( (size_t) n ) <= maxLen ), "Failed to print AliasTTL label" ); dst += n; } } else if( i == 2 ) { n = MemPrintF( dst, maxLen, "%s", kLabelPrefix_Alias ); require_fatal( ( n > 0 ) && ( ( (size_t) n ) <= maxLen ), "Failed to print Alias label" ); dst += n; } else { n = MemPrintF( dst, maxLen, "%s-%u", kLabelPrefix_Alias, i - 1 ); require_fatal( ( n > 0 ) && ( ( (size_t) n ) <= maxLen ), "Failed to print Alias label" ); dst += n; } rdataLabel[ 0 ] = (uint8_t)( dst - &rdataLabel[ 1 ] ); rdataLabelLen = 1 + rdataLabel[ 0 ]; rdataLen = rdataLabelLen + sizeof( parentCPtr ); } else { // This is the final CNAME. rdataLen = sizeof( parentCPtr ); } if( inTruncateLen > 0 ) { recordLen = nameLen + sizeof( recFields ) + rdataLen; if( ( DataBuffer_GetLen( inDB ) + recordLen ) > inTruncateLen ) { status = kQueryStatus_Truncated; goto done; } } // Append CNAME record's NAME to response. if( useNamePtr ) { err = DataBuffer_Append( inDB, nameCPtr, sizeof( nameCPtr ) ); require_noerr( err, exit ); } else { err = DataBuffer_Append( inDB, nameLabel, nameLabelLen ); require_noerr( err, exit ); err = DataBuffer_Append( inDB, parentCPtr, sizeof( parentCPtr ) ); require_noerr( err, exit ); } // Append CNAME record's TYPE, CLASS, TTL, and RDLENGTH to response. aliasTTL = useAliasTTLs ? aliasTTLs[ aliasCount - i ] : me->defaultTTL; dns_fixed_fields_record_init( &recFields, kDNSServiceType_CNAME, kDNSServiceClass_IN, aliasTTL, (uint16_t) rdataLen ); err = DataBuffer_Append( inDB, &recFields, sizeof( recFields ) ); require_noerr( err, exit ); // Save offset of CNAME record's RDATA, which may be used for the name of the next CNAME record. nameOffset = DataBuffer_GetLen( inDB ); // Append CNAME record's RDATA to response. if( i > 1 ) { // There's at least one CNAME left. err = DataBuffer_Append( inDB, rdataLabel, rdataLabelLen ); require_noerr( err, exit ); } err = DataBuffer_Append( inDB, parentCPtr, sizeof( parentCPtr ) ); require_noerr( err, exit ); ++answerCount; if( needSig ) { dns_fixed_fields_rrsig sigFields; const size_t signerLen = DomainNameLength( zone ); uint32_t inceptionSecs; uint8_t signature[ kDNSServerSignatureLengthMax ]; size_t signatureLen; int labelCount; Boolean didSign; // Initialize signing buffer. check( !sigMsg ); sigMsg = &sigDB; DataBuffer_Init( sigMsg, sigBuf, sizeof( sigBuf ), SIZE_MAX ); // Append RRSIG record RDATA fixed fields to signing buffer. memset( &sigFields, 0, sizeof( sigFields ) ); dns_fixed_fields_rrsig_set_type_covered( &sigFields, kDNSServiceType_CNAME ); dns_fixed_fields_rrsig_set_algorithm( &sigFields, DNSKeyInfoGetAlgorithm( zsk ) ); labelCount = DomainNameLabelCount( inQName ); check( labelCount >= 0 ); dns_fixed_fields_rrsig_set_labels( &sigFields, (uint8_t) labelCount ); dns_fixed_fields_rrsig_set_original_ttl( &sigFields, aliasTTL ); inceptionSecs = (uint32_t) now.tv_sec; dns_fixed_fields_rrsig_set_signature_expiration( &sigFields, inceptionSecs + kSecondsPerDay ); dns_fixed_fields_rrsig_set_signature_inception( &sigFields, inceptionSecs ); dns_fixed_fields_rrsig_set_key_tag( &sigFields, DNSKeyInfoGetKeyTag( zsk ) ); err = DataBuffer_Append( sigMsg, &sigFields, sizeof( sigFields ) ); require_noerr( err, exit ); // Append RRSIG record RDATA signer to signing buffer. err = DataBuffer_Append( sigMsg, zone, signerLen ); require_noerr( err, exit ); // Append expanded CNAME record owner to signing buffer. if( i == aliasCount ) { err = DataBuffer_Append( sigMsg, qnameLower, qnameLowerLen ); require_noerr( err, exit ); } else { err = DataBuffer_Append( sigMsg, nameLabel, nameLabelLen ); require_noerr( err, exit ); err = DataBuffer_Append( sigMsg, parentLower, parentLowerLen ); require_noerr( err, exit ); } // Append CNAME record fixed fields to signing buffer. err = DataBuffer_Append( sigMsg, &recFields, sizeof( recFields ) ); require_noerr( err, exit ); // Append expanded CNAME record RDATA to signing buffer. if( i > 1 ) { // There's at least one CNAME left. err = DataBuffer_Append( sigMsg, rdataLabel, rdataLabelLen ); require_noerr( err, exit ); } err = DataBuffer_Append( sigMsg, parentLower, parentLowerLen ); require_noerr( err, exit ); // Compute signature with ZSK. memset( signature, 0, sizeof( signature ) ); didSign = DNSKeyInfoSign( zsk, DataBuffer_GetPtr( sigMsg ), DataBuffer_GetLen( sigMsg ), signature, &signatureLen ); require_quiet( didSign, exit ); #if( DEBUG ) { const uint8_t * tmpPtr; uint8_t tmpBuf[ kDomainNameLengthMax ]; if( i == aliasCount ) { tmpPtr = inQName; } else { memcpy( tmpBuf, nameLabel, nameLabelLen ); memcpy( &tmpBuf[ nameLabelLen ], parentLower, parentLowerLen ); tmpPtr = tmpBuf; } _DNSServerSigCheck( tmpPtr, kDNSServiceType_CNAME, DataBuffer_GetPtr( sigMsg ), DataBuffer_GetLen( sigMsg ), signature, signatureLen, zsk ); } #endif rdataLen = sizeof( sigFields ) + signerLen + signatureLen; recordLen = nameLen + sizeof( recFields ) + rdataLen; if( ( inTruncateLen > 0 ) && ( ( DataBuffer_GetLen( inDB ) + recordLen ) > inTruncateLen ) ) { status = kQueryStatus_Truncated; goto done; } // Append RRSIG record NAME to response. if( useNamePtr ) { err = DataBuffer_Append( inDB, nameCPtr, sizeof( nameCPtr ) ); require_noerr( err, exit ); } else { err = DataBuffer_Append( inDB, nameLabel, nameLabelLen ); require_noerr( err, exit ); err = DataBuffer_Append( inDB, parentCPtr, sizeof( parentCPtr ) ); require_noerr( err, exit ); } // Append RRSIG record TYPE, CLASS, TTL, and RDLENGTH to response. dns_fixed_fields_record_init( &recFields, kDNSServiceType_RRSIG, kDNSServiceClass_IN, aliasTTL, (uint16_t) rdataLen ); err = DataBuffer_Append( inDB, &recFields, sizeof( recFields ) ); require_noerr( err, exit ); // Append RRSIG record RDATA fixed fields and signer to response. err = DataBuffer_Append( inDB, &sigFields, sizeof( sigFields ) ); require_noerr( err, exit ); err = DataBuffer_Append( inDB, zone, signerLen ); require_noerr( err, exit ); // Append RRSIG record RDATA signature to response. err = DataBuffer_Append( inDB, signature, signatureLen ); require_noerr( err, exit ); ++answerCount; DataBuffer_Free( sigMsg ); sigMsg = NULL; } } check_compile_time_code( sizeof( nameCPtr ) == sizeof( parentCPtr ) ); memcpy( nameCPtr, parentCPtr, sizeof( nameCPtr ) ); ownerLower = DomainNameGetNextLabel( qnameLower ); ownerLowerLen = DomainNameLength( ownerLower ); } else { // There are no aliases, so initialize the name compression pointer to point to QNAME. DNSMessageWriteLabelPointer( nameCPtr, kDNSHeaderLength ); ownerLower = qnameLower; ownerLowerLen = qnameLowerLen; } if( ( inQType == kDNSServiceType_A ) || ( inQType == kDNSServiceType_AAAA ) ) { dns_fixed_fields_record recFields; dns_fixed_fields_rrsig sigFields; uint8_t * idPtr; // Pointer to the host identifier portion of an IP address. size_t recordLen; // Length of the entire record. size_t rdataLen; // Length of record's RDATA. size_t signerLen = 0; unsigned int i; // For-loop counter. 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]. Boolean needSig, nameIsV6Only; if( inQType == kDNSServiceType_A ) { uint32_t baseAddr; require_action_quiet( nameFlags & kDNSNameFlag_HasA, done, status = kQueryStatus_OK ); rdataLen = 4; baseAddr = inUseBadAddrs ? kDNSServerBadBaseAddrV4 : kDNSServerBaseAddrV4; WriteBig32Typed( rdata, baseAddr ); idPtr = &rdata[ 3 ]; // The last octet is the host identifier since the IPv4 address block is /24. } else { const uint8_t ( *baseAddr )[ 16 ]; require_action_quiet( nameFlags & kDNSNameFlag_HasAAAA, done, status = kQueryStatus_OK ); rdataLen = 16; baseAddr = inUseBadAddrs ? &kDNSServerBadBaseAddrV6 : &kDNSServerBaseAddrV6; memcpy( rdata, baseAddr, rdataLen ); idPtr = &rdata[ 14 ]; // The last two octets are the host identifier since we allow up to 511 IPv6 addresses. } if( randCount > 0 ) { // Populate the array with all integers between 1 and , inclusive. for( i = 0; i < randCount; ++i ) randIntegers[ i ] = (uint8_t)( i + 1 ); // Prevent dubious static analyzer warning. // Note: _DNSServerParseHostName() 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 randomly chosen integers between // 1 and , 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. for( i = 0; i < addrCount; ++i ) { uint8_t tmp; uint32_t j; j = RandomRange( i, randCount - 1 ); if( i != j ) { tmp = randIntegers[ i ]; randIntegers[ i ] = randIntegers[ j ]; randIntegers[ j ] = tmp; } } } needSig = ( inDNSSEC && ( nameFlags & kDNSNameFlag_HasRRSIG ) ) ? true : false; if( needSig ) { uint32_t inceptionSecs; int labelCount; // Initialize signing buffer. check( !sigMsg ); sigMsg = &sigDB; DataBuffer_Init( sigMsg, sigBuf, sizeof( sigBuf ), SIZE_MAX ); // Append RRSIG record RDATA fixed fields to signing buffer. memset( &sigFields, 0, sizeof( sigFields ) ); dns_fixed_fields_rrsig_set_type_covered( &sigFields, (uint16_t) inQType ); dns_fixed_fields_rrsig_set_algorithm( &sigFields, DNSKeyInfoGetAlgorithm( zsk ) ); labelCount = DomainNameLabelCount( ownerLower ); check( labelCount >= 0 ); dns_fixed_fields_rrsig_set_labels( &sigFields, (uint8_t) labelCount ); dns_fixed_fields_rrsig_set_original_ttl( &sigFields, ttl ); inceptionSecs = (uint32_t) now.tv_sec; dns_fixed_fields_rrsig_set_signature_expiration( &sigFields, inceptionSecs + kSecondsPerDay ); dns_fixed_fields_rrsig_set_signature_inception( &sigFields, inceptionSecs ); dns_fixed_fields_rrsig_set_key_tag( &sigFields, DNSKeyInfoGetKeyTag( zsk ) ); err = DataBuffer_Append( sigMsg, &sigFields, sizeof( sigFields ) ); require_noerr( err, exit ); // Append RRSIG record RDATA signer to signing buffer. signerLen = DomainNameLength( zone ); err = DataBuffer_Append( sigMsg, zone, signerLen ); require_noerr( err, exit ); } recordLen = sizeof( nameCPtr ) + sizeof( recFields ) + rdataLen; dns_fixed_fields_record_init( &recFields, (uint16_t) inQType, kDNSServiceClass_IN, ttl, (uint16_t) rdataLen ); nameIsV6Only = ( ( nameFlags & ( kDNSNameFlag_HasA | kDNSNameFlag_HasAAAA ) ) == kDNSNameFlag_HasAAAA ); for( i = 0; i < addrCount; ++i ) { uint32_t addrHostID; const unsigned int modulus = nameIsV6Only ? 512 : 256; // IPv6-only names can have up to 511 addresses. if( ( inTruncateLen > 0 ) && ( ( DataBuffer_GetLen( inDB ) + recordLen ) > inTruncateLen ) ) { status = kQueryStatus_Truncated; goto done; } // Append A/AAAA record NAME to response. err = DataBuffer_Append( inDB, nameCPtr, sizeof( nameCPtr ) ); require_noerr( err, exit ); // Append A/AAAA record TYPE, CLASS, TTL, and RDLENGTH to response. err = DataBuffer_Append( inDB, &recFields, sizeof( recFields ) ); require_noerr( err, exit ); // Append A/AAAA record RDATA to response. addrHostID = ( randCount > 0 ) ? randIntegers[ i ] : ( i + 1 ); addrHostID = ( addrHostID + offset ) % modulus; if( inQType == kDNSServiceType_A ) { *idPtr = (uint8_t) addrHostID; } else { WriteBig16Typed( idPtr, (uint16_t) addrHostID ); } err = DataBuffer_Append( inDB, rdata, rdataLen ); require_noerr( err, exit ); ++answerCount; if( needSig ) { // Append A/AAAA record to signing buffer. err = DataBuffer_Append( sigMsg, ownerLower, ownerLowerLen ); require_noerr( err, exit ); err = DataBuffer_Append( sigMsg, &recFields, sizeof( recFields ) ); require_noerr( err, exit ); err = DataBuffer_Append( sigMsg, rdata, rdataLen ); require_noerr( err, exit ); } } if( needSig ) { uint8_t signature[ kDNSServerSignatureLengthMax ]; size_t signatureLen; Boolean didSign; // Compute signature with ZSK. memset( signature, 0, sizeof( signature ) ); didSign = DNSKeyInfoSign( zsk, DataBuffer_GetPtr( sigMsg ), DataBuffer_GetLen( sigMsg ), signature, &signatureLen ); require_quiet( didSign, exit ); #if( DEBUG ) _DNSServerSigCheck( ownerLower, inQType, DataBuffer_GetPtr( sigMsg ), DataBuffer_GetLen( sigMsg ), signature, signatureLen, zsk ); #endif rdataLen = sizeof( sigFields ) + signerLen + signatureLen; recordLen = sizeof( nameCPtr ) + sizeof( recFields ) + rdataLen; if( ( inTruncateLen > 0 ) && ( ( DataBuffer_GetLen( inDB ) + recordLen ) > inTruncateLen ) ) { status = kQueryStatus_Truncated; goto done; } // Append RRSIG record NAME to response. err = DataBuffer_Append( inDB, nameCPtr, sizeof( nameCPtr ) ); require_noerr( err, exit ); // Append RRSIG record TYPE, CLASS, TTL, and RDLENGTH to response. dns_fixed_fields_record_init( &recFields, kDNSServiceType_RRSIG, kDNSServiceClass_IN, ttl, (uint16_t) rdataLen ); err = DataBuffer_Append( inDB, &recFields, sizeof( recFields ) ); require_noerr( err, exit ); // Append RRSIG record RDATA fixed fields and signer to response. err = DataBuffer_Append( inDB, &sigFields, sizeof( sigFields ) ); require_noerr( err, exit ); err = DataBuffer_Append( inDB, zone, signerLen ); require_noerr( err, exit ); // Append RRSIG record RDATA signature to response. err = DataBuffer_Append( inDB, signature, signatureLen ); require_noerr( err, exit ); ++answerCount; DataBuffer_Free( sigMsg ); sigMsg = NULL; } } else if( inQType == kDNSServiceType_SRV ) { dns_fixed_fields_record recFields; size_t i; require_action_quiet( nameFlags & kDNSNameFlag_HasSRV, done, status = kQueryStatus_OK ); dns_fixed_fields_record_init( &recFields, kDNSServiceType_SRV, kDNSServiceClass_IN, me->defaultTTL, 0 ); for( i = 0; i < srvCount; ++i ) { dns_fixed_fields_srv srvFields; size_t rdataLen; size_t recordLen; const ParsedSRV * const srv = &srvArray[ i ]; rdataLen = sizeof( srvFields ) + srvDomainLen + srv->targetLen + 1; recordLen = sizeof( nameCPtr ) + sizeof( recFields ) + rdataLen; if( ( inTruncateLen > 0 ) && ( ( DataBuffer_GetLen( inDB ) + recordLen ) > inTruncateLen ) ) { status = kQueryStatus_Truncated; goto done; } // Append record NAME to response. err = DataBuffer_Append( inDB, nameCPtr, sizeof( nameCPtr ) ); require_noerr( err, exit ); // Append record TYPE, CLASS, TTL, and RDLENGTH to response. dns_fixed_fields_record_set_rdlength( &recFields, (uint16_t) rdataLen ); err = DataBuffer_Append( inDB, &recFields, sizeof( recFields ) ); require_noerr( err, exit ); // Append SRV RDATA priority, weight, and port to response. dns_fixed_fields_srv_init( &srvFields, srv->priority, srv->weight, srv->port ); err = DataBuffer_Append( inDB, &srvFields, sizeof( srvFields ) ); require_noerr( err, exit ); // Append SRV RDATA target non-root labels to response. 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 ); } // Append SRV RDATA target root label to response. err = DataBuffer_Append( inDB, "", 1 ); require_noerr( err, exit ); ++answerCount; } } else if( inQType == kDNSServiceType_SOA ) { size_t nameLen, recordLen; require_action_quiet( nameFlags & kDNSNameFlag_HasSOA, done, status = kQueryStatus_OK ); nameLen = DomainNameLength( me->domain ); if( inTruncateLen > 0 ) { 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 ) > inTruncateLen ) { status = kQueryStatus_Truncated; 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; } else if( inQType == kDNSServiceType_PTR ) { dns_fixed_fields_record recFields; size_t domainLen, rdataLen, recordLen; uint8_t label[ 1 + kDomainLabelLengthMax ]; uint8_t * dst = &label[ 1 ]; const uint8_t * lim = &label[ countof( label ) ]; if( nameFlags & kDNSNameFlag_HasPTRv4 ) { size_t maxLen; int n; const uint32_t ipv4Addr = kDNSServerBaseAddrV4 + hostID; maxLen = (size_t)( lim - dst ); n = MemPrintF( dst, maxLen, "ipv4-%u-%u-%u-%u", ( ipv4Addr >> 24 ) & 0xFFU, ( ipv4Addr >> 16 ) & 0xFFU, ( ipv4Addr >> 8 ) & 0xFFU, ipv4Addr & 0xFFU ); require_fatal( ( n > 0 ) && ( ( (size_t) n ) <= maxLen ), "Failed to print reverse IPv4 hostname label" ); dst += n; } else if( nameFlags & kDNSNameFlag_HasPTRv6 ) { size_t maxLen; int n, i; uint8_t ipv6Addr[ 16 ]; maxLen = (size_t)( lim - dst ); n = MemPrintF( dst, maxLen, "ipv6" ); require_fatal( ( n > 0 ) && ( ( (size_t) n ) <= maxLen ), "Failed to print reverse IPv6 hostname label" ); dst += n; memcpy( ipv6Addr, kDNSServerBaseAddrV6, 16 ); ipv6Addr[ 15 ] = (uint8_t) hostID; for( i = 0; i < 8; ++i ) { maxLen = (size_t)( lim - dst ); n = MemPrintF( dst, maxLen, "-%04x", ReadBig16( &ipv6Addr[ i * 2 ] ) ); require_fatal( ( n > 0 ) && ( ( (size_t) n ) <= maxLen ), "Failed to print reverse IPv6 hostname label" ); dst += n; } } else { status = kQueryStatus_OK; goto done; } label[ 0 ] = (uint8_t)( dst - &label[ 1 ] ); domainLen = DomainNameLength( me->domain ); rdataLen = 1 + label[ 0 ] + domainLen; recordLen = sizeof( nameCPtr ) + sizeof( recFields ) + rdataLen; if( ( inTruncateLen > 0 ) && ( ( DataBuffer_GetLen( inDB ) + recordLen ) > inTruncateLen ) ) { status = kQueryStatus_Truncated; goto done; } // Append PTR record NAME to response. err = DataBuffer_Append( inDB, nameCPtr, sizeof( nameCPtr ) ); require_noerr( err, exit ); // Append PTR record TYPE, CLASS, TTL, and RDLENGTH to response. dns_fixed_fields_record_init( &recFields, kDNSServiceType_PTR, kDNSServiceClass_IN, me->defaultTTL, (uint16_t) rdataLen ); err = DataBuffer_Append( inDB, &recFields, sizeof( recFields ) ); require_noerr( err, exit ); // Append PTR record RDATA to response. err = DataBuffer_Append( inDB, label, 1 + label[ 0 ] ); require_noerr( err, exit ); err = DataBuffer_Append( inDB, me->domain, domainLen ); require_noerr( err, exit ); ++answerCount; } else if( inQType == kDNSServiceType_DNSKEY ) { size_t recordLen; size_t signerLen = 0; dns_fixed_fields_record recFields; dns_fixed_fields_rrsig sigFields; Boolean needSig; require_action_quiet( nameFlags & kDNSNameFlag_HasDNSKEY, done, status = kQueryStatus_OK ); recordLen = sizeof( nameCPtr ) + sizeof( recFields ) + DNSKeyInfoGetRDataLen( zsk ); if( ( inTruncateLen > 0 ) && ( ( DataBuffer_GetLen( inDB ) + recordLen ) > inTruncateLen ) ) { status = kQueryStatus_Truncated; goto done; } // Append ZSK DNSKEY record NAME to response. err = DataBuffer_Append( inDB, nameCPtr, sizeof( nameCPtr ) ); require_noerr( err, exit ); // Append ZSK DNSKEY record TYPE, CLASS, TTL, and RDLENGTH to response. dns_fixed_fields_record_init( &recFields, kDNSServiceType_DNSKEY, kDNSServiceClass_IN, ttl, DNSKeyInfoGetRDataLen( zsk ) ); err = DataBuffer_Append( inDB, &recFields, sizeof( recFields ) ); require_noerr( err, exit ); // Append ZSK DNSKEY record RDATA to response. err = DataBuffer_Append( inDB, DNSKeyInfoGetRDataPtr( zsk ), DNSKeyInfoGetRDataLen( zsk ) ); require_noerr( err, exit ); ++answerCount; needSig = ( inDNSSEC && ( nameFlags & kDNSNameFlag_HasRRSIG ) ) ? true : false; if( needSig ) { uint32_t inceptionSecs; int labelCount; // Initialize signing buffer. check( !sigMsg ); sigMsg = &sigDB; DataBuffer_Init( sigMsg, sigBuf, sizeof( sigBuf ), SIZE_MAX ); // Append RRSIG record RDATA fixed fields to signing buffer. memset( &sigFields, 0, sizeof( sigFields ) ); dns_fixed_fields_rrsig_set_type_covered( &sigFields, kDNSServiceType_DNSKEY ); dns_fixed_fields_rrsig_set_algorithm( &sigFields, DNSKeyInfoGetAlgorithm( ksk ) ); labelCount = DomainNameLabelCount( ownerLower ); check( labelCount >= 0 ); dns_fixed_fields_rrsig_set_labels( &sigFields, (uint8_t) labelCount ); dns_fixed_fields_rrsig_set_original_ttl( &sigFields, ttl ); inceptionSecs = (uint32_t) now.tv_sec; dns_fixed_fields_rrsig_set_signature_expiration( &sigFields, inceptionSecs + kSecondsPerDay ); dns_fixed_fields_rrsig_set_signature_inception( &sigFields, inceptionSecs ); dns_fixed_fields_rrsig_set_key_tag( &sigFields, DNSKeyInfoGetKeyTag( ksk ) ); err = DataBuffer_Append( sigMsg, &sigFields, sizeof( sigFields ) ); require_noerr( err, exit ); // Append RRSIG record RDATA signer to signing buffer. signerLen = DomainNameLength( zone ); err = DataBuffer_Append( sigMsg, zone, signerLen ); require_noerr( err, exit ); // Append ZSK DNSKEY record to signing buffer. err = DataBuffer_Append( sigMsg, ownerLower, ownerLowerLen ); require_noerr( err, exit ); err = DataBuffer_Append( sigMsg, &recFields, sizeof( recFields ) ); require_noerr( err, exit ); err = DataBuffer_Append( sigMsg, DNSKeyInfoGetRDataPtr( zsk ), DNSKeyInfoGetRDataLen( zsk ) ); require_noerr( err, exit ); } recordLen = sizeof( nameCPtr ) + sizeof( recFields ) + DNSKeyInfoGetRDataLen( ksk ); if( ( inTruncateLen > 0 ) && ( ( DataBuffer_GetLen( inDB ) + recordLen ) > inTruncateLen ) ) { status = kQueryStatus_Truncated; goto done; } // Append KSK DNSKEY record NAME to response. err = DataBuffer_Append( inDB, nameCPtr, sizeof( nameCPtr ) ); require_noerr( err, exit ); // Append KSK DNSKEY record TYPE, CLASS, TTL, and RDLENGTH to response. err = DataBuffer_Append( inDB, &recFields, sizeof( recFields ) ); require_noerr( err, exit ); // Append KSK DNSKEY record RDATA to response. err = DataBuffer_Append( inDB, DNSKeyInfoGetRDataPtr( ksk ), DNSKeyInfoGetRDataLen( ksk ) ); require_noerr( err, exit ); ++answerCount; if( needSig ) { size_t rdataLen; uint8_t signature[ kDNSServerSignatureLengthMax ]; size_t signatureLen; Boolean didSign; // Append KSK DNSKEY record to signing buffer. err = DataBuffer_Append( sigMsg, ownerLower, ownerLowerLen ); require_noerr( err, exit ); err = DataBuffer_Append( sigMsg, &recFields, sizeof( recFields ) ); require_noerr( err, exit ); err = DataBuffer_Append( sigMsg, DNSKeyInfoGetRDataPtr( ksk ), DNSKeyInfoGetRDataLen( ksk ) ); require_noerr( err, exit ); // Compute signature with KSK. memset( signature, 0, sizeof( signature ) ); didSign = DNSKeyInfoSign( ksk, DataBuffer_GetPtr( sigMsg ), DataBuffer_GetLen( sigMsg ), signature, &signatureLen ); require_quiet( didSign, exit ); #if( DEBUG ) _DNSServerSigCheck( ownerLower, inQType, DataBuffer_GetPtr( sigMsg ), DataBuffer_GetLen( sigMsg ), signature, signatureLen, ksk ); #endif rdataLen = sizeof( sigFields ) + signerLen + signatureLen; recordLen = sizeof( nameCPtr ) + sizeof( recFields ) + rdataLen; if( ( inTruncateLen > 0 ) && ( ( DataBuffer_GetLen( inDB ) + recordLen ) > inTruncateLen ) ) { status = kQueryStatus_Truncated; goto done; } // Append RRSIG record NAME to response. err = DataBuffer_Append( inDB, nameCPtr, sizeof( nameCPtr ) ); require_noerr( err, exit ); // Append RRSIG record TYPE, CLASS, TTL, and RDLENGTH to response. dns_fixed_fields_record_init( &recFields, kDNSServiceType_RRSIG, kDNSServiceClass_IN, ttl, (uint16_t) rdataLen ); err = DataBuffer_Append( inDB, &recFields, sizeof( recFields ) ); require_noerr( err, exit ); // Append RRSIG record RDATA fixed fields and signer to response. err = DataBuffer_Append( inDB, &sigFields, sizeof( sigFields ) ); require_noerr( err, exit ); err = DataBuffer_Append( inDB, zone, signerLen ); require_noerr( err, exit ); // Append RRSIG record RDATA signature to response. err = DataBuffer_Append( inDB, signature, signatureLen ); require_noerr( err, exit ); ++answerCount; DataBuffer_Free( sigMsg ); sigMsg = NULL; } } else if( inQType == kDNSServiceType_DS ) { SHA256_CTX ctx; size_t rdataLen, i, recordLen; size_t signerLen = 0; dns_ds_sha256 * dsPtr; dns_ds_sha256 * dsPtrs[ 2 ]; int cmp; dns_ds_sha256 dsZSK, dsKSK; dns_fixed_fields_rrsig sigFields; dns_fixed_fields_record recFields; Boolean needSig; check_compile_time_code( sizeof( dsPtr->digest ) == SHA256_DIGEST_LENGTH ); require_action_quiet( nameFlags & kDNSNameFlag_HasDS, done, status = kQueryStatus_OK ); // Set up ZSK DS RDATA. dsPtr = &dsZSK; memset( dsPtr, 0, sizeof( *dsPtr ) ); dns_ds_sha256_set_key_tag( dsPtr, DNSKeyInfoGetKeyTag( zsk ) ); dns_ds_sha256_set_algorithm( dsPtr, DNSKeyInfoGetAlgorithm( zsk ) ); dns_ds_sha256_set_digest_type( dsPtr, kDSDigestType_SHA256 ); SHA256_Init( &ctx ); SHA256_Update( &ctx, ownerLower, ownerLowerLen ); SHA256_Update( &ctx, DNSKeyInfoGetRDataPtr( zsk ), DNSKeyInfoGetRDataLen( zsk ) ); SHA256_Final( dsPtr->digest, &ctx ); // Set up KSK DS RDATA. dsPtr = &dsKSK; memset( dsPtr, 0, sizeof( *dsPtr ) ); dns_ds_sha256_set_key_tag( dsPtr, DNSKeyInfoGetKeyTag( ksk ) ); dns_ds_sha256_set_algorithm( dsPtr, DNSKeyInfoGetAlgorithm( ksk ) ); dns_ds_sha256_set_digest_type( dsPtr, kDSDigestType_SHA256 ); SHA256_Init( &ctx ); SHA256_Update( &ctx, ownerLower, ownerLowerLen ); SHA256_Update( &ctx, DNSKeyInfoGetRDataPtr( ksk ), DNSKeyInfoGetRDataLen( ksk ) ); SHA256_Final( dsPtr->digest, &ctx ); // Order the DS RDATAs cmp = memcmp( &dsZSK, &dsKSK, sizeof( dns_ds_sha256 ) ); if( cmp <= 0 ) { dsPtrs[ 0 ] = &dsZSK; dsPtrs[ 1 ] = ( cmp == 0 ) ? NULL : &dsKSK; } else { dsPtrs[ 0 ] = &dsKSK; dsPtrs[ 1 ] = &dsZSK; } needSig = ( inDNSSEC && ( nameFlags & kDNSNameFlag_HasRRSIG ) ) ? true : false; if( needSig ) { uint32_t inceptionSecs; int labelCount; // Initialize signing buffer. check( !sigMsg ); sigMsg = &sigDB; DataBuffer_Init( sigMsg, sigBuf, sizeof( sigBuf ), SIZE_MAX ); // Append RRSIG record RDATA fixed fields to signing buffer. memset( &sigFields, 0, sizeof( sigFields ) ); dns_fixed_fields_rrsig_set_type_covered( &sigFields, kDNSServiceType_DS ); dns_fixed_fields_rrsig_set_algorithm( &sigFields, DNSKeyInfoGetAlgorithm( zskParent ) ); labelCount = DomainNameLabelCount( ownerLower ); check( labelCount >= 0 ); dns_fixed_fields_rrsig_set_labels( &sigFields, (uint8_t) labelCount ); dns_fixed_fields_rrsig_set_original_ttl( &sigFields, ttl ); inceptionSecs = (uint32_t) now.tv_sec; dns_fixed_fields_rrsig_set_signature_expiration( &sigFields, inceptionSecs + kSecondsPerDay ); dns_fixed_fields_rrsig_set_signature_inception( &sigFields, inceptionSecs ); dns_fixed_fields_rrsig_set_key_tag( &sigFields, DNSKeyInfoGetKeyTag( zskParent ) ); err = DataBuffer_Append( sigMsg, &sigFields, sizeof( sigFields ) ); require_noerr( err, exit ); // Append RRSIG record RDATA signer to signing buffer. signerLen = DomainNameLength( zoneParent ); err = DataBuffer_Append( sigMsg, zoneParent, signerLen ); require_noerr( err, exit ); } rdataLen = sizeof( dns_ds_sha256 ); dns_fixed_fields_record_init( &recFields, kDNSServiceType_DS, kDNSServiceClass_IN, ttl, (uint16_t) rdataLen ); for( i = 0; i < countof( dsPtrs ); ++i ) { dsPtr = dsPtrs[ i ]; if( !dsPtr ) continue; recordLen = sizeof( nameCPtr ) + sizeof( recFields ) + rdataLen; if( ( inTruncateLen > 0 ) && ( ( DataBuffer_GetLen( inDB ) + recordLen ) > inTruncateLen ) ) { status = kQueryStatus_Truncated; goto done; } // Append DS record NAME to response. err = DataBuffer_Append( inDB, nameCPtr, sizeof( nameCPtr ) ); require_noerr( err, exit ); // Append DS record TYPE, CLASS, TTL, and RDLENGTH to response. err = DataBuffer_Append( inDB, &recFields, sizeof( recFields ) ); require_noerr( err, exit ); // Append DS record RDATA to response. err = DataBuffer_Append( inDB, dsPtr, sizeof( *dsPtr ) ); require_noerr( err, exit ); ++answerCount; if( needSig ) { // Append DS record to signing buffer. err = DataBuffer_Append( sigMsg, ownerLower, ownerLowerLen ); require_noerr( err, exit ); err = DataBuffer_Append( sigMsg, &recFields, sizeof( recFields ) ); require_noerr( err, exit ); err = DataBuffer_Append( sigMsg, dsPtr, sizeof( *dsPtr ) ); require_noerr( err, exit ); } } if( needSig ) { uint8_t signature[ kDNSServerSignatureLengthMax ]; size_t signatureLen; Boolean didSign; // Compute signature with parent ZSK. memset( signature, 0, sizeof( signature ) ); didSign = DNSKeyInfoSign( zskParent, DataBuffer_GetPtr( sigMsg ), DataBuffer_GetLen( sigMsg ), signature, &signatureLen ); require_quiet( didSign, exit ); #if( DEBUG ) _DNSServerSigCheck( ownerLower, inQType, DataBuffer_GetPtr( sigMsg ), DataBuffer_GetLen( sigMsg ), signature, signatureLen, zskParent ); #endif rdataLen = sizeof( sigFields ) + signerLen + signatureLen; recordLen = sizeof( nameCPtr ) + sizeof( recFields ) + rdataLen; if( ( inTruncateLen > 0 ) && ( ( DataBuffer_GetLen( inDB ) + recordLen ) > inTruncateLen ) ) { status = kQueryStatus_Truncated; goto done; } // Append RRSIG record NAME to response. err = DataBuffer_Append( inDB, nameCPtr, sizeof( nameCPtr ) ); require_noerr( err, exit ); // Append RRSIG record TYPE, CLASS, TTL, and RDLENGTH to response. dns_fixed_fields_record_init( &recFields, kDNSServiceType_RRSIG, kDNSServiceClass_IN, ttl, (uint16_t) rdataLen ); err = DataBuffer_Append( inDB, &recFields, sizeof( recFields ) ); require_noerr( err, exit ); // Append RRSIG record RDATA fixed fields and signer to response. err = DataBuffer_Append( inDB, &sigFields, sizeof( sigFields ) ); require_noerr( err, exit ); err = DataBuffer_Append( inDB, zoneParent, signerLen ); require_noerr( err, exit ); // Append RRSIG record RDATA signature to response. err = DataBuffer_Append( inDB, signature, signatureLen ); require_noerr( err, exit ); ++answerCount; DataBuffer_Free( sigMsg ); sigMsg = NULL; } } status = kQueryStatus_OK; done: hdr = (DNSHeader *) DataBuffer_GetPtr( inDB ); flags = DNSHeaderGetFlags( hdr ); switch( status ) { case kQueryStatus_OK: case kQueryStatus_Truncated: flags |= kDNSHeaderFlag_AuthAnswer; if( status == kQueryStatus_Truncated ) flags |= kDNSHeaderFlag_Truncation; rcode = nameExists ? kDNSRCode_NoError : kDNSRCode_NXDomain; break; case kQueryStatus_NotImplemented: rcode = kDNSRCode_NotImp; break; case kQueryStatus_Refused: rcode = kDNSRCode_Refused; break; case kQueryStatus_Null: default: err = kInternalErr; goto exit; } if( rcodeOverride >= 0 ) { dns_fixed_fields_record recFields; const char * rcodeStr; size_t maxLen, txtStrLen, rdataLen, recordLen; int n; uint8_t rdataBuf[ 1 + 255 ]; // Enough space for one TXT record string. // Create the RDATA for an informational TXT record that contains the original rcode to put in the Additional // section. maxLen = sizeof( rdataBuf ) - 1; rcodeStr = DNSRCodeToString( rcode ); if( rcodeStr ) n = MemPrintF( &rdataBuf[ 1 ], maxLen, "original-rcode=%s", rcodeStr ); else n = MemPrintF( &rdataBuf[ 1 ], maxLen, "original-rcode=%d", rcode ); txtStrLen = ( n > 0 ) ? Min( (size_t) n, maxLen ) : 0; rdataBuf[ 0 ] = (uint8_t) txtStrLen; rdataLen = 1 + txtStrLen; // The TXT record isn't strictly necessary, so only include it if it fits. recordLen = sizeof( nameCPtr ) + sizeof( recFields ) + rdataLen; maxLen = ( inTruncateLen > 0 ) ? inTruncateLen : kDNSMaxTCPMessageSize; if( ( DataBuffer_GetLen( inDB ) < maxLen ) && ( ( maxLen - DataBuffer_GetLen( inDB ) ) >= recordLen ) ) { // Append TXT record NAME to response. err = DataBuffer_Append( inDB, nameCPtr, sizeof( nameCPtr ) ); require_noerr( err, exit ); // Append TXT record TYPE, CLASS, TTL, and RDLENGTH to response. dns_fixed_fields_record_init( &recFields, kDNSRecordType_TXT, kDNSClassType_IN, 0, (uint16_t) rdataLen ); err = DataBuffer_Append( inDB, &recFields, sizeof( recFields ) ); require_noerr( err, exit ); // Append TXT record RDATA to response. err = DataBuffer_Append( inDB, rdataBuf, rdataLen ); require_noerr( err, exit ); ++additionalCount; } rcode = rcodeOverride; } DNSFlagsSetRCode( flags, rcode ); DNSHeaderSetFlags( hdr, flags ); DNSHeaderSetAnswerCount( hdr, answerCount ); DNSHeaderSetAdditionalCount( hdr, additionalCount ); if( procDelayMs > 0 ) { const uint64_t delayTicks = MillisecondsToUpTicks( procDelayMs ); const uint64_t elapsedTicks = UpTicks() - startTicks; if( delayTicks > elapsedTicks ) SleepForUpTicks( delayTicks - elapsedTicks ); } err = kNoErr; exit: FreeNullSafe( qnameLower ); if( sigMsg ) DataBuffer_Free( sigMsg ); return( err ); } static Boolean _DNSServerNameIsDNSSECZone( const uint8_t * inName, const uint8_t ** outZoneParent, DNSKeyInfoRef * outZSK, DNSKeyInfoRef * outKSK, DNSKeyInfoRef * outParentZSK ); static Boolean _DNSServerParseHostName( DNSServerRef me, const uint8_t * inQName, uint32_t * outAliasCount, uint32_t outAliasTTLs[ kAliasTTLCountMax ], uint32_t * outAliasTTLCount, uint32_t * outCount, uint32_t * outRandCount, uint32_t * outIndex, int * outRCode, uint32_t * outTTL, uint32_t * outOffset, uint32_t * outProcDelayMs, DNSNameFlags * outFlags, const uint8_t ** outZone, const uint8_t ** outZoneParent, DNSKeyInfoRef * outZSK, DNSKeyInfoRef * outKSK, DNSKeyInfoRef * outParentZSK, DNSServerAction * outAction ) { OSStatus err; const uint8_t * label; size_t labelLen; const uint8_t * labelNext; uint32_t aliasTTLCount = 0; // Count of TTL args from Alias-TTL label. uint32_t aliasCount = 0; // Arg from Alias label. Valid values are in [2, 2^31 - 1]. int32_t count = -1; // First arg from Count label. Valid values are in [0, 255]. uint32_t randCount = 0; // Second arg from Count label. Valid values are in [count, 255]. uint32_t index = 0; // Arg from Index label. Valid values are in [1, 2^32 - 1]. int rcode = -1; // Arg from RCode label. Valid values are in [0, 15]. int32_t ttl = -1; // Arg from TTL label. Valid values are in [0, 2^31 - 1]. int32_t offset = -1; // Arg from Offset label. Valid values are in [0, 2^31 - 1]. int32_t procDelayMs = -1; // Arg from PDelay label. Valid values are in [0, 2000]. Units are in ms. DNSNameFlags flags = 0; int32_t maxCount; const uint8_t * zone = NULL; const uint8_t * zoneParent = NULL; DNSKeyInfoRef zsk = NULL; DNSKeyInfoRef ksk = NULL; DNSKeyInfoRef parentZSK = NULL; DNSServerAction action = kDNSServerAction_None; Boolean isAlias = false; for( label = inQName; ( labelLen = *label ) != 0; label = labelNext ) { const uint8_t * labelData; uint32_t arg; if( labelLen > kDomainLabelLengthMax ) break; labelData = &label[ 1 ]; labelNext = &labelData[ labelLen ]; if( label == inQName ) { // Check if the first label is a valid alias TTL sequence label. // Note: Since "alias" is a prefix of "alias-ttl", check for "alias-ttl" first. if( strnicmp_prefix( labelData, labelLen, kLabelPrefix_AliasTTL ) == 0 ) { const char * ptr = (const char *) &labelData[ sizeof_string( kLabelPrefix_AliasTTL ) ]; const char * const end = (const char *) labelNext; while( ptr < end ) { if( *ptr++ != '-' ) break; err = DecimalTextToUInt32( ptr, end, &arg, &ptr ); if( err || ( arg > INT32_MAX ) ) break; // TTL must be in [0, 2^31 - 1]. if( outAliasTTLs ) outAliasTTLs[ aliasTTLCount ] = arg; ++aliasTTLCount; } if( ( aliasTTLCount == 0 ) || ( ptr != end ) ) break; isAlias = true; continue; } // Check if the first label is a valid alias label. if( ( strnicmp_prefix( labelData, labelLen, kLabelPrefix_Alias ) == 0 ) ) { const char * ptr = (const char *) &labelData[ sizeof_string( kLabelPrefix_Alias ) ]; const char * const end = (const char *) labelNext; 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; } isAlias = true; continue; } } // Check if this label is a valid count label. if( strnicmp_prefix( labelData, labelLen, kLabelPrefix_Count ) == 0 ) { const char * ptr = (const char *) &labelData[ sizeof_string( kLabelPrefix_Count ) ]; const char * const end = (const char *) labelNext; if( count >= 0 ) break; // Count cannot be specified more than once. err = DecimalTextToUInt32( ptr, end, &arg, &ptr ); if( err || ( arg > INT32_MAX ) ) break; // The actual upper bound for Count will be verified below. count = (int32_t) 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 = arg; if( ptr != end ) break; } continue; } // Check if this label is a valid Index label. if( strnicmp_prefix( labelData, labelLen, kLabelPrefix_Index ) == 0 ) { const char * ptr = (const char *) &labelData[ sizeof_string( kLabelPrefix_Index ) ]; const char * const end = (const char *) labelNext; if( index > 0 ) break; // Index cannot be specified more than once. err = DecimalTextToUInt32( ptr, end, &arg, &ptr ); if( err || ( arg < 1 ) || ( arg > UINT32_MAX ) ) break; // Index must be in [1, 2^32 - 1]. index = arg; if( ptr != end ) break; continue; } // Check if this label is a valid IPv4 label. if( strnicmpx( labelData, labelLen, kLabel_IPv4 ) == 0 ) { // Valid names have at most one IPv4 or IPv6 label. if( flags & ( kDNSNameFlag_HasA | kDNSNameFlag_HasAAAA ) ) break; flags |= kDNSNameFlag_HasA; continue; } // Check if this label is a valid IPv6 label. if( strnicmpx( labelData, labelLen, kLabel_IPv6 ) == 0 ) { // Valid names have at most one IPv4 or IPv6 label. if( flags & ( kDNSNameFlag_HasA | kDNSNameFlag_HasAAAA ) ) break; flags |= kDNSNameFlag_HasAAAA; continue; } // Check if this label is a valid tag label. if( strnicmp_prefix( labelData, labelLen, kLabelPrefix_Tag ) == 0 ) { continue; } // Check if this label is a valid RCode label. if( strnicmp_prefix( labelData, labelLen, kLabelPrefix_RCode ) == 0 ) { const char * ptr = (const char *) &labelData[ sizeof_string( kLabelPrefix_RCode ) ]; const char * const end = (const char *) labelNext; const char * src; char * dst; const char * lim; char argStr[ kDomainLabelLengthMax + 1 ]; if( rcode >= 0 ) break; // RCode cannot be specified more than once. // First check if the RCode label's argument is an RCODE mnemonic, e.g., ServFail, Refused, etc. // The argument part of a label consists of all of the characters up to the start of the next label. // In order to treat the argument as a C string, the argument must not contain any NUL characters. // For example, a malformed label such as "rcode-refused\x00garbage" has the argument "refused\x00garbage", // but as a C string, the NUL character makes it "refused". src = ptr; dst = argStr; lim = &argStr[ countof( argStr ) - 1 ]; while( ( src < end ) && ( *src != '\0' ) && ( dst < lim ) ) *dst++ = *src++; if( src == end ) { *dst = '\0'; rcode = DNSRCodeFromString( argStr ); } // If we don't have a valid rcode yet, try to parse the argument as a decimal integer. if( rcode < 0 ) { err = DecimalTextToUInt32( ptr, end, &arg, &ptr ); if( err || ( arg > 15 ) ) break; // RCode must be in [0, 15]. rcode = (int) arg; if( ptr != end ) break; } continue; } // Check if this label is a valid TTL label. if( strnicmp_prefix( labelData, labelLen, kLabelPrefix_TTL ) == 0 ) { const char * ptr = (const char *) &labelData[ sizeof_string( kLabelPrefix_TTL ) ]; const char * const end = (const char *) labelNext; 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; continue; } // Check if this label is a valid Offset label. if( strnicmp_prefix( labelData, labelLen, kLabelPrefix_Offset ) == 0 ) { const char * ptr = (const char *) &labelData[ sizeof_string( kLabelPrefix_Offset ) ]; const char * const end = (const char *) labelNext; if( offset >= 0 ) break; // Offset cannot be specified more than once. err = DecimalTextToUInt32( ptr, end, &arg, &ptr ); if( err ) break; offset = (int32_t) arg; if( ptr != end ) break; continue; } // Check if this label is a valid PDelay label. if( strnicmp_prefix( labelData, labelLen, kLabelPrefix_PDelay ) == 0 ) { const char * ptr = (const char *) &labelData[ sizeof_string( kLabelPrefix_PDelay ) ]; const char * const end = (const char *) labelNext; if( procDelayMs >= 0 ) break; // PDelay cannot be specified more than once. err = DecimalTextToUInt32( ptr, end, &arg, &ptr ); if( err || ( arg > 2000 ) ) break; // PDelay must be in [0, 2000]. procDelayMs = (int32_t) arg; if( ptr != end ) break; continue; } // Check if this label is a valid CommandSuspend label. if( strnicmpx( labelData, labelLen, kLabel_CommandSuspend ) == 0 ) { if( action != kDNSServerAction_None ) break; // A command cannot be specified more than once. action = kDNSServerAction_Suspend; continue; } // Check if this label is a valid CommandResume label. if( strnicmpx( labelData, labelLen, kLabel_CommandResume ) == 0 ) { if( action != kDNSServerAction_None ) break; // A command cannot be specified more than once. action = kDNSServerAction_Resume; continue; } // 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. if( _DNSServerNameIsDNSSECZone( label, &zoneParent, &zsk, &ksk, &parentZSK ) ) { zone = label; flags |= kDNSNameFlag_HasRRSIG; if( ( label == inQName ) || ( isAlias && ( label == DomainNameGetNextLabel( inQName ) ) ) ) { flags |= kDNSNameFlag_HasSOA; flags |= kDNSNameFlag_HasDNSKEY; flags |= kDNSNameFlag_HasDS; } } else if( DomainNameEqual( label, me->domain ) ) { zone = label; if( ( label == inQName ) || ( isAlias && ( label == DomainNameGetNextLabel( inQName ) ) ) ) { flags |= kDNSNameFlag_HasSOA; } } break; } require_quiet( zone, exit ); // If a Count value of 0 was specified, then the hostname has no A or AAAA records. // Otherwise, if the hostname has no IPv4 or IPv6 labels, then it has both A and AAAA records. if( count == 0 ) { flags &= ~( kDNSNameFlag_HasA | kDNSNameFlag_HasAAAA ); } else if( !( flags & ( kDNSNameFlag_HasA | kDNSNameFlag_HasAAAA ) ) ) { flags |= ( kDNSNameFlag_HasA | kDNSNameFlag_HasAAAA ); } // Allow IPv6-only hostnames to have a maximum address count of up to 511 instead of the normal 255. maxCount = ( ( flags & ( kDNSNameFlag_HasA | kDNSNameFlag_HasAAAA ) ) == kDNSNameFlag_HasAAAA ) ? 511 : 255; require_action_quiet( count <= maxCount, exit, zone = NULL ); if( outAliasCount ) *outAliasCount = aliasCount; if( outAliasTTLCount ) *outAliasTTLCount = aliasTTLCount; if( outCount ) *outCount = ( count >= 0 ) ? ( (uint32_t) count ) : 1; if( outRandCount ) *outRandCount = randCount; if( outIndex ) *outIndex = index; if( outRCode ) *outRCode = rcode; if( outTTL ) *outTTL = ( ttl >= 0 ) ? ( (uint32_t) ttl ) : me->defaultTTL; if( outOffset ) *outOffset = ( offset >= 0 ) ? ( (uint32_t) offset ) : 0; if( outProcDelayMs ) *outProcDelayMs = ( procDelayMs >= 0 ) ? ( (uint32_t) procDelayMs ) : 0; if( outFlags ) *outFlags = flags; if( outZone ) *outZone = zone; if( outZoneParent ) *outZoneParent = zoneParent; if( outZSK ) *outZSK = zsk; if( outKSK ) *outKSK = ksk; if( outParentZSK ) *outParentZSK = parentZSK; if( outAction ) *outAction = action; exit: return( zone ? true : false ); } //=========================================================================================================================== static Boolean _DNSServerParseSRVName( DNSServerRef me, const uint8_t * inName, const uint8_t ** outDomainPtr, size_t * outDomainLen, ParsedSRV outSRVArray[ kParsedSRVCountMax ], 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 = DomainNameGetNextLabel( 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 = DomainNameGetNextLabel( 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 = DomainNameGetNextLabel( 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 = DomainNameGetNextLabel( 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 = DomainNameGetNextLabel( label ) ) { if( DomainNameEqual( label, me->domain ) || ( strnicmp_prefix( &label[ 1 ], label[ 0 ], kLabelPrefix_SRV ) == 0 ) ) break; } require_quiet( *label, exit ); if( outSRVArray ) { outSRVArray[ srvCount ].priority = (uint16_t) priority; outSRVArray[ srvCount ].weight = (uint16_t) weight; outSRVArray[ srvCount ].port = (uint16_t) port; outSRVArray[ srvCount ].targetPtr = target; outSRVArray[ 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 ); } //=========================================================================================================================== static Boolean _DNSServerParseReverseIPv4Name( DNSServerRef me, const uint8_t *inQName, unsigned int *outHostID ) { OSStatus err; const uint8_t * label; size_t labelLen; const uint8_t * labelData; const uint8_t * labelNext; const uint8_t * ptr; uint32_t hostID; int isNameValid = false; Unused( me ); label = inQName; labelLen = *label; require_quiet( labelLen > 0, exit ); labelData = &label[ 1 ]; labelNext = &labelData[ labelLen ]; err = DecimalTextToUInt32( (const char *) labelData, (const char *) labelNext, &hostID, (const char **) &ptr ); require_noerr_quiet( err, exit ); require_quiet( ( hostID >= 1 ) && ( hostID <= 255 ), exit ); require_quiet( ptr == labelNext, exit ); require_quiet( DomainNameEqual( labelNext, kDNSServerReverseIPv4DomainName ), exit ); isNameValid = true; if( outHostID ) *outHostID = (unsigned int) hostID; exit: return( isNameValid ? true : false ); } //=========================================================================================================================== static Boolean _DNSServerParseReverseIPv6Name( DNSServerRef me, const uint8_t *inQName, unsigned int *outHostID ) { const uint8_t * label; unsigned int hostID; int i; int isNameValid = false; Unused( me ); hostID = 0; label = inQName; for( i = 0; i < 2; ++i ) { unsigned int labelLen, c; labelLen = label[ 0 ]; require_quiet( labelLen == 1, exit ); c = label[ 1 ]; require_quiet( isxdigit_safe( c ), exit ); hostID = hostID | ( HexCharToValue( c ) << ( 4 * i ) ); label = &label[ 1 + labelLen ]; } require_quiet( ( hostID >= 1 ) && ( hostID <= 255 ), exit ); require_quiet( DomainNameEqual( label, kDNSServerReverseIPv6DomainName ), exit ); isNameValid = true; if( outHostID ) *outHostID = hostID; exit: return( isNameValid ? true : false ); } #if( DEBUG ) //=========================================================================================================================== static void _DNSServerSigCheck( const uint8_t * inOwner, int inTypeCovered, const void * inMsgPtr, size_t inMsgLen, const uint8_t * inSignaturePtr, const size_t inSignatureLen, DNSKeyInfoRef inKeyInfo ) { if( !DNSKeyInfoVerify( inKeyInfo, inMsgPtr, inMsgLen, inSignaturePtr, inSignatureLen ) ) { const char * typeStr; char typeBuf[ 16 ]; typeStr = DNSRecordTypeValueToString( inTypeCovered ); if( !typeStr ) { SNPrintF( typeBuf, sizeof( typeBuf ), "TYPE%d", inTypeCovered ); typeStr = typeBuf; } ds_ulog( kLogLevelError, "Signature for %{du:dname} %s is invalid! -- algorithm: %s (%d), public key: '%H'\n", inOwner, typeStr, DNSKeyInfoGetAlgorithmDescription( inKeyInfo ), DNSKeyInfoGetAlgorithm( inKeyInfo ), DNSKeyInfoGetPubKeyPtr( inKeyInfo ), DNSKeyInfoGetPubKeyLen( inKeyInfo ), SIZE_MAX ); } } #endif //=========================================================================================================================== #define kDNSServerDefaultDNSSECAlgorithm 14 // TODO: Think about adding an option for the default algorithm. static Boolean _DNSServerNameIsDNSSECZone( const uint8_t * inName, const uint8_t ** outZoneParent, DNSKeyInfoRef * outZSK, DNSKeyInfoRef * outKSK, DNSKeyInfoRef * outParentZSK ) { const uint8_t * label; size_t labelLen; const uint8_t * labelNext; const uint8_t * zoneParent = NULL; DNSKeyInfoRef zsk; DNSKeyInfoRef ksk; DNSKeyInfoRef parentZSK; uint32_t zoneAlgorithm = 0; uint32_t zoneIndex = 0; uint32_t zoneParentAlgorithm = 0; uint32_t zoneParentIndex = 0; Boolean parsedAllLabels = false; Boolean nameIsValid = false; for( label = inName; ( labelLen = *label ) != 0; label = labelNext ) { const uint8_t * labelData; if( labelLen > kDomainLabelLengthMax ) break; labelData = &label[ 1 ]; labelNext = &labelData[ labelLen ]; if( strnicmp_prefix( labelData, labelLen, kLabelPrefix_Zone ) == 0 ) { OSStatus err; const char * ptr = (const char *) &labelData[ sizeof_string( kLabelPrefix_Zone ) ]; const char * const end = (const char *) labelNext; uint32_t algorithm; uint32_t index; err = DecimalTextToUInt32( ptr, end, &algorithm, &ptr ); if( err ) break; if( ( ptr >= end ) || ( *ptr++ != '-' ) ) break; err = DecimalTextToUInt32( ptr, end, &index, &ptr ); if( err || ( index < kZoneLabelIndexArgMin ) || ( index > kZoneLabelIndexArgMax ) ) break; if( ptr != end ) break; if( zoneIndex == 0 ) { zoneAlgorithm = algorithm; zoneIndex = index; } else if( zoneParentIndex == 0 ) { zoneParentAlgorithm = algorithm; zoneParent = label; zoneParentIndex = index; } continue; } if( DomainNameEqual( label, kDNSServerDomain_DNSSEC ) ) { if( !zoneParent ) zoneParent = label; parsedAllLabels = true; } break; } require_quiet( parsedAllLabels, exit ); if( zoneAlgorithm == 0 ) zoneAlgorithm = kDNSServerDefaultDNSSECAlgorithm; zsk = GetDNSKeyInfoZSK( zoneAlgorithm, zoneIndex ); require_quiet( zsk, exit ); ksk = GetDNSKeyInfoKSK( zoneAlgorithm, zoneIndex ); require_quiet( ksk, exit ); if( zoneParentAlgorithm == 0 ) zoneParentAlgorithm = kDNSServerDefaultDNSSECAlgorithm; parentZSK = GetDNSKeyInfoZSK( zoneParentAlgorithm, zoneParentIndex ); require_quiet( parentZSK, exit ); if( outZoneParent ) *outZoneParent = zoneParent; if( outZSK ) *outZSK = zsk; if( outKSK ) *outKSK = ksk; if( outParentZSK ) *outParentZSK = parentZSK; nameIsValid = true; exit: return( nameIsValid ); } //=========================================================================================================================== static OSStatus _DNSServerConnectionCreate( DNSServerRef inServer, const struct sockaddr * inLocal, const struct sockaddr * inRemote, size_t inIndex, DNSServerConnectionRef * outCnx ) { OSStatus err; DNSServerConnectionRef obj; CF_OBJECT_CREATE( DNSServerConnection, obj, err, exit ); obj->index = inIndex; obj->server = inServer; CFRetain( obj->server ); SockAddrCopy( inLocal, &obj->local ); SockAddrCopy( inRemote, &obj->remote ); *outCnx = obj; err = kNoErr; exit: return( err ); } //=========================================================================================================================== static void _DNSServerConnectionFinalize( CFTypeRef inObj ) { const DNSServerConnectionRef me = (DNSServerConnectionRef) inObj; check( !me->readSource ); check( !me->writeSource ); ForgetCF( &me->server ); ForgetMem( &me->msgPtr ); } //=========================================================================================================================== static OSStatus _DNSServerConnectionStart( DNSServerConnectionRef me, SocketRef inSock ) { OSStatus err; SocketContext * sockCtx = NULL; err = SocketMakeNonBlocking( inSock ); require_noerr( err, exit ); #if( defined( SO_NOSIGPIPE ) ) setsockopt( inSock, SOL_SOCKET, SO_NOSIGPIPE, &(int){ 1 }, (socklen_t) sizeof( int ) ); #endif me->readSource = dispatch_source_create( DISPATCH_SOURCE_TYPE_READ, (uintptr_t) inSock, 0, me->server->queue ); require_action( me->readSource, exit, err = kNoResourcesErr ); me->readSuspended = true; me->writeSource = dispatch_source_create( DISPATCH_SOURCE_TYPE_WRITE, (uintptr_t) inSock, 0, me->server->queue ); require_action( me->writeSource, exit, err = kNoResourcesErr ); me->writeSuspended = true; sockCtx = SocketContextCreateEx( inSock, me, SocketContextFinalizerCF, &err ); require_noerr( err, exit ); CFRetain( me ); SocketContextRetain( sockCtx ); dispatch_set_context( me->readSource, sockCtx ); dispatch_source_set_event_handler_f( me->readSource, _DNSServerConnectionReadHandler ); dispatch_source_set_cancel_handler_f( me->readSource, SocketContextCancelHandler ); dispatch_resume_if_suspended( me->readSource, &me->readSuspended ); SocketContextRetain( sockCtx ); dispatch_set_context( me->writeSource, sockCtx ); dispatch_source_set_event_handler_f( me->writeSource, _DNSServerConnectionWriteHandler ); dispatch_source_set_cancel_handler_f( me->writeSource, SocketContextCancelHandler ); _DNSServerConnectionRenewExpiration( me ); exit: if( sockCtx ) SocketContextRelease( sockCtx ); if( err ) _DNSServerConnectionStop( me, true ); return( err ); } //=========================================================================================================================== static void _DNSServerConnectionStop( DNSServerConnectionRef me, Boolean inRemoveFromList ) { dispatch_source_forget_ex( &me->readSource, &me->readSuspended ); dispatch_source_forget_ex( &me->writeSource, &me->writeSuspended ); if( inRemoveFromList ) { DNSServerConnectionRef * ptr; ptr = &me->server->connectionList; while( *ptr && ( *ptr != me ) ) ptr = &( *ptr )->next; if( *ptr ) { *ptr = me->next; me->next = NULL; CFRelease( me ); } } } //=========================================================================================================================== static void _DNSServerConnectionReadHandler( void *inContext ) { OSStatus err; const SocketContext * const sockCtx = (SocketContext *) inContext; const DNSServerConnectionRef me = (DNSServerConnectionRef) sockCtx->userContext; uint8_t * respPtr = NULL; // malloc'd size_t respLen; // Receive message length. if( !me->haveLen ) { err = SocketReadData( sockCtx->sock, me->lenBuf, sizeof( me->lenBuf ), &me->offset ); if( ( err == EWOULDBLOCK ) || ( err == kConnectionErr ) ) goto exit; require_noerr( err, exit ); me->haveLen = true; me->offset = 0; me->msgLen = ReadBig16( me->lenBuf ); if( me->msgLen < kDNSHeaderLength ) { ds_ulog( kLogLevelInfo, "TCP: Message length of %zu bytes from %##a to %##a is too small (< %d bytes)\n", me->msgLen, &me->remote, &me->local, kDNSHeaderLength ); err = kSizeErr; goto exit; } me->msgPtr = malloc( me->msgLen ); require_action( me->msgPtr, exit, err = kNoMemoryErr ); } // Receive message. err = SocketReadData( sockCtx->sock, me->msgPtr, me->msgLen, &me->offset ); if( ( err == EWOULDBLOCK ) || ( err == kConnectionErr ) ) goto exit; require_noerr( err, exit ); dispatch_suspend_if_resumed( me->readSource, &me->readSuspended ); me->offset = 0; me->haveLen = false; ds_ulog( kLogLevelInfo, "TCP: Received %zu bytes from %##a to %##a -- %.1{du:dnsmsg}\n", me->msgLen, &me->remote, &me->local, me->msgPtr, me->msgLen ); // Create response. err = _DNSServerAnswerQueryForTCP( me->server, me->msgPtr, me->msgLen, me->index + 1, &respPtr, &respLen ); if( err == kSkipErr ) ds_ulog( kLogLevelInfo, "TCP: Ignoring query\n" ); require_noerr_quiet( err, exit ); _DNSServerConnectionRenewExpiration( me ); // Prepare to send response. FreeNullSafe( me->msgPtr ); me->msgPtr = respPtr; me->msgLen = respLen; respPtr = NULL; ds_ulog( kLogLevelInfo, "TCP: Sending %zu byte response from %##a to %##a -- %.1{du:dnsmsg}\n", me->msgLen, &me->local, &me->remote, me->msgPtr, me->msgLen ); check( me->msgLen <= UINT16_MAX ); WriteBig16Typed( me->lenBuf, (uint16_t) me->msgLen ); me->iov[ 0 ].iov_base = me->lenBuf; me->iov[ 0 ].iov_len = sizeof( me->lenBuf ); me->iov[ 1 ].iov_base = me->msgPtr; me->iov[ 1 ].iov_len = me->msgLen; me->iovPtr = me->iov; me->iovCount = 2; dispatch_resume_if_suspended( me->writeSource, &me->writeSuspended ); exit: FreeNullSafe( respPtr ); if( err && ( err != EWOULDBLOCK ) ) { _DNSServerConnectionStop( me, true ); } } //=========================================================================================================================== static void _DNSServerConnectionWriteHandler( void *inContext ) { OSStatus err; const SocketContext * const sockCtx = (SocketContext *) inContext; const DNSServerConnectionRef me = (DNSServerConnectionRef) sockCtx->userContext; err = SocketWriteData( sockCtx->sock, &me->iovPtr, &me->iovCount ); if( !err ) { me->iovPtr = NULL; me->iovCount = 0; memset( me->iov, 0, sizeof( me->iov ) ); ForgetPtrLen( &me->msgPtr, &me->msgLen ); dispatch_suspend_if_resumed( me->writeSource, &me->writeSuspended ); dispatch_resume_if_suspended( me->readSource, &me->readSuspended ); } else if( err != EWOULDBLOCK ) { _DNSServerConnectionStop( me, true ); } } //=========================================================================================================================== static void _DNSServerConnectionRenewExpiration( DNSServerConnectionRef me ) { me->expirationTicks = UpTicks() + SecondsToUpTicks( kDNSServerConnectionExpirationTimeSecs ); } //=========================================================================================================================== // 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 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 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( kDNSSDUtilIdentifier, 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 ) { context->followPID = _StringToPID( gMDNSReplier_FollowPID, &err ); 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 " ()" or // "-" 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; sockCtx = SocketContextCreate( sockV4, context, &err ); 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; sockCtx = SocketContextCreate( sockV6, context, &err ); 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 ). 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 -- %#.1{du:dnsmsg}\n", 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 & ~kMDNSClassUnicastResponseBit ) != 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 & kMDNSClassUnicastResponseBit ) { qclass &= ~kMDNSClassUnicastResponseBit; 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 . 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; WriteBig16Typed( &rdataPtr[ 1 ], (uint16_t) 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 ) { const uint8_t ( *baseAddr )[ 16 ]; err = DomainNameDupLower( inName, &recordName, NULL ); require_noerr( err, exit ); rdataLen = 16; baseAddr = ( i == 1 ) ? &kMDNSReplierLinkLocalBaseAddrV6 : &kMDNSReplierBaseAddrV6; rdataPtr = (uint8_t *) _memdup( baseAddr, rdataLen ); require_action( rdataPtr, exit, err = kNoMemoryErr ); WriteBig16Typed( &rdataPtr[ 12 ], (uint16_t) 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 ) ) { dns_fixed_fields_srv * fields; uint8_t * ptr; uint8_t * lim; uint8_t * targetPtr; err = DomainNameDupLower( inName, &recordName, NULL ); require_noerr( err, exit ); rdataLen = sizeof( dns_fixed_fields_srv ) + 1 + hostname[ 0 ] + 10 + kLocalNameLen; rdataPtr = (uint8_t *) malloc( rdataLen ); require_action( rdataPtr, exit, err = kNoMemoryErr ); lim = &rdataPtr[ rdataLen ]; fields = (dns_fixed_fields_srv *) rdataPtr; dns_fixed_fields_srv_init( 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 -- %#.1{du:dnsmsg}\n", 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 ) { dns_fixed_fields_record 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 |= kMDNSClassCacheFlushBit; } dns_fixed_fields_record_init( &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; WriteBig16Typed( rdLengthPtr, (uint16_t) 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 ) { DNSMessageWriteLabelPointer( 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 //=========================================================================================================================== #define _MemIEqual( PTR1, LEN1, PTR2, LEN2 ) \ ( ( ( LEN1 ) == ( LEN2 ) ) && ( _memicmp( ( PTR1 ), ( PTR2 ), ( LEN1 ) ) == 0 ) ) 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 = DomainNameGetNextLabel( inName ); if( !_MemIEqual( &subname[ 1 ], subname[ 0 ], &hostname[ 1 ], hostname[ 0 ] ) ) goto exit; subname = DomainNameGetNextLabel( 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( dns_fixed_fields_srv ), exit, err = kMalformedErr ); obj->target = obj->rdata + sizeof( dns_fixed_fields_srv ); } *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 ) typedef struct GAITesterPrivate * GAITesterRef; typedef struct GAITestCase GAITestCase; typedef struct { const char * name; // Domain name that was resolved. uint64_t connectionTimeUs; // Time in microseconds that it took to create a DNS-SD connection. uint64_t firstTimeUs; // Time in microseconds that it took to get the first address result. uint64_t timeUs; // Time in microseconds that it took to get all expected address results. OSStatus error; } GAITestItemResult; typedef void ( *GAITesterStopHandler_f )( void *inContext, OSStatus inError ); typedef void ( *GAITesterResultsHandler_f )( const char * inCaseTitle, NanoTime64 inCaseStartTime, NanoTime64 inCaseEndTime, const GAITestItemResult * inResultArray, size_t inResultCount, void * inContext ); typedef unsigned int GAITestAddrType; #define kGAITestAddrType_None 0 #define kGAITestAddrType_IPv4 ( 1U << 0 ) #define kGAITestAddrType_IPv6 ( 1U << 1 ) #define kGAITestAddrType_Both ( kGAITestAddrType_IPv4 | kGAITestAddrType_IPv6 ) #define GAITestAddrTypeIsValid( X ) \ ( ( (X) & kGAITestAddrType_Both ) && ( ( (X) & ~kGAITestAddrType_Both ) == 0 ) ) typedef struct { GAITesterRef tester; // GAI tester object. CFMutableArrayRef testCaseResults; // Array of test case results. unsigned int iterTimeLimitMs; // Amount of time to allow each iteration to complete. 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. DNSProtocol protocol; // DNS protocol to use, e.g., Do53, DoT, or DoH. 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 GAIPerfTesterStopHandler( void *inContext, OSStatus inError ); static void GAIPerfResultsHandler( const char * inCaseTitle, NanoTime64 inCaseStartTime, NanoTime64 inCaseEndTime, const GAITestItemResult * inResultArray, size_t inResultCount, void * inContext ); static void GAIPerfSignalHandler( void *inContext ); static CFTypeID GAITesterGetTypeID( void ); static OSStatus GAITesterCreate( dispatch_queue_t inQueue, DNSProtocol inProtocol, unsigned int inCallDelayMs, int inServerDelayMs, int inServerDefaultTTL, Boolean inSkipPathEvaluation, Boolean inBadUDPMode, GAITesterRef * outTester ); static void GAITesterStart( GAITesterRef inTester ); static void GAITesterStop( GAITesterRef inTester ); static OSStatus GAITesterAddTestCase( GAITesterRef inTester, GAITestCase *inCase ); static void GAITesterSetStopHandler( GAITesterRef inTester, GAITesterStopHandler_f inEventHandler, void * inEventContext ); static void GAITesterSetResultsHandler( GAITesterRef inTester, GAITesterResultsHandler_f inResultsHandler, void * inResultsContext ); static OSStatus GAITestCaseCreate( const char *inTitle, GAITestCase **outCase ); static void GAITestCaseFree( GAITestCase *inCase ); static OSStatus GAITestCaseAddItem( GAITestCase * inCase, unsigned int inAliasCount, unsigned int inAddressCount, int inTTL, GAITestAddrType inHasAddrs, GAITestAddrType inWantAddrs, unsigned int inTimeLimitMs, unsigned int inItemCount ); static OSStatus GAITestCaseAddLocalHostItem( GAITestCase * inCase, GAITestAddrType inWantAddrs, unsigned int inTimeLimitMs, unsigned int inItemCount ); static void GAIPerfCmd( void ) { OSStatus err; GAIPerfContext * context = NULL; err = CheckRootUser(); require_noerr_quiet( err, exit ); err = CheckIntegerArgument( gGAIPerf_CallDelayMs, "call delay (ms)", 0, INT_MAX ); require_noerr_quiet( err, exit ); 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 ); err = CheckIntegerArgument( gGAIPerf_IterationTimeLimitMs, "iteration time limit (ms)", 0, INT_MAX ); require_noerr_quiet( err, exit ); 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->iterTimeLimitMs = (unsigned int) gGAIPerf_IterationTimeLimitMs; context->callDelayMs = (unsigned int) gGAIPerf_CallDelayMs; context->serverDelayMs = (unsigned int) gGAIPerf_ServerDelayMs; context->defaultIterCount = (unsigned int) gGAIPerf_IterationCount; context->skipPathEval = gGAIPerf_SkipPathEvalulation ? true : false; context->badUDPMode = gGAIPerf_BadUDPMode ? true : false; context->protocol = (DNSProtocol) CLIArgToValue( "protocol", gGAIPerf_Protocol, &err, kDNSProtocolStr_Do53, (int) kDNSProtocol_Do53, kDNSProtocolStr_DoT, (int) kDNSProtocol_DoT, kDNSProtocolStr_DoH, (int) kDNSProtocol_DoH, NULL ); require_noerr_quiet( err, exit ); if( gGAIPerf_OutputFilePath ) { context->outputFilePath = strdup( gGAIPerf_OutputFilePath ); require_action( context->outputFilePath, exit, err = kNoMemoryErr ); } err = OutputFormatFromArgString( gGAIPerf_OutputFormat, &context->outputFormat ); require_noerr_quiet( err, exit ); err = GAITesterCreate( dispatch_get_main_queue(), context->protocol, context->callDelayMs, (int) context->serverDelayMs, kGAIPerfStandardTTL, context->skipPathEval, context->badUDPMode, &context->tester ); require_noerr( err, exit ); check( gGAIPerf_TestSuite ); if( strcasecmp( gGAIPerf_TestSuite, kGAIPerfTestSuiteName_Basic ) == 0 ) { 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 ); goto exit; } GAITesterSetStopHandler( context->tester, GAIPerfTesterStopHandler, context ); GAITesterSetResultsHandler( context->tester, GAIPerfResultsHandler, context ); signal( SIGINT, SIG_IGN ); err = DispatchSignalSourceCreate( SIGINT, dispatch_get_main_queue(), GAIPerfSignalHandler, context, &context->sigIntSource ); require_noerr( err, exit ); dispatch_resume( context->sigIntSource ); signal( SIGTERM, SIG_IGN ); err = DispatchSignalSourceCreate( SIGTERM, dispatch_get_main_queue(), GAIPerfSignalHandler, context, &context->sigTermSource ); require_noerr( err, exit ); dispatch_resume( context->sigTermSource ); GAITesterStart( context->tester ); dispatch_main(); exit: if( context ) GAIPerfContextFree( context ); exit( 1 ); } //=========================================================================================================================== // GAIPerfContextFree //=========================================================================================================================== static void GAIPerfContextFree( GAIPerfContext *inContext ) { ForgetCF( &inContext->tester ); ForgetCF( &inContext->testCaseResults ); ForgetMem( &inContext->outputFilePath ); dispatch_source_forget( &inContext->sigIntSource ); dispatch_source_forget( &inContext->sigTermSource ); free( inContext ); } //=========================================================================================================================== // GAIPerfAddAdvancedTestCases //=========================================================================================================================== #define kTestCaseTitleBufferSize 128 static void _GAIPerfWriteTestCaseTitle( char inBuffer[ kTestCaseTitleBufferSize ], unsigned int inCNAMERecordCount, unsigned int inARecordCount, unsigned int inAAAARecordCount, GAITestAddrType inRequested, unsigned int inIterationCount, Boolean inIterationsAreUnique ); static void _GAIPerfWriteLocalHostTestCaseTitle( char inBuffer[ kTestCaseTitleBufferSize ], GAITestAddrType inRequested, unsigned int inIterationCount ); #define kGAIPerfAdvancedTestSuite_MaxAliasCount 4 #define kGAIPerfAdvancedTestSuite_MaxAddrCount 8 static OSStatus GAIPerfAddAdvancedTestCases( GAIPerfContext *inContext ) { OSStatus err; unsigned int aliasCount, addressCount, i; GAITestCase * testCase = NULL; char title[ kTestCaseTitleBufferSize ]; aliasCount = 0; while( aliasCount <= kGAIPerfAdvancedTestSuite_MaxAliasCount ) { for( addressCount = 1; addressCount <= kGAIPerfAdvancedTestSuite_MaxAddrCount; addressCount *= 2 ) { // Add a test case to resolve a domain name with // // CNAME records, A records, and AAAA records // // to its IPv4 and IPv6 addresses. Each iteration resolves a unique instance of such a domain name, which // requires server queries. _GAIPerfWriteTestCaseTitle( title, aliasCount, addressCount, addressCount, kGAITestAddrType_Both, inContext->defaultIterCount, true ); err = GAITestCaseCreate( title, &testCase ); require_noerr( err, exit ); for( i = 0; i < inContext->defaultIterCount; ++i ) { err = GAITestCaseAddItem( testCase, aliasCount, addressCount, kGAIPerfStandardTTL, kGAITestAddrType_Both, kGAITestAddrType_Both, inContext->iterTimeLimitMs, 1 ); require_noerr( err, exit ); } err = GAITesterAddTestCase( inContext->tester, testCase ); require_noerr( err, exit ); testCase = NULL; // Add a test case to resolve a domain name with // // CNAME records, A records, and AAAA records // // to its IPv4 and IPv6 addresses. A preliminary iteration resolves a unique domain name, which requires a server // query. The subsequent iterations resolve the same domain name as the preliminary iteration, which should // ideally require no server queries, i.e., the results should come from the cache. _GAIPerfWriteTestCaseTitle( title, aliasCount, addressCount, addressCount, kGAITestAddrType_Both, inContext->defaultIterCount, false ); err = GAITestCaseCreate( title, &testCase ); require_noerr( err, exit ); err = GAITestCaseAddItem( testCase, aliasCount, addressCount, kGAIPerfStandardTTL, kGAITestAddrType_Both, kGAITestAddrType_Both, inContext->iterTimeLimitMs, inContext->defaultIterCount + 1 ); require_noerr( err, exit ); err = GAITesterAddTestCase( inContext->tester, testCase ); require_noerr( err, exit ); testCase = NULL; } 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 ); err = GAITestCaseCreate( title, &testCase ); require_noerr( err, exit ); err = GAITestCaseAddLocalHostItem( testCase, kGAITestAddrType_Both, inContext->iterTimeLimitMs, inContext->defaultIterCount ); require_noerr( err, exit ); err = GAITesterAddTestCase( inContext->tester, testCase ); require_noerr( err, exit ); testCase = NULL; exit: if( testCase ) GAITestCaseFree( testCase ); return( err ); } //=========================================================================================================================== // _GAIPerfWriteTestCaseTitle //=========================================================================================================================== #define GAITestAddrTypeToRequestKeyValue( X ) ( \ ( (X) == kGAITestAddrType_Both ) ? "ipv4\\,ipv6" : \ ( (X) == kGAITestAddrType_IPv4 ) ? "ipv4" : \ ( (X) == kGAITestAddrType_IPv6 ) ? "ipv6" : \ "" ) static void _GAIPerfWriteTestCaseTitle( char inBuffer[ kTestCaseTitleBufferSize ], unsigned int inCNAMERecordCount, unsigned int inARecordCount, unsigned int inAAAARecordCount, GAITestAddrType inRequested, unsigned int inIterationCount, Boolean inIterationsAreUnique ) { SNPrintF( inBuffer, kTestCaseTitleBufferSize, "name=dynamic,cname=%u,a=%u,aaaa=%u,req=%s,iterations=%u%?s", inCNAMERecordCount, inARecordCount, inAAAARecordCount, GAITestAddrTypeToRequestKeyValue( inRequested ), inIterationCount, inIterationsAreUnique, ",unique" ); } //=========================================================================================================================== // _GAIPerfWriteLocalHostTestCaseTitle //=========================================================================================================================== static void _GAIPerfWriteLocalHostTestCaseTitle( char inBuffer[ kTestCaseTitleBufferSize ], GAITestAddrType inRequested, unsigned int inIterationCount ) { SNPrintF( inBuffer, kTestCaseTitleBufferSize, "name=localhost,req=%s,iterations=%u", GAITestAddrTypeToRequestKeyValue( inRequested ), inIterationCount ); } //=========================================================================================================================== // GAIPerfAddBasicTestCases //=========================================================================================================================== #define kGAIPerfBasicTestSuite_AliasCount 2 #define kGAIPerfBasicTestSuite_AddrCount 4 static OSStatus GAIPerfAddBasicTestCases( GAIPerfContext *inContext ) { OSStatus err; GAITestCase * testCase = NULL; char title[ kTestCaseTitleBufferSize ]; unsigned int i; // Test Case #1: // Resolve a domain name with // // 2 CNAME records, 4 A records, and 4 AAAA records // // to its IPv4 and IPv6 addresses. Each of the iterations resolves a unique domain name, which requires server // queries. _GAIPerfWriteTestCaseTitle( title, kGAIPerfBasicTestSuite_AliasCount, kGAIPerfBasicTestSuite_AddrCount, kGAIPerfBasicTestSuite_AddrCount, kGAITestAddrType_Both, inContext->defaultIterCount, true ); 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, inContext->iterTimeLimitMs, 1 ); require_noerr( err, exit ); } err = GAITesterAddTestCase( inContext->tester, testCase ); require_noerr( err, exit ); testCase = NULL; // Test Case #2: // Resolve a domain name with // // 2 CNAME records, 4 A records, and 4 AAAA records // // to its IPv4 and IPv6 addresses. A preliminary iteration resolves a unique instance of such a domain name, which // requires server queries. Each of the subsequent iterations resolves the same domain name as the preliminary // iteration, which should ideally require no additional server queries, i.e., the results should come from the cache. _GAIPerfWriteTestCaseTitle( title, kGAIPerfBasicTestSuite_AliasCount, kGAIPerfBasicTestSuite_AddrCount, kGAIPerfBasicTestSuite_AddrCount, kGAITestAddrType_Both, inContext->defaultIterCount, false ); err = GAITestCaseCreate( title, &testCase ); require_noerr( err, exit ); err = GAITestCaseAddItem( testCase, kGAIPerfBasicTestSuite_AliasCount, kGAIPerfBasicTestSuite_AddrCount, kGAIPerfStandardTTL, kGAITestAddrType_Both, kGAITestAddrType_Both, inContext->iterTimeLimitMs, inContext->defaultIterCount + 1 ); require_noerr( err, exit ); err = GAITesterAddTestCase( inContext->tester, testCase ); require_noerr( err, exit ); testCase = NULL; // Test Case #3: // Each iteration resolves localhost to its IPv4 and IPv6 addresses. _GAIPerfWriteLocalHostTestCaseTitle( title, kGAITestAddrType_Both, inContext->defaultIterCount ); err = GAITestCaseCreate( title, &testCase ); require_noerr( err, exit ); err = GAITestCaseAddLocalHostItem( testCase, kGAITestAddrType_Both, inContext->iterTimeLimitMs, inContext->defaultIterCount ); require_noerr( err, exit ); err = GAITesterAddTestCase( inContext->tester, testCase ); require_noerr( err, exit ); testCase = NULL; exit: if( testCase ) GAITestCaseFree( testCase ); return( err ); } //=========================================================================================================================== // GAIPerfTesterStopHandler //=========================================================================================================================== #define kGAIPerfResultsKey_Info CFSTR( "info" ) #define kGAIPerfResultsKey_TestCases CFSTR( "testCases" ) #define kGAIPerfResultsKey_Success CFSTR( "success" ) #define kGAIPerfInfoKey_CallDelay CFSTR( "callDelayMs" ) #define kGAIPerfInfoKey_Protocol CFSTR( "protocol" ) #define kGAIPerfInfoKey_ServerDelay CFSTR( "serverDelayMs" ) #define kGAIPerfInfoKey_SkippedPathEval CFSTR( "skippedPathEval" ) #define kGAIPerfInfoKey_UsedBadUDPMode CFSTR( "usedBadUPDMode" ) static void GAIPerfTesterStopHandler( void *inContext, OSStatus inError ) { OSStatus err; GAIPerfContext * const context = (GAIPerfContext *) inContext; CFPropertyListRef plist; int exitCode; err = inError; require_noerr_quiet( err, exit ); err = CFPropertyListCreateFormatted( kCFAllocatorDefault, &plist, "{" "%kO=" // info "{" "%kO=%lli" // callDelayMs "%kO=%s" // protocol "%kO=%lli" // serverDelayMs "%kO=%b" // skippedPathEval "%kO=%b" // usedBadUPDMode "}" "%kO=%O" // testCases "%kO=%b" // success "}", kGAIPerfResultsKey_Info, kGAIPerfInfoKey_CallDelay, (int64_t) context->callDelayMs, kGAIPerfInfoKey_Protocol, _DNSProtocolToString( context->protocol ), 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 ); err = OutputPropertyList( plist, context->outputFormat, context->outputFilePath ); CFRelease( plist ); require_noerr( err, exit ); exit: exitCode = err ? 1 : ( context->testFailed ? 2 : 0 ); GAIPerfContextFree( context ); exit( exitCode ); } //=========================================================================================================================== // GAIPerfResultsHandler //=========================================================================================================================== // Keys for test case dictionary #define kGAIPerfTestCaseKey_Title CFSTR( "title" ) #define kGAIPerfTestCaseKey_StartTime CFSTR( "startTime" ) #define kGAIPerfTestCaseKey_EndTime CFSTR( "endTime" ) #define kGAIPerfTestCaseKey_Results CFSTR( "results" ) #define kGAIPerfTestCaseKey_FirstStats CFSTR( "firstStats" ) #define kGAIPerfTestCaseKey_ConnectionStats CFSTR( "connectionStats" ) #define kGAIPerfTestCaseKey_Stats CFSTR( "stats" ) // Keys for test case results array entry dictionaries #define kGAIPerfTestCaseResultKey_Name CFSTR( "name" ) #define kGAIPerfTestCaseResultKey_ConnectionTime CFSTR( "connectionTimeUs" ) #define kGAIPerfTestCaseResultKey_FirstTime CFSTR( "firstTimeUs" ) #define kGAIPerfTestCaseResultKey_Time CFSTR( "timeUs" ) // Keys for test case stats dictionaries #define kGAIPerfTestCaseStatsKey_Count CFSTR( "count" ) #define kGAIPerfTestCaseStatsKey_Min CFSTR( "min" ) #define kGAIPerfTestCaseStatsKey_Max CFSTR( "max" ) #define kGAIPerfTestCaseStatsKey_Mean CFSTR( "mean" ) #define kGAIPerfTestCaseStatsKey_StdDev CFSTR( "sd" ) typedef struct { double min; double max; double mean; double stdDev; } GAIPerfStats; #define GAIPerfStatsInit( X ) \ do { (X)->min = DBL_MAX; (X)->max = DBL_MIN; (X)->mean = 0.0; (X)->stdDev = 0.0; } while( 0 ) static void GAIPerfResultsHandler( const char * inCaseTitle, NanoTime64 inCaseStartTime, NanoTime64 inCaseEndTime, const GAITestItemResult * inResultArray, size_t inResultCount, void * inContext ) { OSStatus err; GAIPerfContext * const context = (GAIPerfContext *) inContext; int namesAreDynamic, namesAreUnique; const char * ptr; size_t startIndex; CFMutableArrayRef results = NULL; GAIPerfStats stats, firstStats, connStats; double sum, firstSum, connSum; size_t keyValueLen, i; uint32_t count; char keyValue[ 16 ]; // Size must be at least strlen( "name=dynamic" ) + 1 bytes. char startTime[ 32 ]; char endTime[ 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. namesAreDynamic = false; namesAreUnique = false; ptr = inCaseTitle; while( _ParseQuotedEscapedString( ptr, NULL, ",", keyValue, sizeof( keyValue ), &keyValueLen, NULL, &ptr ) ) { if( strnicmpx( keyValue, keyValueLen, "name=dynamic" ) == 0 ) { namesAreDynamic = true; } else if( strnicmpx( keyValue, keyValueLen, "unique" ) == 0 ) { namesAreUnique = true; } if( namesAreDynamic && namesAreUnique ) break; } startIndex = ( ( inResultCount > 0 ) && namesAreDynamic && !namesAreUnique ) ? 1 : 0; results = CFArrayCreateMutable( NULL, (CFIndex)( inResultCount - startIndex ), &kCFTypeArrayCallBacks ); require_action( results, exit, err = kNoMemoryErr ); GAIPerfStatsInit( &stats ); GAIPerfStatsInit( &firstStats ); GAIPerfStatsInit( &connStats ); sum = 0.0; firstSum = 0.0; connSum = 0.0; count = 0; for( i = startIndex; i < inResultCount; ++i ) { double value; result = &inResultArray[ i ]; err = CFPropertyListAppendFormatted( kCFAllocatorDefault, results, "{" "%kO=%s" // name "%kO=%lli" // connectionTimeUs "%kO=%lli" // firstTimeUs "%kO=%lli" // timeUs "%kO=%lli" // error "}", kGAIPerfTestCaseResultKey_Name, result->name, kGAIPerfTestCaseResultKey_ConnectionTime, (int64_t) result->connectionTimeUs, kGAIPerfTestCaseResultKey_FirstTime, (int64_t) result->firstTimeUs, kGAIPerfTestCaseResultKey_Time, (int64_t) result->timeUs, CFSTR( "error" ), (int64_t) result->error ); require_noerr( err, exit ); if( !result->error ) { value = (double) result->timeUs; if( value < stats.min ) stats.min = value; if( value > stats.max ) stats.max = value; sum += value; value = (double) result->firstTimeUs; if( value < firstStats.min ) firstStats.min = value; if( value > firstStats.max ) firstStats.max = value; firstSum += value; value = (double) result->connectionTimeUs; if( value < connStats.min ) connStats.min = value; if( value > connStats.max ) connStats.max = value; connSum += value; ++count; } else { context->testFailed = true; } } if( count > 0 ) { stats.mean = sum / (double) count; firstStats.mean = firstSum / (double) count; connStats.mean = connSum / (double) count; sum = 0.0; firstSum = 0.0; connSum = 0.0; for( i = startIndex; i < inResultCount; ++i ) { double diff; result = &inResultArray[ i ]; if( result->error ) continue; diff = stats.mean - (double) result->timeUs; sum += ( diff * diff ); diff = firstStats.mean - (double) result->firstTimeUs; firstSum += ( diff * diff ); diff = connStats.mean - (double) result->connectionTimeUs; connSum += ( diff * diff ); } stats.stdDev = sqrt( sum / (double) count ); firstStats.stdDev = sqrt( firstSum / (double) count ); connStats.stdDev = sqrt( connSum / (double) count ); } err = CFPropertyListAppendFormatted( kCFAllocatorDefault, context->testCaseResults, "{" "%kO=%s" "%kO=%s" "%kO=%s" "%kO=%O" "%kO=" "{" "%kO=%lli" "%kO=%f" "%kO=%f" "%kO=%f" "%kO=%f" "}" "%kO=" "{" "%kO=%lli" "%kO=%f" "%kO=%f" "%kO=%f" "%kO=%f" "}" "%kO=" "{" "%kO=%lli" "%kO=%f" "%kO=%f" "%kO=%f" "%kO=%f" "}" "}", kGAIPerfTestCaseKey_Title, inCaseTitle, kGAIPerfTestCaseKey_StartTime, _NanoTime64ToTimestamp( inCaseStartTime, startTime, sizeof( startTime ) ), kGAIPerfTestCaseKey_EndTime, _NanoTime64ToTimestamp( inCaseEndTime, endTime, sizeof( endTime ) ), kGAIPerfTestCaseKey_Results, results, kGAIPerfTestCaseKey_Stats, kGAIPerfTestCaseStatsKey_Count, (int64_t) count, kGAIPerfTestCaseStatsKey_Min, stats.min, kGAIPerfTestCaseStatsKey_Max, stats.max, kGAIPerfTestCaseStatsKey_Mean, stats.mean, kGAIPerfTestCaseStatsKey_StdDev, stats.stdDev, kGAIPerfTestCaseKey_FirstStats, kGAIPerfTestCaseStatsKey_Count, (int64_t) count, kGAIPerfTestCaseStatsKey_Min, firstStats.min, kGAIPerfTestCaseStatsKey_Max, firstStats.max, kGAIPerfTestCaseStatsKey_Mean, firstStats.mean, kGAIPerfTestCaseStatsKey_StdDev, firstStats.stdDev, kGAIPerfTestCaseKey_ConnectionStats, kGAIPerfTestCaseStatsKey_Count, (int64_t) count, kGAIPerfTestCaseStatsKey_Min, connStats.min, kGAIPerfTestCaseStatsKey_Max, connStats.max, kGAIPerfTestCaseStatsKey_Mean, connStats.mean, kGAIPerfTestCaseStatsKey_StdDev, connStats.stdDev ); require_noerr( err, exit ); exit: CFReleaseNullSafe( results ); if( err ) exit( 1 ); } //=========================================================================================================================== // GAIPerfSignalHandler //=========================================================================================================================== static void GAIPerfSignalHandler( void *inContext ) { GAIPerfContext * const context = (GAIPerfContext *) inContext; if( !context->tester ) exit( 1 ); GAITesterStop( context->tester ); context->tester = NULL; } //=========================================================================================================================== // GAITesterCreate //=========================================================================================================================== // A character set of lower-case alphabet characters and digits and a string length of six allows for 36^6 = 2,176,782,336 // possible strings to use in the Tag label. #define kGAITesterTagStringLen 6 typedef struct GAITestItem GAITestItem; struct GAITestItem { GAITestItem * next; // Next test item in list. char * name; // Domain name to resolve. uint64_t connectionTimeUs; // Time in microseconds that it took to create a DNS-SD connection. uint64_t firstTimeUs; // Time in microseconds that it took to get the first address result. uint64_t timeUs; // Time in microseconds that it took to get all expected address results. unsigned int addressCount; // Address count of the domain name, i.e., the Count label argument. OSStatus error; // Current status/error. unsigned int timeLimitMs; // Time limit in milliseconds for the test item's completion. Boolean hasV4; // True if the domain name has one or more IPv4 addresses. Boolean hasV6; // True if the domain name has one or more IPv6 addresses. Boolean wantV4; // True if DNSServiceGetAddrInfo() should be called to get IPv4 addresses. Boolean wantV6; // True if DNSServiceGetAddrInfo() should be called to get IPv6 addresses. }; struct GAITestCase { GAITestCase * next; // Next test case in list. GAITestItem * itemList; // List of test items. char * title; // Title of the test case. }; struct GAITesterPrivate { CFRuntimeBase base; // CF object base. dispatch_queue_t queue; // Serial work queue. DNSServiceRef connection; // Reference to the shared DNS-SD connection. DNSServiceRef getAddrInfo; // Reference to the current DNSServiceGetAddrInfo operation. GAITestCase * caseList; // List of test cases. GAITestCase * currentCase; // Pointer to the current test case. GAITestItem * currentItem; // Pointer to the current test item. DNSProtocol protocol; // DNS protocol to use, e.g., Do53, DoT, or DoH. 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. unsigned int callDelayMs; // Amount of time to wait before calling DNSServiceGetAddrInfo(). Boolean skipPathEval; // True if DNSServiceGetAddrInfo() path evaluation is to be skipped. Boolean stopped; // True if the tester has been stopped. Boolean badUDPMode; // True if the test DNS server is to run in Bad UDP mode. dispatch_source_t timer; // Timer for enforcing a test item's time limit. pcap_t * pcap; // Captures traffic between mDNSResponder and test DNS server. pid_t serverPID; // PID of the test DNS server. int serverDelayMs; // Additional time to have the server delay its responses by. int serverDefaultTTL; // Default TTL for the server's records. GAITesterStopHandler_f stopHandler; // User's stop handler. void * stopContext; // User's event handler context. GAITesterResultsHandler_f resultsHandler; // User's results handler. void * resultsContext; // User's results handler context. int probeTryCount; // Number of remaining probe queries. uint16_t serverPortDo53; // Do53 server port. Only relevant if server protocol is Do53. // Variables for current test item. uint64_t bitmapV4; // Bitmap of IPv4 results that have yet to be received. uint64_t bitmapV6; // Bitmap of IPv6 results that have yet to be received. uint64_t startTicks; // Start ticks of DNSServiceGetAddrInfo(). uint64_t connTicks; // Ticks when the connection was created. uint64_t firstTicks; // Ticks when the first DNSServiceGetAddrInfo result was received. uint64_t endTicks; // Ticks when the last DNSServiceGetAddrInfo result was received. Boolean gotFirstResult; // True if the first result has been received. }; CF_CLASS_DEFINE( GAITester ); static void _GAITesterStartNextTest( GAITesterRef inTester ); static OSStatus _GAITesterCreatePacketCapture( uint16_t inDNSServerPort, pcap_t **outPCap ); static void _GAITesterProbeTimerEventHandler( void *inContext ); static void _GAITesterTimeout( void *inContext ); static void DNSSD_API _GAITesterProbeCallback( DNSServiceRef inSDRef, DNSServiceFlags inFlags, uint32_t inInterfaceIndex, DNSServiceErrorType inError, const char * inHostname, const struct sockaddr * inSockAddr, uint32_t inTTL, void * inContext ); static void DNSSD_API _GAITesterGetAddrInfoCallback( DNSServiceRef inSDRef, DNSServiceFlags inFlags, uint32_t inInterfaceIndex, DNSServiceErrorType inError, const char * inHostname, const struct sockaddr * inSockAddr, uint32_t inTTL, void * inContext ); static void _GAITesterCompleteCurrentTest( GAITesterRef inTester, OSStatus inError ); #define ForgetPacketCapture( X ) ForgetCustom( X, pcap_close ) static OSStatus GAITestItemCreate( const char * inName, unsigned int inAddressCount, GAITestAddrType inHasAddrs, GAITestAddrType inWantAddrs, unsigned int inTimeLimitMs, GAITestItem ** outItem ); static OSStatus GAITestItemDup( const GAITestItem *inItem, GAITestItem **outItem ); static void GAITestItemFree( GAITestItem *inItem ); static OSStatus GAITesterCreate( dispatch_queue_t inQueue, DNSProtocol inProtocol, unsigned int inCallDelayMs, int inServerDelayMs, int inServerDefaultTTL, Boolean inSkipPathEvaluation, Boolean inBadUDPMode, GAITesterRef * outTester ) { OSStatus err; GAITesterRef obj = NULL; CF_OBJECT_CREATE( GAITester, obj, err, exit ); ReplaceDispatchQueue( &obj->queue, inQueue ); obj->protocol = inProtocol; obj->callDelayMs = inCallDelayMs; obj->serverPID = -1; obj->serverDelayMs = inServerDelayMs; obj->serverDefaultTTL = inServerDefaultTTL; obj->skipPathEval = inSkipPathEvaluation; obj->badUDPMode = inBadUDPMode; *outTester = obj; obj = NULL; err = kNoErr; exit: CFReleaseNullSafe( obj ); return( err ); } //=========================================================================================================================== // _GAITesterFinalize //=========================================================================================================================== static void _GAITesterFinalize( CFTypeRef inObj ) { GAITesterRef const me = (GAITesterRef) inObj; GAITestCase * testCase; check( !me->getAddrInfo ); check( !me->connection ); check( !me->timer ); dispatch_forget( &me->queue ); while( ( testCase = me->caseList ) != NULL ) { me->caseList = testCase->next; GAITestCaseFree( testCase ); } } //=========================================================================================================================== // GAITesterStart //=========================================================================================================================== static void _GAITesterStart( void *inContext ); static void _GAITesterStop( GAITesterRef me, OSStatus inError ); static void GAITesterStart( GAITesterRef me ) { CFRetain( me ); dispatch_async_f( me->queue, me, _GAITesterStart ); } static void _GAITesterStart( void *inContext ) { OSStatus err; GAITesterRef const me = (GAITesterRef) inContext; const char * const protocolStr = _DNSProtocolToString( me->protocol ); require_action( protocolStr, exit, err = kValueErr ); int serverPort = -1; switch( me->protocol ) { case kDNSProtocol_Do53: // Use a specific port for the Do53 server so that it can be used by the PCAP to filter Do53 traffic. #if( DNSSDUTIL_TEST_USE_ALTERNATE_SERVER_PORT_FOR_DO53 ) me->serverPortDo53 = kDNSPort_Do53Alt; #else me->serverPortDo53 = kDNSPort_Do53; #endif serverPort = me->serverPortDo53; break; case kDNSProtocol_DoT: #if( DNSSDUTIL_TEST_USE_ALTERNATE_SERVER_PORT_FOR_DOT ) serverPort = 0; // Use any ephemeral port. #endif break; case kDNSProtocol_DoH: break; } err = _SpawnCommand( &me->serverPID, NULL, NULL, "dnssdutil server --loopback --follow %lld --protocol %s%?s%?d%?s%?d%?s%?s%?d", (int64_t) getpid(), protocolStr, me->serverDefaultTTL >= 0, " --defaultTTL ", me->serverDefaultTTL >= 0, me->serverDefaultTTL, me->serverDelayMs >= 0, " --responseDelay ", me->serverDelayMs >= 0, me->serverDelayMs, me->badUDPMode, " --badUDPMode", serverPort >= 0, " --port ", serverPort >= 0, serverPort ); require_noerr_quiet( err, exit ); me->probeTryCount = 4; err = DispatchTimerCreate( DISPATCH_TIME_NOW, 1 * kNanosecondsPerSecond, 0, me->queue, _GAITesterProbeTimerEventHandler, NULL, me, &me->timer ); require_noerr( err, exit ); dispatch_resume( me->timer ); exit: if( err ) _GAITesterStop( me, err ); } //=========================================================================================================================== // GAITesterStop //=========================================================================================================================== static void _GAITesterUserStop( void *inContext ); static void GAITesterStop( GAITesterRef me ) { CFRetain( me ); dispatch_async_f( me->queue, me, _GAITesterUserStop ); } static void _GAITesterUserStop( void *inContext ) { GAITesterRef const me = (GAITesterRef) inContext; _GAITesterStop( me, kCanceledErr ); CFRelease( me ); } static void _GAITesterStop( GAITesterRef me, OSStatus inError ) { OSStatus err; ForgetPacketCapture( &me->pcap ); dispatch_source_forget( &me->timer ); DNSServiceForget( &me->getAddrInfo ); DNSServiceForget( &me->connection ); if( me->serverPID != -1 ) { err = kill( me->serverPID, SIGTERM ); err = map_global_noerr_errno( err ); check_noerr( err ); me->serverPID = -1; } if( !me->stopped ) { me->stopped = true; if( me->stopHandler ) me->stopHandler( me->stopContext, inError ); CFRelease( me ); } } //=========================================================================================================================== // GAITesterAddTestCase //=========================================================================================================================== static OSStatus GAITesterAddTestCase( GAITesterRef me, GAITestCase *inCase ) { OSStatus err; GAITestCase ** ptr; require_action_quiet( inCase->itemList, exit, err = kCountErr ); for( ptr = &me->caseList; *ptr; ptr = &( *ptr )->next ) {} *ptr = inCase; err = kNoErr; exit: return( err ); } //=========================================================================================================================== // GAITesterSetStopHandler //=========================================================================================================================== static void GAITesterSetStopHandler( GAITesterRef me, GAITesterStopHandler_f inStopHandler, void *inStopContext ) { me->stopHandler = inStopHandler; me->stopContext = inStopContext; } //=========================================================================================================================== // GAITesterSetResultsHandler //=========================================================================================================================== static void GAITesterSetResultsHandler( GAITesterRef me, GAITesterResultsHandler_f inResultsHandler, void *inResultsContext ) { me->resultsHandler = inResultsHandler; me->resultsContext = inResultsContext; } //=========================================================================================================================== // _GAITesterStartNextTest //=========================================================================================================================== static void _GAITesterStartNextTest( GAITesterRef me ) { OSStatus err; GAITestItem * item; DNSServiceFlags flags; DNSServiceProtocol protocols; int done = false; if( me->currentItem ) me->currentItem = me->currentItem->next; if( !me->currentItem ) { if( me->currentCase ) { // No more test items means that the current test case has completed. me->caseEndTime = NanoTimeGetCurrent(); if( me->resultsHandler ) { size_t resultCount, i; GAITestItemResult * resultArray; resultCount = 0; for( item = me->currentCase->itemList; item; item = item->next ) ++resultCount; check( resultCount > 0 ); resultArray = (GAITestItemResult *) calloc( resultCount, sizeof( *resultArray ) ); require_action( resultArray, exit, err = kNoMemoryErr ); item = me->currentCase->itemList; for( i = 0; i < resultCount; ++i ) { resultArray[ i ].name = item->name; resultArray[ i ].connectionTimeUs = item->connectionTimeUs; resultArray[ i ].firstTimeUs = item->firstTimeUs; resultArray[ i ].timeUs = item->timeUs; resultArray[ i ].error = item->error; item = item->next; } me->resultsHandler( me->currentCase->title, me->caseStartTime, me->caseEndTime, resultArray, resultCount, me->resultsContext ); ForgetMem( &resultArray ); } me->currentCase = me->currentCase->next; if( !me->currentCase ) { done = true; err = kNoErr; goto exit; } } else { me->currentCase = me->caseList; } require_action_quiet( me->currentCase->itemList, exit, err = kInternalErr ); me->currentItem = me->currentCase->itemList; } item = me->currentItem; check( ( item->addressCount >= 1 ) && ( item->addressCount <= 64 ) ); if( !item->wantV4 ) me->bitmapV4 = 0; else if( !item->hasV4 ) me->bitmapV4 = 1; else if( item->addressCount < 64 ) me->bitmapV4 = ( UINT64_C( 1 ) << item->addressCount ) - 1; else me->bitmapV4 = ~UINT64_C( 0 ); if( !item->wantV6 ) me->bitmapV6 = 0; else if( !item->hasV6 ) me->bitmapV6 = 1; else if( item->addressCount < 64 ) me->bitmapV6 = ( UINT64_C( 1 ) << item->addressCount ) - 1; else me->bitmapV6 = ~UINT64_C( 0 ); check( ( me->bitmapV4 != 0 ) || ( me->bitmapV6 != 0 ) ); me->gotFirstResult = false; // Perform preliminary tasks if this is the start of a new test case. if( item == me->currentCase->itemList ) { // Flush mDNSResponder's cache. err = systemf( NULL, "killall -HUP mDNSResponder" ); require_noerr( err, exit ); sleep( 1 ); me->caseStartTime = NanoTimeGetCurrent(); me->caseEndTime = kNanoTime_Invalid; } // Start a packet capture for Do53 traffic. if( me->protocol == kDNSProtocol_Do53 ) { check( !me->pcap ); err = _GAITesterCreatePacketCapture( me->serverPortDo53, &me->pcap ); require_noerr( err, exit ); } // Start timer for test item's time limit. check( !me->timer ); if( item->timeLimitMs > 0 ) { unsigned int timeLimitMs; timeLimitMs = item->timeLimitMs; if( me->callDelayMs > 0 ) timeLimitMs += (unsigned int) me->callDelayMs; if( me->serverDelayMs > 0 ) timeLimitMs += (unsigned int) me->serverDelayMs; err = DispatchTimerCreate( dispatch_time_milliseconds( timeLimitMs ), DISPATCH_TIME_FOREVER, ( (uint64_t) timeLimitMs ) * kNanosecondsPerMillisecond / 10, me->queue, _GAITesterTimeout, NULL, me, &me->timer ); require_noerr( err, exit ); dispatch_resume( me->timer ); } // Call DNSServiceGetAddrInfo(). if( me->callDelayMs > 0 ) usleep( ( (useconds_t) me->callDelayMs ) * kMicrosecondsPerMillisecond ); flags = kDNSServiceFlagsShareConnection | kDNSServiceFlagsReturnIntermediates; if( me->skipPathEval ) flags |= kDNSServiceFlagsPathEvaluationDone; protocols = 0; if( item->wantV4 ) protocols |= kDNSServiceProtocol_IPv4; if( item->wantV6 ) protocols |= kDNSServiceProtocol_IPv6; me->startTicks = UpTicks(); check( !me->connection ); err = DNSServiceCreateConnection( &me->connection ); require_noerr( err, exit ); err = DNSServiceSetDispatchQueue( me->connection, me->queue ); require_noerr( err, exit ); me->connTicks = UpTicks(); check( !me->getAddrInfo ); me->getAddrInfo = me->connection; err = DNSServiceGetAddrInfo( &me->getAddrInfo, flags, kDNSServiceInterfaceIndexAny, protocols, item->name, _GAITesterGetAddrInfoCallback, me ); require_noerr( err, exit ); exit: if( err || done ) _GAITesterStop( me, err ); } //=========================================================================================================================== // _GAITesterCreatePacketCapture //=========================================================================================================================== static OSStatus _GAITesterCreatePacketCapture( const uint16_t inDNSServerPort, pcap_t ** const outPCap ) { OSStatus err; pcap_t * pcap; struct bpf_program program; char errBuf[ PCAP_ERRBUF_SIZE ]; pcap = pcap_create( "lo0", errBuf ); require_action_string( pcap, exit, err = kUnknownErr, errBuf ); err = pcap_set_buffer_size( pcap, 512 * kBytesPerKiloByte ); require_noerr_action( err, exit, err = kUnknownErr ); err = pcap_set_snaplen( pcap, 512 ); require_noerr_action( err, exit, err = kUnknownErr ); err = pcap_set_immediate_mode( pcap, 0 ); require_noerr_action_string( err, exit, err = kUnknownErr, pcap_geterr( pcap ) ); err = pcap_activate( pcap ); require_noerr_action_string( err, exit, err = kUnknownErr, pcap_geterr( pcap ) ); err = pcap_setdirection( pcap, PCAP_D_INOUT ); require_noerr_action_string( err, exit, err = kUnknownErr, pcap_geterr( pcap ) ); err = pcap_setnonblock( pcap, 1, errBuf ); require_noerr_action_string( err, exit, err = kUnknownErr, errBuf ); char *programStr = NULL; ASPrintF( &programStr, "udp port %u", inDNSServerPort ); require_action( programStr, exit, err = kNoMemoryErr ); err = pcap_compile( pcap, &program, programStr, 1, PCAP_NETMASK_UNKNOWN ); ForgetMem( &programStr ); require_noerr_action_string( err, exit, err = kUnknownErr, pcap_geterr( pcap ) ); err = pcap_setfilter( pcap, &program ); pcap_freecode( &program ); require_noerr_action_string( err, exit, err = kUnknownErr, pcap_geterr( pcap ) ); *outPCap = pcap; pcap = NULL; exit: if( pcap ) pcap_close( pcap ); return( err ); } //=========================================================================================================================== // _GAITesterProbeTimerEventHandler //=========================================================================================================================== static void _GAITesterProbeTimerEventHandler( void *inContext ) { OSStatus err; GAITesterRef const me = (GAITesterRef) inContext; if( me->probeTryCount > 0 ) { DNSServiceFlags flags; char name[ 64 ]; char tag[ kGAITesterTagStringLen + 1 ]; flags = 0; if( me->skipPathEval ) flags |= kDNSServiceFlagsPathEvaluationDone; SNPrintF( name, sizeof( name ), "tag-gaitester-probe-%s.ipv4.d.test.", _RandomStringExact( kLowerAlphaNumericCharSet, kLowerAlphaNumericCharSetSize, sizeof( tag ) - 1, tag ) ); DNSServiceForget( &me->getAddrInfo ); err = DNSServiceGetAddrInfo( &me->getAddrInfo, flags, kDNSServiceInterfaceIndexAny, kDNSServiceProtocol_IPv4, name, _GAITesterProbeCallback, me ); require_noerr( err, exit ); err = DNSServiceSetDispatchQueue( me->getAddrInfo, me->queue ); require_noerr( err, exit ); --me->probeTryCount; } else { err = kNotPreparedErr; } exit: if( err ) _GAITesterStop( me, err ); } //=========================================================================================================================== // _GAITesterTimeout //=========================================================================================================================== static void _GAITesterTimeout( void *inContext ) { GAITesterRef const me = (GAITesterRef) inContext; _GAITesterCompleteCurrentTest( me, kTimeoutErr ); } //=========================================================================================================================== // _GAITesterProbeCallback //=========================================================================================================================== static void DNSSD_API _GAITesterProbeCallback( DNSServiceRef inSDRef, DNSServiceFlags inFlags, uint32_t inInterfaceIndex, DNSServiceErrorType inError, const char * inHostname, const struct sockaddr * inSockAddr, uint32_t inTTL, void * inContext ) { GAITesterRef const me = (GAITesterRef) inContext; Unused( inSDRef ); Unused( inInterfaceIndex ); Unused( inHostname ); Unused( inSockAddr ); Unused( inTTL ); if( ( inFlags & kDNSServiceFlagsAdd ) && !inError ) { dispatch_source_forget( &me->timer ); DNSServiceForget( &me->getAddrInfo ); _GAITesterStartNextTest( me ); } } //=========================================================================================================================== // _GAITesterGetAddrInfoCallback //=========================================================================================================================== static void DNSSD_API _GAITesterGetAddrInfoCallback( DNSServiceRef inSDRef, DNSServiceFlags inFlags, uint32_t inInterfaceIndex, DNSServiceErrorType inError, const char * inHostname, const struct sockaddr * inSockAddr, uint32_t inTTL, void * inContext ) { OSStatus err; GAITesterRef const me = (GAITesterRef) inContext; GAITestItem * const item = me->currentItem; const sockaddr_ip * const sip = (const sockaddr_ip *) inSockAddr; uint64_t nowTicks; uint64_t * bitmapPtr; uint64_t bitmask; int hasAddr; Unused( inSDRef ); Unused( inInterfaceIndex ); Unused( inHostname ); Unused( inTTL ); nowTicks = UpTicks(); require_action_quiet( inFlags & kDNSServiceFlagsAdd, exit, err = kFlagErr ); // Check if we were expecting an IP address result of this type. if( sip->sa.sa_family == AF_INET ) { bitmapPtr = &me->bitmapV4; hasAddr = item->hasV4; } else if( sip->sa.sa_family == AF_INET6 ) { bitmapPtr = &me->bitmapV6; hasAddr = item->hasV6; } else { err = kTypeErr; goto exit; } bitmask = 0; if( hasAddr ) { uint32_t addrOffset; require_noerr_action_quiet( inError, exit, err = inError ); if( sip->sa.sa_family == AF_INET ) { const uint32_t addrV4 = ntohl( sip->v4.sin_addr.s_addr ); if( strcasecmp( item->name, "localhost." ) == 0 ) { if( addrV4 == INADDR_LOOPBACK ) bitmask = 1; } else { addrOffset = addrV4 - kDNSServerBaseAddrV4; if( ( addrOffset >= 1 ) && ( addrOffset <= item->addressCount ) ) { bitmask = UINT64_C( 1 ) << ( addrOffset - 1 ); } } } else { const uint8_t * const addrV6 = sip->v6.sin6_addr.s6_addr; if( strcasecmp( item->name, "localhost." ) == 0 ) { if( memcmp( addrV6, in6addr_loopback.s6_addr, 16 ) == 0 ) bitmask = 1; } else if( memcmp( addrV6, kDNSServerBaseAddrV6, 15 ) == 0 ) { addrOffset = addrV6[ 15 ]; if( ( addrOffset >= 1 ) && ( addrOffset <= item->addressCount ) ) { bitmask = UINT64_C( 1 ) << ( addrOffset - 1 ); } } } } else { require_action_quiet( inError == kDNSServiceErr_NoSuchRecord, exit, err = inError ? inError : kUnexpectedErr ); bitmask = 1; } require_action_quiet( bitmask != 0, exit, err = kValueErr ); require_action_quiet( *bitmapPtr & bitmask, exit, err = kDuplicateErr ); *bitmapPtr &= ~bitmask; if( !me->gotFirstResult ) { me->firstTicks = nowTicks; me->gotFirstResult = true; } err = kNoErr; exit: if( err || ( ( me->bitmapV4 == 0 ) && ( me->bitmapV6 == 0 ) ) ) { me->endTicks = nowTicks; _GAITesterCompleteCurrentTest( me, err ); } } //=========================================================================================================================== // _GAITesterCompleteCurrentTest //=========================================================================================================================== static OSStatus _GAITesterGetDNSMessageFromPacket( const uint8_t * inPacketPtr, size_t inPacketLen, const uint8_t ** outMsgPtr, size_t * outMsgLen ); static void _GAITesterCompleteCurrentTest( GAITesterRef me, OSStatus inError ) { OSStatus err; GAITestItem * const item = me->currentItem; struct timeval timeStamps[ 4 ]; struct timeval * tsPtr; struct timeval * tsQA = NULL; struct timeval * tsQAAAA = NULL; struct timeval * tsRA = NULL; struct timeval * tsRAAAA = NULL; struct timeval * t1; struct timeval * t2; int64_t idleTimeUs; uint8_t name[ kDomainNameLengthMax ]; dispatch_source_forget( &me->timer ); DNSServiceForget( &me->getAddrInfo ); DNSServiceForget( &me->connection ); item->error = inError; if( item->error ) { err = kNoErr; goto exit; } err = DomainNameFromString( name, item->name, NULL ); require_noerr( err, exit ); tsPtr = &timeStamps[ 0 ]; if( me->pcap ) { for( ;; ) { int status; struct pcap_pkthdr * pktHdr; const uint8_t * packet; const uint8_t * msgPtr; size_t msgLen; const DNSHeader * hdr; unsigned int flags; const uint8_t * ptr; uint16_t qtype, qclass; uint8_t qname[ kDomainNameLengthMax ]; status = pcap_next_ex( me->pcap, &pktHdr, &packet ); if( status != 1 ) break; if( _GAITesterGetDNSMessageFromPacket( packet, pktHdr->caplen, &msgPtr, &msgLen ) != kNoErr ) continue; if( msgLen < kDNSHeaderLength ) continue; hdr = (const DNSHeader *) msgPtr; flags = DNSHeaderGetFlags( hdr ); if( DNSFlagsGetOpCode( flags ) != kDNSOpCode_Query ) continue; if( DNSHeaderGetQuestionCount( hdr ) < 1 ) continue; ptr = (const uint8_t *) &hdr[ 1 ]; if( DNSMessageExtractQuestion( msgPtr, msgLen, ptr, qname, &qtype, &qclass, NULL ) != kNoErr ) continue; if( qclass != kDNSServiceClass_IN ) continue; if( !DomainNameEqual( qname, name ) ) continue; if( item->wantV4 && ( qtype == kDNSServiceType_A ) ) { if( flags & kDNSHeaderFlag_Response ) { if( tsQA && !tsRA ) { tsRA = tsPtr++; *tsRA = pktHdr->ts; } } else if( !tsQA ) { tsQA = tsPtr++; *tsQA = pktHdr->ts; } } else if( item->wantV6 && ( qtype == kDNSServiceType_AAAA ) ) { if( flags & kDNSHeaderFlag_Response ) { if( tsQAAAA && !tsRAAAA ) { tsRAAAA = tsPtr++; *tsRAAAA = pktHdr->ts; } } else if( !tsQAAAA ) { tsQAAAA = tsPtr++; *tsQAAAA = pktHdr->ts; } } } } // t1 is the time when the last query was sent. if( tsQA && tsQAAAA ) t1 = TIMEVAL_GT( *tsQA, *tsQAAAA ) ? tsQA : tsQAAAA; else t1 = tsQA ? tsQA : tsQAAAA; // t2 is when the first response was received. if( tsRA && tsRAAAA ) t2 = TIMEVAL_LT( *tsRA, *tsRAAAA ) ? tsRA : tsRAAAA; else t2 = tsRA ? tsRA : tsRAAAA; if( t1 && t2 ) { idleTimeUs = TIMEVAL_USEC64_DIFF( *t2, *t1 ); if( idleTimeUs < 0 ) idleTimeUs = 0; } else { idleTimeUs = 0; } item->connectionTimeUs = UpTicksToMicroseconds( me->connTicks - me->startTicks ); item->firstTimeUs = UpTicksToMicroseconds( me->firstTicks - me->connTicks ) - (uint64_t) idleTimeUs; item->timeUs = UpTicksToMicroseconds( me->endTicks - me->connTicks ) - (uint64_t) idleTimeUs; exit: ForgetPacketCapture( &me->pcap ); if( err ) _GAITesterStop( me, err ); else _GAITesterStartNextTest( me ); } //=========================================================================================================================== // _GAITesterGetDNSMessageFromPacket //=========================================================================================================================== #define kHeaderSizeNullLink 4 #define kHeaderSizeIPv4Min 20 #define kHeaderSizeIPv6 40 #define kHeaderSizeUDP 8 #define kIPProtocolUDP 0x11 static OSStatus _GAITesterGetDNSMessageFromPacket( const uint8_t * inPacketPtr, size_t inPacketLen, const uint8_t ** outMsgPtr, size_t * outMsgLen ) { OSStatus err; const uint8_t * nullLink; uint32_t addressFamily; const uint8_t * ip; int ipHeaderLen; int protocol; const uint8_t * msg; const uint8_t * const end = &inPacketPtr[ inPacketLen ]; nullLink = &inPacketPtr[ 0 ]; require_action_quiet( ( end - nullLink ) >= kHeaderSizeNullLink, exit, err = kUnderrunErr ); addressFamily = ReadHost32( &nullLink[ 0 ] ); ip = &nullLink[ kHeaderSizeNullLink ]; if( addressFamily == AF_INET ) { require_action_quiet( ( end - ip ) >= kHeaderSizeIPv4Min, exit, err = kUnderrunErr ); ipHeaderLen = ( ip[ 0 ] & 0x0F ) * 4; protocol = ip[ 9 ]; } else if( addressFamily == AF_INET6 ) { require_action_quiet( ( end - ip ) >= kHeaderSizeIPv6, exit, err = kUnderrunErr ); ipHeaderLen = kHeaderSizeIPv6; protocol = ip[ 6 ]; } else { err = kTypeErr; goto exit; } require_action_quiet( protocol == kIPProtocolUDP, exit, err = kTypeErr ); require_action_quiet( ( end - ip ) >= ( ipHeaderLen + kHeaderSizeUDP ), exit, err = kUnderrunErr ); msg = &ip[ ipHeaderLen + kHeaderSizeUDP ]; *outMsgPtr = msg; *outMsgLen = (size_t)( end - msg ); err = kNoErr; exit: return( err ); } //=========================================================================================================================== // GAITestCaseCreate //=========================================================================================================================== static OSStatus GAITestCaseCreate( const char *inTitle, GAITestCase **outCase ) { OSStatus err; GAITestCase * obj; obj = (GAITestCase *) calloc( 1, sizeof( *obj ) ); require_action( obj, exit, err = kNoMemoryErr ); obj->title = strdup( inTitle ); require_action( obj->title, exit, err = kNoMemoryErr ); *outCase = obj; obj = NULL; err = kNoErr; exit: if( obj ) GAITestCaseFree( obj ); return( err ); } //=========================================================================================================================== // GAITestCaseFree //=========================================================================================================================== static void GAITestCaseFree( GAITestCase *inCase ) { GAITestItem * item; while( ( item = inCase->itemList ) != NULL ) { inCase->itemList = item->next; GAITestItemFree( item ); } ForgetMem( &inCase->title ); free( inCase ); } //=========================================================================================================================== // GAITestCaseAddItem //=========================================================================================================================== static OSStatus GAITestCaseAddItem( GAITestCase * inCase, unsigned int inAliasCount, unsigned int inAddressCount, int inTTL, GAITestAddrType inHasAddrs, GAITestAddrType inWantAddrs, unsigned int inTimeLimitMs, unsigned int inItemCount ) { OSStatus err; GAITestItem * item; GAITestItem * item2; GAITestItem * newItemList = NULL; GAITestItem ** itemPtr; char * ptr; char * end; unsigned int i; char name[ 64 ]; char tag[ kGAITesterTagStringLen + 1 ]; require_action_quiet( inItemCount > 0, exit, err = kNoErr ); // Limit address count to 64 because we use 64-bit bitmaps for keeping track of addresses. require_action_quiet( ( inAddressCount >= 1 ) && ( inAddressCount <= 64 ), exit, err = kCountErr ); require_action_quiet( ( inAliasCount >= 0 ) && ( inAliasCount <= INT32_MAX ), exit, err = kCountErr ); require_action_quiet( GAITestAddrTypeIsValid( inHasAddrs ), exit, err = kValueErr ); ptr = &name[ 0 ]; end = &name[ countof( name ) ]; // Add Alias label. if( inAliasCount == 1 ) SNPrintF_Add( &ptr, end, "alias." ); else if( inAliasCount >= 2 ) SNPrintF_Add( &ptr, end, "alias-%u.", inAliasCount ); // Add Count label. SNPrintF_Add( &ptr, end, "count-%u.", inAddressCount ); // Add TTL label. if( inTTL >= 0 ) SNPrintF_Add( &ptr, end, "ttl-%d.", inTTL ); // Add Tag label. SNPrintF_Add( &ptr, end, "tag-%s.", _RandomStringExact( kLowerAlphaNumericCharSet, kLowerAlphaNumericCharSetSize, sizeof( tag ) - 1, tag ) ); // Add IPv4 or IPv6 label if necessary. if( inHasAddrs == kGAITestAddrType_IPv4 ) SNPrintF_Add( &ptr, end, "ipv4." ); else if( inHasAddrs == kGAITestAddrType_IPv6 ) SNPrintF_Add( &ptr, end, "ipv6." ); // Finally, add the d.test. labels. SNPrintF_Add( &ptr, end, "d.test." ); // Create item. err = GAITestItemCreate( name, inAddressCount, inHasAddrs, inWantAddrs, inTimeLimitMs, &item ); require_noerr( err, exit ); newItemList = item; itemPtr = &item->next; // Create repeat items. for( i = 1; i < inItemCount; ++i ) { err = GAITestItemDup( item, &item2 ); require_noerr( err, exit ); *itemPtr = item2; itemPtr = &item2->next; } // Append to test case's item list. for( itemPtr = &inCase->itemList; *itemPtr; itemPtr = &( *itemPtr )->next ) {} *itemPtr = newItemList; newItemList = NULL; exit: while( ( item = newItemList ) != NULL ) { newItemList = item->next; GAITestItemFree( item ); } return( err ); } //=========================================================================================================================== // GAITestCaseAddLocalHostItem //=========================================================================================================================== static OSStatus GAITestCaseAddLocalHostItem( GAITestCase * inCase, GAITestAddrType inWantAddrs, unsigned int inTimeLimitMs, unsigned int inItemCount ) { OSStatus err; GAITestItem * item; GAITestItem * item2; GAITestItem * newItemList = NULL; GAITestItem ** itemPtr; unsigned int i; require_action_quiet( inItemCount > 1, exit, err = kNoErr ); err = GAITestItemCreate( "localhost.", 1, kGAITestAddrType_Both, inWantAddrs, inTimeLimitMs, &item ); require_noerr( err, exit ); newItemList = item; itemPtr = &item->next; // Create repeat items. for( i = 1; i < inItemCount; ++i ) { err = GAITestItemDup( item, &item2 ); require_noerr( err, exit ); *itemPtr = item2; itemPtr = &item2->next; } for( itemPtr = &inCase->itemList; *itemPtr; itemPtr = &( *itemPtr )->next ) {} *itemPtr = newItemList; newItemList = NULL; exit: while( ( item = newItemList ) != NULL ) { newItemList = item->next; GAITestItemFree( item ); } return( err ); } //=========================================================================================================================== // GAITestItemCreate //=========================================================================================================================== static OSStatus GAITestItemCreate( const char * inName, unsigned int inAddressCount, GAITestAddrType inHasAddrs, GAITestAddrType inWantAddrs, unsigned int inTimeLimitMs, GAITestItem ** outItem ) { OSStatus err; GAITestItem * obj = NULL; require_action_quiet( inAddressCount >= 1, exit, err = kCountErr ); require_action_quiet( GAITestAddrTypeIsValid( inHasAddrs ), exit, err = kValueErr ); require_action_quiet( GAITestAddrTypeIsValid( inWantAddrs ), exit, err = kValueErr ); obj = (GAITestItem *) calloc( 1, sizeof( *obj ) ); require_action( obj, exit, err = kNoMemoryErr ); obj->name = strdup( inName ); require_action( obj->name, exit, err = kNoMemoryErr ); obj->addressCount = inAddressCount; obj->hasV4 = ( inHasAddrs & kGAITestAddrType_IPv4 ) ? true : false; obj->hasV6 = ( inHasAddrs & kGAITestAddrType_IPv6 ) ? true : false; obj->wantV4 = ( inWantAddrs & kGAITestAddrType_IPv4 ) ? true : false; obj->wantV6 = ( inWantAddrs & kGAITestAddrType_IPv6 ) ? true : false; obj->error = kInProgressErr; obj->timeLimitMs = inTimeLimitMs; *outItem = obj; obj = NULL; err = kNoErr; exit: if( obj ) GAITestItemFree( obj ); return( err ); } //=========================================================================================================================== // GAITestItemDup //=========================================================================================================================== static OSStatus GAITestItemDup( const GAITestItem *inItem, GAITestItem **outItem ) { OSStatus err; GAITestItem * obj; obj = (GAITestItem *) calloc( 1, sizeof( *obj ) ); require_action( obj, exit, err = kNoMemoryErr ); *obj = *inItem; obj->next = NULL; if( inItem->name ) { obj->name = strdup( inItem->name ); require_action( obj->name, exit, err = kNoMemoryErr ); } *outItem = obj; obj = NULL; err = kNoErr; exit: if( obj ) GAITestItemFree( obj ); return( err ); } //=========================================================================================================================== // GAITestItemFree //=========================================================================================================================== static void GAITestItemFree( GAITestItem *inItem ) { ForgetMem( &inItem->name ); free( inItem ); } //=========================================================================================================================== // MDNSDiscoveryTestCmd //=========================================================================================================================== #define kMDNSDiscoveryTestFirstQueryTimeoutSecs 4 typedef struct { DNSServiceRef query; // Reference to DNSServiceQueryRecord for replier's "about" TXT record. dispatch_source_t queryTimer; // Used to time out the "about" TXT record query. NanoTime64 startTime; // When the test started. NanoTime64 endTime; // When the test ended. uint64_t heapByteLimit; // If > 0, limit for mDNSResponder's heap memory usage in bytes. [1] uint64_t heapByteCount; // mDNSResponder's heap memory usage in bytes. [1] char * memgraphPath; // Path to mDNSResponder's initial memgraph file. [1] 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 useNewGAI; // True if the browser is to use dnssd_getaddrinfo to resolve hostnames. 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[ 16 + 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; // Notes: // 1. If a non-zero heap memory limit is specified, then mDNSResponder's memgraph will be captured with the leaks // command before the test starts any client requests. Then, right before all of the test's client requests are // stopped, the heap command will be used to determine the difference in mDNSResponder's heap memory usage at // that point relative to the memgraph. If the difference exceeds the specified limit, then the test will be // considered a failure. 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; context->useNewGAI = gMDNSDiscoveryTest_UseNewGAI ? true : false; if( gMDNSDiscoveryTest_Interface ) { err = InterfaceIndexFromArgString( gMDNSDiscoveryTest_Interface, &context->ifIndex ); require_noerr_quiet( err, exit ); } else { err = _MDNSInterfaceGetAny( kMDNSInterfaceSubset_All, NULL, &context->ifIndex ); require_noerr_quiet( err, exit ); } err = OutputFormatFromArgString( gMDNSDiscoveryTest_OutputFormat, &context->outputFormat ); require_noerr_quiet( err, exit ); if( gMDNSDiscoveryTest_HeapBytesLimit ) { context->heapByteLimit = _StringToUInt64( gMDNSDiscoveryTest_HeapBytesLimit, &err ); if( err ) { FPrintF( stderr, "error: Invalid heap bytes limit '%s'. Valid range is [0, %llu].\n", gMDNSDiscoveryTest_HeapBytesLimit, UINT64_MAX ); } if( context->heapByteLimit > 0 ) { err = CheckRootUser(); 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 ); 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, NULL, NULL, "%s", context->replierCommand ); require_noerr_quiet( err, exit ); if( context->heapByteLimit > 0 ) { uuid_t uuid; uuid_generate_random( uuid ); uuid_string_t uuidStr; uuid_unparse_upper( uuid, uuidStr ); ASPrintF( &context->memgraphPath, "/tmp/mDNSResponder-%s.memgraph", uuidStr ); require_action( context->memgraphPath, exit, err = kNoMemoryErr ); err = systemf( NULL, "/usr/bin/leaks mDNSResponder --outputGraph=%s 2>&1", context->memgraphPath ); require_noerr( err, exit ); atexit_b( ^ { remove( context->memgraphPath ); } ); } // Query for the replier's about TXT record. A response means that 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: FPrintF( stderr, "error: %#m\n", err ); 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 ); ServiceBrowserSetUseNewGAI( context->browser, context->useNewGAI ); ServiceBrowserSetCallback( context->browser, _MDNSDiscoveryTestServiceBrowserCallback, context ); ServiceBrowserStart( context->browser ); exit: if( err ) exit( 1 ); } //=========================================================================================================================== // _MDNSDiscoveryTestRunHeap //=========================================================================================================================== static OSStatus _MDNSDiscoveryTestRunHeap( MDNSDiscoveryTestContext * const inContext ) { OSStatus err; char * line = NULL; require_action_quiet( inContext->memgraphPath, exit, err = kNoErr ); char *heapCmd = NULL; ASPrintF( &heapCmd, "/usr/bin/heap --showSizes --diffFrom=%s mDNSResponder", inContext->memgraphPath ); require_action( heapCmd, exit, err = kNoMemoryErr ); FILE *heapStream = popen( heapCmd, "r" ); ForgetMem( &heapCmd ); err = map_global_value_errno( heapStream, heapStream ); require_noerr( err, exit ); Boolean heapByteCountObtained = false; for( ;; ) { err = fcopyline( heapStream, &line, NULL ); if( err == kEndingErr ) { err = kNoErr; break; } require_noerr( err, exit ); if( !heapByteCountObtained ) { unsigned long long heapByteCount = 0; int n = 0; const int matchCount = sscanf( line, "All zones: %*u nodes (%llu bytes)%n", &heapByteCount, &n ); if( ( matchCount == 1 ) && ( n > 0 ) ) { inContext->heapByteCount = heapByteCount; heapByteCountObtained = true; } } FPrintF( stderr, "%s\n", line ); ForgetMem( &line ); } if( !heapByteCountObtained ) { FPrintF( stderr, "error: Did not match line for total heap byte count.\n" ); } err = pclose( heapStream ); heapStream = NULL; if( err == -1 ) { const int errnoVal = errno_compat(); if( errnoVal ) { err = errnoVal; } } else if( err ) { err = WEXITSTATUS( err ); } require_noerr( err, exit ); require_action( heapByteCountObtained, exit, err = kFormatErr ); exit: ForgetMem( &line ); return( err ); } //=========================================================================================================================== // _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_UsedNewGAI CFSTR( "usedNewGAI" ) #define kMDNSDiscoveryTestResultsKey_UnexpectedInstances CFSTR( "unexpectedInstances" ) #define kMDNSDiscoveryTestResultsKey_MissingInstances CFSTR( "missingInstances" ) #define kMDNSDiscoveryTestResultsKey_IncorrectInstances CFSTR( "incorrectInstances" ) #define kMDNSDiscoveryTestResultsKey_HeapByteLimit CFSTR( "heapByteLimit" ) #define kMDNSDiscoveryTestResultsKey_HeapByteCount CFSTR( "heapByteCount" ) #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 startTime[ 32 ]; char endTime[ 32 ]; context->endTime = NanoTimeGetCurrent(); err = inError; require_noerr( err, exit ); err = _MDNSDiscoveryTestRunHeap( context ); require_noerr( err, exit ); _NanoTime64ToTimestamp( context->startTime, startTime, sizeof( startTime ) ); _NanoTime64ToTimestamp( context->endTime, endTime, sizeof( endTime ) ); 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 #if( MDNSRESPONDER_PROJECT ) "%kO=%b" // usedNewGAI #endif "%kO=[%@]" // unexpectedInstances "%kO=[%@]" // missingInstances "%kO=[%@]" // incorrectInstances "%kO=%lli" // heapByteLimit "}", 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, startTime, kMDNSDiscoveryTestResultsKey_EndTime, endTime, kMDNSDiscoveryTestResultsKey_BrowseTimeSecs, (int64_t) context->browseTimeSecs, kMDNSDiscoveryTestResultsKey_ServiceType, context->serviceType, kMDNSDiscoveryTestResultsKey_FlushedCache, context->flushedCache, #if( MDNSRESPONDER_PROJECT ) kMDNSDiscoveryTestResultsKey_UsedNewGAI, context->useNewGAI, #endif kMDNSDiscoveryTestResultsKey_UnexpectedInstances, &unexpectedInstances, kMDNSDiscoveryTestResultsKey_MissingInstances, &missingInstances, kMDNSDiscoveryTestResultsKey_IncorrectInstances, &incorrectInstances, kMDNSDiscoveryTestResultsKey_HeapByteLimit, (int64_t) context->heapByteLimit ); require_noerr( err, exit ); if( context->heapByteLimit > 0 ) { err = CFPropertyListAppendFormatted( kCFAllocatorDefault, plist, "%kO=%lli", kMDNSDiscoveryTestResultsKey_HeapByteCount, context->heapByteCount ); 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 ]; uint8_t addrV6LL[ 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; WriteBig16Typed( &addrV4[ 1 ], (uint16_t) i ); addrV4[ 3 ] = 0; memcpy( addrV6, kMDNSReplierBaseAddrV6, 16 ); WriteBig16Typed( &addrV6[ 12 ], (uint16_t) i ); memcpy( addrV6LL, kMDNSReplierLinkLocalBaseAddrV6, 16 ); WriteBig16Typed( &addrV6LL[ 12 ], (uint16_t) 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 ) { const struct sockaddr_in6 * const sin6 = &ipaddr->sip.v6; addrPtr = sin6->sin6_addr.s6_addr; lsb = addrPtr[ 15 ]; if( ( lsb >= 1 ) && ( lsb <= context->recordCountAAAA ) ) { const uint32_t scopeID = ( lsb == 1 ) ? context->ifIndex : 0; if( ( memcmp( addrPtr, ( lsb == 1 ) ? addrV6LL : addrV6, 15 ) == 0 ) && ( sin6->sin6_scope_id == scopeID ) ) { 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 ) { struct sockaddr_in6 sin6; uint8_t missingIPv6[ 16 ]; addrV6Bitmap &= ~bitmask; memcpy( missingIPv6, ( j == 1 ) ? addrV6LL : addrV6, 16 ); missingIPv6[ 15 ] = (uint8_t) j; _SockAddrInitIPv6( &sin6, missingIPv6, ( j == 1 ) ? context->ifIndex : 0, 0 ); err = CFPropertyListAppendFormatted( kCFAllocatorDefault, missingAddrs, "%##a", &sin6 ); 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 ); if( ( context->heapByteLimit == 0 ) || ( context->heapByteCount <= context->heapByteLimit ) ) { success = true; } } CFDictionarySetBoolean( plist, kMDNSDiscoveryTestResultsKey_Success, success ); err = OutputPropertyList( plist, context->outputFormat, 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 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-". } 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 = _MDNSInterfaceGetAny( kMDNSInterfaceSubset_All, NULL, &context->ifIndex ); require_noerr_quiet( err, exit ); } if( gDotLocalTest_OutputFilePath ) { context->outputFilePath = strdup( gDotLocalTest_OutputFilePath ); require_action( context->outputFilePath, exit, err = kNoMemoryErr ); } err = OutputFormatFromArgString( gDotLocalTest_OutputFormat, &context->outputFormat ); require_noerr_quiet( err, exit ); 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. 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, NULL, NULL, "%s", context->replierCmd ); require_noerr( err, exit ); // Spawn a test DNS server. // Use the --registerSC option because this test depends on a GAI operation for a domain name that matches a *.local // seach domain. SystemConfiguration will set up a search domain for each of the DNS service's match domains. ASPrintF( &context->serverCmd, DNSSDUTIL_TEST_DNS_SERVER_COMMAND_PREAMBLE " --loopback --registerSC --follow %lld --defaultTTL 300" " --domain %s.local.", (int64_t) getpid(), context->labelStr ); require_action_quiet( context->serverCmd, exit, err = kUnknownErr ); err = _SpawnCommand( &context->serverPID, NULL, NULL, "%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, (uint16_t) rdataLen, 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, kMDNSReplierLinkLocalBaseAddrV6, 16 ); // fe80::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, kMDNSReplierLinkLocalBaseAddrV6, 16 ); // fe80::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 startTime[ 32 ]; char endTime[ 32 ]; subtest = inContext->subtest; inContext->subtest = NULL; subtest->endTime = NanoTimeGetCurrent(); _NanoTime64ToTimestamp( subtest->startTime, startTime, sizeof( startTime ) ); _NanoTime64ToTimestamp( subtest->endTime, endTime, sizeof( endTime ) ); reportDict = NULL; err = CFPropertyListCreateFormatted( kCFAllocatorDefault, &reportDict, "{" "%kO=%s" // startTime "%kO=%s" // endTime "%kO=%s" // queryName "%kO=%s" // description "%kO={%@}" // results "}", kDotLocalTestReportKey_StartTime, startTime, kDotLocalTestReportKey_EndTime, endTime, 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 timestampStart[ 32 ]; char timestampEnd[ 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 ); _NanoTime64ToTimestamp( inContext->startTime, timestampStart, sizeof( timestampStart ) ); _NanoTime64ToTimestamp( inContext->endTime, timestampEnd, sizeof( timestampEnd ) ); 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, timestampStart, kDotLocalTestReportKey_EndTime, timestampEnd, 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->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 dns_fixed_fields_srv * 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( dns_fixed_fields_srv ), exit, err = kSizeErr ); fields = (const dns_fixed_fields_srv *) inRDataPtr; priority = dns_fixed_fields_srv_get_priority( fields ); weight = dns_fixed_fields_srv_get_weight( fields ); port = dns_fixed_fields_srv_get_port( fields ); 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, &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 "pctest-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; // Wait 1 second before sending gratuitous response. #define kPCTProgPreWait "wait 1000;" // Wait at least 10 seconds after sending gratuitous response. This allows ~2.75 seconds for probing, ~5 seconds for a // rename, and a 2 second fudge factor for unexpected system delays. #define kProbeConflictTestPostWaitMs ( 10 * kMillisecondsPerSecond ) static const ProbeConflictTestCase kProbeConflictTestCases[] = { // No conflicts { "No probe conflicts.", kPCTProgPreWait "probes n-n-n;" "send;", false }, // One multicast probe conflict { "One multicast probe conflict (1).", kPCTProgPreWait "probes m;" "send;", false }, { "One multicast probe conflict (2).", kPCTProgPreWait "probes n-m;" "send;", false }, { "One multicast probe conflict (3).", kPCTProgPreWait "probes n-n-m;" "send;", false }, // One unicast probe conflict { "One unicast probe conflict (1).", kPCTProgPreWait "probes u;" "send;", true }, { "One unicast probe conflict (2).", kPCTProgPreWait "probes n-u;" "send;", true }, { "One unicast probe conflict (3).", kPCTProgPreWait "probes n-n-u;" "send;", true }, // One multicast and one unicast probe conflict { "Multicast and unicast probe conflict (1).", kPCTProgPreWait "probes m-u;" "send;", true }, { "Multicast and unicast probe conflict (2).", kPCTProgPreWait "probes m-n-u;" "send;", true }, { "Multicast and unicast probe conflict (3).", kPCTProgPreWait "probes m-n-n-u;" "send;", true }, { "Multicast and unicast probe conflict (4).", kPCTProgPreWait "probes n-m-u;" "send;", true }, { "Multicast and unicast probe conflict (5).", kPCTProgPreWait "probes n-m-n-u;" "send;", true }, { "Multicast and unicast probe conflict (6).", kPCTProgPreWait "probes n-m-n-n-u;" "send;", true }, { "Multicast and unicast probe conflict (7).", kPCTProgPreWait "probes n-n-m-u;" "send;", true }, { "Multicast and unicast probe conflict (8).", kPCTProgPreWait "probes n-n-m-n-u;" "send;", true }, { "Multicast and unicast probe conflict (9).", kPCTProgPreWait "probes n-n-m-n-n-u;" "send;", true }, // Two multicast probe conflicts { "Two multicast probe conflicts (1).", kPCTProgPreWait "probes m-m;" "send;", true }, { "Two multicast probe conflicts (2).", kPCTProgPreWait "probes m-n-m;" "send;", true }, { "Two multicast probe conflicts (3).", kPCTProgPreWait "probes m-n-n-m;" "send;", true }, { "Two multicast probe conflicts (4).", kPCTProgPreWait "probes n-m-m;" "send;", true }, { "Two multicast probe conflicts (5).", kPCTProgPreWait "probes n-m-n-m-n;" "send;", true }, { "Two multicast probe conflicts (6).", kPCTProgPreWait "probes n-m-n-n-m;" "send;", true }, { "Two multicast probe conflicts (7).", kPCTProgPreWait "probes n-n-m-m;" "send;", true }, { "Two multicast probe conflicts (8).", kPCTProgPreWait "probes n-n-m-n-m;" "send;", true }, { "Two multicast probe conflicts (9).", kPCTProgPreWait "probes n-n-m-n-n-m;" "send;", 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 records). (malloced) dispatch_source_t sigSourceINT; // SIGINT signal handler. dispatch_source_t sigSourceTERM; // SIGTERM signal handler. CFStringRef exComputerName; // Previous ComputerName. CFStringRef exLocalHostName; // Previous LocalHostName. CFStringEncoding exCompNameEnc; // Previous ComputerName's encoding. unsigned int testCaseIndex; // Index of the current test case. uint32_t conflictIfIndex; // Index of the interface that the collider is to operate on. uint32_t registerIfIndex; // Index of the interface to register the test service on. uint32_t extraWaitMs; // Extra amount of time to wait for renames in milliseconds. MDNSColliderProtocols protocol; // mDNS collider's IP protocol. char * outputFilePath; // File to write test results to. If NULL, write to stdout. (malloced) OutputFormatType outputFormat; // Format of test report 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 _ProbeConflictTestRestoreSystemNames( ProbeConflictTestContext *inContext ); static void _ProbeConflictTestSignalHandler( void *inContext ); static void ProbeConflictTestCmd( void ) { OSStatus err; ProbeConflictTestContext * context; const char * serviceName; CFStringRef computerName = NULL; CFStringRef localHostName = NULL; char * uniqueName; char tag[ 6 + 1 ]; context = (ProbeConflictTestContext *) calloc( 1, sizeof( *context ) ); require_action( context, exit, err = kNoMemoryErr ); if( gProbeConflictTest_Interface ) { err = InterfaceIndexFromArgString( gProbeConflictTest_Interface, &context->conflictIfIndex ); require_noerr_quiet( err, exit ); } else { err = _MDNSInterfaceGetAny( kMDNSInterfaceSubset_All, NULL, &context->conflictIfIndex ); require_noerr_quiet( err, exit ); } if( gProbeConflictTest_UseIPv6 ) { if( gProbeConflictTest_UseIPv4 ) { FPrintF( stderr, "error: --ipv4 and --ipv6 are mutually exclusive options.\n" ); goto exit; } context->protocol = kMDNSColliderProtocol_IPv6; } else { context->protocol = kMDNSColliderProtocol_IPv4; } context->registerIfIndex = gProbeConflictTest_RegisterOnAny ? kDNSServiceInterfaceIndexAny : context->conflictIfIndex; // Make sure that the sum of kProbeConflictTestPostWaitMs and extraWaitMs doesn't wrap under uint32_t arithmetic. check_compile_time_code( INT_MAX <= ( UINT32_MAX - kProbeConflictTestPostWaitMs ) ); err = CheckIntegerArgument( gProbeConflictTest_ExtraWaitMs, "extraWait", 0, INT_MAX ); require_noerr_quiet( err, exit ); context->extraWaitMs = (uint32_t) gProbeConflictTest_ExtraWaitMs; if( gProbeConflictTest_OutputFilePath ) { context->outputFilePath = strdup( gProbeConflictTest_OutputFilePath ); require_action( context->outputFilePath, exit, err = kNoMemoryErr ); } err = OutputFormatFromArgString( gProbeConflictTest_OutputFormat, &context->outputFormat ); require_noerr_quiet( err, exit ); context->results = CFArrayCreateMutable( NULL, kProbeConflictTestCaseCount, &kCFTypeArrayCallBacks ); require_action( context->results, exit, err = kNoMemoryErr ); context->testStartTime = NanoTimeGetCurrent(); // Set a unique ComputerName. computerName = SCDynamicStoreCopyComputerName( NULL, &context->exCompNameEnc ); err = map_scerror( computerName ); require_noerr( err, exit ); _RandomStringExact( kLowerAlphaNumericCharSet, kLowerAlphaNumericCharSetSize, sizeof( tag ) - 1, tag ); ASPrintF( &uniqueName, "dnssdutil-pctest-computer-name-%s", tag ); require_action( uniqueName, exit, err = kNoMemoryErr ); err = _SetComputerNameWithUTF8CString( uniqueName ); ForgetMem( &uniqueName ); require_noerr( err, exit ); context->exComputerName = computerName; computerName = NULL; // Set a unique LocalHostName. localHostName = SCDynamicStoreCopyLocalHostName( NULL ); err = map_scerror( localHostName ); require_noerr( err, exit ); ASPrintF( &uniqueName, "dnssdutil-pctest-local-hostname-%s", tag ); require_action( uniqueName, exit, err = kNoMemoryErr ); err = _SetLocalHostNameWithUTF8CString( uniqueName ); ForgetMem( &uniqueName ); require_noerr( err, exit ); context->exLocalHostName = localHostName; localHostName = NULL; // Set up SIGINT signal handler. signal( SIGINT, SIG_IGN ); err = DispatchSignalSourceCreate( SIGINT, dispatch_get_main_queue(), _ProbeConflictTestSignalHandler, context, &context->sigSourceINT ); require_noerr( err, exit ); dispatch_resume( context->sigSourceINT ); // Set up SIGTERM signal handler. signal( SIGTERM, SIG_IGN ); err = DispatchSignalSourceCreate( SIGTERM, dispatch_get_main_queue(), _ProbeConflictTestSignalHandler, context, &context->sigSourceTERM ); require_noerr( err, exit ); dispatch_resume( context->sigSourceTERM ); // Register the test service instance. serviceName = gProbeConflictTest_UseComputerName ? NULL : kProbeConflictTestService_DefaultName; ASPrintF( &context->serviceType, "_pctest-%s._udp", tag ); require_action( context->serviceType, exit, err = kNoMemoryErr ); err = DNSServiceRegister( &context->registration, 0, context->registerIfIndex, 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: CFReleaseNullSafe( computerName ); CFReleaseNullSafe( localHostName ); if( context ) _ProbeConflictTestRestoreSystemNames( context ); ErrQuit( 1, "error: %#m\n", err ); } //=========================================================================================================================== // _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 * const inContext ) { OSStatus err; const ProbeConflictTestCase * testCase; char * program = NULL; uint32_t totalWaitMs; 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 ); totalWaitMs = kProbeConflictTestPostWaitMs + inContext->extraWaitMs; ASPrintF( &program, "%s wait %u;", testCase->program, totalWaitMs ); require_action( program, exit, err = kNoMemoryErr ); err = MDNSColliderSetProgram( inContext->collider, program ); require_noerr( err, exit ); err = MDNSColliderSetRecord( inContext->collider, inContext->recordName, kDNSServiceType_TXT, kProbeConflictTestTXTPtr, kProbeConflictTestTXTLen ); require_noerr( err, exit ); MDNSColliderSetProtocols( inContext->collider, inContext->protocol ); MDNSColliderSetInterfaceIndex( inContext->collider, inContext->conflictIfIndex ); MDNSColliderSetStopHandler( inContext->collider, _ProbeConflictTestColliderStopHandler, inContext ); inContext->startTime = NanoTimeGetCurrent(); err = MDNSColliderStart( inContext->collider ); require_noerr( err, exit ); exit: ForgetMem( &program ); 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 now; Boolean passed; char startTime[ 32 ]; char endTime[ 32 ]; now = 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; _NanoTime64ToTimestamp( inContext->startTime, startTime, sizeof( startTime ) ); _NanoTime64ToTimestamp( now, endTime, sizeof( endTime ) ); 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, startTime, kProbeConflictTestCaseResultKey_EndTime, endTime, 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_RegistrationInterface CFSTR( "registrationInterface" ) #define kProbeConflictTestReportKey_ConflictInterface CFSTR( "conflictInterface" ) #define kProbeConflictTestReportKey_Index CFSTR( "index" ) #define kProbeConflictTestReportKey_Name CFSTR( "name" ) #define kProbeConflictTestReportKey_ExtraWaitMs CFSTR( "extraWaitMs" ) #define kProbeConflictTestReportKey_Results CFSTR( "results" ) #define kProbeConflictTestReportKey_Passed CFSTR( "passed" ) static void _ProbeConflictTestFinalizeAndExit( ProbeConflictTestContext *inContext ) { OSStatus err; CFPropertyListRef plist; NanoTime64 now; int exitCode; char startTime[ 32 ]; char endTime[ 32 ]; char registerIfName[ kInterfaceNameBufLen ]; char conflictIfName[ kInterfaceNameBufLen ]; now = NanoTimeGetCurrent(); check( !inContext->collider ); _NanoTime64ToTimestamp( inContext->testStartTime, startTime, sizeof( startTime ) ); _NanoTime64ToTimestamp( now, endTime, sizeof( endTime ) ); InterfaceIndexToName( inContext->registerIfIndex, registerIfName ); InterfaceIndexToName( inContext->conflictIfIndex, conflictIfName ); err = CFPropertyListCreateFormatted( kCFAllocatorDefault, &plist, "{" "%kO=%s" // startTime "%kO=%s" // endTime "%kO=%s" // serviceType "%kO=" // registrationInterface "{" "%kO=%lli" // index "%kO=%s" // name "}" "%kO=" // conflictInterface "{" "%kO=%lli" // index "%kO=%s" // name "}" "%kO=%lli" // extraWaitMs "%kO=%O" // results "%kO=%b" // passed "}", kProbeConflictTestReportKey_StartTime, startTime, kProbeConflictTestReportKey_EndTime, endTime, kProbeConflictTestReportKey_ServiceType, inContext->serviceType, kProbeConflictTestReportKey_RegistrationInterface, kProbeConflictTestReportKey_Index, (int64_t) inContext->registerIfIndex, kProbeConflictTestReportKey_Name, registerIfName, kProbeConflictTestReportKey_ConflictInterface, kProbeConflictTestReportKey_Index, (int64_t) inContext->conflictIfIndex, kProbeConflictTestReportKey_Name, conflictIfName, kProbeConflictTestReportKey_ExtraWaitMs, (int64_t) inContext->extraWaitMs, kProbeConflictTestReportKey_Results, inContext->results, kProbeConflictTestReportKey_Passed, inContext->testFailed ? false : true ); require_noerr( err, exit ); ForgetCF( &inContext->results ); err = OutputPropertyList( plist, inContext->outputFormat, inContext->outputFilePath ); CFRelease( plist ); require_noerr( err, exit ); exit: _ProbeConflictTestRestoreSystemNames( inContext ); if( err ) { FPrintF( stderr, "error: %#m\n", err ); exitCode = 1; } else { exitCode = inContext->testFailed ? 2 : 0; } exit( exitCode ); } //=========================================================================================================================== // _ProbeConflictTestRestoreSystemNames //=========================================================================================================================== static void _ProbeConflictTestRestoreSystemNames( ProbeConflictTestContext *inContext ) { OSStatus err; if( inContext->exComputerName ) { err = _SetComputerName( inContext->exComputerName, inContext->exCompNameEnc ); check_noerr( err ); ForgetCF( &inContext->exComputerName ); } if( inContext->exLocalHostName ) { err = _SetLocalHostName( inContext->exLocalHostName ); check_noerr( err ); ForgetCF( &inContext->exLocalHostName ); } } //=========================================================================================================================== // _ProbeConflictTestSignalHandler //=========================================================================================================================== static void _ProbeConflictTestSignalHandler( void *inContext ) { _ProbeConflictTestRestoreSystemNames( inContext ); FPrintF( stderr, "Probe conflict test got a SIGINT or SIGTERM signal, exiting...\n" ); exit( 1 ); } #if( MDNSRESPONDER_PROJECT ) //=========================================================================================================================== // FallbackTestCmd //=========================================================================================================================== typedef struct { unsigned int serverIndex; // Index of server that is soley capable of answering query. } FallbackSubtestParams; #define kFallbackTestSubtestCount 8 const FallbackSubtestParams kFallbackSubtestParams[] = { { 2 }, { 4 }, { 1 }, { 3 }, { 2 }, { 1 }, { 4 }, { 3 } }; check_compile_time( countof( kFallbackSubtestParams ) == kFallbackTestSubtestCount ); typedef struct { char * hostname; // Hostname to resolve. NanoTime64 startTime; // Subtest's start time. NanoTime64 endTime; // Subtest's end time. OSStatus error; // Subtest's current error. } FallbackSubtest; typedef struct { dispatch_queue_t queue; // Serial queue for test events. dispatch_semaphore_t doneSem; // Semaphore to signal when the test is done. DNSServiceRef gai; // Current DNSServiceGetAddrInfo request. dispatch_source_t timer; // Timer for enforcing time limit on current DNSServiceGetAddrInfo request. size_t subtestIndex; // Index of current subtest. pid_t serverPID; // PID of spawned test DNS server. OSStatus error; // Current test error. NanoTime64 startTime; // Test's start time. NanoTime64 endTime; // Test's end time. char * serverCmd; // Command used to invoke the test DNS server. char * probeHostname; // Hostname queried to verify that server is up and running. FallbackSubtest subtests[ kFallbackTestSubtestCount ]; Boolean useRefused; // True if server uses Refused RCODE for queries it's not allowed to answer. } FallbackTest; static OSStatus _FallbackTestCreate( FallbackTest **outTest ); static OSStatus _FallbackTestRun( FallbackTest *inTest ); static void _FallbackTestFree( FallbackTest *inTest ); ulog_define_ex( kDNSSDUtilIdentifier, FallbackTest, kLogLevelInfo, kLogFlags_None, "FallbackTest", NULL ); #define ft_ulog( LEVEL, ... ) ulog( &log_category_from_name( FallbackTest ), (LEVEL), __VA_ARGS__ ) static void FallbackTestCmd( void ) { OSStatus err; FallbackTest * test = NULL; CFPropertyListRef plist = NULL; OutputFormatType outputFormat; size_t i; CFMutableArrayRef results; Boolean testPassed = false; Boolean subtestFailed; char startTime[ 32 ]; char endTime[ 32 ]; err = CheckRootUser(); require_noerr_quiet( err, exit ); err = OutputFormatFromArgString( gFallbackTest_OutputFormat, &outputFormat ); require_noerr_quiet( err, exit ); err = _FallbackTestCreate( &test ); require_noerr( err, exit ); if( gFallbackTest_UseRefused ) test->useRefused = true; err = _FallbackTestRun( test ); require_noerr( err, exit ); _NanoTime64ToTimestamp( test->startTime, startTime, sizeof( startTime ) ); _NanoTime64ToTimestamp( test->endTime, endTime, sizeof( endTime ) ); err = CFPropertyListCreateFormatted( kCFAllocatorDefault, &plist, "{" "%kO=%s" // startTime "%kO=%s" // endTime "%kO=%s" // serverCmd "%kO=[%@]" // results "}", CFSTR( "startTime" ), startTime, CFSTR( "endTime" ), endTime, CFSTR( "serverCmd" ), test->serverCmd, CFSTR( "results" ), &results ); require_noerr( err, exit ); subtestFailed = false; check( test->subtestIndex == kFallbackTestSubtestCount ); for( i = 0; i < kFallbackTestSubtestCount; ++i ) { CFMutableDictionaryRef resultDict; FallbackSubtest * const subtest = &test->subtests[ i ]; char errorDesc[ 128 ]; err = CFPropertyListAppendFormatted( kCFAllocatorDefault, results, "{%@}", &resultDict ); require_noerr( err, exit ); err = CFDictionarySetCString( resultDict, CFSTR( "name" ), subtest->hostname, kSizeCString ); require_noerr( err, exit ); _NanoTime64ToTimestamp( subtest->startTime, startTime, sizeof( startTime ) ); err = CFDictionarySetCString( resultDict, CFSTR( "startTime" ), startTime, kSizeCString ); require_noerr( err, exit ); _NanoTime64ToTimestamp( subtest->endTime, endTime, sizeof( endTime ) ); err = CFDictionarySetCString( resultDict, CFSTR( "endTime" ), endTime, kSizeCString ); require_noerr( err, exit ); SNPrintF( errorDesc, sizeof( errorDesc ), "%m", subtest->error ); err = CFPropertyListAppendFormatted( kCFAllocatorDefault, resultDict, "%kO=" "{" "%kO=%lli" // code "%kO=%s" // description "}", CFSTR( "error" ), CFSTR( "code" ), (int64_t) subtest->error, CFSTR( "description" ), errorDesc ); require_noerr( err, exit ); if( subtest->error ) subtestFailed = true; } if( !subtestFailed ) testPassed = true; CFPropertyListAppendFormatted( kCFAllocatorDefault, plist, "%kO=%b", CFSTR( "pass" ), testPassed ); err = OutputPropertyList( plist, outputFormat, gFallbackTest_OutputFilePath ); require_noerr( err, exit ); exit: if( test ) _FallbackTestFree( test ); CFReleaseNullSafe( plist ); gExitCode = err ? 1 : ( testPassed ? 0 : 2 ); } //=========================================================================================================================== static void _FallbackTestStart( void *inContext ); static void _FallbackTestStop( FallbackTest *inTest, OSStatus inError ); static void DNSSD_API _FallbackTestProbeGAICallback( DNSServiceRef inSDRef, DNSServiceFlags inFlags, uint32_t inInterfaceIndex, DNSServiceErrorType inError, const char * inHostname, const struct sockaddr * inSockAddr, uint32_t inTTL, void * inContext ); static void _FallbackTestProbeTimerHandler( void *inContext ); static void DNSSD_API _FallbackTestGetAddrInfoCallback( DNSServiceRef inSDRef, DNSServiceFlags inFlags, uint32_t inInterfaceIndex, DNSServiceErrorType inError, const char * inHostname, const struct sockaddr * inSockAddr, uint32_t inTTL, void * inContext ); static void _FallbackTestGAITimerHandler( void *inContext ); static OSStatus _FallbackTestStartSubtest( FallbackTest *inTest ); static void _FallbackTestForgetSources( FallbackTest *inTest ); #define kFallbackTestProbeTimeLimitSecs 5 #define kFallbackTestGAITimeLimitSecs 75 static OSStatus _FallbackTestCreate( FallbackTest **outTest ) { OSStatus err; FallbackTest * test; test = (FallbackTest *) calloc( 1, sizeof( *test ) ); require_action( test, exit, err = kNoMemoryErr ); test->error = kInProgressErr; test->serverPID = -1; test->queue = dispatch_queue_create( "com.apple.dnssdutil.fallback-test", DISPATCH_QUEUE_SERIAL ); require_action( test->queue, exit, err = kNoResourcesErr ); test->doneSem = dispatch_semaphore_create( 0 ); require_action( test->doneSem, exit, err = kNoResourcesErr ); *outTest = test; test = NULL; err = kNoErr; exit: if( test ) _FallbackTestFree( test ); return( err ); } //=========================================================================================================================== static OSStatus _FallbackTestRun( FallbackTest *inTest ) { dispatch_async_f( inTest->queue, inTest, _FallbackTestStart ); dispatch_semaphore_wait( inTest->doneSem, DISPATCH_TIME_FOREVER ); return( inTest->error ); } //=========================================================================================================================== static void _FallbackTestFree( FallbackTest *inTest ) { size_t i; check( !inTest->gai ); check( !inTest->timer ); check( inTest->serverPID < 0 ); ForgetMem( &inTest->serverCmd ); ForgetMem( &inTest->probeHostname ); dispatch_forget( &inTest->queue ); dispatch_forget( &inTest->doneSem ); for( i = 0; i < kFallbackTestSubtestCount; ++i ) { FallbackSubtest * const subtest = &inTest->subtests[ i ]; ForgetMem( &subtest->hostname ); } free( inTest ); } //=========================================================================================================================== static void _FallbackTestStart( void *inContext ) { OSStatus err; FallbackTest * const test = (FallbackTest *) inContext; char tag[ 6 + 1 ]; test->startTime = NanoTimeGetCurrent(); // The "dnssdutil server" command will create a resolver entry for the server's "d.test." domain containing an array // of the server's IP addresses. Because configd favors IPv6 addresses, when there's a mix of IPv4 and IPv6 // addresses, configd may rearrange the array in order to ensure that IPv6 addresses come before the IPv4 addresses. // To preserve the original address order, the server is specified to run in IPv6-only mode. This way, // mDNSResponder's view of the address will be such that address with index value 1 is first, address with index // value 2 is second, etc. ASPrintF( &test->serverCmd, DNSSDUTIL_TEST_DNS_SERVER_COMMAND_PREAMBLE " --loopback --follow %lld --ipv6 --extraIPv6 3%s%s", (int64_t) getpid(), test->useRefused ? " --useRefused" : "" ); require_action_quiet( test->serverCmd, exit, err = kUnknownErr ); err = _SpawnCommand( &test->serverPID, "/dev/null", "/dev/null", "%s", test->serverCmd ); require_noerr( err, exit ); ASPrintF( &test->probeHostname, "tag-fallback-test-probe-%s.count-1.ipv4.ttl-900.d.test.", _RandomStringExact( kLowerAlphaNumericCharSet, kLowerAlphaNumericCharSetSize, sizeof( tag ) - 1, tag ) ); require_action( test->probeHostname, exit, err = kNoMemoryErr ); ft_ulog( kLogLevelInfo, "Starting GetAddrInfo request for %s\n", test->probeHostname ); err = DNSServiceGetAddrInfo( &test->gai, 0, kDNSServiceInterfaceIndexAny, kDNSServiceProtocol_IPv4, test->probeHostname, _FallbackTestProbeGAICallback, test ); require_noerr( err, exit ); err = DNSServiceSetDispatchQueue( test->gai, test->queue ); require_noerr( err, exit ); err = DispatchTimerOneShotCreate( dispatch_time_seconds( kFallbackTestProbeTimeLimitSecs ), kFallbackTestProbeTimeLimitSecs * ( UINT64_C_safe( kNanosecondsPerSecond ) / 10 ), test->queue, _FallbackTestProbeTimerHandler, test, &test->timer ); require_noerr( err, exit ); dispatch_resume( test->timer ); exit: if( err ) _FallbackTestStop( test, err ); } //=========================================================================================================================== static void _FallbackTestStop( FallbackTest *inTest, OSStatus inError ) { inTest->error = inError; inTest->endTime = NanoTimeGetCurrent(); _FallbackTestForgetSources( inTest ); if( inTest->serverPID >= 0 ) { OSStatus err; err = kill( inTest->serverPID, SIGTERM ); err = map_global_noerr_errno( err ); check_noerr( err ); inTest->serverPID = -1; } dispatch_semaphore_signal( inTest->doneSem ); } //=========================================================================================================================== static void DNSSD_API _FallbackTestProbeGAICallback( DNSServiceRef inSDRef, DNSServiceFlags inFlags, uint32_t inInterfaceIndex, DNSServiceErrorType inError, const char * inHostname, const struct sockaddr * inSockAddr, uint32_t inTTL, void * inContext ) { OSStatus err; FallbackTest * const test = (FallbackTest *) inContext; Unused( inSDRef ); Unused( inInterfaceIndex ); Unused( inHostname ); Unused( inTTL ); if( ( inFlags & kDNSServiceFlagsAdd ) && !inError ) { _FallbackTestForgetSources( test ); ft_ulog( kLogLevelInfo, "Probe: Got GAI address %##a for %s\n", inSockAddr, test->probeHostname ); check( test->subtestIndex == 0 ); err = _FallbackTestStartSubtest( test ); require_noerr( err, exit ); } err = kNoErr; exit: if( err ) _FallbackTestStop( test, err ); } //=========================================================================================================================== static void _FallbackTestProbeTimerHandler( void *inContext ) { FallbackTest * const test = (FallbackTest *) inContext; ft_ulog( kLogLevelInfo, "GetAddrInfo probe request for \"%s\" timed out.\n", test->probeHostname ); _FallbackTestStop( test, kNotPreparedErr ); } //=========================================================================================================================== static void DNSSD_API _FallbackTestGetAddrInfoCallback( DNSServiceRef inSDRef, DNSServiceFlags inFlags, uint32_t inInterfaceIndex, DNSServiceErrorType inError, const char * inHostname, const struct sockaddr * inSockAddr, uint32_t inTTL, void * inContext ) { OSStatus err; struct sockaddr_in sin; FallbackTest * const test = (FallbackTest *) inContext; FallbackSubtest *const subtest = &test->subtests[ test->subtestIndex ]; Boolean complete = false; Unused( inSDRef ); Unused( inInterfaceIndex ); Unused( inTTL ); _FallbackTestForgetSources( test ); if( strcasecmp( inHostname, subtest->hostname ) != 0 ) { ft_ulog( kLogLevelError, "GetAddrInfo(%s) result: Got unexpected hostname \"%s\".\n", subtest->hostname, inHostname ); err = kUnexpectedErr; goto done; } if( inError ) { ft_ulog( kLogLevelError, "GetAddrInfo(%s) result: Got unexpected error %#m.\n", subtest->hostname, inError ); err = inError; goto done; } if( ( inFlags & kDNSServiceFlagsAdd ) == 0 ) { ft_ulog( kLogLevelError, "GetAddrInfo(%s) result: Missing Add flag.\n", subtest->hostname ); err = kUnexpectedErr; goto done; } _SockAddrInitIPv4( &sin, kDNSServerBaseAddrV4 + 1, 0 ); if( SockAddrCompareAddr( inSockAddr, &sin ) != 0 ) { ft_ulog( kLogLevelError, "GetAddrInfo(%s) result: Got unexpected address %##a (expected %##a).\n", subtest->hostname, inSockAddr, &sin ); err = kUnexpectedErr; goto done; } ft_ulog( kLogLevelInfo, "Subtest %zu/%d: Got expected GAI address %##a for %s\n", test->subtestIndex + 1, kFallbackTestSubtestCount, inSockAddr, subtest->hostname ); err = kNoErr; done: subtest->endTime = NanoTimeGetCurrent(); subtest->error = err; err = kNoErr; if( ++test->subtestIndex < kFallbackTestSubtestCount ) { err = _FallbackTestStartSubtest( test ); require_noerr( err, exit ); } else { complete = true; } exit: if( err || complete ) _FallbackTestStop( test, err ); } //=========================================================================================================================== static void _FallbackTestGAITimerHandler( void *inContext ) { OSStatus err; FallbackTest * const test = (FallbackTest *) inContext; FallbackSubtest * const subtest = &test->subtests[ test->subtestIndex ]; Boolean complete = false; _FallbackTestForgetSources( test ); ft_ulog( kLogLevelInfo, "GetAddrInfo request for \"%s\" timed out.\n", subtest->hostname ); subtest->endTime = NanoTimeGetCurrent(); subtest->error = kTimeoutErr; if( ++test->subtestIndex < kFallbackTestSubtestCount ) { err = _FallbackTestStartSubtest( test ); require_noerr( err, exit ); } else { complete = true; err = kNoErr; } exit: if( err || complete ) _FallbackTestStop( test, err ); } //=========================================================================================================================== static OSStatus _FallbackTestStartSubtest( FallbackTest *inTest ) { OSStatus err; FallbackSubtest * const subtest = &inTest->subtests[ inTest->subtestIndex ]; char tag[ 6 + 1 ]; subtest->error = kInProgressErr; subtest->startTime = NanoTimeGetCurrent(); ForgetMem( &subtest->hostname ); ASPrintF( &subtest->hostname, "index-%u.tag-fallback-test-%s.count-1.ipv4.ttl-900.d.test.", kFallbackSubtestParams[ inTest->subtestIndex ].serverIndex, _RandomStringExact( kLowerAlphaNumericCharSet, kLowerAlphaNumericCharSetSize, sizeof( tag ) - 1, tag ) ); require_action( subtest->hostname, exit, err = kNoMemoryErr ); ft_ulog( kLogLevelInfo, "Starting GetAddrInfo request for %s\n", subtest->hostname ); check( !inTest->gai ); err = DNSServiceGetAddrInfo( &inTest->gai, 0, kDNSServiceInterfaceIndexAny, kDNSServiceProtocol_IPv4, subtest->hostname, _FallbackTestGetAddrInfoCallback, inTest ); require_noerr( err, exit ); err = DNSServiceSetDispatchQueue( inTest->gai, inTest->queue ); require_noerr( err, exit ); check( !inTest->timer ); err = DispatchTimerOneShotCreate( dispatch_time_seconds( kFallbackTestGAITimeLimitSecs ), kFallbackTestGAITimeLimitSecs * ( UINT64_C_safe( kNanosecondsPerSecond ) / 10 ), inTest->queue, _FallbackTestGAITimerHandler, inTest, &inTest->timer ); require_noerr( err, exit ); dispatch_resume( inTest->timer ); exit: return( err ); } //=========================================================================================================================== static void _FallbackTestForgetSources( FallbackTest *inTest ) { DNSServiceForget( &inTest->gai ); dispatch_source_forget( &inTest->timer ); } //=========================================================================================================================== // ExpensiveConstrainedsTestCmd //=========================================================================================================================== #define NOTIFICATION_TIME_THRESHOLD 1500 // The maximum wating time allowed before notification happens #define TEST_REPETITION 2 // the number of repetition that one test has to passed #define LOOPBACK_INTERFACE_NAME "lo0" #define WIFI_TEST_QUESTION_NAME "www.example.com" #define EXPENSIVE_CONSTRAINED_MAX_RETRIES 1 #define EXPENSIVE_CONSTRAINED_TEST_INTERVAL 5 // Use "-n tag-expensive-test.ttl-86400.d.test." to run the test locally // #define LOOPBACK_TEST_QUESTION_NAME "tag-expensive-test.ttl-86400.d.test." #define EXPENSIVE_CONSTRAINED_TEST_REPORT_KEY_START_TIME CFSTR( "Start Time" ) #define EXPENSIVE_CONSTRAINED_TEST_REPORT_KEY_END_TIME CFSTR( "End Time" ) #define EXPENSIVE_CONSTRAINED_TEST_REPORT_KEY_ALL_PASSED CFSTR( "All Tests Passed" ) #define EXPENSIVE_CONSTRAINED_TEST_REPORT_KEY_SUBTEST_RESULT CFSTR( "Subtest Results" ) #define EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_START_TIME CFSTR( "Start Time" ) #define EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_END_TIME CFSTR( "End Time" ) #define EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_QNAME CFSTR( "Question Name" ) #define EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_FLAGS CFSTR( "DNS Service Flags" ) #define EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_PROTOCOLS CFSTR( "Protocols" ) #define EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_INTERFACE_INDEX CFSTR( "Interface Index" ) #define EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_INTERFACE_NAME CFSTR( "Interface Name" ) #define EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_RESULT CFSTR( "Result" ) #define EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_ERROR CFSTR( "Error Description" ) #define EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_TEST_PROGRESS CFSTR( "Test Progress" ) #define EXPENSIVE_CONSTRAINED_SUBTEST_PROGRESS_KEY_START_TIME CFSTR( "Start Time" ) #define EXPENSIVE_CONSTRAINED_SUBTEST_PROGRESS_KEY_END_TIME CFSTR( "End Time" ) #define EXPENSIVE_CONSTRAINED_SUBTEST_PROGRESS_KEY_STATE CFSTR( "State" ) #define EXPENSIVE_CONSTRAINED_SUBTEST_PROGRESS_KEY_EXPECT_RESULT CFSTR( "Expected Result" ) #define EXPENSIVE_CONSTRAINED_SUBTEST_PROGRESS_KEY_ACTUAL_RESULT CFSTR( "Actual Result" ) #define EXPENSIVE_CONSTRAINED_SUBTEST_PROGRESS_KEY_EXPENSIVE_PREV_NOW CFSTR( "Expensive Prev->Now" ) #define EXPENSIVE_CONSTRAINED_SUBTEST_PROGRESS_KEY_CONSTRAINED_PREV_NOW CFSTR( "Constrained Prev->Now" ) #define EXPENSIVE_CONSTRAINED_SUBTEST_PROGRESS_KEY_CALL_BACK CFSTR( "Call Back" ) #define EXPENSIVE_CONSTRAINED_SUBTEST_ACTUAL_RESULT_KEY_TIMESTAMP CFSTR( "Timestamp" ) #define EXPENSIVE_CONSTRAINED_SUBTEST_ACTUAL_RESULT_KEY_NAME CFSTR( "Answer Name" ) #define EXPENSIVE_CONSTRAINED_SUBTEST_ACTUAL_RESULT_KEY_FLAGS CFSTR( "Add or Remove" ) #define EXPENSIVE_CONSTRAINED_SUBTEST_ACTUAL_RESULT_KEY_INTERFACE CFSTR( "Interface Index" ) #define EXPENSIVE_CONSTRAINED_SUBTEST_ACTUAL_RESULT_KEY_ADDRESS CFSTR( "Address" ) // All the states that ends with _PREPARE represents the state where the test state is reset and initialized. enum ExpensiveConstrainedTestState { TEST_BEGIN, TEST_EXPENSIVE_PREPARE, TEST_EXPENSIVE, // Test if mDNSResponder can handle "expensive" status change of the corresponding interface TEST_CONSTRAINED_PREPARE, TEST_CONSTRAINED, // Test if mDNSResponder can handle "constrained" status change of the corresponding interface TEST_EXPENSIVE_CONSTRAINED_PREPARE, TEST_EXPENSIVE_CONSTRAINED, // Test if mDNSResponder can handle "expensive" and "constrained" status change of the corresponding interface at the same time TEST_FAILED, TEST_SUCCEEDED }; enum ExpensiveConstrainedTestOperation { RESULT_ADD, // received response for the given query, which means mDNSResponder is able to send out the query over the interface, because the interface status is changed. RESULT_RMV, // received negative response for the given query, which means mDNSResponder is not able to send out the query over the interface, because the interface status is changed. NO_UPDATE // no status update notification }; typedef struct { uint32_t subtestIndex; // The index of parameter for the subtest DNSServiceRef opRef; // sdRef for the DNSServiceGetAddrInfo operation. const char * name; // Hostname to resolve. DNSServiceFlags flags; // Flags argument for DNSServiceGetAddrInfo(). DNSServiceProtocol protocols; // Protocols argument for DNSServiceGetAddrInfo(). uint32_t ifIndex; // Interface index argument for DNSServiceGetAddrInfo(). char ifName[IFNAMSIZ]; // Interface name for the given interface index. dispatch_source_t timer; // The test will check if the current behavior is valid, which is called by // the timer per 2s. pid_t serverPID; Boolean isExpensivePrev; // If the interface is expensive in the previous test step. Boolean isExpensiveNow; // If the interface is expensive now. Boolean isConstrainedPrev; // If the interface is constrained in the previous test step. Boolean isConstrainedNow; // If the interface is constrained now. Boolean startFromExpensive; // All the test will start from expensive/constrained interface, so there won's be an answer until the interface is changed. uint8_t numOfRetries; // the number of retries we can have if the test fail struct timeval updateTime; // The time when interface status(expensive or constrained) is changed. struct timeval notificationTime; // The time when callback function, which is passed to DNSServiceGetAddrInfo, gets called. uint32_t counter; // To record how many times the test has repeated. enum ExpensiveConstrainedTestState state; // The current test state. enum ExpensiveConstrainedTestOperation expectedOperation; // the test expects this kind of notification enum ExpensiveConstrainedTestOperation operation; // represents what notification the callback function gets. NanoTime64 testReport_startTime; // when the entire test starts CFMutableArrayRef subtestReport; // stores the log message for every subtest NanoTime64 subtestReport_startTime; // when the subtest starts CFMutableArrayRef subtestProgress; // one test iteration NanoTime64 subtestProgress_startTime; // when the test iteration starts CFMutableArrayRef subtestProgress_callBack; // array of ADD/REMOVE events char * outputFilePath; // File to write test results to. If NULL, then write to stdout. (malloced) OutputFormatType outputFormat; // Format of test report output. } ExpensiveConstrainedContext; // structure that controls how the subtest is run typedef struct { const char *qname; // the name of the query, when the ends with ".d.test.", test will send query to local DNS server Boolean deny_expensive; // if the query should avoid using expensive interface Boolean deny_constrained; // if the query should avoid using constrained interface Boolean start_from_expensive; // if the query should starts from using an expensive interface Boolean ipv4_query; // only allow IPv4 query Boolean ipv6_query; // only allow IPv6 query int8_t test_passed; // if the subtest passes } ExpensiveConstrainedTestParams; static ExpensiveConstrainedTestParams ExpensiveConstrainedSubtestParams[] = { // qname deny_expensive deny_constrained start_from_expensive ipv4_query ipv6_query {"tag-expensive_constrained-test.ttl-86400.d.test.", true, false, false, true, true, -1}, {"tag-expensive_constrained-test.ttl-86400.d.test.", true, false, true, true, true, -1}, {"tag-expensive_constrained-test.ttl-86400.d.test.", false, true, false, true, true, -1}, {"tag-expensive_constrained-test.ttl-86400.d.test.", false, true, true, true, true, -1}, {"tag-expensive_constrained-test.ttl-86400.d.test.", true, true, false, true, true, -1}, {"tag-expensive_constrained-test.ttl-86400.d.test.", true, true, true, true, true, -1}, // IPv4 Only {"tag-expensive_constrained-test.ttl-86400.d.test.", true, false, false, true, false, -1}, {"tag-expensive_constrained-test.ttl-86400.d.test.", true, false, true, true, false, -1}, {"tag-expensive_constrained-test.ttl-86400.d.test.", false, true, false, true, false, -1}, {"tag-expensive_constrained-test.ttl-86400.d.test.", false, true, true, true, false, -1}, {"tag-expensive_constrained-test.ttl-86400.d.test.", true, true, false, true, false, -1}, {"tag-expensive_constrained-test.ttl-86400.d.test.", true, true, true, true, false, -1}, // IPv6 Only {"tag-expensive_constrained-test.ttl-86400.d.test.", true, false, false, false, true, -1}, {"tag-expensive_constrained-test.ttl-86400.d.test.", true, false, true, false, true, -1}, {"tag-expensive_constrained-test.ttl-86400.d.test.", false, true, false, false, true, -1}, {"tag-expensive_constrained-test.ttl-86400.d.test.", false, true, true, false, true, -1}, {"tag-expensive_constrained-test.ttl-86400.d.test.", true, true, false, false, true, -1}, {"tag-expensive_constrained-test.ttl-86400.d.test.", true, true, true, false, true, -1} }; static void ExpensiveConstrainedSetupLocalDNSServer( ExpensiveConstrainedContext *context ); static void ExpensiveConstrainedStartTestHandler( ExpensiveConstrainedContext *context ); static void ExpensiveConstrainedStopTestHandler( ExpensiveConstrainedContext *context ); static void ExpensiveConstrainedSetupTimer( ExpensiveConstrainedContext *context, uint32_t second ); static void ExpensiveConstrainedTestTimerEventHandler( ExpensiveConstrainedContext *context ); static void DNSSD_API ExpensiveConstrainedCallback( DNSServiceRef inSDRef, DNSServiceFlags inFlags, uint32_t inInterfaceIndex, DNSServiceErrorType inError, const char * inHostname, const struct sockaddr * inSockAddr, uint32_t inTTL, void * inContext ); static void ExpensiveConstrainedInitializeContext( ExpensiveConstrainedContext *context ); static void ExpensiveConstrainedStopAndCleanTheTest( ExpensiveConstrainedContext *context ); static void ExpensiveConstrainedSubtestProgressReport( ExpensiveConstrainedContext *context ); static void ExpensiveConstrainedSubtestReport( ExpensiveConstrainedContext *context, const char *error_description ); static void ExpensiveConstrainedFinalResultReport( ExpensiveConstrainedContext *context, Boolean allPassed ); static const char *ExpensiveConstrainedProtocolString(DNSServiceProtocol protocol); static const char *ExpensiveConstrainedStateString(enum ExpensiveConstrainedTestState state); static const char *ExpensiveConstrainedOperationString(enum ExpensiveConstrainedTestOperation operation); static Boolean expensiveConstrainedEndsWith( const char *str, const char *suffix ); //=========================================================================================================================== // ExpensiveConstrainedTestCmd //=========================================================================================================================== static void ExpensiveConstrainedTestCmd( void ) { OSStatus err; dispatch_source_t signalSource = NULL; ExpensiveConstrainedContext * context = NULL; // Set up SIGINT handler. signal( SIGINT, SIG_IGN ); err = DispatchSignalSourceCreate( SIGINT, dispatch_get_main_queue(), Exit, kExitReason_SIGINT, &signalSource ); require_noerr( err, exit ); dispatch_resume( signalSource ); // create the test context context = (ExpensiveConstrainedContext *) calloc( 1, sizeof(*context) ); require_action( context, exit, err = kNoMemoryErr ); // get the command line option err = OutputFormatFromArgString( gExpensiveConstrainedTest_OutputFormat, &context->outputFormat ); require_noerr_quiet( err, exit ); if ( gExpensiveConstrainedTest_OutputFilePath ) { context->outputFilePath = strdup( gExpensiveConstrainedTest_OutputFilePath ); require_noerr_quiet( context->outputFilePath, exit ); } // initialize context context->subtestIndex = 0; context->numOfRetries = EXPENSIVE_CONSTRAINED_MAX_RETRIES; // initialize the CFArray used to store the log context->subtestReport = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks ); context->testReport_startTime = NanoTimeGetCurrent(); // setup local DNS server ExpensiveConstrainedSetupLocalDNSServer( context ); ExpensiveConstrainedStartTestHandler( context ); dispatch_main(); exit: exit( 1 ); } //=========================================================================================================================== // ExpensiveConstrainedSetupLocalDNSServer //=========================================================================================================================== static void ExpensiveConstrainedSetupLocalDNSServer( ExpensiveConstrainedContext *context ) { pid_t current_pid = getpid(); OSStatus err = _SpawnCommand( &context->serverPID, NULL, NULL, "dnssdutil server -l --port 0 --follow %d", current_pid ); if (err != 0) { FPrintF( stdout, "dnssdutil server -l --port 0 --follow failed, error: %d\n", err ); exit( 1 ); } sleep(2); } //=========================================================================================================================== // ExpensiveConstrainedStartTestHandler //=========================================================================================================================== static void ExpensiveConstrainedStartTestHandler( ExpensiveConstrainedContext *context ) { // setup 3s timer ExpensiveConstrainedSetupTimer( context, EXPENSIVE_CONSTRAINED_TEST_INTERVAL ); // set the event handler for the 3s timer dispatch_source_set_event_handler( context->timer, ^{ ExpensiveConstrainedTestTimerEventHandler( context ); } ); dispatch_resume( context->timer ); } //=========================================================================================================================== // ExpensiveConstrainedStartTestHandler //=========================================================================================================================== static void ExpensiveConstrainedStopTestHandler( ExpensiveConstrainedContext *context ) { dispatch_cancel( context->timer ); dispatch_release( context->timer ); context->timer = NULL; } //=========================================================================================================================== // ExpensiveConstrainedSetupTimer //=========================================================================================================================== static void ExpensiveConstrainedSetupTimer( ExpensiveConstrainedContext *context, uint32_t second ) { // set the timer source, the event handler will be called for every "second" seconds context->timer = dispatch_source_create( DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue() ); if ( context->timer == NULL ) { FPrintF( stdout, "dispatch_source_create:DISPATCH_SOURCE_TYPE_TIMER failed\n" ); exit( 1 ); } // the first block will be put into the queue "second"s after calling dispatch_resume dispatch_source_set_timer( context->timer, dispatch_time( DISPATCH_TIME_NOW, second * NSEC_PER_SEC ), (unsigned long long)(second) * NSEC_PER_SEC, 100ull * NSEC_PER_MSEC ); } //=========================================================================================================================== // ExpensiveConstrainedTestTimerEventHandler //=========================================================================================================================== static void ExpensiveConstrainedTestTimerEventHandler( ExpensiveConstrainedContext *context ) { OSStatus err; char buffer[ 1024 ]; const char *errorDescription = NULL; // do not log the state if we are in transition state if (context->state != TEST_BEGIN && context->state != TEST_SUCCEEDED && context->state != TEST_CONSTRAINED_PREPARE && context->state != TEST_EXPENSIVE_CONSTRAINED_PREPARE) ExpensiveConstrainedSubtestProgressReport( context ); switch ( context->state ) { case TEST_BEGIN: { ExpensiveConstrainedStopTestHandler( context ); // clear mDNSResponder cache err = systemf( NULL, "killall -HUP mDNSResponder" ); require_noerr_action( err, test_failed, errorDescription = "systemf failed"); // initialize the global parameters ExpensiveConstrainedInitializeContext( context ); // The local DNS server is set up on the local only interface. gExpensiveConstrainedTest_Interface = LOOPBACK_INTERFACE_NAME; strncpy( context->ifName, gExpensiveConstrainedTest_Interface, sizeof( context->ifName ) ); // The local DNS server is unscoped, so we must set our question to unscoped. context->ifIndex = kDNSServiceInterfaceIndexAny; // The question name must end with "d.test.", "tag-expensive-test.ttl-86400.d.test." for example, then the test will // use the local dns server set up previously to run the test locally. require_action( gExpensiveConstrainedTest_Name != NULL && expensiveConstrainedEndsWith( gExpensiveConstrainedTest_Name, "d.test." ), test_failed, SNPrintF( buffer, sizeof( buffer ), "The question name (%s) must end with \"d.test.\".\n", gExpensiveConstrainedTest_Name ); errorDescription = buffer ); // get the quesion name context->name = gExpensiveConstrainedTest_Name; // set the initial state for the interface context->startFromExpensive = gExpensiveConstrainedTest_StartFromExpensive; err = systemf( NULL, "ifconfig %s %sexpensive && ifconfig %s -constrained", context->ifName, context->startFromExpensive ? "" : "-", context->ifName ); require_noerr_action( err, test_failed, errorDescription = "systemf failed"); sleep( 5 ); // wait for 5s to allow the interface change event de delivered to others // get question flag if ( gExpensiveConstrainedTest_DenyExpensive ) context->flags |= kDNSServiceFlagsDenyExpensive; if ( gExpensiveConstrainedTest_DenyConstrained ) context->flags |= kDNSServiceFlagsDenyConstrained; if ( gExpensiveConstrainedTest_ProtocolIPv4 ) context->protocols |= kDNSServiceProtocol_IPv4; if ( gExpensiveConstrainedTest_ProtocolIPv6 ) context->protocols |= kDNSServiceProtocol_IPv6; // prevent mDNSResponder from doing extra path evaluation and changing the interface to others(such as Bluetooth) #if( TARGET_OS_WATCH ) context->flags |= kDNSServiceFlagsPathEvaluationDone; #endif // start the query DNSServiceGetAddrInfo( &context->opRef, context->flags, context->ifIndex, context->protocols, context->name, ExpensiveConstrainedCallback, context ); // set the initial test status context->subtestReport_startTime = NanoTimeGetCurrent(); context->subtestProgress_startTime = NanoTimeGetCurrent(); context->state = TEST_EXPENSIVE_PREPARE; // start from expensive test context->isExpensiveNow = context->startFromExpensive ? true : false; context->isConstrainedNow = false; context->expectedOperation = context->isExpensiveNow && ( context->flags & kDNSServiceFlagsDenyExpensive ) ? NO_UPDATE : RESULT_ADD; context->operation = NO_UPDATE; context->subtestProgress = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks); require_action( context->subtestProgress != NULL, test_failed, errorDescription = "CFArrayCreateMutable failed" ); context->subtestProgress_callBack = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks); require_action( context->subtestProgress != NULL, test_failed, errorDescription = "CFArrayCreateMutable failed" ); // set the queue where the callback will be called when there is an answer for the query err = DNSServiceSetDispatchQueue( context->opRef, dispatch_get_main_queue() ); require_noerr( err, test_failed ); ExpensiveConstrainedStartTestHandler( context ); } break; case TEST_EXPENSIVE_PREPARE: require_action( context->isConstrainedNow == false, test_failed, SNPrintF( buffer, sizeof( buffer ), "Interface %s should be unconstrained.\n", context->ifName ); errorDescription = buffer ); require_action( context->expectedOperation == context->operation, test_failed, errorDescription = "Operation is not expected" ); context->subtestProgress_startTime = NanoTimeGetCurrent(); context->state = TEST_EXPENSIVE; // begin to test expensive flag context->counter = 0; // the number of test repetition that has passed context->isExpensivePrev = context->isExpensiveNow; context->isExpensiveNow = !context->isExpensiveNow; // flip the expensive status context->isConstrainedPrev = false; // the interface is currently unconstrained context->isConstrainedNow = false; // the interface will be unconstrained in the current test if ( gExpensiveConstrainedTest_DenyExpensive ) context->expectedOperation = context->isExpensiveNow ? RESULT_RMV : RESULT_ADD; else context->expectedOperation = NO_UPDATE; context->operation = NO_UPDATE; // NO_UPDATE means the call back function has not been called context->subtestProgress_callBack = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks ); require_action( context->subtestProgress_callBack != NULL, test_failed, errorDescription = "CFArrayCreateMutable failed" ); err = systemf( NULL, "ifconfig %s %sexpensive", context->ifName, context->isExpensiveNow ? "" : "-" ); require_noerr_action( err, test_failed, errorDescription = "systemf failed" ); // record the starting timestamp gettimeofday( &context->updateTime, NULL ); break; case TEST_EXPENSIVE: // Since we are testing expensive flag, we should always turn the expensive flag on and off. require_action( context->isExpensivePrev ^ context->isExpensiveNow, test_failed, SNPrintF( buffer, sizeof( buffer ), "The current expensive status should be different with the previous one: %d -> %d\n", context->isExpensivePrev, context->isExpensiveNow); errorDescription = buffer ); // constrained flag is always turned off when testing expensive require_action( context->isConstrainedNow == false, test_failed, SNPrintF( buffer, sizeof( buffer ), "The interface %s should be unconstrained when testing \"expensive\"\n", context->ifName ); errorDescription = buffer ); require_action( context->expectedOperation == context->operation, test_failed, errorDescription = "Operation is not expected" ); context->counter++; // one test repetition has passed if ( context->counter == TEST_REPETITION ) // expensive test finished { // prepare to test constrained flag context->state = TEST_CONSTRAINED_PREPARE; // reset the interface err = systemf( NULL, "ifconfig %s -expensive && ifconfig %s -constrained", context->ifName, context->ifName ); require_noerr_action( err, test_failed, errorDescription = "systemf failed" ); context->isExpensiveNow = false; context->isConstrainedNow = false; gettimeofday( &context->updateTime, NULL ); } else { context->subtestProgress_startTime = NanoTimeGetCurrent(); context->isExpensivePrev = context->isExpensiveNow; context->isExpensiveNow = !context->isExpensiveNow; // flip the expensive status if ( gExpensiveConstrainedTest_DenyExpensive ) context->expectedOperation = context->isExpensiveNow ? RESULT_RMV : RESULT_ADD; else context->expectedOperation = NO_UPDATE; context->operation = NO_UPDATE; context->subtestProgress_callBack = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks ); require_action( context->subtestProgress_callBack != NULL, test_failed, errorDescription = "CFArrayCreateMutable failed" ); err = systemf( NULL, "ifconfig %s %sexpensive", context->ifName, context->isExpensiveNow ? "" : "-" ); require_noerr_action( err, test_failed, errorDescription = "systemf failed" ); gettimeofday( &context->updateTime, NULL ); } break; case TEST_CONSTRAINED_PREPARE: // The interface should be inexpensive and unconstrained when the constrained test starts require_action( context->isExpensiveNow == false, test_failed, SNPrintF( buffer, sizeof( buffer ), "Interface %s should be inexpensive.", context->ifName ); errorDescription = buffer ); require_action( context->isConstrainedNow == false, test_failed, SNPrintF( buffer, sizeof( buffer ), "Interface %s should be unconstrained.\n", context->ifName ); errorDescription = buffer ); context->subtestProgress_startTime = NanoTimeGetCurrent(); context->state = TEST_CONSTRAINED; // constrained interface is now under testing context->counter = 0; context->isExpensivePrev = false; context->isExpensiveNow = false; context->isConstrainedPrev = false; context->isConstrainedNow = true; // will set constrained flag on the interface if ( gExpensiveConstrainedTest_DenyConstrained ) context->expectedOperation = RESULT_RMV; else context->expectedOperation = NO_UPDATE; context->operation = NO_UPDATE; context->subtestProgress_callBack = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks ); require_action( context->subtestProgress_callBack != NULL, test_failed, errorDescription = "CFArrayCreateMutable failed" ); // change interface to the constrained one err = systemf( NULL, "ifconfig %s -expensive && ifconfig %s constrained", context->ifName, context->ifName ); require_noerr_action( err, test_failed, errorDescription = "systemf failed" ); gettimeofday( &context->updateTime, NULL ); break; case TEST_CONSTRAINED: // Since we are testing constrained flag, we should always turn the constrained flag on and off. require_action( context->isConstrainedPrev ^ context->isConstrainedNow, test_failed, SNPrintF( buffer, sizeof( buffer ), "The current constrained status should be different with the previous one: %d -> %d\n", context->isConstrainedPrev, context->isConstrainedNow ); errorDescription = buffer ); require_action( context->isExpensiveNow == false, test_failed, SNPrintF( buffer, sizeof( buffer ), "The interface %s should be inexpensive when testing \"constrained\"\n", context->ifName ); errorDescription = buffer ); require_action( context->expectedOperation == context->operation, test_failed, errorDescription = "Operation is not expected"); context->counter++; if (context->counter == TEST_REPETITION) { // test changing expensive and constrained flags at the same time context->state = TEST_EXPENSIVE_CONSTRAINED_PREPARE; // reset interface err = systemf( NULL, "ifconfig %s -expensive && ifconfig %s -constrained", context->ifName, context->ifName ); require_noerr_action( err, test_failed, errorDescription = "systemf failed" ); context->isExpensiveNow = false; context->isConstrainedNow = false; gettimeofday( &context->updateTime, NULL ); } else { context->subtestProgress_startTime = NanoTimeGetCurrent(); context->isConstrainedPrev = context->isConstrainedNow; context->isConstrainedNow = !context->isConstrainedNow; // flip constrained flag if ( gExpensiveConstrainedTest_DenyConstrained ) context->expectedOperation = context->isConstrainedNow ? RESULT_RMV : RESULT_ADD; else context->expectedOperation = NO_UPDATE; context->operation = NO_UPDATE; context->subtestProgress_callBack = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks ); require_action( context->subtestProgress_callBack != NULL, test_failed, errorDescription = "CFArrayCreateMutable failed" ); err = systemf( NULL, "ifconfig %s %sconstrained", context->ifName, context->isConstrainedNow ? "" : "-" ); require_noerr_action( err, test_failed, errorDescription = "systemf failed" ); gettimeofday(&context->updateTime, NULL); } break; case TEST_EXPENSIVE_CONSTRAINED_PREPARE: // The interface should be inexpensive and unconstrained when the constrained test starts require_action( context->isExpensiveNow == false, test_failed, SNPrintF( buffer, sizeof( buffer ), "Interface %s should be inexpensive.\n", context->ifName ); errorDescription = buffer ); require_action( context->isConstrainedNow == false, test_failed, SNPrintF(buffer, sizeof( buffer ), "Interface %s should be unconstrained.\n", context->ifName ); errorDescription = buffer ); // now flip expensive and constrained at the same time context->subtestProgress_startTime = NanoTimeGetCurrent(); context->state = TEST_EXPENSIVE_CONSTRAINED; context->counter = 0; context->isExpensivePrev = false; context->isExpensiveNow = true; context->isConstrainedPrev = false; context->isConstrainedNow = true; if (gExpensiveConstrainedTest_DenyConstrained || gExpensiveConstrainedTest_DenyExpensive) context->expectedOperation = RESULT_RMV; else context->expectedOperation = NO_UPDATE; context->operation = NO_UPDATE; context->subtestProgress_callBack = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks ); require_action( context->subtestProgress_callBack != NULL, test_failed, errorDescription = "CFArrayCreateMutable failed" ); err = systemf(NULL, "ifconfig %s expensive && ifconfig %s constrained", context->ifName, context->ifName ); require_noerr_action( err, test_failed, errorDescription = "systemf failed" ); gettimeofday( &context->updateTime, NULL ); break; case TEST_EXPENSIVE_CONSTRAINED: // expensive and constrained flag should always be changed require_action( ( context->isExpensivePrev ^ context->isExpensiveNow ) && ( context->isConstrainedPrev ^ context->isConstrainedNow ), test_failed, SNPrintF( buffer, sizeof( buffer ), "Both expensive and constrained status need to be changed" ); errorDescription = buffer ); require_action( context->isExpensiveNow == context->isConstrainedNow, test_failed, errorDescription = "context->isExpensiveNow != context->isConstrainedNow" ); require_action( context->expectedOperation == context->operation, test_failed, errorDescription = "Operation is not expected" ); context->counter++; if ( context->counter == TEST_REPETITION ) { context->state = TEST_SUCCEEDED; } else { context->subtestProgress_startTime = NanoTimeGetCurrent(); context->isExpensivePrev = context->isExpensiveNow; context->isExpensiveNow = !context->isExpensiveNow; context->isConstrainedPrev = context->isConstrainedNow; context->isConstrainedNow = !context->isConstrainedNow; if (gExpensiveConstrainedTest_DenyConstrained || gExpensiveConstrainedTest_DenyExpensive) context->expectedOperation = context->isExpensiveNow ? RESULT_RMV : RESULT_ADD; else context->expectedOperation = NO_UPDATE; context->operation = NO_UPDATE; context->subtestProgress_callBack = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks ); require_action( context->subtestProgress_callBack != NULL, test_failed, errorDescription = "CFArrayCreateMutable failed" ); err = systemf( NULL, "ifconfig %s %sexpensive && ifconfig %s %sconstrained", context->ifName, context->isExpensiveNow ? "" : "-", context->ifName, context->isConstrainedNow ? "" : "-" ); require_noerr_action( err, test_failed, errorDescription = "systemf failed" ); gettimeofday( &context->updateTime, NULL ); } break; case TEST_FAILED: test_failed: ExpensiveConstrainedSubtestReport( context, errorDescription ); ExpensiveConstrainedStopAndCleanTheTest( context ); if ( context->numOfRetries > 0 ) { context->state = TEST_BEGIN; context->numOfRetries--; break; } ExpensiveConstrainedSubtestParams[context->subtestIndex++].test_passed = 0; if (context->subtestIndex == (int) countof( ExpensiveConstrainedSubtestParams )) { ExpensiveConstrainedFinalResultReport( context, false ); exit( 2 ); } if (context->timer == NULL) { // If timer is NULL, it means that we encounter error before we set up the test handler, which is unrecoverable. ExpensiveConstrainedFinalResultReport( context, false ); exit( 1 ); } context->state = TEST_BEGIN; break; case TEST_SUCCEEDED: ExpensiveConstrainedSubtestReport( context, NULL ); ExpensiveConstrainedStopAndCleanTheTest( context ); ExpensiveConstrainedSubtestParams[context->subtestIndex++].test_passed = 1; if (context->subtestIndex == (int) countof( ExpensiveConstrainedSubtestParams )) { // all the subtests have been run Boolean hasFailed = false; for ( int i = 0; i < (int) countof( ExpensiveConstrainedSubtestParams ) && !hasFailed; i++ ) hasFailed = ( ExpensiveConstrainedSubtestParams[i].test_passed != 1 ); ExpensiveConstrainedFinalResultReport( context, !hasFailed ); exit( hasFailed ? 2 : 0 ); } context->state = TEST_BEGIN; break; default: FPrintF( stdout, "unknown error\n" ); exit( 1 ); } } //=========================================================================================================================== // ExpensiveConstrainedCallback //=========================================================================================================================== static void DNSSD_API ExpensiveConstrainedCallback( __unused DNSServiceRef inSDRef, DNSServiceFlags inFlags, uint32_t inInterfaceIndex, DNSServiceErrorType inError, const char * inHostname, const struct sockaddr * inSockAddr, __unused uint32_t inTTL, void * inContext ) { ExpensiveConstrainedContext * const context = (ExpensiveConstrainedContext *)inContext; OSStatus err; const char * addrStr; char addrStrBuf[ kSockAddrStringMaxSize ]; char inFlagsDescription[ 128 ]; NanoTime64 now; char nowTimestamp[ 32 ]; switch ( inError ) { case kDNSServiceErr_NoError: case kDNSServiceErr_NoSuchRecord: break; case kDNSServiceErr_Timeout: Exit( kExitReason_Timeout ); default: err = inError; goto exit; } if( ( inSockAddr->sa_family != AF_INET ) && ( inSockAddr->sa_family != AF_INET6 ) ) { dlogassert( "Unexpected address family: %d", inSockAddr->sa_family ); err = kTypeErr; goto exit; } if( !inError ) { err = SockAddrToString( inSockAddr, kSockAddrStringFlagsNone, addrStrBuf ); require_noerr( err, exit ); addrStr = addrStrBuf; } else { addrStr = ( inSockAddr->sa_family == AF_INET ) ? kNoSuchRecordAStr : kNoSuchRecordAAAAStr; } now = NanoTimeGetCurrent(); _NanoTime64ToTimestamp( now, nowTimestamp, sizeof( nowTimestamp ) ); SNPrintF( inFlagsDescription, sizeof( inFlagsDescription ), "%{du:cbflags}", inFlags ); err = CFPropertyListAppendFormatted( kCFAllocatorDefault, context->subtestProgress_callBack, "{" "%kO=%s" "%kO=%s" "%kO=%s" "%kO=%lli" "%kO=%s" "}", EXPENSIVE_CONSTRAINED_SUBTEST_ACTUAL_RESULT_KEY_TIMESTAMP, nowTimestamp, EXPENSIVE_CONSTRAINED_SUBTEST_ACTUAL_RESULT_KEY_NAME, inHostname, EXPENSIVE_CONSTRAINED_SUBTEST_ACTUAL_RESULT_KEY_FLAGS, inFlagsDescription, EXPENSIVE_CONSTRAINED_SUBTEST_ACTUAL_RESULT_KEY_INTERFACE, (int64_t) inInterfaceIndex, EXPENSIVE_CONSTRAINED_SUBTEST_ACTUAL_RESULT_KEY_ADDRESS, addrStr ); require_noerr_quiet( err, exit ); if ( inFlags & kDNSServiceFlagsMoreComing ) return; if ( inFlags & kDNSServiceFlagsAdd ) context->operation = RESULT_ADD; else context->operation = RESULT_RMV; gettimeofday(&context->notificationTime, NULL); exit: if( err ) exit( 1 ); } //=========================================================================================================================== // ExpensiveConstrainedInitializeContext //=========================================================================================================================== static void ExpensiveConstrainedInitializeContext( ExpensiveConstrainedContext *context ) { // clear the flags of the previous subtest context->flags = 0; context->protocols = 0; // get the parameter for the current subtest const ExpensiveConstrainedTestParams *param = &ExpensiveConstrainedSubtestParams[context->subtestIndex]; gExpensiveConstrainedTest_Name = param->qname; gExpensiveConstrainedTest_DenyExpensive = param->deny_expensive; gExpensiveConstrainedTest_DenyConstrained = param->deny_constrained; gExpensiveConstrainedTest_StartFromExpensive = param->start_from_expensive; gExpensiveConstrainedTest_ProtocolIPv4 = param->ipv4_query; gExpensiveConstrainedTest_ProtocolIPv6 = param->ipv6_query; } //=========================================================================================================================== // ExpensiveConstrainedStopAndCleanTheTest //=========================================================================================================================== static void ExpensiveConstrainedStopAndCleanTheTest( ExpensiveConstrainedContext *context ) { // Stop the ongoing query if ( context->opRef != NULL ) DNSServiceRefDeallocate( context->opRef ); context->opRef = NULL; context->flags = 0; context->protocols = 0; } //=========================================================================================================================== // ExpensiveConstrainedSubtestProgressReport //=========================================================================================================================== static void ExpensiveConstrainedSubtestProgressReport( ExpensiveConstrainedContext *context ) { OSStatus err; NanoTime64 now; char startTime[ 32 ]; char endTime[ 32 ]; char expensive[ 32 ]; char constrained[ 32 ]; now = NanoTimeGetCurrent(); _NanoTime64ToTimestamp( context->subtestProgress_startTime, startTime, sizeof( startTime ) ); _NanoTime64ToTimestamp( now, endTime, sizeof( endTime ) ); snprintf( expensive, sizeof( expensive ), "%s -> %s", context->isExpensivePrev ? "True" : "False", context->isExpensiveNow ? "True" : "False" ); snprintf( constrained, sizeof( constrained ), "%s -> %s", context->isConstrainedPrev ? "True" : "False", context->isConstrainedNow ? "True" : "False" ); err = CFPropertyListAppendFormatted( kCFAllocatorDefault, context->subtestProgress, "{" "%kO=%s" "%kO=%s" "%kO=%s" "%kO=%s" "%kO=%s" "%kO=%s" "%kO=%s" "%kO=%O" "}", EXPENSIVE_CONSTRAINED_SUBTEST_PROGRESS_KEY_START_TIME, startTime, EXPENSIVE_CONSTRAINED_SUBTEST_PROGRESS_KEY_END_TIME, endTime, EXPENSIVE_CONSTRAINED_SUBTEST_PROGRESS_KEY_STATE, ExpensiveConstrainedStateString(context->state), EXPENSIVE_CONSTRAINED_SUBTEST_PROGRESS_KEY_EXPECT_RESULT, ExpensiveConstrainedOperationString(context->expectedOperation), EXPENSIVE_CONSTRAINED_SUBTEST_PROGRESS_KEY_ACTUAL_RESULT, ExpensiveConstrainedOperationString(context->operation), EXPENSIVE_CONSTRAINED_SUBTEST_PROGRESS_KEY_EXPENSIVE_PREV_NOW, expensive, EXPENSIVE_CONSTRAINED_SUBTEST_PROGRESS_KEY_CONSTRAINED_PREV_NOW, constrained, EXPENSIVE_CONSTRAINED_SUBTEST_PROGRESS_KEY_CALL_BACK, context->subtestProgress_callBack ); require_noerr( err, exit ); ForgetCF( &context->subtestProgress_callBack ); return; exit: ErrQuit( 1, "error: %#m\n", err ); } //=========================================================================================================================== // ExpensiveConstrainedFinalSubtestReport //=========================================================================================================================== static void ExpensiveConstrainedSubtestReport( ExpensiveConstrainedContext *context, const char *error_description ) { OSStatus err; NanoTime64 now; char startTime[ 32 ]; char endTime[ 32 ]; char flagDescription[ 1024 ]; now = NanoTimeGetCurrent(); _NanoTime64ToTimestamp( context->subtestReport_startTime, startTime, sizeof( startTime ) ); _NanoTime64ToTimestamp( now, endTime, sizeof( endTime ) ); SNPrintF( flagDescription, sizeof( flagDescription ), "%#{flags}", context->flags, kDNSServiceFlagsDescriptors ); if (error_description != NULL) { err = CFPropertyListAppendFormatted( kCFAllocatorDefault, context->subtestReport, "{" "%kO=%s" "%kO=%s" "%kO=%s" "%kO=%s" "%kO=%s" "%kO=%lli" "%kO=%s" "%kO=%O" "%kO=%s" "%kO=%O" "}", EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_START_TIME, startTime, EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_END_TIME, endTime, EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_QNAME, context->name, EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_FLAGS, flagDescription, EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_PROTOCOLS, ExpensiveConstrainedProtocolString( context->protocols ), EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_INTERFACE_INDEX, (int64_t) context->ifIndex, EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_INTERFACE_NAME, context->ifName, EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_RESULT, CFSTR( "Fail" ), EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_ERROR, error_description, EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_TEST_PROGRESS, context->subtestProgress ); } else { err = CFPropertyListAppendFormatted( kCFAllocatorDefault, context->subtestReport, "{" "%kO=%s" "%kO=%s" "%kO=%s" "%kO=%s" "%kO=%s" "%kO=%lli" "%kO=%s" "%kO=%O" "%kO=%O" "}", EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_START_TIME, startTime, EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_END_TIME, endTime, EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_QNAME, context->name, EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_FLAGS, flagDescription, EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_PROTOCOLS, ExpensiveConstrainedProtocolString( context->protocols ), EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_INTERFACE_INDEX, (int64_t) context->ifIndex, EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_INTERFACE_NAME, context->ifName, EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_RESULT, CFSTR( "Pass" ), EXPENSIVE_CONSTRAINED_SUBTEST_REPORT_KEY_TEST_PROGRESS, context->subtestProgress ); } require_noerr( err, exit ); ForgetCF( &context->subtestProgress ); return; exit: ErrQuit( 1, "error: %#m\n", err ); } //=========================================================================================================================== // ExpensiveConstrainedFinalResultReport //=========================================================================================================================== static void ExpensiveConstrainedFinalResultReport( ExpensiveConstrainedContext *context, Boolean allPassed ) { OSStatus err; CFPropertyListRef plist; NanoTime64 now; char startTime[ 32 ]; char endTime[ 32 ]; now = NanoTimeGetCurrent(); _NanoTime64ToTimestamp( context->testReport_startTime, startTime, sizeof( startTime ) ); _NanoTime64ToTimestamp( now, endTime, sizeof( endTime ) ); err = CFPropertyListCreateFormatted( kCFAllocatorDefault, &plist, "{" "%kO=%s" "%kO=%s" "%kO=%b" "%kO=%O" "}", EXPENSIVE_CONSTRAINED_TEST_REPORT_KEY_START_TIME, startTime, EXPENSIVE_CONSTRAINED_TEST_REPORT_KEY_END_TIME, endTime, EXPENSIVE_CONSTRAINED_TEST_REPORT_KEY_ALL_PASSED, allPassed, EXPENSIVE_CONSTRAINED_TEST_REPORT_KEY_SUBTEST_RESULT, context->subtestReport ); require_noerr( err, exit ); ForgetCF( &context->subtestReport ); err = OutputPropertyList( plist, context->outputFormat, context->outputFilePath ); CFRelease( plist ); require_noerr( err, exit ); return; exit: ErrQuit( 1, "error: %#m\n", err ); } //=========================================================================================================================== // ExpensiveConstrainedProtocolString //=========================================================================================================================== static const char *ExpensiveConstrainedProtocolString( DNSServiceProtocol protocol ) { const char *str = NULL; switch ( protocol ) { case kDNSServiceProtocol_IPv4: str = "IPv4"; break; case kDNSServiceProtocol_IPv6: str = "IPv6"; break; case kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6: str = "IPv4 & IPv6"; break; default: break; } return str; } //=========================================================================================================================== // ExpensiveConstrainedStateString //=========================================================================================================================== static const char *ExpensiveConstrainedStateString( enum ExpensiveConstrainedTestState state ) { const char *str = NULL; switch ( state ) { case TEST_BEGIN: str = "TEST_BEGIN"; break; case TEST_EXPENSIVE_PREPARE: str = "TEST_EXPENSIVE_PREPARE"; break; case TEST_EXPENSIVE: str = "TEST_EXPENSIVE"; break; case TEST_CONSTRAINED_PREPARE: str = "TEST_CONSTRAINED_PREPARE"; break; case TEST_CONSTRAINED: str = "TEST_CONSTRAINED"; break; case TEST_EXPENSIVE_CONSTRAINED_PREPARE: str = "TEST_EXPENSIVE_CONSTRAINED_PREPARE"; break; case TEST_EXPENSIVE_CONSTRAINED: str = "TEST_EXPENSIVE_CONSTRAINED"; break; case TEST_FAILED: str = "TEST_FAILED"; break; case TEST_SUCCEEDED: str = "TEST_SUCCEEDED"; break; default: str = "UNKNOWN"; break; } return str; } //=========================================================================================================================== // ExpensiveConstrainedOperationString //=========================================================================================================================== static const char *ExpensiveConstrainedOperationString( enum ExpensiveConstrainedTestOperation operation ) { const char *str = NULL; switch ( operation ) { case RESULT_ADD: str = "RESULT_ADD"; break; case RESULT_RMV: str = "RESULT_RMV"; break; case NO_UPDATE: str = "NO_UPDATE"; break; default: str = "UNKNOWN"; break; } return str; } //=========================================================================================================================== // expensiveConstrainedEndsWith //=========================================================================================================================== static Boolean expensiveConstrainedEndsWith( const char *str, const char *suffix ) { if ( !str || !suffix ) return false; size_t lenstr = strlen( str ); size_t lensuffix = strlen( suffix ); if ( lensuffix > lenstr ) return false; return strncmp( str + lenstr - lensuffix, suffix, lensuffix ) == 0; } //=========================================================================================================================== // DNSProxyTestCmd //=========================================================================================================================== // DNS Proxy Test Mode Parameters typedef enum { kDNSProxyTestMode_Normal = 0, kDNSProxyTestMode_ForceAAAASynthesis, kDNSProxyTestMode_Count } DNSProxyTestMode; check_compile_time( kDNSProxyTestMode_Count > 0 ); // DNS Proxy Test DNS64 Prefix Parameters // See . static const char * const kDNSProxyTestParams_DNS64Prefixes[] = { NULL, // No prefix. "3ffe:ffff::/32", // 32-bit prefix. Note: Prefix is from deprecated 3ffe::/16 block (see RFC 3701). "2001:db8:ff00::/40", // 40-bit prefix. "2001:db8:ffff::/48", // 48-bit prefix. "2001:db8:ffff:ff00::/56", // 56-bit prefix. "2001:db8:ffff:ffff::/64", // 64-bit prefix. "2001:db8:ffff:ff00:ffff:ffff::/96" // 96-bit prefix. Note: bits 64 - 71 MUST be zero. }; // DNS Proxy Test Transport Parameters typedef enum { kDNSProxyTestTransport_UDPv4 = 0, kDNSProxyTestTransport_TCPv4, kDNSProxyTestTransport_UDPv6, kDNSProxyTestTransport_TCPv6, kDNSProxyTestTransport_Count } DNSProxyTestTransport; check_compile_time( kDNSProxyTestTransport_Count > 0 ); // DNS Proxy Test Query Parameters typedef enum { kDNSProxyTestQuery_A = 0, kDNSProxyTestQuery_AAAA, kDNSProxyTestQuery_IPv6OnlyA, kDNSProxyTestQuery_IPv6OnlyAAAA, kDNSProxyTestQuery_IPv4OnlyAAAA, kDNSProxyTestQuery_AliasA, kDNSProxyTestQuery_AliasAAAA, kDNSProxyTestQuery_AliasIPv6OnlyA, kDNSProxyTestQuery_AliasIPv6OnlyAAAA, kDNSProxyTestQuery_AliasIPv4OnlyAAAA, kDNSProxyTestQuery_NXDomainA, kDNSProxyTestQuery_NXDomainAAAA, kDNSProxyTestQuery_ReverseIPv6, kDNSProxyTestQuery_ReverseIPv6NXDomain, kDNSProxyTestQuery_ReverseIPv6DNS64, kDNSProxyTestQuery_ReverseIPv6DNS64NXDomain, kDNSProxyTestQuery_Count } DNSProxyTestQuery; check_compile_time( kDNSProxyTestQuery_Count > 0 ); #define kDNSProxyTestQueryIterationCount 2 typedef struct DNSProxyTest * DNSProxyTestRef; struct DNSProxyTest { dispatch_queue_t queue; // Serial queue for test events. dispatch_semaphore_t doneSem; // Semaphore to signal when the test is done. DNSServiceRef probeGAI; // Probe GAI for DNS server. char * probeHostname; // Probe hostname. mrc_dns_proxy_t dnsProxy; // DNS proxy reference. dispatch_source_t timer; // Timer to put time limit on queries. mdns_resolver_t resolver; // Resolver to represent the DNS proxy as a DNS service. CFMutableDictionaryRef report; // Test's report. CFMutableArrayRef modeResults; // "Weak" pointer to the 1st-level array of mode results. CFMutableArrayRef prefixResults; // "Weak" pointer to current 2nd-level array of DNS64 prefix results. CFMutableArrayRef transportResults; // "Weak" pointer to current 3rd-level array of transport results. CFMutableArrayRef queryResults; // "Weak" pointer to current 4th-level array of query results. DNSProxyTestMode modeParam; // Current mode parameter. unsigned int prefixParamIdx; // Current DNS64 prefix parameter index. DNSProxyTestTransport transportParam; // Current transport parameter. DNSProxyTestQuery queryParam; // Current query parameter. unsigned int queryParamIter; // Current query iteration. uint32_t loopbackIndex; // Loopback interface's index. mdns_querier_t querier; // Subtest's querier to send queries to DNS proxy. NanoTime64 startTime; // Subtest's start time. char * subtestDesc; // Subtest's description. char * qnameStr; // Subtest's query QNAME as a C string. uint8_t * qname; // Subtest's query QNAME in label format. uint16_t qtype; // Subtest's query QTYPE. unsigned int aliasCount; // Subtest's expected number of CNAMEs in response answer section. unsigned int answerCount; // Subtest's expected number of QTYPE records. int responseCode; // Subtest's expected response code. uint8_t * canonicalName; // Subtest's expected CNAME rdata for reverse IPv6 queries. uint8_t * answerName; // Subtest's expected PTR rdata for reverse IPv6 queries. pid_t serverPID; // PID of spawned DNS server. int subtestCount; // Number of subtests that have completed or are in progress. int subtestPassCount; // Number of subtests that have passed so far. int32_t refCount; // Test object's reference count. OSStatus error; // Overall test's error. int dns64PrefixBitLen; // Current DNS64 prefix length (valid if > 0). uint8_t dns64Prefix[ 16 ]; // Current DNS64 prefix (valid if dns64PrefixBitLen > 0). char tag[ 6 + 1 ]; // Current subtest's random tag to uniquify QNAMEs. Boolean synthesizedAAAA; // True if the current subtest expects DNS64 synthesized AAAA records. Boolean startedSubtests; // True if the test has started running subtests. }; ulog_define_ex( kDNSSDUtilIdentifier, DNSProxyTest, kLogLevelInfo, kLogFlags_None, "DNSProxyTest", NULL ); #define dpt_ulog( LEVEL, ... ) ulog( &log_category_from_name( DNSProxyTest ), (LEVEL), __VA_ARGS__ ) static OSStatus _DNSProxyTestCreate( DNSProxyTestRef *outTest ); static OSStatus _DNSProxyTestRun( DNSProxyTestRef inTest, Boolean *outPassed ); static void _DNSProxyTestRetain( DNSProxyTestRef inTest ); static void _DNSProxyTestRelease( DNSProxyTestRef inTest ); static void DNSProxyTestCmd( void ) { OSStatus err; OutputFormatType outputFormat; DNSProxyTestRef test = NULL; Boolean passed = false; err = OutputFormatFromArgString( gDNSProxyTest_OutputFormat, &outputFormat ); require_noerr_quiet( err, exit ); err = _DNSProxyTestCreate( &test ); require_noerr( err, exit ); err = _DNSProxyTestRun( test, &passed ); require_noerr( err, exit ); err = OutputPropertyList( test->report, outputFormat, gDNSProxyTest_OutputFilePath ); require_noerr( err, exit ); exit: if( test ) _DNSProxyTestRelease( test ); gExitCode = err ? 1 : ( passed ? 0 : 2 ); } //=========================================================================================================================== static void _DNSProxyTestStart( void *inCtx ); static void _DNSProxyTestStop( DNSProxyTestRef inTest, OSStatus inError ); static OSStatus _DNSProxyTestContinue( DNSProxyTestRef inTest, OSStatus inSubtestError, Boolean *outDone ); static const char * _DNSProxyTestGetCurrentDNS64PrefixParam( DNSProxyTestRef inTest ); static OSStatus _DNSProxyTestPrepareMode( DNSProxyTestRef inTest ); static OSStatus _DNSProxyTestPrepareDNSProxy( DNSProxyTestRef inTest ); static OSStatus _DNSProxyTestPrepareResolver( DNSProxyTestRef inTest ); static OSStatus _DNSProxyTestStartQuery( DNSProxyTestRef inTest, Boolean *outSkipQuery ); static OSStatus _DNSProxyTestSynthesizeIPv6( const uint8_t * inIPv6Prefix, int inIPv6PrefixBitLen, uint32_t inIPv4Addr, uint8_t outIPv6Addr[ STATIC_PARAM 16 ] ); static void _DNSProxyTestHandleQuerierResult( DNSProxyTestRef inTest ); static OSStatus _DNSProxyTestVerifyAddressResponse( const uint8_t * inMsgPtr, size_t inMsgLen, const uint8_t * inQName, uint16_t inQType, int inResponseCode, unsigned int inAliasCount, unsigned int inAnswerCount, const uint8_t * inDNS64Prefix, int inDNS64PrefixBitLen ); static OSStatus _DNSProxyTestVerifyReverseIPv6Response( const uint8_t * inMsgPtr, size_t inMsgLen, const uint8_t * inQName, int inResponseCode, const uint8_t * inCanonicalName, const uint8_t * inAnswerName ); static void DNSSD_API _DNSProxyTestProbeGAICallback( DNSServiceRef inSDRef, DNSServiceFlags inFlags, uint32_t inInterfaceIndex, DNSServiceErrorType inError, const char * inHostname, const struct sockaddr * inSockAddr, uint32_t inTTL, void * inCtx ); static void _DNSProxyTestProbeTimerHandler( void *inCtx ); static OSStatus _DNSProxyTestCreate( DNSProxyTestRef *outTest ) { OSStatus err; DNSProxyTestRef obj; obj = (DNSProxyTestRef) calloc( 1, sizeof( *obj ) ); require_action( obj, exit, err = kNoMemoryErr ); obj->refCount = 1; obj->error = kInProgressErr; obj->serverPID = -1; obj->queue = dispatch_queue_create( "com.apple.dnssdutil.dns-proxy-test", DISPATCH_QUEUE_SERIAL ); require_action( obj->queue, exit, err = kNoResourcesErr ); obj->doneSem = dispatch_semaphore_create( 0 ); require_action( obj->doneSem, exit, err = kNoResourcesErr ); *outTest = obj; obj = NULL; err = kNoErr; exit: if( obj ) _DNSProxyTestRelease( obj ); return( err ); } //=========================================================================================================================== static OSStatus _DNSProxyTestRun( DNSProxyTestRef me, Boolean *outPassed ) { Boolean passed; dispatch_async_f( me->queue, me, _DNSProxyTestStart ); dispatch_semaphore_wait( me->doneSem, DISPATCH_TIME_FOREVER ); passed = ( !me->error && ( me->subtestPassCount == me->subtestCount ) ) ? true : false; CFDictionarySetBoolean( me->report, CFSTR( "pass" ), passed ); dpt_ulog( kLogLevelInfo, "Test result: %s\n", passed ? "pass" : "fail" ); if( outPassed ) *outPassed = passed; return( me->error ); } //=========================================================================================================================== static void _DNSProxyTestRetain( DNSProxyTestRef me ) { atomic_add_32( &me->refCount, 1 ); } //=========================================================================================================================== static void _DNSProxyTestRelease( DNSProxyTestRef me ) { if( atomic_add_and_fetch_32( &me->refCount, -1 ) == 0 ) { check( !me->probeGAI ); check( !me->probeHostname ); check( !me->dnsProxy ); check( !me->timer ); check( !me->resolver ); check( !me->modeResults ); check( !me->prefixResults ); check( !me->transportResults ); check( !me->queryResults ); check( !me->querier ); check( !me->subtestDesc ); check( !me->qnameStr ); check( !me->qname ); check( !me->canonicalName ); check( !me->answerName ); check( me->serverPID < 0 ); dispatch_forget( &me->queue ); dispatch_forget( &me->doneSem ); ForgetCF( &me->report ); free( me ); } } //=========================================================================================================================== #define kDNSProxyTestProbeQueryTimeoutSecs 5 static void _DNSProxyTestStart( void *inCtx ) { OSStatus err; const DNSProxyTestRef me = (DNSProxyTestRef) inCtx; char * serverCmd = NULL; NanoTime64 startTime; char startTimeStr[ 32 ]; char tag[ 6 + 1 ]; startTime = NanoTimeGetCurrent(); dpt_ulog( kLogLevelInfo, "Starting test\n" ); me->error = kInProgressErr; me->loopbackIndex = if_nametoindex( "lo0" ); err = map_global_value_errno( me->loopbackIndex != 0, me->loopbackIndex ); require_noerr_action_quiet( err, exit, dpt_ulog( kLogLevelError, "Failed to get interface index for lo0: %#m", err ) ); // The test DNS server uses an ephemeral port because the DNS proxy will use port 53 for itself. serverCmd = NULL; ASPrintF( &serverCmd, "dnssdutil server --loopback --follow %lld --port 0 --defaultTTL 300 --responseDelay 10", (int64_t) getpid() ); require_action_quiet( serverCmd, exit, err = kUnknownErr ); err = _SpawnCommand( &me->serverPID, "/dev/null", "/dev/null", "%s", serverCmd ); require_noerr( err, exit ); check( !me->probeHostname ); ASPrintF( &me->probeHostname, "tag-dns-proxy-test-probe-%s.ipv4.d.test.", _RandomStringExact( kLowerAlphaNumericCharSet, kLowerAlphaNumericCharSetSize, sizeof( tag ) - 1, tag ) ); require_action( me->probeHostname, exit, err = kNoMemoryErr ); err = DNSServiceGetAddrInfo( &me->probeGAI, 0, kDNSServiceInterfaceIndexAny, kDNSServiceProtocol_IPv4, me->probeHostname, _DNSProxyTestProbeGAICallback, me ); require_noerr( err, exit ); err = DNSServiceSetDispatchQueue( me->probeGAI, me->queue ); require_noerr( err, exit ); check( !me->timer ); err = DispatchTimerOneShotCreate( dispatch_time_seconds( kDNSProxyTestProbeQueryTimeoutSecs ), kDNSProxyTestProbeQueryTimeoutSecs * ( UINT64_C_safe( kNanosecondsPerSecond ) / 10 ), me->queue, _DNSProxyTestProbeTimerHandler, me, &me->timer ); require_noerr( err, exit ); dispatch_resume( me->timer ); _NanoTime64ToTimestamp( startTime, startTimeStr, sizeof( startTimeStr ) ); err = CFPropertyListCreateFormatted( kCFAllocatorDefault, &me->report, "{" "%kO=%s" // startTime "%kO=%s" // serverCmd "%kO=%s" // probeHostname "%kO=[%@]" // results "}", CFSTR( "startTime" ), startTimeStr, CFSTR( "serverCmd" ), serverCmd, CFSTR( "probeHostname" ), me->probeHostname, CFSTR( "results" ), &me->modeResults ); require_noerr( err, exit ); exit: FreeNullSafe( serverCmd ); if( err ) _DNSProxyTestStop( me, err ); } //=========================================================================================================================== static void _DNSProxyTestSubtestCleanup( DNSProxyTestRef inTest ); #define _mrc_dns_proxy_forget( X ) ForgetCustomEx( X, mrc_dns_proxy_invalidate, mrc_release ) static void _DNSProxyTestStop( DNSProxyTestRef me, OSStatus inError ) { OSStatus err; NanoTime64 endTime; char endTimeStr[ 32 ]; endTime = NanoTimeGetCurrent(); me->error = inError; dpt_ulog( kLogLevelInfo, "Stopping test with error: %#m\n", me->error ); DNSServiceForget( &me->probeGAI ); ForgetMem( &me->probeHostname ); _mrc_dns_proxy_forget( &me->dnsProxy ); dispatch_source_forget( &me->timer ); mdns_resolver_forget( &me->resolver ); me->prefixResults = NULL; me->modeResults = NULL; me->transportResults = NULL; me->queryResults = NULL; _DNSProxyTestSubtestCleanup( me ); if( me->serverPID >= 0 ) { OSStatus killErr; killErr = kill( me->serverPID, SIGTERM ); killErr = map_global_noerr_errno( killErr ); check_noerr( killErr ); me->serverPID = -1; } _NanoTime64ToTimestamp( endTime, endTimeStr, sizeof( endTimeStr ) ); err = CFPropertyListAppendFormatted( kCFAllocatorDefault, me->report, "%kO=%s" // endTime "%kO=%lli" // subtestCount "%kO=%lli", // subtestPassCount CFSTR( "endTime" ), endTimeStr, CFSTR( "subtestCount" ), (int64_t) me->subtestCount, CFSTR( "subtestPassCount" ), (int64_t) me->subtestPassCount ); check_noerr( err ); if( err && !me->error ) me->error = err; dispatch_semaphore_signal( me->doneSem ); } //=========================================================================================================================== static void _DNSProxyTestSubtestCleanup( DNSProxyTestRef me ) { dispatch_source_forget( &me->timer ); mdns_querier_forget( &me->querier ); ForgetMem( &me->subtestDesc ); ForgetMem( &me->qnameStr ); ForgetMem( &me->qname ); ForgetMem( &me->canonicalName ); ForgetMem( &me->answerName ); } //=========================================================================================================================== static OSStatus _DNSProxyTestHandleSubtestCompletion( DNSProxyTestRef inTest, OSStatus inSubtestError ); static char * _DNSProxyTestCreateSubtestDescription( DNSProxyTestRef inTest ); static const char * _DNSProxyTestQueryToString( DNSProxyTestQuery inQuery ); static const char * _DNSProxyTestTransportToString( DNSProxyTestTransport inTransport ); static const char * _DNSProxyTestModeToString( DNSProxyTestMode inMode ); static OSStatus _DNSProxyTestContinue( DNSProxyTestRef me, OSStatus inSubtestError, Boolean *outDone ) { OSStatus err; Boolean skipQueries = false; Boolean done = false; do { if( me->startedSubtests ) { if( !skipQueries ) { err = _DNSProxyTestHandleSubtestCompletion( me, inSubtestError ); require_noerr( err, exit ); } else { dpt_ulog( kLogLevelInfo, "Skipped subtest: %s\n", me->subtestDesc ); } _DNSProxyTestSubtestCleanup( me ); if( skipQueries || ( ++me->queryParamIter == kDNSProxyTestQueryIterationCount ) ) { me->queryParamIter = 0; if( ++me->queryParam == kDNSProxyTestQuery_Count ) { me->queryParam = 0; me->queryResults = NULL; dpt_ulog( kLogLevelInfo, "Invalidating resolver: %@\n", me->resolver ); mdns_resolver_forget( &me->resolver ); if( ++me->transportParam == kDNSProxyTestTransport_Count ) { me->transportParam = 0; me->transportResults = NULL; dpt_ulog( kLogLevelInfo, "Disabling DNS proxy\n" ); _mrc_dns_proxy_forget( &me->dnsProxy ); if( ++me->prefixParamIdx == countof( kDNSProxyTestParams_DNS64Prefixes ) ) { me->prefixParamIdx = 0; me->prefixResults = NULL; if( ++me->modeParam == kDNSProxyTestMode_Count ) { me->modeParam = 0; me->modeResults = NULL; done = true; err = kNoErr; goto exit; } } } } } } else { me->startedSubtests = true; } if( ( me->queryParamIter == 0 ) && ( me->queryParam == 0 ) ) { if( me->transportParam == 0 ) { if( me->prefixParamIdx == 0 ) { err = _DNSProxyTestPrepareMode( me ); require_noerr( err, exit ); } err = _DNSProxyTestPrepareDNSProxy( me ); require_noerr( err, exit ); } err = _DNSProxyTestPrepareResolver( me ); require_noerr( err, exit ); } err = _DNSProxyTestStartQuery( me, &skipQueries ); require_noerr( err, exit ); check( !me->subtestDesc ); me->subtestDesc = _DNSProxyTestCreateSubtestDescription( me ); require_action( me->subtestDesc, exit, err = kNoMemoryErr ); } while( skipQueries ); ++me->subtestCount; dpt_ulog( kLogLevelInfo, "Started subtest #%d: %s\n", me->subtestCount, me->subtestDesc ); exit: if( outDone ) *outDone = done; return( err ); } static OSStatus _DNSProxyTestHandleSubtestCompletion( DNSProxyTestRef me, OSStatus inSubtestError ) { OSStatus err; NanoTime64 endTime; char errorStr[ 128 ]; char startTimeStr[ 32 ]; char endTimeStr[ 32 ]; endTime = NanoTimeGetCurrent(); if( !inSubtestError ) ++me->subtestPassCount; dpt_ulog( kLogLevelInfo, "Subtest #%d result: %s (pass rate: %d/%d)\n", me->subtestCount, inSubtestError ? "fail" : "pass", me->subtestPassCount, me->subtestCount ); _NanoTime64ToTimestamp( me->startTime, startTimeStr, sizeof( startTimeStr ) ); _NanoTime64ToTimestamp( endTime, endTimeStr, sizeof( endTimeStr ) ); SNPrintF( errorStr, sizeof( errorStr ), "%m", inSubtestError ); err = CFPropertyListAppendFormatted( kCFAllocatorDefault, me->queryResults, "{" "%kO=%s" // description "%kO=%s" // startTime "%kO=%s" // endTime "%kO=%s" // qname "%kO=%s" // qtype "%kO=%b" // pass "%kO=" // error "{" "%kO=%lli" // code "%kO=%s" // description "}" "}", CFSTR( "description" ), me->subtestDesc, CFSTR( "startTime" ), startTimeStr, CFSTR( "endTime" ), endTimeStr, CFSTR( "qname" ), me->qnameStr, CFSTR( "qtype" ), DNSRecordTypeValueToString( me->qtype ), CFSTR( "pass" ), inSubtestError ? false : true, CFSTR( "error" ), CFSTR( "code" ), (int64_t) inSubtestError, CFSTR( "description" ), errorStr ); return( err ); } static char * _DNSProxyTestCreateSubtestDescription( DNSProxyTestRef me ) { const char * queryStr; const char * transportStr; const char * dns64PrefixStr; const char * modeStr; char * description; queryStr = _DNSProxyTestQueryToString( me->queryParam ); transportStr = _DNSProxyTestTransportToString( me->transportParam ); dns64PrefixStr = _DNSProxyTestGetCurrentDNS64PrefixParam( me ); modeStr = _DNSProxyTestModeToString( me->modeParam ); description = NULL; if( dns64PrefixStr ) { ASPrintF( &description, "%s over %s to DNS proxy using DNS64 prefix %s in %s mode (%u of %d)", queryStr, transportStr, dns64PrefixStr, modeStr, me->queryParamIter + 1, kDNSProxyTestQueryIterationCount ); } else { ASPrintF( &description, "%s over %s to DNS proxy in %s mode (%u of %d)", queryStr, transportStr, modeStr, me->queryParamIter + 1, kDNSProxyTestQueryIterationCount ); } return( description ); } //=========================================================================================================================== static const char * _DNSProxyTestQueryToString( DNSProxyTestQuery inQuery ) { switch( inQuery ) { case kDNSProxyTestQuery_A: return( "A record query" ); case kDNSProxyTestQuery_AAAA: return( "AAAA record query" ); case kDNSProxyTestQuery_IPv6OnlyA: return( "IPv6-only A record query" ); case kDNSProxyTestQuery_IPv6OnlyAAAA: return( "IPv6-only AAAA record query" ); case kDNSProxyTestQuery_IPv4OnlyAAAA: return( "IPv4-only AAAA record query" ); case kDNSProxyTestQuery_AliasA: return( "A record query with CNAMEs" ); case kDNSProxyTestQuery_AliasAAAA: return( "AAAA record query with CNAMEs" ); case kDNSProxyTestQuery_AliasIPv6OnlyA: return( "IPv6-only A record query with CNAMEs" ); case kDNSProxyTestQuery_AliasIPv6OnlyAAAA: return( "IPv6-only AAAA record query with CNAMEs" ); case kDNSProxyTestQuery_AliasIPv4OnlyAAAA: return( "IPv4-only AAAA record query with CNAMEs" ); case kDNSProxyTestQuery_NXDomainA: return( "A record query (NXDomain)" ); case kDNSProxyTestQuery_NXDomainAAAA: return( "AAAA record query (NXDomain)" ); case kDNSProxyTestQuery_ReverseIPv6: return( "Reverse IPv6 query" ); case kDNSProxyTestQuery_ReverseIPv6NXDomain: return( "Reverse IPv6 query (NXDomain)" ); case kDNSProxyTestQuery_ReverseIPv6DNS64: return( "Reverse IPv6 query with DNS64 prefix" ); case kDNSProxyTestQuery_ReverseIPv6DNS64NXDomain: return( "Reverse IPv6 query with DNS64 prefix (NXDomain)" ); default: return( "" ); } } //=========================================================================================================================== static const char * _DNSProxyTestTransportToString( DNSProxyTestTransport inTransport ) { switch( inTransport ) { case kDNSProxyTestTransport_UDPv4: return( "IPv4-UDP" ); case kDNSProxyTestTransport_TCPv4: return( "IPv4-TCP" ); case kDNSProxyTestTransport_UDPv6: return( "IPv6-UDP" ); case kDNSProxyTestTransport_TCPv6: return( "IPv6-TCP" ); default: return( "" ); } } //=========================================================================================================================== static const char * _DNSProxyTestModeToString( DNSProxyTestMode inMode ) { switch( inMode ) { case kDNSProxyTestMode_Normal: return( "normal" ); case kDNSProxyTestMode_ForceAAAASynthesis: return( "force-AAAA-synthesis" ); default: return( "" ); } } //=========================================================================================================================== static const char * _DNSProxyTestGetCurrentDNS64PrefixParam( DNSProxyTestRef me ) { const unsigned int index = me->prefixParamIdx; const unsigned int count = countof( kDNSProxyTestParams_DNS64Prefixes ); require_fatal( index < count, "DNSProxyTest DNS Proxy DNS64 prefix parameter index %u out-of-range.", index ); return( kDNSProxyTestParams_DNS64Prefixes[ index ] ); } //=========================================================================================================================== static OSStatus _DNSProxyTestPrepareMode( DNSProxyTestRef me ) { OSStatus err; err = CFPropertyListAppendFormatted( kCFAllocatorDefault, me->modeResults, "{" "%kO=%s" // mode "%kO=[%@]" // results "}", CFSTR( "mode" ), _DNSProxyTestModeToString( me->modeParam ), CFSTR( "results" ), &me->prefixResults ); return( err ); } //=========================================================================================================================== static OSStatus _DNSProxyTestPrepareDNSProxy( DNSProxyTestRef me ) { OSStatus err; const char * dns64PrefixStr; Boolean forceAAAASynthesis; me->dns64PrefixBitLen = 0; memset( me->dns64Prefix, 0, sizeof( me->dns64Prefix ) ); dns64PrefixStr = _DNSProxyTestGetCurrentDNS64PrefixParam( me ); if( dns64PrefixStr ) { const char * end; err = _StringToIPv6Address( dns64PrefixStr, kStringToIPAddressFlagsNoPort | kStringToIPAddressFlagsNoScope, me->dns64Prefix, NULL, NULL, &me->dns64PrefixBitLen, &end ); if( !err && ( *end != '\0' ) ) err = kMalformedErr; require_noerr_quiet( err, exit ); } switch( me->modeParam ) { case kDNSProxyTestMode_Normal: forceAAAASynthesis = false; break; case kDNSProxyTestMode_ForceAAAASynthesis: forceAAAASynthesis = true; break; default: FatalErrorF( "Unhandled DNSProxyTestMode value %ld", (long) me->modeParam ); } mrc_dns_proxy_parameters_t params = mrc_dns_proxy_parameters_create( &err ); require_noerr( err, exit ); mrc_dns_proxy_parameters_add_input_interface( params, me->loopbackIndex ); mrc_dns_proxy_parameters_set_output_interface( params, 0 ); if( dns64PrefixStr ) { mrc_dns_proxy_parameters_set_nat64_prefix( params, me->dns64Prefix, (size_t) me->dns64PrefixBitLen ); if( forceAAAASynthesis ) mrc_dns_proxy_parameters_set_force_aaaa_synthesis( params, true ); dpt_ulog( kLogLevelInfo, "Starting DNS proxy with DNS64 prefix %.16a/%d\n", me->dns64Prefix, me->dns64PrefixBitLen ); } else { dpt_ulog( kLogLevelInfo, "Starting DNS proxy (without a DNS64 prefix)\n" ); } check( !me->dnsProxy ); me->dnsProxy = mrc_dns_proxy_create( params, &err ); mrc_forget( ¶ms ); require_noerr_quiet( err, exit ); mrc_dns_proxy_set_queue( me->dnsProxy, me->queue ); mrc_dns_proxy_set_event_handler( me->dnsProxy, ^( const mrc_dns_proxy_event_t inEvent, const OSStatus inError ) { switch( inEvent ) { case mrc_dns_proxy_event_started: dpt_ulog( kLogLevelInfo, "DNS proxy was started\n" ); break; case mrc_dns_proxy_event_interruption: dpt_ulog( kLogLevelInfo, "DNS proxy was interrupted\n" ); break; case mrc_dns_proxy_event_invalidation: if( inError ) dpt_ulog( kLogLevelError, "DNS proxy invalidated with error: %#m\n", inError ); else dpt_ulog( kLogLevelInfo, "DNS proxy invalidated\n" ); break; default: case mrc_dns_proxy_event_none: FatalErrorF( "Unhandled DNS proxy event value: %d", inEvent ); } } ); mrc_dns_proxy_activate( me->dnsProxy ); err = CFPropertyListAppendFormatted( kCFAllocatorDefault, me->prefixResults, "{" "%kO=%s" // dns64Prefix "%kO=[%@]" // results "}", CFSTR( "dns64Prefix" ), dns64PrefixStr ? dns64PrefixStr : "", CFSTR( "results" ), &me->transportResults ); require_noerr( err, exit ); exit: return( err ); } //=========================================================================================================================== #define kDNSProxyTestDNSProxyAddrStr_IPv4 "127.0.0.1:53" #define kDNSProxyTestDNSProxyAddrStr_IPv6 "[::1]:53" static OSStatus _DNSProxyTestPrepareResolver( DNSProxyTestRef me ) { OSStatus err; const char * addrStr; mdns_address_t addr = NULL; mdns_resolver_type_t resolverType; switch( me->transportParam ) { case kDNSProxyTestTransport_UDPv4: addrStr = kDNSProxyTestDNSProxyAddrStr_IPv4; resolverType = mdns_resolver_type_normal; break; case kDNSProxyTestTransport_TCPv4: addrStr = kDNSProxyTestDNSProxyAddrStr_IPv4; resolverType = mdns_resolver_type_tcp; break; case kDNSProxyTestTransport_UDPv6: addrStr = kDNSProxyTestDNSProxyAddrStr_IPv6; resolverType = mdns_resolver_type_normal; break; case kDNSProxyTestTransport_TCPv6: addrStr = kDNSProxyTestDNSProxyAddrStr_IPv6; resolverType = mdns_resolver_type_tcp; break; default: FatalErrorF( "Unhandled DNSProxyTestTransport value %ld", (long) me->transportParam ); } check( !me->resolver ); me->resolver = mdns_resolver_create( resolverType, 0, &err ); require_noerr( err, exit ); addr = mdns_address_create_from_ip_address_string( addrStr ); require_action( addr, exit, err = kUnknownErr ); err = mdns_resolver_add_server_address( me->resolver, addr ); mdns_forget( &addr ); require_noerr( err, exit ); dpt_ulog( kLogLevelInfo, "Activating resolver: %@\n", me->resolver ); mdns_resolver_activate( me->resolver ); _RandomStringExact( kLowerAlphaNumericCharSet, kLowerAlphaNumericCharSetSize, sizeof( me->tag ) - 1, me->tag ); err = CFPropertyListAppendFormatted( kCFAllocatorDefault, me->transportResults, "{" "%kO=%s" // transport "%kO=[%@]" // results "}", CFSTR( "transport" ), _DNSProxyTestTransportToString( me->transportParam ), CFSTR( "results" ), &me->queryResults ); require_noerr( err, exit ); exit: mdns_release_null_safe( addr ); return( err ); } //=========================================================================================================================== #define kDNSProxyTestQuerierTimeLimitSecs 5 #define kDNSProxyTestRecordTTL ( 5 * kSecondsPerMinute ) #define kDNSProxyTestAddressCount 4 #define kDNSProxyTestAliasCount 4 static void _DNSProxyTestQuerierTimerHandler( void *inCtx ); static OSStatus _DNSProxyTestStartQuery( DNSProxyTestRef me, Boolean *outSkipQuery ) { OSStatus err; mdns_querier_t querier; uint8_t tmpName[ kDomainNameLengthMax ]; Boolean skipQuery = false; me->startTime = NanoTimeGetCurrent(); me->qtype = 0; me->aliasCount = 0; me->answerCount = 0; me->responseCode = 0; me->canonicalName = NULL; me->answerName = NULL; me->synthesizedAAAA = false; check( !me->qnameStr ); switch( me->queryParam ) { case kDNSProxyTestQuery_A: me->qtype = kDNSRecordType_A; me->answerCount = kDNSProxyTestAddressCount; me->responseCode = kDNSRCode_NoError; ASPrintF( &me->qnameStr, "count-%u.ttl-%u.tag-a-%s.d.test.", kDNSProxyTestAddressCount, kDNSProxyTestRecordTTL, me->tag ); require_action( me->qnameStr, exit, err = kNoMemoryErr ); break; case kDNSProxyTestQuery_AAAA: me->qtype = kDNSRecordType_AAAA; me->answerCount = kDNSProxyTestAddressCount; me->responseCode = kDNSRCode_NoError; if( ( me->dns64PrefixBitLen > 0 ) && ( me->modeParam == kDNSProxyTestMode_ForceAAAASynthesis ) ) { me->synthesizedAAAA = true; } else { me->synthesizedAAAA = false; } ASPrintF( &me->qnameStr, "count-%u.ttl-%u.tag-aaaa-%s.d.test.", kDNSProxyTestAddressCount, kDNSProxyTestRecordTTL, me->tag ); require_action( me->qnameStr, exit, err = kNoMemoryErr ); break; case kDNSProxyTestQuery_IPv6OnlyA: me->qtype = kDNSRecordType_A; me->answerCount = 0; me->responseCode = kDNSRCode_NoError; ASPrintF( &me->qnameStr, "ipv6.ttl-%u.tag-ipv6-only-a-%s.d.test.", kDNSProxyTestRecordTTL, me->tag ); require_action( me->qnameStr, exit, err = kNoMemoryErr ); break; case kDNSProxyTestQuery_IPv6OnlyAAAA: me->qtype = kDNSRecordType_AAAA; me->responseCode = kDNSRCode_NoError; if( ( me->dns64PrefixBitLen > 0 ) && ( me->modeParam == kDNSProxyTestMode_ForceAAAASynthesis ) ) { me->answerCount = 0; } else { me->answerCount = kDNSProxyTestAddressCount; } ASPrintF( &me->qnameStr, "count-%u.ipv6.ttl-%u.tag-ipv6-only-aaaa-%s.d.test.", kDNSProxyTestAddressCount, kDNSProxyTestRecordTTL, me->tag ); require_action( me->qnameStr, exit, err = kNoMemoryErr ); break; case kDNSProxyTestQuery_IPv4OnlyAAAA: me->qtype = kDNSRecordType_AAAA; me->responseCode = kDNSRCode_NoError; if( me->dns64PrefixBitLen > 0 ) { me->answerCount = kDNSProxyTestAddressCount; me->synthesizedAAAA = true; } else { me->answerCount = 0; me->synthesizedAAAA = false; } ASPrintF( &me->qnameStr, "count-%u.ipv4.ttl-%u.tag-ipv4-only-aaaa-%s.d.test.", kDNSProxyTestAddressCount, kDNSProxyTestRecordTTL, me->tag ); require_action( me->qnameStr, exit, err = kNoMemoryErr ); break; case kDNSProxyTestQuery_AliasA: me->qtype = kDNSRecordType_A; me->aliasCount = kDNSProxyTestAliasCount; me->answerCount = kDNSProxyTestAddressCount; me->responseCode = kDNSRCode_NoError; ASPrintF( &me->qnameStr, "alias-%u.count-%u.ttl-%u.tag-alias-a-%s.d.test.", kDNSProxyTestAliasCount, kDNSProxyTestAddressCount, kDNSProxyTestRecordTTL, me->tag ); require_action( me->qnameStr, exit, err = kNoMemoryErr ); break; case kDNSProxyTestQuery_AliasAAAA: me->qtype = kDNSRecordType_AAAA; me->aliasCount = kDNSProxyTestAliasCount; me->answerCount = kDNSProxyTestAddressCount; me->responseCode = kDNSRCode_NoError; if( ( me->dns64PrefixBitLen > 0 ) && ( me->modeParam == kDNSProxyTestMode_ForceAAAASynthesis ) ) { me->synthesizedAAAA = true; } else { me->synthesizedAAAA = false; } ASPrintF( &me->qnameStr, "alias-%u.count-%u.ttl-%u.tag-alias-aaaa-%s.d.test.", kDNSProxyTestAliasCount, kDNSProxyTestAddressCount, kDNSProxyTestRecordTTL, me->tag ); require_action( me->qnameStr, exit, err = kNoMemoryErr ); break; case kDNSProxyTestQuery_AliasIPv6OnlyA: me->qtype = kDNSRecordType_A; me->aliasCount = kDNSProxyTestAliasCount; me->answerCount = 0; me->responseCode = kDNSRCode_NoError; ASPrintF( &me->qnameStr, "alias-%u.ipv6.ttl-%u.tag-alias-ipv6-only-a-%s.d.test.", kDNSProxyTestAliasCount, kDNSProxyTestRecordTTL, me->tag ); require_action( me->qnameStr, exit, err = kNoMemoryErr ); break; case kDNSProxyTestQuery_AliasIPv6OnlyAAAA: me->qtype = kDNSRecordType_AAAA; me->aliasCount = kDNSProxyTestAliasCount; me->responseCode = kDNSRCode_NoError; if( ( me->dns64PrefixBitLen > 0 ) && ( me->modeParam == kDNSProxyTestMode_ForceAAAASynthesis ) ) { me->answerCount = 0; } else { me->answerCount = kDNSProxyTestAddressCount; } ASPrintF( &me->qnameStr, "alias-%u.ipv6.count-%u.ttl-%u.tag-alias-ipv6-only-aaaa-%s.d.test.", kDNSProxyTestAliasCount, kDNSProxyTestAddressCount, kDNSProxyTestRecordTTL, me->tag ); require_action( me->qnameStr, exit, err = kNoMemoryErr ); break; case kDNSProxyTestQuery_AliasIPv4OnlyAAAA: me->qtype = kDNSRecordType_AAAA; me->aliasCount = kDNSProxyTestAliasCount; me->responseCode = kDNSRCode_NoError; if( me->dns64PrefixBitLen > 0 ) { me->answerCount = kDNSProxyTestAddressCount; me->synthesizedAAAA = true; } else { me->answerCount = 0; me->synthesizedAAAA = false; } ASPrintF( &me->qnameStr, "alias-%u.count-%u.ipv4.ttl-%u.tag-alias-ipv4-only-aaaa-%s.d.test.", kDNSProxyTestAliasCount, kDNSProxyTestAddressCount, kDNSProxyTestRecordTTL, me->tag ); require_action( me->qnameStr, exit, err = kNoMemoryErr ); break; case kDNSProxyTestQuery_NXDomainA: me->qtype = kDNSRecordType_A; me->answerCount = 0; me->responseCode = kDNSRCode_NXDomain; ASPrintF( &me->qnameStr, "does-not-exist.tag-nx-domain-a-%s.d.test.", me->tag ); require_action( me->qnameStr, exit, err = kNoMemoryErr ); break; case kDNSProxyTestQuery_NXDomainAAAA: me->qtype = kDNSRecordType_AAAA; me->answerCount = 0; me->responseCode = kDNSRCode_NXDomain; ASPrintF( &me->qnameStr, "does-not-exist.tag-nx-domain-aaaa-%s.d.test.", me->tag ); require_action( me->qnameStr, exit, err = kNoMemoryErr ); break; case kDNSProxyTestQuery_ReverseIPv6: case kDNSProxyTestQuery_ReverseIPv6NXDomain: { unsigned int hostID; uint8_t ipv6Addr[ 16 ]; char reverseIPv6NameStr[ kReverseIPv6DomainNameBufLen ]; // To force mDNSResponder to have to send a query on the first query iteration, use a different reverse IPv6 // PTR query for each mode/DNS64 prefix/transport combination. check_compile_time_code( kDNSProxyTestTransport_Count <= 4 ); check_compile_time_code( countof( kDNSProxyTestParams_DNS64Prefixes ) <= 8 ); check_compile_time_code( kDNSProxyTestMode_Count <= 4 ); hostID = ( (unsigned int) me->transportParam ) & 0x03; // Set bits 1 - 0 to transport param. hostID |= ( me->prefixParamIdx & 0x07 ) << 2; // Set bits 4 - 2 to prefix param index. hostID |= ( ( (unsigned int) me->modeParam ) & 0x03 ) << 5; // Set bits 6 - 5 to mode param. hostID |= 1U << 7; // Set bit 7 to ensure a non-zero hostID. check( ( hostID >= 1 ) && ( hostID <= 255 ) ); memcpy( ipv6Addr, kDNSServerBaseAddrV6, 16 ); ipv6Addr[ 15 ] = (uint8_t) hostID; me->qtype = kDNSRecordType_PTR; if( me->queryParam == kDNSProxyTestQuery_ReverseIPv6 ) { char answerNameStr[ 128 ]; char * dst = answerNameStr; char * const lim = &answerNameStr[ countof( answerNameStr ) ]; int i; me->responseCode = kDNSRCode_NoError; // Create the expected RDATA. SNPrintF_Add( &dst, lim, "ipv6" ); for( i = 0; i < 8; ++i ) { SNPrintF_Add( &dst, lim, "-%04x", ReadBig16( &ipv6Addr[ i * 2 ] ) ); } SNPrintF_Add( &dst, lim, ".d.test." ); err = DomainNameFromString( tmpName, answerNameStr, NULL ); require_noerr_quiet( err, exit ); err = DomainNameDup( tmpName, &me->answerName, NULL ); require_noerr( err, exit ); } else { me->responseCode = kDNSRCode_NXDomain; me->answerName = NULL; // Make the IPv6 address invalid by setting bits 15-8. This makes the host identifier bogus. // The DNS server's IPv6 prefix is 96 bits, but only host identifiers in [1, 255] are recognized. ipv6Addr[ 14 ] = 0xFF; } _WriteReverseIPv6DomainNameString( ipv6Addr, reverseIPv6NameStr ); me->qnameStr = strdup( reverseIPv6NameStr ); require_action( me->qnameStr, exit, err = kNoMemoryErr ); break; } case kDNSProxyTestQuery_ReverseIPv6DNS64: case kDNSProxyTestQuery_ReverseIPv6DNS64NXDomain: { uint32_t ipv4Addr; uint8_t ipv6Addr[ 16 ]; char reverseIPNameStr[ kReverseIPv6DomainNameBufLen ]; if( me->dns64PrefixBitLen <= 0 ) { skipQuery = true; err = kNoErr; goto exit; } me->qtype = kDNSRecordType_PTR; if( me->queryParam == kDNSProxyTestQuery_ReverseIPv6DNS64 ) { char answerNameStr[ 64 ]; me->responseCode = kDNSRCode_NoError; ipv4Addr = kDNSServerBaseAddrV4 + 1; _WriteReverseIPv4DomainNameString( ipv4Addr, reverseIPNameStr ); err = DomainNameFromString( tmpName, reverseIPNameStr, NULL ); require_noerr_quiet( err, exit ); err = DomainNameDup( tmpName, &me->canonicalName, NULL ); require_noerr( err, exit ); SNPrintF( answerNameStr, sizeof( answerNameStr ), "ipv4-%u-%u-%u-%u.d.test.", ( ipv4Addr >> 24 ) & 0xFF, ( ipv4Addr >> 16 ) & 0xFF, ( ipv4Addr >> 8 ) & 0xFF, ipv4Addr & 0xFF ); err = DomainNameFromString( tmpName, answerNameStr, NULL ); require_noerr_quiet( err, exit ); err = DomainNameDup( tmpName, &me->answerName, NULL ); require_noerr( err, exit ); } else { ipv4Addr = kDNSServerBaseAddrV4 + 0; me->responseCode = kDNSRCode_NXDomain; me->canonicalName = NULL; me->answerName = NULL; } err = _DNSProxyTestSynthesizeIPv6( me->dns64Prefix, me->dns64PrefixBitLen, ipv4Addr, ipv6Addr ); require_noerr( err, exit ); _WriteReverseIPv6DomainNameString( ipv6Addr, reverseIPNameStr ); me->qnameStr = strdup( reverseIPNameStr ); require_action( me->qnameStr, exit, err = kNoMemoryErr ); break; } default: FatalErrorF( "Unhandled DNSProxyTestQuery value %ld", (long) me->queryParam ); } check( !me->qname ); err = DomainNameFromString( tmpName, me->qnameStr, NULL ); require_noerr_quiet( err, exit ); err = DomainNameDup( tmpName, &me->qname, NULL ); require_noerr( err, exit ); check( !me->querier ); me->querier = mdns_resolver_create_querier( me->resolver, &err ); require_noerr( err, exit ); err = mdns_querier_set_query( me->querier, me->qname, me->qtype, kDNSClassType_IN ); require_noerr( err, exit ); mdns_querier_set_queue( me->querier, me->queue ); _DNSProxyTestRetain( me ); querier = me->querier; mdns_retain( querier ); mdns_querier_set_result_handler( me->querier, ^{ if( me->querier == querier ) _DNSProxyTestHandleQuerierResult( me ); _DNSProxyTestRelease( me ); mdns_release( querier ); } ); mdns_querier_activate( me->querier ); check( !me->timer ); err = DispatchTimerOneShotCreate( dispatch_time_seconds( kDNSProxyTestQuerierTimeLimitSecs ), kDNSProxyTestQuerierTimeLimitSecs * ( UINT64_C_safe( kNanosecondsPerSecond ) / 10 ), me->queue, _DNSProxyTestQuerierTimerHandler, me, &me->timer ); require_noerr( err, exit ); dispatch_resume( me->timer ); exit: if( outSkipQuery ) *outSkipQuery = skipQuery; return( err ); } static void _DNSProxyTestQuerierTimerHandler( void *inCtx ) { OSStatus err; const DNSProxyTestRef me = (DNSProxyTestRef) inCtx; Boolean done; dpt_ulog( kLogLevelInfo, "Query for '%{du:dname}' timed out.\n", me->qname ); err = _DNSProxyTestContinue( me, kTimeoutErr, &done ); check_noerr( err ); if( err || done ) _DNSProxyTestStop( me, err ); } //=========================================================================================================================== static OSStatus _DNSProxyTestSynthesizeIPv6( const uint8_t * inIPv6Prefix, int inIPv6PrefixBitLen, uint32_t inIPv4Addr, uint8_t outIPv6Addr[ STATIC_PARAM 16 ] ) { // From : // // 2.2. IPv4-Embedded IPv6 Address Format // // IPv4-converted IPv6 addresses and IPv4-translatable IPv6 addresses // follow the same format, described here as the IPv4-embedded IPv6 // address Format. IPv4-embedded IPv6 addresses are composed of a // variable-length prefix, the embedded IPv4 address, and a variable- // length suffix, as presented in the following diagram, in which PL // designates the prefix length: // // +--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ // |PL| 0-------------32--40--48--56--64--72--80--88--96--104---------| // +--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ // |32| prefix |v4(32) | u | suffix | // +--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ // |40| prefix |v4(24) | u |(8)| suffix | // +--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ // |48| prefix |v4(16) | u | (16) | suffix | // +--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ // |56| prefix |(8)| u | v4(24) | suffix | // +--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ // |64| prefix | u | v4(32) | suffix | // +--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ // |96| prefix | v4(32) | // +--+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ // // Figure 1 switch( inIPv6PrefixBitLen ) { case 32: case 40: case 48: case 56: case 64: case 96: { const int prefixLen = inIPv6PrefixBitLen / 8; int i, j; uint8_t v4Addr[ 4 ]; memcpy( outIPv6Addr, inIPv6Prefix, (size_t) prefixLen ); WriteBig32Typed( v4Addr, inIPv4Addr ); // 1. Bits 64 - 71, i.e., reserved octet "u", MUST be zero. // 2. Except for bits 64 - 71, the 32 bits following the prefix are the bits of the embedded IPv4 address. // 3. The remaining bits, if any, are the suffix bits, which SHOULD be zero. j = 0; for( i = prefixLen; i < 16; ++i ) { if( ( j < 4 ) && ( i != 8 ) ) outIPv6Addr[ i ] = v4Addr[ j++ ]; else outIPv6Addr[ i ] = 0; } return( kNoErr ); } default: return( kSizeErr ); } } //=========================================================================================================================== static void _DNSProxyTestHandleQuerierResult( DNSProxyTestRef me ) { OSStatus err, verifyErr; const uint8_t * msgPtr; size_t msgLen; const mdns_querier_result_type_t resultType = mdns_querier_get_result_type( me->querier ); Boolean done = false; if( resultType == mdns_querier_result_type_response ) { msgPtr = mdns_querier_get_response_ptr( me->querier ); msgLen = mdns_querier_get_response_length( me->querier ); dpt_ulog( kLogLevelInfo, "Querier response: %.1{du:dnsmsg}\n", msgPtr, msgLen ); } else { if( resultType == mdns_querier_result_type_error ) { err = mdns_querier_get_error( me->querier ); if( !err ) err = kUnknownErr; } else { err = kUnexpectedErr; } dpt_ulog( kLogLevelError, "Querier result: %s, error: %#m\n", mdns_querier_get_result_type( me->querier ), err ); goto exit; } switch( me->queryParam ) { case kDNSProxyTestQuery_A: case kDNSProxyTestQuery_AAAA: case kDNSProxyTestQuery_IPv6OnlyA: case kDNSProxyTestQuery_IPv6OnlyAAAA: case kDNSProxyTestQuery_IPv4OnlyAAAA: case kDNSProxyTestQuery_AliasA: case kDNSProxyTestQuery_AliasAAAA: case kDNSProxyTestQuery_AliasIPv6OnlyA: case kDNSProxyTestQuery_AliasIPv6OnlyAAAA: case kDNSProxyTestQuery_AliasIPv4OnlyAAAA: case kDNSProxyTestQuery_NXDomainA: case kDNSProxyTestQuery_NXDomainAAAA: verifyErr = _DNSProxyTestVerifyAddressResponse( msgPtr, msgLen, me->qname, me->qtype, me->responseCode, me->aliasCount, me->answerCount, me->synthesizedAAAA ? me->dns64Prefix : NULL, me->synthesizedAAAA ? me->dns64PrefixBitLen : 0 ); break; case kDNSProxyTestQuery_ReverseIPv6: case kDNSProxyTestQuery_ReverseIPv6NXDomain: case kDNSProxyTestQuery_ReverseIPv6DNS64: case kDNSProxyTestQuery_ReverseIPv6DNS64NXDomain: verifyErr = _DNSProxyTestVerifyReverseIPv6Response( msgPtr, msgLen, me->qname, me->responseCode, me->canonicalName, me->answerName ); break; default: FatalErrorF( "Unhandled DNSProxyTestQuery value %ld", (long) me->queryParam ); } err = _DNSProxyTestContinue( me, verifyErr, &done ); require_noerr( err, exit ); exit: if( err || done ) _DNSProxyTestStop( me, err ); } //=========================================================================================================================== static OSStatus _DNSProxyTestVerifyAddressResponse( const uint8_t * inMsgPtr, size_t inMsgLen, const uint8_t * inQName, uint16_t inQType, int inResponseCode, unsigned int inAliasCount, unsigned int inAnswerCount, const uint8_t * inDNS64Prefix, int inDNS64PrefixBitLen ) { OSStatus err; const DNSHeader * hdr; const uint8_t * ptr; const uint8_t * answerSection; unsigned int flags, qCount, answerCount; int rcode; uint16_t qtype, qclass; uint8_t qname[ kDomainNameLengthMax ]; require_action_quiet( inMsgLen >= kDNSHeaderLength, exit, err = kMalformedErr ); hdr = (const DNSHeader *) inMsgPtr; flags = DNSHeaderGetFlags( hdr ); rcode = DNSFlagsGetRCode( flags ); require_action_quiet( rcode == inResponseCode, exit, err = kValueErr ); qCount = DNSHeaderGetQuestionCount( hdr ); require_action_quiet( qCount == 1, exit, err = kCountErr ); ptr = (const uint8_t *) &hdr[ 1 ]; err = DNSMessageExtractQuestion( inMsgPtr, inMsgLen, ptr, qname, &qtype, &qclass, &ptr ); require_noerr_quiet( err, exit ); require_action_quiet( DomainNameEqual( qname, inQName ), exit, err = kNameErr ); require_action_quiet( qtype == inQType, exit, err = kTypeErr ); require_action_quiet( qclass == kDNSClassType_IN, exit, kTypeErr ); answerCount = DNSHeaderGetAnswerCount( hdr ); require_action_quiet( answerCount == ( inAliasCount + inAnswerCount ), exit, err = kCountErr ); answerSection = ptr; if( inAliasCount > 0 ) { unsigned int i; const uint8_t * parentDomain; uint8_t target[ kDomainNameLengthMax ]; parentDomain = DomainNameGetNextLabel( inQName ); require_fatal( parentDomain, "Invalid qname '%{du:dname}' for non-zero alias count.", inQName ); target[ 0 ] = 0; err = DomainNameAppendDomainName( target, inQName, NULL ); require_noerr( err, exit ); for( i = 0; i < inAliasCount; ++i ) { unsigned int j; const unsigned int aliasNumber = inAliasCount - i; Boolean foundCNAME = false; ptr = answerSection; for( j = 0; j < answerCount; ++j ) { const uint8_t * rdataPtr; unsigned int nextAliasNumber; uint16_t type; uint16_t class; uint8_t name[ kDomainNameLengthMax ]; char aliasLabelStr[ 32 ]; err = DNSMessageExtractRecord( inMsgPtr, inMsgLen, ptr, name, &type, &class, NULL, &rdataPtr, NULL, &ptr ); require_noerr( err, exit ); if( type != kDNSRecordType_CNAME ) continue; if( class != kDNSClassType_IN ) continue; if( !DomainNameEqual( name, target ) ) continue; target[ 0 ] = 0; nextAliasNumber = aliasNumber - 1; if( nextAliasNumber >= 2 ) { SNPrintF( aliasLabelStr, sizeof( aliasLabelStr ), "alias-%u", nextAliasNumber ); err = DomainNameAppendString( target, aliasLabelStr, NULL ); require_noerr( err, exit ); } else if( nextAliasNumber == 1 ) { SNPrintF( aliasLabelStr, sizeof( aliasLabelStr ), "alias" ); err = DomainNameAppendString( target, aliasLabelStr, NULL ); require_noerr( err, exit ); } err = DomainNameAppendDomainName( target, parentDomain, NULL ); require_noerr( err, exit ); err = DNSMessageExtractDomainName( inMsgPtr, inMsgLen, rdataPtr, name, NULL ); require_noerr( err, exit ); if( !DomainNameEqual( name, target ) ) continue; foundCNAME = true; break; } require_action_quiet( foundCNAME, exit, err = kNotFoundErr ); } } if( inAnswerCount > 0 ) { const uint8_t * target; unsigned int i; if( inAliasCount > 0 ) { target = DomainNameGetNextLabel( inQName ); require_fatal( target, "Invalid qname '%{du:dname}' for non-zero alias count.", inQName ); } else { target = inQName; } for( i = 0; i < inAnswerCount; ++i ) { unsigned int j; size_t expectedLen; uint8_t expectedData[ 16 ]; Boolean foundRecord = false; if( inQType == kDNSRecordType_A ) { const uint32_t ipv4Addr = kDNSServerBaseAddrV4 + ( i + 1 ); WriteBig32Typed( expectedData, ipv4Addr ); expectedLen = 4; } else { if( inDNS64PrefixBitLen != 0 ) { const uint32_t ipv4Addr = kDNSServerBaseAddrV4 + ( i + 1 ); err = _DNSProxyTestSynthesizeIPv6( inDNS64Prefix, inDNS64PrefixBitLen, ipv4Addr, expectedData ); require_noerr( err, exit ); } else { memcpy( expectedData, kDNSServerBaseAddrV6, 16 ); expectedData[ 15 ] = (uint8_t)( i + 1 ); } expectedLen = 16; } ptr = answerSection; for( j = 0; j < answerCount; ++j ) { const uint8_t * rdataPtr; size_t rdataLen; uint16_t type; uint16_t class; uint8_t name[ kDomainNameLengthMax ]; err = DNSMessageExtractRecord( inMsgPtr, inMsgLen, ptr, name, &type, &class, NULL, &rdataPtr, &rdataLen, &ptr ); require_noerr( err, exit ); if( type != inQType ) continue; if( class != kDNSClassType_IN ) continue; if( !DomainNameEqual( name, target ) ) continue; if( !MemEqual( rdataPtr, rdataLen, expectedData, expectedLen ) ) continue; foundRecord = true; break; } require_action_quiet( foundRecord, exit, err = kNotFoundErr ); } } exit: return( err ); } //=========================================================================================================================== static OSStatus _DNSProxyTestFindDomainNameRecord( const uint8_t * inMsgPtr, size_t inMsgLen, const uint8_t * inRecordSection, unsigned int inRecordCount, const uint8_t * inRecordName, uint16_t inRecordType, const uint8_t * inRDataName ); static OSStatus _DNSProxyTestVerifyReverseIPv6Response( const uint8_t * inMsgPtr, size_t inMsgLen, const uint8_t * inQName, int inResponseCode, const uint8_t * inCanonicalName, const uint8_t * inAnswerName ) { OSStatus err; const DNSHeader * hdr; const uint8_t * ptr; const uint8_t * answerSection; const uint8_t * ownerName; unsigned int flags, qCount, answerCount, answerCountExpected; int rcode; uint16_t qtype, qclass; uint8_t qname[ kDomainNameLengthMax ]; require_action_quiet( inMsgLen >= kDNSHeaderLength, exit, err = kMalformedErr ); hdr = (const DNSHeader *) inMsgPtr; flags = DNSHeaderGetFlags( hdr ); rcode = DNSFlagsGetRCode( flags ); require_action_quiet( rcode == inResponseCode, exit, err = kValueErr ); qCount = DNSHeaderGetQuestionCount( hdr ); require_action_quiet( qCount == 1, exit, err = kCountErr ); ptr = (const uint8_t *) &hdr[ 1 ]; err = DNSMessageExtractQuestion( inMsgPtr, inMsgLen, ptr, qname, &qtype, &qclass, &ptr ); require_noerr_quiet( err, exit ); require_action_quiet( DomainNameEqual( qname, inQName ), exit, err = kNameErr ); require_action_quiet( qtype == kDNSRecordType_PTR, exit, err = kTypeErr ); require_action_quiet( qclass == kDNSClassType_IN, exit, kTypeErr ); answerCountExpected = ( inCanonicalName ? 1 : 0 ) + ( inAnswerName ? 1 : 0 ); answerCount = DNSHeaderGetAnswerCount( hdr ); require_action_quiet( answerCount == answerCountExpected, exit, err = kCountErr ); answerSection = ptr; ownerName = inQName; if( inCanonicalName ) { err = _DNSProxyTestFindDomainNameRecord( inMsgPtr, inMsgLen, answerSection, answerCount, ownerName, kDNSRecordType_CNAME, inCanonicalName ); require_noerr( err, exit ); ownerName = inCanonicalName; } if( inAnswerName ) { err = _DNSProxyTestFindDomainNameRecord( inMsgPtr, inMsgLen, answerSection, answerCount, ownerName, kDNSRecordType_PTR, inAnswerName ); require_noerr( err, exit ); } exit: return( err ); } static OSStatus _DNSProxyTestFindDomainNameRecord( const uint8_t * inMsgPtr, size_t inMsgLen, const uint8_t * inRecordSection, unsigned int inRecordCount, const uint8_t * inRecordName, uint16_t inRecordType, const uint8_t * inRDataName ) { OSStatus err; const uint8_t * ptr; unsigned int i; Boolean found = false; ptr = inRecordSection; for( i = 0; i < inRecordCount; ++i ) { const uint8_t * rdataPtr; uint16_t type; uint16_t class; uint8_t name[ kDomainNameLengthMax ]; err = DNSMessageExtractRecord( inMsgPtr, inMsgLen, ptr, name, &type, &class, NULL, &rdataPtr, NULL, &ptr ); require_noerr( err, exit ); if( type != inRecordType ) continue; if( class != kDNSClassType_IN ) continue; if( !DomainNameEqual( name, inRecordName ) ) continue; err = DNSMessageExtractDomainName( inMsgPtr, inMsgLen, rdataPtr, name, NULL ); require_noerr( err, exit ); if( !DomainNameEqual( name, inRDataName ) ) continue; found = true; break; } err = found ? kNoErr : kNotFoundErr; exit: return( err ); } //=========================================================================================================================== static void DNSSD_API _DNSProxyTestProbeGAICallback( DNSServiceRef inSDRef, DNSServiceFlags inFlags, uint32_t inInterfaceIndex, DNSServiceErrorType inError, const char * inHostname, const struct sockaddr * inSockAddr, uint32_t inTTL, void * inCtx ) { OSStatus err; const DNSProxyTestRef me = (DNSProxyTestRef) inCtx; Boolean done = false; Unused( inSDRef ); Unused( inInterfaceIndex ); Unused( inHostname ); Unused( inTTL ); if( ( inFlags & kDNSServiceFlagsAdd ) && !inError ) { DNSServiceForget( &me->probeGAI ); dispatch_source_forget( &me->timer ); dpt_ulog( kLogLevelInfo, "Probe: Got GAI address %##a for %s\n", inSockAddr, me->probeHostname ); err = _DNSProxyTestContinue( me, kNoErr, &done ); require_noerr( err, exit ); } err = kNoErr; exit: if( err || done ) _DNSProxyTestStop( me, err ); } //=========================================================================================================================== static void _DNSProxyTestProbeTimerHandler( void *inCtx ) { const DNSProxyTestRef me = (DNSProxyTestRef) inCtx; dpt_ulog( kLogLevelInfo, "Probe: GAI request for '%s' timed out.\n", me->probeHostname ); _DNSProxyTestStop( me, kNotPreparedErr ); } //=========================================================================================================================== // RCodeTestCmd //=========================================================================================================================== #define kRCodeTestMaxRCodeValue 15 typedef struct RCodeTest * RCodeTestRef; struct RCodeTest { dispatch_queue_t queue; // Serial queue for test events. dispatch_semaphore_t doneSem; // Semaphore to signal when the test is done. dnssd_getaddrinfo_t gai; // Current subtest's GAI object. (Also used for probing test DNS server.) dispatch_source_t timer; // Timer for enforcing time limit on current dnssd_getaddrinfo. CFMutableDictionaryRef report; // Test's report, as a plist. CFMutableArrayRef subtestResults; // Pointer to report's subtest results. CFMutableArrayRef gaiResults; // Array of dnssd_getaddrinfo_result objects for the current GAI. char * hostname; // Current subtest's hostname. (Also used for probing test DNS server.) char * description; // Current subtest description. NanoTime64 startTime; // Current subtest's start time. pid_t serverPID; // PID of spawned test DNS server. OSStatus error; // Current test error. int32_t refCount; // Test's reference count. int rcode; // Argument to use in current subtest's hostname's RCODE label. int indexIdx; // Index of argument to use in current subtest's hostname's Index label. int subtestCount; // Number of subtests that have completed or are in progress. int subtestPassCount; // Number of subtests that have passed so far. Boolean hostnameIsAlias; // True if current subtest's hostname is an alias. Boolean hostnameHasAddr; // True if current subtest's hostname that has an IPv4 address. Boolean done; // True if all subtests have completed. }; ulog_define_ex( kDNSSDUtilIdentifier, RCodeTest, kLogLevelInfo, kLogFlags_None, "RCodeTest", NULL ); #define rct_ulog( LEVEL, ... ) ulog( &log_category_from_name( RCodeTest ), (LEVEL), __VA_ARGS__ ) static OSStatus _RCodeTestCreate( RCodeTestRef *outTest ); static OSStatus _RCodeTestRun( RCodeTestRef inTest, Boolean *outPassed ); static void _RCodeTestRetain( RCodeTestRef inTest ); static void _RCodeTestRelease( RCodeTestRef inTest ); static void RCodeTestCmd( void ) { OSStatus err; OutputFormatType outputFormat; RCodeTestRef test = NULL; Boolean passed = false; err = CheckRootUser(); require_noerr_quiet( err, exit ); err = OutputFormatFromArgString( gRCodeTest_OutputFormat, &outputFormat ); require_noerr_quiet( err, exit ); err = _RCodeTestCreate( &test ); require_noerr( err, exit ); err = _RCodeTestRun( test, &passed ); require_noerr( err, exit ); err = OutputPropertyList( test->report, outputFormat, gRCodeTest_OutputFilePath ); require_noerr( err, exit ); exit: if( test ) _RCodeTestRelease( test ); gExitCode = err ? 1 : ( passed ? 0 : 2 ); } //=========================================================================================================================== static OSStatus _RCodeTestCreate( RCodeTestRef *outTest ) { OSStatus err; RCodeTestRef obj; obj = (RCodeTestRef) calloc( 1, sizeof( *obj ) ); require_action( obj, exit, err = kNoMemoryErr ); obj->refCount = 1; obj->error = kInProgressErr; obj->serverPID = -1; obj->queue = dispatch_queue_create( "com.apple.dnssdutil.rcode-test", DISPATCH_QUEUE_SERIAL ); require_action( obj->queue, exit, err = kNoResourcesErr ); obj->doneSem = dispatch_semaphore_create( 0 ); require_action( obj->doneSem, exit, err = kNoResourcesErr ); obj->report = CFDictionaryCreateMutable( NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks ); require_action( obj->report, exit, err = kNoMemoryErr ); obj->indexIdx = -1; *outTest = obj; obj = NULL; err = kNoErr; exit: if( obj ) _RCodeTestRelease( obj ); return( err ); } //=========================================================================================================================== static void _RCodeTestStart( void *inCtx ); static void _RCodeTestStop( RCodeTestRef inTest, OSStatus inError ); static OSStatus _RCodeTestStartSubtest( RCodeTestRef inTest ); static OSStatus _RCodeTestContinue( RCodeTestRef inType, OSStatus inSubtestError, Boolean *outDone ); static Boolean _RCodeTestRCodeIsGood( const int inRCode ); static OSStatus _RCodeTestRun( RCodeTestRef me, Boolean *outPassed ) { Boolean passed; dispatch_async_f( me->queue, me, _RCodeTestStart ); dispatch_semaphore_wait( me->doneSem, DISPATCH_TIME_FOREVER ); passed = ( !me->error && ( me->subtestPassCount == me->subtestCount ) ) ? true : false; CFDictionarySetBoolean( me->report, CFSTR( "pass" ), passed ); rct_ulog( kLogLevelInfo, "Test result: %s\n", passed ? "pass" : "fail" ); if( outPassed ) *outPassed = passed; return( me->error ); } //=========================================================================================================================== static void _RCodeTestRetain( const RCodeTestRef me ) { atomic_add_32( &me->refCount, 1 ); } //=========================================================================================================================== static void _RCodeTestRelease( const RCodeTestRef me ) { if( atomic_add_and_fetch_32( &me->refCount, -1 ) == 0 ) { check( !me->gai ); check( !me->timer ); check( !me->subtestResults ); check( !me->gaiResults ); check( !me->hostname ); check( !me->description ); check( me->serverPID < 0 ); dispatch_forget( &me->queue ); dispatch_forget( &me->doneSem ); ForgetCF( &me->report ); free( me ); } } //=========================================================================================================================== #define kRCodeTestProbeQueryTimeoutSecs 5 static void _RCodeTestHandleGAIProbeResults( RCodeTestRef inTest, dnssd_getaddrinfo_result_t *inResults, size_t inCount ); static void _RCodeTestProbeQueryTimerHandler( void *inCtx ); static void _RCodeTestStart( void * const inCtx ) { OSStatus err; const RCodeTestRef me = (RCodeTestRef) inCtx; char * serverCmd = NULL; dnssd_getaddrinfo_t gai; NanoTime64 startTime; char startTimeStr[ 32 ]; char tag[ 6 + 1 ]; startTime = NanoTimeGetCurrent(); rct_ulog( kLogLevelInfo, "Starting test\n" ); // The "dnssdutil server" command will create a resolver entry for the server's "d.test." domain containing an array // of the server's IP addresses. Because configd favors IPv6 addresses, when there's a mix of IPv4 and IPv6 // addresses, configd may rearrange the array in order to ensure that IPv6 addresses come before the IPv4 addresses. // To preserve the original address order, the server is specified to run in IPv6-only mode. This way, // mDNSResponder's view of the address will be such that address with index value 1 is first, address with index // value 2 is second, etc. serverCmd = NULL; ASPrintF( &serverCmd, DNSSDUTIL_TEST_DNS_SERVER_COMMAND_PREAMBLE " --loopback --follow %lld --responseDelay 20 --ipv6 --extraIPv6 3", (int64_t) getpid() ); require_action_quiet( serverCmd, exit, err = kNoMemoryErr ); err = _SpawnCommand( &me->serverPID, "/dev/null", "/dev/null", "%s", serverCmd ); require_noerr( err, exit ); check( !me->hostname ); me->hostname = NULL; ASPrintF( &me->hostname, "tag-rcode-test-probe-%s.ipv4.d.test.", _RandomStringExact( kLowerAlphaNumericCharSet, kLowerAlphaNumericCharSetSize, sizeof( tag ) - 1, tag ) ); require_action( me->hostname, exit, err = kNoMemoryErr ); check( !me->gai ); me->gai = dnssd_getaddrinfo_create(); require_action( me->gai, exit, err = kNoResourcesErr ); dnssd_getaddrinfo_set_hostname( me->gai, me->hostname ); dnssd_getaddrinfo_set_flags( me->gai, 0 ); dnssd_getaddrinfo_set_interface_index( me->gai, kDNSServiceInterfaceIndexAny ); dnssd_getaddrinfo_set_protocols( me->gai, kDNSServiceProtocol_IPv4 ); dnssd_getaddrinfo_set_queue( me->gai, me->queue ); gai = me->gai; dnssd_retain( gai ); _RCodeTestRetain( me ); dnssd_getaddrinfo_set_result_handler( me->gai, ^( dnssd_getaddrinfo_result_t * const inResults, const size_t inCount ) { require_return( me->gai == gai ); _RCodeTestHandleGAIProbeResults( me, inResults, inCount ); } ); dnssd_getaddrinfo_set_event_handler( me->gai, ^( const dnssd_event_t inEvent, const DNSServiceErrorType inGAIError ) { if( inEvent == dnssd_event_invalidated ) { dnssd_release( gai ); _RCodeTestRelease( me ); } else if( inEvent == dnssd_event_error ) { require_return( me->gai == gai ); rct_ulog( kLogLevelError, "dnssd_getaddrinfo error: %#m\n", inGAIError ); _RCodeTestStop( me, inGAIError ); } } ); dnssd_getaddrinfo_activate( me->gai ); check( !me->timer ); err = DispatchTimerOneShotCreate( dispatch_time_seconds( kRCodeTestProbeQueryTimeoutSecs ), kRCodeTestProbeQueryTimeoutSecs * ( UINT64_C_safe( kNanosecondsPerSecond ) / 10 ), me->queue, _RCodeTestProbeQueryTimerHandler, me, &me->timer ); require_noerr( err, exit ); dispatch_resume( me->timer ); _NanoTime64ToTimestamp( startTime, startTimeStr, sizeof( startTimeStr ) ); err = CFPropertyListAppendFormatted( kCFAllocatorDefault, me->report, "%kO=%s" // startTime "%kO=%s" // serverCmd "%kO=%s" // probeHostname "%kO=[%@]", // results CFSTR( "startTime" ), startTimeStr, CFSTR( "serverCmd" ), serverCmd, CFSTR( "probeHostname" ), me->hostname, CFSTR( "results" ), &me->subtestResults ); require_noerr( err, exit ); exit: FreeNullSafe( serverCmd ); if( err ) _RCodeTestStop( me, err ); } static void _RCodeTestHandleGAIProbeResults( const RCodeTestRef me, dnssd_getaddrinfo_result_t * const inResults, const size_t inCount ) { size_t i; Boolean startSubtests = false; for( i = 0; i < inCount; ++i ) { const dnssd_getaddrinfo_result_t result = inResults[ i ]; if( dnssd_getaddrinfo_result_get_type( result ) == dnssd_getaddrinfo_result_type_add ) { rct_ulog( kLogLevelInfo, "Probe GAI got %##a for %s\n", dnssd_getaddrinfo_result_get_address( result ), me->hostname ); startSubtests = true; break; } } if( startSubtests ) { OSStatus err; dnssd_getaddrinfo_forget( &me->gai ); dispatch_source_forget( &me->timer ); err = _RCodeTestStartSubtest( me ); if( err ) _RCodeTestStop( me, err ); } } static void _RCodeTestProbeQueryTimerHandler( void * const inCtx ) { const RCodeTestRef me = (RCodeTestRef) inCtx; rct_ulog( kLogLevelInfo, "Probe GAI request for '%s' timed out.\n", me->hostname ); _RCodeTestStop( me, kNotPreparedErr ); } //=========================================================================================================================== static void _RCodeTestStop( RCodeTestRef me, OSStatus inError ) { OSStatus err; NanoTime64 endTime; char endTimeStr[ 32 ]; endTime = NanoTimeGetCurrent(); me->error = inError; rct_ulog( kLogLevelInfo, "Stopping test with error: %#m\n", me->error ); dnssd_getaddrinfo_forget( &me->gai ); dispatch_source_forget( &me->timer ); me->subtestResults = NULL; ForgetCF( &me->gaiResults ); ForgetMem( &me->hostname ); ForgetMem( &me->description ); if( me->serverPID >= 0 ) { OSStatus killErr; killErr = kill( me->serverPID, SIGTERM ); killErr = map_global_noerr_errno( killErr ); check_noerr( killErr ); me->serverPID = -1; } _NanoTime64ToTimestamp( endTime, endTimeStr, sizeof( endTimeStr ) ); err = CFPropertyListAppendFormatted( kCFAllocatorDefault, me->report, "%kO=%s" // endTime "%kO=%lli" // subtestCount "%kO=%lli", // subtestPassCount CFSTR( "endTime" ), endTimeStr, CFSTR( "subtestCount" ), (int64_t) me->subtestCount, CFSTR( "subtestPassCount" ), (int64_t) me->subtestPassCount ); check_noerr( err ); if( err && !me->error ) me->error = err; dispatch_semaphore_signal( me->doneSem ); } //=========================================================================================================================== #define kRCodeSubtestRegularTimeLimitSecs 4 #define kRCodeSubtestExtendedTimeLimitSecs 12 // Allow three seconds for each of the four server addresses. static void _RCodeSubtestHandleGAIResults( RCodeTestRef inTest, dnssd_getaddrinfo_result_t *inResults, size_t inCount ); static void _RCodeSubtestTimerHandler( void *inCtx ); static const void * _DNSSDObjectCFArrayCallbackRetain( CFAllocatorRef inAllocator, const void *inObject ); static void _DNSSDObjectCFArrayCallbackRelease( CFAllocatorRef inAllocator, const void *inObject ); static const CFArrayCallBacks kDNSSDObjectArrayCallbacks = { .version = 0, .retain = _DNSSDObjectCFArrayCallbackRetain, .release = _DNSSDObjectCFArrayCallbackRelease }; // Arguments to use for Index labels. Since the test uses a test DNS server with four IPv6 addresses, which // mDNSResponder views as four different servers, this is an arbitrary mix of the four possible index values. An Index // label makes it so that only the server specified by the Index label's argument responds with the correct response. // The point of the mix is to force mDNSResponder to have to send queries to more than one server before it gets the // right response. static const int kRCodeTestIndexArguments[] = { 2, 4, 1, 3, 2, 1, 4, 3 }; #define kRCodeTestMaxIndexArgumentIndex ( countof( kRCodeTestIndexArguments ) - 1 ) static OSStatus _RCodeTestStartSubtest( const RCodeTestRef me ) { OSStatus err; dnssd_getaddrinfo_t gai; const char * rcodeStr; int index; unsigned int timeLimitSecs; char rcodeStrBuf[ 32 ]; char indexLabelStr[ 32 ]; char rcodeLabelStr[ 32 ]; char tag[ 6 + 1 ]; require_action_quiet( !me->done, exit, err = kInternalErr ); check( !me->gai ); check( !me->timer ); me->startTime = NanoTimeGetCurrent(); if( me->indexIdx < 0 ) { index = -1; indexLabelStr[ 0 ] = '\0'; require_fatal( ( me->rcode >= 0 ) && ( me->rcode <= kRCodeTestMaxRCodeValue ), "Unexpected subtest rcode value %d.", me->rcode ); } else { index = kRCodeTestIndexArguments[ me->indexIdx ]; SNPrintF( indexLabelStr, sizeof( indexLabelStr ), "index-%d.", index ); me->hostnameHasAddr = true; me->hostnameIsAlias = true; require_fatal( ( me->rcode >= -1 ) && ( me->rcode <= kRCodeTestMaxRCodeValue ), "Unexpected subtest rcode value %d.", me->rcode ); } if( me->rcode > 0 ) { SNPrintF( rcodeLabelStr, sizeof( rcodeLabelStr ), "rcode-%d.", me->rcode ); } else { rcodeLabelStr[ 0 ] = '\0'; } ForgetMem( &me->hostname ); ASPrintF( &me->hostname, "%s%s%scount-%d.tag-rcode-test-%s.d.test.", me->hostnameIsAlias ? "alias." : "", indexLabelStr, rcodeLabelStr, me->hostnameHasAddr ? 1 : 0, _RandomStringExact( kLowerAlphaNumericCharSet, kLowerAlphaNumericCharSetSize, sizeof( tag ) - 1, tag ) ); require_action( me->hostname, exit, err = kNoMemoryErr ); CFReleaseNullSafe( me->gaiResults ); me->gaiResults = CFArrayCreateMutable( NULL, 0, &kDNSSDObjectArrayCallbacks ); require_action( me->gaiResults, exit, err = kNoMemoryErr ); me->gai = dnssd_getaddrinfo_create(); require_action( me->gai, exit, err = kNoResourcesErr ); dnssd_getaddrinfo_set_hostname( me->gai, me->hostname ); dnssd_getaddrinfo_set_flags( me->gai, kDNSServiceFlagsReturnIntermediates ); dnssd_getaddrinfo_set_interface_index( me->gai, kDNSServiceInterfaceIndexAny ); dnssd_getaddrinfo_set_protocols( me->gai, kDNSServiceProtocol_IPv4 ); dnssd_getaddrinfo_set_queue( me->gai, me->queue ); gai = me->gai; dnssd_retain( gai ); _RCodeTestRetain( me ); dnssd_getaddrinfo_set_result_handler( me->gai, ^( dnssd_getaddrinfo_result_t * const inResults, const size_t inCount ) { require_return( me->gai == gai ); _RCodeSubtestHandleGAIResults( me, inResults, inCount ); } ); dnssd_getaddrinfo_set_event_handler( gai, ^( const dnssd_event_t inEvent, const DNSServiceErrorType inGAIError ) { if( inEvent == dnssd_event_invalidated ) { dnssd_release( gai ); _RCodeTestRelease( me ); } else if( inEvent == dnssd_event_error ) { OSStatus testErr; Boolean done; require_return( me->gai == gai ); rct_ulog( kLogLevelError, "dnssd_getaddrinfo error: %#m\n", inGAIError ); testErr = _RCodeTestContinue( me, inGAIError, &done ); if( testErr || done ) _RCodeTestStop( me, testErr ); } } ); // If an Index label is being used without an RCode label, then only the server specified by the index label will // respond to the query, so we need more time to allow for query retries due to unresponsive servers. if( ( index > 0 ) && ( me->rcode < 0 ) ) { timeLimitSecs = kRCodeSubtestExtendedTimeLimitSecs; } else { timeLimitSecs = kRCodeSubtestRegularTimeLimitSecs; } check( !me->timer ); err = DispatchTimerOneShotCreate( dispatch_time_seconds( timeLimitSecs ), timeLimitSecs * ( UINT64_C_safe( kNanosecondsPerSecond ) / 20 ), me->queue, _RCodeSubtestTimerHandler, me, &me->timer ); require_noerr( err, exit ); rcodeStr = DNSRCodeToString( me->rcode ); if( !rcodeStr ) { SNPrintF( rcodeStrBuf, sizeof( rcodeStrBuf ), "RCODE%d", me->rcode ); rcodeStr = rcodeStrBuf; } ForgetMem( &me->description ); if( me->indexIdx < 0 ) { ASPrintF( &me->description, "DNS response with RCODE %s (%d), %s CNAME record, and %s A record from all servers", rcodeStr, me->rcode, me->hostnameIsAlias ? "one" : "no", me->hostnameHasAddr ? "one" : "no" ); require_action( me->description, exit, err = kNoMemoryErr ); } else { int n; ASPrintF( &me->description, "DNS response with RCODE NoError (0), %s CNAME record, and %s A record from server #%d.", me->hostnameIsAlias ? "one" : "no", me->hostnameHasAddr ? "one" : "no", index ); require_action( me->description, exit, err = kNoMemoryErr ); if( me->rcode < 0 ) { n = AppendPrintF( &me->description, " No DNS respones from all other servers." ); require_action( n >= 0, exit, err = kNoMemoryErr ); } else { n = AppendPrintF( &me->description, " DNS responses with RCODE %s (%d) and no records from all other servers.", rcodeStr, me->rcode ); require_action( n >= 0, exit, err = kNoMemoryErr ); } } ++me->subtestCount; rct_ulog( kLogLevelInfo, "Starting subtest #%d: %s\n", me->subtestCount, me->description ); dnssd_getaddrinfo_activate( me->gai ); dispatch_resume( me->timer ); exit: return( err ); } static void _RCodeSubtestHandleGAIResults( const RCodeTestRef me, dnssd_getaddrinfo_result_t * const inResults, const size_t inCount ) { size_t i; for( i = 0; i < inCount; ++i ) { const dnssd_getaddrinfo_result_t result = inResults[ i ]; rct_ulog( kLogLevelInfo, "GAI result -- %@\n", result ); CFArrayAppendValue( me->gaiResults, result ); } } static void _RCodeSubtestTimerHandler( void * const inCtx ) { OSStatus err; const RCodeTestRef me = (RCodeTestRef) inCtx; dnssd_getaddrinfo_result_t result; const sockaddr_ip * sip; const uint8_t * targetName; CFIndex n; dnssd_getaddrinfo_result_type_t resultType; int expectedResultCount; uint8_t hostname[ kDomainNameLengthMax ]; uint8_t actualHostname[ kDomainNameLengthMax ]; Boolean expectAddress, expectCanonName, done; // mDNSResponder has traditionally ignored responses with RCODEs not equal to NoError, NXDomain, or NotAuth. // Such responses result in a kDNSServiceErr_NoSuchRecord error, or NoAddress in the case of dnssd_getaddrinfo. if( ( me->indexIdx >= 0 ) || _RCodeTestRCodeIsGood( me->rcode ) ) { expectAddress = me->hostnameHasAddr; expectCanonName = me->hostnameIsAlias; expectedResultCount = 1; } else { expectAddress = false; expectCanonName = false; expectedResultCount = 1; } n = CFArrayGetCount( me->gaiResults ); require_action_quiet( n == expectedResultCount, exit, err = kCountErr ); if( n != 0 ) { result = (dnssd_getaddrinfo_result_t) CFArrayGetValueAtIndex( me->gaiResults, 0 ); resultType = dnssd_getaddrinfo_result_get_type( result ); sip = (const sockaddr_ip *) dnssd_getaddrinfo_result_get_address( result ); require_action_quiet( sip->sa.sa_family == AF_INET, exit, err = kAddressErr ); if( expectAddress ) { require_action_quiet( resultType == dnssd_getaddrinfo_result_type_add, exit, err = kTypeErr ); require_action_quiet( ntohl( sip->v4.sin_addr.s_addr ) == ( kDNSServerBaseAddrV4 + 1 ), exit, err = kAddressErr ); } else { require_action_quiet( resultType == dnssd_getaddrinfo_result_type_no_address, exit, err = kTypeErr ); } err = DomainNameFromString( hostname, me->hostname, NULL ); require_noerr_fatal( err, "Failed to convert hostname to DNS wire format -- hostname: %s, error: %#m", me->hostname, err ); err = DomainNameFromString( actualHostname, dnssd_getaddrinfo_result_get_actual_hostname( result ), NULL ); require_noerr_quiet( err, exit ); if( expectCanonName ) { size_t firstLabelLen; firstLabelLen = hostname[ 0 ]; require_fatal( firstLabelLen > 0, "Hostname's first label length is zero -- hostname: %s", me->hostname ); targetName = &hostname[ 1 + firstLabelLen ]; } else { targetName = hostname; } require_action_quiet( DomainNameEqual( actualHostname, targetName ), exit, err = kNameErr ); } err = kNoErr; exit: err = _RCodeTestContinue( me, err, &done ); if( err || done ) _RCodeTestStop( me, err ); } static const void * _DNSSDObjectCFArrayCallbackRetain( __unused const CFAllocatorRef inAllocator, const void *inObject ) { dnssd_retain( (dnssd_object_t) inObject ); return inObject; } static void _DNSSDObjectCFArrayCallbackRelease( __unused const CFAllocatorRef inAllocator, const void *inObject ) { dnssd_release( (dnssd_object_t) inObject ); } //=========================================================================================================================== static OSStatus _RCodeTestStopSubtest( RCodeTestRef inTest, OSStatus inError ); static OSStatus _RCodeTestContinue( const RCodeTestRef me, OSStatus inSubtestError, Boolean *outDone ) { OSStatus err; require_action_quiet( !me->done, exit, err = kNoErr ); err = _RCodeTestStopSubtest( me, inSubtestError ); require_noerr( err, exit ); require_action_quiet( !me->done, exit, err = kNoErr ); err = _RCodeTestStartSubtest( me ); require_noerr( err, exit ); exit: if( outDone ) *outDone = me->done; return( err ); } //=========================================================================================================================== static OSStatus _RCodeTestStopSubtest( const RCodeTestRef me, const OSStatus inError ) { OSStatus err; NanoTime64 endTime; CFMutableArrayRef gaiResultDescs; char errorStr[ 128 ]; char startTimeStr[ 32 ]; char endTimeStr[ 32 ]; CFIndex i, n; dnssd_getaddrinfo_forget( &me->gai ); dispatch_source_forget( &me->timer ); endTime = NanoTimeGetCurrent(); if( !inError ) ++me->subtestPassCount; rct_ulog( kLogLevelInfo, "Subtest #%d result: %s (pass rate: %d/%d)\n", me->subtestCount, inError ? "fail" : "pass", me->subtestPassCount, me->subtestCount ); _NanoTime64ToTimestamp( me->startTime, startTimeStr, sizeof( startTimeStr ) ); _NanoTime64ToTimestamp( endTime, endTimeStr, sizeof( endTimeStr ) ); SNPrintF( errorStr, sizeof( errorStr ), "%m", inError ); err = CFPropertyListAppendFormatted( kCFAllocatorDefault, me->subtestResults, "{" "%kO=%s" // description "%kO=%s" // startTime "%kO=%s" // endTime "%kO=%s" // hostname "%kO=%b" // pass "%kO=" // error "{" "%kO=%lli" // code "%kO=%s" // description "}" "%kO=[%@]" // GAIResults "}", CFSTR( "description" ), me->description, CFSTR( "startTime" ), startTimeStr, CFSTR( "endTime" ), endTimeStr, CFSTR( "hostname" ), me->hostname, CFSTR( "pass" ), !inError ? true : false, CFSTR( "error" ), CFSTR( "code" ), (int64_t) inError, CFSTR( "description" ), errorStr, CFSTR( "GAIResults" ), &gaiResultDescs ); require_noerr( err, exit ); n = CFArrayGetCount( me->gaiResults ); for( i = 0; i < n; ++ i ) { dnssd_getaddrinfo_result_t result = (dnssd_getaddrinfo_result_t) CFArrayGetValueAtIndex( me->gaiResults, i ); char * resultDesc; resultDesc = dnssd_copy_description( result ); require_action_quiet( resultDesc, exit, err = kNoMemoryErr ); err = CFPropertyListAppendFormatted( NULL, gaiResultDescs, "%s", resultDesc ); ForgetMem( &resultDesc ); require_noerr( err, exit ); } if( me->indexIdx < 0 ) { // Each subtest is one of the 64 possible combinations of // rcode ∈ {0, 1, 2, …, 15}, hostnameIsAlias ∈ {false, true}, hostnameHasAddr ∈ {false, true}. if( !me->hostnameHasAddr ) { me->hostnameHasAddr = true; } else { me->hostnameHasAddr = false; if( !me->hostnameIsAlias ) { me->hostnameIsAlias = true; } else { me->hostnameIsAlias = false; if( me->rcode < kRCodeTestMaxRCodeValue ) { ++me->rcode; } else { me->indexIdx = 0; // At this point we move on to using Index labels. me->rcode = -1; // Start with rcode set to -1 to indicate no RCode label. } } } } else { if( me->indexIdx < (int) kRCodeTestMaxIndexArgumentIndex ) { ++me->indexIdx; } else { me->indexIdx = 0; // When using Index labels to limit responses to one server, we want the other servers to respond // with bad RCODEs, so if the current RCODE value is good, try incrementing again. do { if( me->rcode < kRCodeTestMaxRCodeValue ) { ++me->rcode; } else { me->done = true; } } while( !me->done && _RCodeTestRCodeIsGood( me->rcode ) ); } } exit: return( err ); } //=========================================================================================================================== static Boolean _RCodeTestRCodeIsGood( const int inRCode ) { return( ( inRCode == kDNSRCode_NoError ) || ( inRCode == kDNSRCode_NXDomain ) || ( inRCode == kDNSRCode_NotAuth ) ); } //=========================================================================================================================== // DNSQueryTestCmd //=========================================================================================================================== typedef uint32_t DNSQueryTestGAIOpts; #define kDNSQueryTestGAIOpts_None 0 #define kDNSQueryTestGAIOpts_WantIPv4 ( 1U << 0 ) #define kDNSQueryTestGAIOpts_WantIPv6 ( 1U << 1 ) typedef struct { unsigned int cnameCount; unsigned int addrCount; DNSQueryTestGAIOpts gaiOpts; } DNSQuerySubtestParams; const DNSQuerySubtestParams kDNSQuerySubtestParams[] = { { .cnameCount = 0, .addrCount = 1, .gaiOpts = kDNSQueryTestGAIOpts_WantIPv4 }, { .cnameCount = 0, .addrCount = 1, .gaiOpts = kDNSQueryTestGAIOpts_WantIPv6 }, { .cnameCount = 0, .addrCount = 1, .gaiOpts = kDNSQueryTestGAIOpts_WantIPv4 | kDNSQueryTestGAIOpts_WantIPv6 }, { .cnameCount = 3, .addrCount = 1, .gaiOpts = kDNSQueryTestGAIOpts_WantIPv4 }, { .cnameCount = 3, .addrCount = 1, .gaiOpts = kDNSQueryTestGAIOpts_WantIPv6 }, { .cnameCount = 3, .addrCount = 1, .gaiOpts = kDNSQueryTestGAIOpts_WantIPv4 | kDNSQueryTestGAIOpts_WantIPv6 }, { .cnameCount = 0, .addrCount = 3, .gaiOpts = kDNSQueryTestGAIOpts_WantIPv4 }, { .cnameCount = 0, .addrCount = 3, .gaiOpts = kDNSQueryTestGAIOpts_WantIPv6 }, { .cnameCount = 0, .addrCount = 3, .gaiOpts = kDNSQueryTestGAIOpts_WantIPv4 | kDNSQueryTestGAIOpts_WantIPv6 }, { .cnameCount = 3, .addrCount = 3, .gaiOpts = kDNSQueryTestGAIOpts_WantIPv4 }, { .cnameCount = 3, .addrCount = 3, .gaiOpts = kDNSQueryTestGAIOpts_WantIPv6 }, { .cnameCount = 3, .addrCount = 3, .gaiOpts = kDNSQueryTestGAIOpts_WantIPv4 | kDNSQueryTestGAIOpts_WantIPv6 } }; typedef struct DNSQueryTest * DNSQueryTestRef; struct DNSQueryTest { dispatch_queue_t queue; // Serial queue for test events. dispatch_semaphore_t doneSem; // Semaphore to signal when the test is done. dnssd_getaddrinfo_t gai; // Current subtest's GAI object. (Also used for probing test DNS server.) dispatch_source_t timer; // Timer for enforcing time limit on current dnssd_getaddrinfo. CFMutableDictionaryRef report; // Test's report, as a plist. CFMutableArrayRef subtestResults; // Pointer to report's subtest results. CFMutableArrayRef gaiResults; // All dnssd_getaddrinfo_result objects from the current GAI. CFMutableArrayRef unexpectedResults; // Unexpected dnssd_getaddrinfo_result from the current GAI. CFMutableArrayRef missingAddrs; // Address results missing from the current GAI. CFMutableArrayRef queryCountStats; // Stats regarding a subtests query counts. char * hostname; // Current subtest's hostname. (Also used for probing test DNS server.) char * domain; // High-level domain of current hostname. char * description; // Current subtest description. pcap_t * pcap; // Captures traffic between mDNSResponder and test DNS server. NanoTime64 startTime; // Current subtest's start time. size_t subtestIndex; // Index of the current subtest. pid_t serverPID; // PID of spawned test DNS server. uint32_t addressOffset; // Current subtest's address offset for hostname addresses. int32_t refCount; // Test's reference count. OSStatus error; // Current test error. int subtestCount; // Number of subtests that have completed or are in progress. int subtestPassCount; // Number of subtests that have passed so far. uint16_t serverPort; // Port number used by DNS server. Boolean haveBadQueryCounts; // True if the current subtest's query counts are incorrect. Boolean done; // True if all subtests have completed. }; ulog_define_ex( kDNSSDUtilIdentifier, DNSQueryTest, kLogLevelInfo, kLogFlags_None, "DNSQueryTest", NULL ); #define dqt_ulog( LEVEL, ... ) ulog( &log_category_from_name( DNSQueryTest ), (LEVEL), __VA_ARGS__ ) static OSStatus _DNSQueryTestCreate( DNSQueryTestRef *outTest ); static OSStatus _DNSQueryTestRun( DNSQueryTestRef inTest, Boolean *outPassed ); static void _DNSQueryTestRetain( DNSQueryTestRef inTest ); static void _DNSQueryTestRelease( DNSQueryTestRef inTest ); static void DNSQueryTestCmd( void ) { OSStatus err; OutputFormatType outputFormat; DNSQueryTestRef test = NULL; Boolean passed = false; err = CheckRootUser(); require_noerr_quiet( err, exit ); err = OutputFormatFromArgString( gDNSQueryTest_OutputFormat, &outputFormat ); require_noerr_quiet( err, exit ); err = _DNSQueryTestCreate( &test ); require_noerr( err, exit ); err = _DNSQueryTestRun( test, &passed ); require_noerr( err, exit ); err = OutputPropertyList( test->report, outputFormat, gDNSQueryTest_OutputFilePath ); require_noerr( err, exit ); exit: if( test ) _DNSQueryTestRelease( test ); gExitCode = err ? 1 : ( passed ? 0 : 2 ); } //=========================================================================================================================== static OSStatus _DNSQueryTestCreate( DNSQueryTestRef * const outTest ) { OSStatus err; DNSQueryTestRef obj; obj = (DNSQueryTestRef) calloc( 1, sizeof( *obj ) ); require_action( obj, exit, err = kNoMemoryErr ); obj->refCount = 1; obj->error = kInProgressErr; obj->serverPID = -1; obj->queue = dispatch_queue_create( "com.apple.dnssdutil.dns-query-test", DISPATCH_QUEUE_SERIAL ); require_action( obj->queue, exit, err = kNoResourcesErr ); obj->doneSem = dispatch_semaphore_create( 0 ); require_action( obj->doneSem, exit, err = kNoResourcesErr ); obj->report = CFDictionaryCreateMutable( NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks ); require_action( obj->report, exit, err = kNoMemoryErr ); *outTest = obj; obj = NULL; err = kNoErr; exit: if( obj ) _DNSQueryTestRelease( obj ); return( err ); } //=========================================================================================================================== static void _DNSQueryTestStart( void *inCtx ); static void _DNSQueryTestStop( DNSQueryTestRef inTest, OSStatus inError ); static OSStatus _DNSQueryTestStartSubtest( DNSQueryTestRef inTest ); static OSStatus _DNSQueryTestContinue( DNSQueryTestRef inType, OSStatus inSubtestError ); static OSStatus _DNSQueryTestRun( const DNSQueryTestRef me, Boolean * const outPassed ) { Boolean passed; dispatch_async_f( me->queue, me, _DNSQueryTestStart ); dispatch_semaphore_wait( me->doneSem, DISPATCH_TIME_FOREVER ); passed = ( !me->error && ( me->subtestPassCount == me->subtestCount ) ) ? true : false; CFDictionarySetBoolean( me->report, CFSTR( "pass" ), passed ); dqt_ulog( kLogLevelInfo, "Test result: %s\n", passed ? "pass" : "fail" ); if( outPassed ) *outPassed = passed; return( me->error ); } //=========================================================================================================================== static void _DNSQueryTestRetain( const DNSQueryTestRef me ) { atomic_add_32( &me->refCount, 1 ); } //=========================================================================================================================== static void _DNSQueryTestRelease( const DNSQueryTestRef me ) { if( atomic_add_and_fetch_32( &me->refCount, -1 ) == 0 ) { check( !me->gai ); check( !me->timer ); check( !me->subtestResults ); check( !me->gaiResults ); check( !me->unexpectedResults ); check( !me->missingAddrs ); check( !me->queryCountStats ); check( !me->hostname ); check( !me->domain ); check( !me->description ); check( !me->pcap ); check( me->serverPID < 0 ); dispatch_forget( &me->queue ); dispatch_forget( &me->doneSem ); ForgetCF( &me->report ); free( me ); } } //=========================================================================================================================== #define kDNSQueryTestProbeQueryTimeoutSecs 5 static void _DNSQueryTestHandleGAIProbeResults( DNSQueryTestRef inTest, dnssd_getaddrinfo_result_t * inResults, size_t inCount ); static void _DNSQueryTestProbeQueryTimerHandler( void *inCtx ); static void _DNSQueryTestSubtestCleanup( DNSQueryTestRef test ); static void _DNSQueryTestStart( void * const inCtx ) { OSStatus err; const DNSQueryTestRef me = (DNSQueryTestRef) inCtx; char * serverCmd = NULL; dnssd_getaddrinfo_t gai; NanoTime64 startTime; char startTimeStr[ 32 ]; char tag[ 6 + 1 ]; startTime = NanoTimeGetCurrent(); dqt_ulog( kLogLevelInfo, "Starting test\n" ); serverCmd = NULL; #if( DNSSDUTIL_TEST_USE_ALTERNATE_SERVER_PORT_FOR_DO53 ) me->serverPort = kDNSPort_Do53Alt; #else me->serverPort = kDNSPort_Do53; #endif ASPrintF( &serverCmd, "dnssdutil server --loopback --follow %lld --port %u --responseDelay 10", (int64_t) getpid(), me->serverPort ); require_action_quiet( serverCmd, exit, err = kNoMemoryErr ); err = _SpawnCommand( &me->serverPID, "/dev/null", "/dev/null", "%s", serverCmd ); require_noerr( err, exit ); check( !me->hostname ); ASPrintF( &me->hostname, "tag-dns-query-test-probe-%s.ipv4.d.test.", _RandomStringExact( kLowerAlphaNumericCharSet, kLowerAlphaNumericCharSetSize, sizeof( tag ) - 1, tag ) ); require_action( me->hostname, exit, err = kNoMemoryErr ); check( !me->gai ); me->gai = dnssd_getaddrinfo_create(); require_action( me->gai, exit, err = kNoResourcesErr ); dnssd_getaddrinfo_set_hostname( me->gai, me->hostname ); dnssd_getaddrinfo_set_flags( me->gai, 0 ); dnssd_getaddrinfo_set_interface_index( me->gai, kDNSServiceInterfaceIndexAny ); dnssd_getaddrinfo_set_protocols( me->gai, kDNSServiceProtocol_IPv4 ); dnssd_getaddrinfo_set_queue( me->gai, me->queue ); gai = me->gai; dnssd_retain( gai ); _DNSQueryTestRetain( me ); dnssd_getaddrinfo_set_result_handler( me->gai, ^( dnssd_getaddrinfo_result_t * const inResults, const size_t inCount ) { require_return( me->gai == gai ); _DNSQueryTestHandleGAIProbeResults( me, inResults, inCount ); } ); dnssd_getaddrinfo_set_event_handler( me->gai, ^( const dnssd_event_t inEvent, const DNSServiceErrorType inGAIError ) { if( inEvent == dnssd_event_invalidated ) { dnssd_release( gai ); _DNSQueryTestRelease( me ); } else if( inEvent == dnssd_event_error ) { require_return( me->gai == gai ); dqt_ulog( kLogLevelError, "dnssd_getaddrinfo error: %#m\n", inGAIError ); _DNSQueryTestStop( me, inGAIError ); } } ); dnssd_getaddrinfo_activate( me->gai ); check( !me->timer ); err = DispatchTimerOneShotCreate( dispatch_time_seconds( kDNSQueryTestProbeQueryTimeoutSecs ), kDNSQueryTestProbeQueryTimeoutSecs * ( UINT64_C_safe( kNanosecondsPerSecond ) / 10 ), me->queue, _DNSQueryTestProbeQueryTimerHandler, me, &me->timer ); require_noerr( err, exit ); dispatch_resume( me->timer ); _NanoTime64ToTimestamp( startTime, startTimeStr, sizeof( startTimeStr ) ); err = CFPropertyListAppendFormatted( kCFAllocatorDefault, me->report, "%kO=%s" // startTime "%kO=%s" // serverCmd "%kO=%s" // probeHostname "%kO=[%@]", // results CFSTR( "startTime" ), startTimeStr, CFSTR( "serverCmd" ), serverCmd, CFSTR( "probeHostname" ), me->hostname, CFSTR( "results" ), &me->subtestResults ); require_noerr( err, exit ); exit: FreeNullSafe( serverCmd ); if( err ) _DNSQueryTestStop( me, err ); } static void _DNSQueryTestHandleGAIProbeResults( const DNSQueryTestRef me, dnssd_getaddrinfo_result_t * const inResults, const size_t inCount ) { size_t i; Boolean startSubtests = false; for( i = 0; i < inCount; ++i ) { const dnssd_getaddrinfo_result_t result = inResults[ i ]; if( dnssd_getaddrinfo_result_get_type( result ) == dnssd_getaddrinfo_result_type_add ) { dqt_ulog( kLogLevelInfo, "Probe GAI got %##a for %s\n", dnssd_getaddrinfo_result_get_address( result ), me->hostname ); startSubtests = true; break; } } if( startSubtests ) { OSStatus err; _DNSQueryTestSubtestCleanup( me ); err = _DNSQueryTestStartSubtest( me ); if( err ) _DNSQueryTestStop( me, err ); } } static void _DNSQueryTestProbeQueryTimerHandler( void * const inCtx ) { const DNSQueryTestRef me = (DNSQueryTestRef) inCtx; dqt_ulog( kLogLevelInfo, "Probe GAI request for '%s' timed out.\n", me->hostname ); _DNSQueryTestStop( me, kNotPreparedErr ); } //=========================================================================================================================== static void _DNSQueryTestStop( const DNSQueryTestRef me, const OSStatus inError ) { OSStatus err; NanoTime64 endTime; char endTimeStr[ 32 ]; endTime = NanoTimeGetCurrent(); me->error = inError; dqt_ulog( kLogLevelInfo, "Stopping test with error: %#m\n", me->error ); _DNSQueryTestSubtestCleanup( me ); me->subtestResults = NULL; if( me->serverPID >= 0 ) { OSStatus killErr; killErr = kill( me->serverPID, SIGTERM ); killErr = map_global_noerr_errno( killErr ); check_noerr( killErr ); me->serverPID = -1; } _NanoTime64ToTimestamp( endTime, endTimeStr, sizeof( endTimeStr ) ); err = CFPropertyListAppendFormatted( kCFAllocatorDefault, me->report, "%kO=%s" // endTime "%kO=%i" // subtestCount "%kO=%i", // subtestPassCount CFSTR( "endTime" ), endTimeStr, CFSTR( "subtestCount" ), me->subtestCount, CFSTR( "subtestPassCount" ), me->subtestPassCount ); check_noerr( err ); if( err && !me->error ) me->error = err; dispatch_semaphore_signal( me->doneSem ); } //=========================================================================================================================== typedef struct { size_t queryCountA; size_t queryCountAAAA; size_t queryCountOther; } DNSQueryPCapStats; static void _DNSQuerySubtestHandleGAIResults( DNSQueryTestRef inTest, dnssd_getaddrinfo_result_t * inResults, size_t inCount ); static void _DNSQuerySubtestTimerHandler( void *inCtx ); static OSStatus _DNSQuerySubtestProcessResults( DNSQueryTestRef inTest ); static OSStatus _DNSQueryTestProcessPacketCapture( pcap_t *inPCap, const char *inDomain, DNSQueryPCapStats *outStats ); #define kDNSQuerySubtestTimeSecs 5 static OSStatus _DNSQueryTestStartSubtest( const DNSQueryTestRef me ) { OSStatus err; dnssd_getaddrinfo_t gai; const DNSQuerySubtestParams * const params = &kDNSQuerySubtestParams[ me->subtestIndex ]; DNSServiceProtocol protocols; char aliasLabelStr[ 32 ]; char tag[ 6 + 1 ]; const Boolean wantIPv4 = ( params->gaiOpts & kDNSQueryTestGAIOpts_WantIPv4 ) != 0; const Boolean wantIPv6 = ( params->gaiOpts & kDNSQueryTestGAIOpts_WantIPv6 ) != 0; require_action_quiet( !me->done, exit, err = kInternalErr ); check( !me->gai ); check( !me->timer ); me->startTime = NanoTimeGetCurrent(); switch( params->cnameCount ) { case 0: aliasLabelStr[ 0 ] = '\0'; break; case 1: SNPrintF( aliasLabelStr, sizeof( aliasLabelStr ), "alias." ); break; default: SNPrintF( aliasLabelStr, sizeof( aliasLabelStr ), "alias-%u.", params->cnameCount ); break; } check( !me->domain ); ASPrintF( &me->domain, "tag-dns-query-test-%s.d.test.", _RandomStringExact( kLowerAlphaNumericCharSet, kLowerAlphaNumericCharSetSize, sizeof( tag ) - 1, tag ) ); require_action( me->domain, exit, err = kNoMemoryErr ); me->addressOffset = RandomRange( 0, 255 ); check( !me->hostname ); ASPrintF( &me->hostname, "%scount-%u.offset-%u.pdelay-100.%s", aliasLabelStr, params->addrCount, me->addressOffset, me->domain ); require_action( me->hostname, exit, err = kNoMemoryErr ); check( !me->gaiResults ); me->gaiResults = CFArrayCreateMutable( NULL, 0, &kDNSSDObjectArrayCallbacks ); require_action( me->gaiResults, exit, err = kNoMemoryErr ); check( !me->gai ); me->gai = dnssd_getaddrinfo_create(); require_action( me->gai, exit, err = kNoResourcesErr ); dnssd_getaddrinfo_set_hostname( me->gai, me->hostname ); dnssd_getaddrinfo_set_flags( me->gai, kDNSServiceFlagsReturnIntermediates ); dnssd_getaddrinfo_set_interface_index( me->gai, kDNSServiceInterfaceIndexAny ); protocols = 0; if( params->gaiOpts & kDNSQueryTestGAIOpts_WantIPv4 ) protocols |= kDNSServiceProtocol_IPv4; if( params->gaiOpts & kDNSQueryTestGAIOpts_WantIPv6 ) protocols |= kDNSServiceProtocol_IPv6; dnssd_getaddrinfo_set_protocols( me->gai, protocols ); dnssd_getaddrinfo_set_queue( me->gai, me->queue ); gai = me->gai; dnssd_retain( gai ); _DNSQueryTestRetain( me ); dnssd_getaddrinfo_set_result_handler( me->gai, ^( dnssd_getaddrinfo_result_t * const inResults, const size_t inCount ) { require_return( me->gai == gai ); _DNSQuerySubtestHandleGAIResults( me, inResults, inCount ); } ); dnssd_getaddrinfo_set_event_handler( gai, ^( const dnssd_event_t inEvent, const DNSServiceErrorType inGAIError ) { if( inEvent == dnssd_event_invalidated ) { dnssd_release( gai ); _DNSQueryTestRelease( me ); } else if( inEvent == dnssd_event_error ) { OSStatus testErr; require_return( me->gai == gai ); dqt_ulog( kLogLevelError, "dnssd_getaddrinfo error: %#m\n", inGAIError ); testErr = _DNSQueryTestContinue( me, inGAIError ); if( testErr || me->done ) _DNSQueryTestStop( me, testErr ); } } ); check( !me->pcap ); err = _GAITesterCreatePacketCapture( me->serverPort, &me->pcap ); require_noerr( err, exit ); check( !me->timer ); err = DispatchTimerOneShotCreate( dispatch_time_seconds( kDNSQuerySubtestTimeSecs ), kDNSQuerySubtestTimeSecs * ( UINT64_C_safe( kNanosecondsPerSecond ) / 20 ), me->queue, _DNSQuerySubtestTimerHandler, me, &me->timer ); require_noerr( err, exit ); check( !me->description ); ASPrintF( &me->description, "%s%s%s GAI request for hostname with %u CNAME records and %u address records", wantIPv4 ? "IPv4" : "", ( wantIPv4 && wantIPv6 ) ? "+" : "", wantIPv6 ? "IPv6" : "", params->cnameCount, params->addrCount ); require_action( me->description, exit, err = kNoMemoryErr ); ++me->subtestCount; dqt_ulog( kLogLevelInfo, "Starting subtest #%d: %s\n", me->subtestCount, me->description ); dnssd_getaddrinfo_activate( me->gai ); dispatch_resume( me->timer ); exit: return( err ); } static void _DNSQuerySubtestHandleGAIResults( const DNSQueryTestRef me, dnssd_getaddrinfo_result_t * const inResults, const size_t inCount ) { size_t i; for( i = 0; i < inCount; ++i ) { const dnssd_getaddrinfo_result_t result = inResults[ i ]; dqt_ulog( kLogLevelInfo, "GAI result -- %@\n", result ); CFArrayAppendValue( me->gaiResults, result ); } } static void _DNSQuerySubtestTimerHandler( void * const inCtx ) { OSStatus err; const DNSQueryTestRef me = (DNSQueryTestRef) inCtx; err = _DNSQuerySubtestProcessResults( me ); require_noerr( err, exit ); err = _DNSQueryTestContinue( me, kNoErr ); require_noerr( err, exit ); exit: if( err || me->done ) _DNSQueryTestStop( me, err ); } static OSStatus _DNSQuerySubtestProcessResults( const DNSQueryTestRef me ) { OSStatus err; DNSQueryPCapStats stats; const DNSQuerySubtestParams * const params = &kDNSQuerySubtestParams[ me->subtestIndex ]; uint64_t addrBitmapV4, addrBitmapV6; CFIndex i, n; unsigned int expectedQueryCountA, expectedQueryCountAAAA, j; const Boolean wantIPv4 = ( params->gaiOpts & kDNSQueryTestGAIOpts_WantIPv4 ) != 0; const Boolean wantIPv6 = ( params->gaiOpts & kDNSQueryTestGAIOpts_WantIPv6 ) != 0; err = _DNSQueryTestProcessPacketCapture( me->pcap, me->domain, &stats ); ForgetPacketCapture( &me->pcap ); require_noerr( err, exit ); expectedQueryCountA = wantIPv4 ? 1 : 0; expectedQueryCountAAAA = wantIPv6 ? 1 : 0; me->haveBadQueryCounts = ( stats.queryCountA != expectedQueryCountA ) || ( stats.queryCountAAAA != expectedQueryCountAAAA ) || ( stats.queryCountOther != 0 ); check( !me->queryCountStats ); err = CFPropertyListCreateFormatted( kCFAllocatorDefault, &me->queryCountStats, "{" "%kO=%lli" // expectedQueryCountA "%kO=%lli" // expectedQueryCountAAAA "%kO=%lli" // expectedQueryCountOther "%kO=%lli" // actualQueryCountA "%kO=%lli" // actualQueryCountAAAA "%kO=%lli" // actualQueryCountOther "}", CFSTR( "expectedQueryCountA" ), (int64_t) expectedQueryCountA, CFSTR( "expectedQueryCountAAAA" ), (int64_t) expectedQueryCountAAAA, CFSTR( "expectedQueryCountOther" ), (int64_t) 0, CFSTR( "actualQueryCountA" ), (int64_t) stats.queryCountA, CFSTR( "actualQueryCountAAAA" ), (int64_t) stats.queryCountAAAA, CFSTR( "actualQueryCountOther" ), (int64_t) stats.queryCountOther ); require_noerr( err, exit ); require_noerr( err, exit ); check( ( params->addrCount >= 0 ) && ( params->addrCount <= 64 ) ); addrBitmapV4 = 0; if( wantIPv4 ) { if( params->addrCount < 64 ) addrBitmapV4 = ( UINT64_C( 1 ) << params->addrCount ) - 1; else addrBitmapV4 = ~UINT64_C( 0 ); } addrBitmapV6 = 0; if( wantIPv6 ) { if( params->addrCount < 64 ) addrBitmapV6 = ( UINT64_C( 1 ) << params->addrCount ) - 1; else addrBitmapV6 = ~UINT64_C( 0 ); } n = CFArrayGetCount( me->gaiResults ); for( i = 0; i < n; ++i ) { dnssd_getaddrinfo_result_t result; const sockaddr_ip * sip; dnssd_getaddrinfo_result_type_t resultType; Boolean validResult = false; result = (dnssd_getaddrinfo_result_t) CFArrayGetValueAtIndex( me->gaiResults, i ); resultType = dnssd_getaddrinfo_result_get_type( result ); if( resultType == dnssd_getaddrinfo_result_type_add ) { uint64_t * bitmapPtr = NULL; uint32_t addrValue = 0; sip = (const sockaddr_ip *) dnssd_getaddrinfo_result_get_address( result ); switch( sip->sa.sa_family ) { case AF_INET: if( params->gaiOpts & kDNSQueryTestGAIOpts_WantIPv4 ) { const uint32_t addr = ntohl( sip->v4.sin_addr.s_addr ); if( ( addr & UINT32_C( 0xFFFFFF00 ) ) == kDNSServerBaseAddrV4 ) { addrValue = addr & 0xFFU; bitmapPtr = &addrBitmapV4; } } break; case AF_INET6: if( params->gaiOpts & kDNSQueryTestGAIOpts_WantIPv6 ) { const uint8_t * const addr = sip->v6.sin6_addr.s6_addr; if( memcmp( addr, kDNSServerBaseAddrV6, 15 ) == 0 ) { addrValue = addr[ 15 ]; bitmapPtr = &addrBitmapV6; } } break; } if( bitmapPtr ) { addrValue = ( addrValue - me->addressOffset ) % 256; if( ( addrValue >= 1 ) && ( addrValue <= params->addrCount ) ) { const uint64_t bitmask = UINT64_C( 1 ) << ( addrValue - 1 ); if( *bitmapPtr & bitmask ) { *bitmapPtr &= ~bitmask; validResult = true; } } } } if( !validResult ) { if( !me->unexpectedResults ) { me->unexpectedResults = CFArrayCreateMutable( NULL, 0, &kDNSSDObjectArrayCallbacks ); require_action( me->unexpectedResults, exit, err = kNoMemoryErr ); } CFArrayAppendValue( me->unexpectedResults, result ); } } if( ( addrBitmapV4 != 0 ) || ( addrBitmapV6 != 0 ) ) { check( !me->missingAddrs ); me->missingAddrs = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks ); require_action( me->missingAddrs, exit, err = kNoMemoryErr ); } for( j = 1; ( j <= 64 ) && ( addrBitmapV4 != 0 ); ++j ) { const uint64_t bitmask = UINT64_C( 1 ) << ( j - 1 ); if( addrBitmapV4 & bitmask ) { uint8_t missingAddr[ 4 ]; addrBitmapV4 &= ~bitmask; WriteBig32Typed( missingAddr, kDNSServerBaseAddrV4 ); missingAddr[ 3 ] = (uint8_t)( ( me->addressOffset + j ) % 256 ); err = CFPropertyListAppendFormatted( kCFAllocatorDefault, me->missingAddrs, "%.4a", missingAddr ); require_noerr( err, exit ); } } for( j = 1; ( j <= 64 ) && ( addrBitmapV6 != 0 ); ++j ) { const uint64_t bitmask = UINT64_C( 1 ) << ( j - 1 ); if( addrBitmapV6 & bitmask ) { uint8_t missingAddr[ 16 ]; addrBitmapV6 &= ~bitmask; memcpy( missingAddr, kDNSServerBaseAddrV6, 16 ); missingAddr[ 15 ] = (uint8_t)( ( me->addressOffset + j ) % 256 ); err = CFPropertyListAppendFormatted( kCFAllocatorDefault, me->missingAddrs, "%.16a", missingAddr ); require_noerr( err, exit ); } } err = kNoErr; exit: return( err ); } static OSStatus _DNSQueryTestProcessPacketCapture( pcap_t * const inPCap, const char * const inDomain, DNSQueryPCapStats * const outStats ) { OSStatus err; int domainLabelCount; uint8_t domain[ kDomainNameLengthMax ]; err = DomainNameFromString( domain, inDomain, NULL ); require_noerr( err, exit ); domainLabelCount = DomainNameLabelCount( domain ); if( outStats ) memset( outStats, 0, sizeof( *outStats ) ); for( ;; ) { int status; struct pcap_pkthdr * pktHdr; const uint8_t * packet; const uint8_t * msgPtr; size_t msgLen; const DNSHeader * hdr; unsigned int flags; int labelDiff; const uint8_t * questionSection; const uint8_t * qnameAncestor; uint16_t qtype, qclass; uint8_t qname[ kDomainNameLengthMax ]; status = pcap_next_ex( inPCap, &pktHdr, &packet ); if( status != 1 ) break; if( _GAITesterGetDNSMessageFromPacket( packet, pktHdr->caplen, &msgPtr, &msgLen ) != kNoErr ) continue; if( msgLen < kDNSHeaderLength ) continue; hdr = (const DNSHeader *) msgPtr; flags = DNSHeaderGetFlags( hdr ); if( DNSFlagsGetOpCode( flags ) != kDNSOpCode_Query ) continue; if( DNSHeaderGetQuestionCount( hdr ) < 1 ) continue; questionSection = (const uint8_t *) &hdr[ 1 ]; if( DNSMessageExtractQuestion( msgPtr, msgLen, questionSection, qname, &qtype, &qclass, NULL ) != kNoErr ) continue; if( qclass != kDNSServiceClass_IN ) continue; labelDiff = DomainNameLabelCount( qname ) - domainLabelCount; if( labelDiff < 0 ) continue; qnameAncestor = qname; while( labelDiff-- > 0 ) { qnameAncestor = DomainNameGetNextLabel( qnameAncestor ); } if( !DomainNameEqual( qnameAncestor, domain ) ) continue; if( outStats && ( flags & kDNSHeaderFlag_Response ) ) { switch( qtype ) { case kDNSRecordType_A: ++outStats->queryCountA; break; case kDNSRecordType_AAAA: ++outStats->queryCountAAAA; break; default: ++outStats->queryCountOther; break; } } } exit: return( err ); } //=========================================================================================================================== static OSStatus _DNSQueryTestStopSubtest( DNSQueryTestRef inTest, OSStatus inError ); static OSStatus _DNSQueryTestContinue( const DNSQueryTestRef me, const OSStatus inSubtestError ) { OSStatus err; require_action_quiet( !me->done, exit, err = kNoErr ); err = _DNSQueryTestStopSubtest( me, inSubtestError ); require_noerr( err, exit ); require_action_quiet( !me->done, exit, err = kNoErr ); err = _DNSQueryTestStartSubtest( me ); require_noerr( err, exit ); exit: return( err ); } //=========================================================================================================================== static void _DNSQueryTestSubtestCleanup( const DNSQueryTestRef me ) { dnssd_getaddrinfo_forget( &me->gai ); dispatch_source_forget( &me->timer ); CFForget( &me->gaiResults ); CFForget( &me->unexpectedResults ); CFForget( &me->missingAddrs ); CFForget( &me->queryCountStats ); ForgetMem( &me->hostname ); ForgetMem( &me->domain ); ForgetMem( &me->description ); ForgetPacketCapture( &me->pcap ); me->haveBadQueryCounts = false; } static OSStatus _DNSQueryTestStopSubtest( const DNSQueryTestRef me, const OSStatus inError ) { OSStatus err, subtestErr; NanoTime64 endTime; CFMutableArrayRef gaiResultStrings, unexpectedResultStrings; CFIndex i, n; char errorStr[ 128 ]; char startTimeStr[ 32 ]; char endTimeStr[ 32 ]; endTime = NanoTimeGetCurrent(); if( inError ) subtestErr = inError; else if( me->missingAddrs ) subtestErr = kAddressErr; else if( me->unexpectedResults ) subtestErr = kUnexpectedErr; else if( me->haveBadQueryCounts ) subtestErr = kCountErr; else subtestErr = kNoErr; if( !subtestErr ) ++me->subtestPassCount; dqt_ulog( kLogLevelInfo, "Subtest #%d result: %s (pass rate: %d/%d)\n", me->subtestCount, subtestErr ? "fail" : "pass", me->subtestPassCount, me->subtestCount ); _NanoTime64ToTimestamp( me->startTime, startTimeStr, sizeof( startTimeStr ) ); _NanoTime64ToTimestamp( endTime, endTimeStr, sizeof( endTimeStr ) ); SNPrintF( errorStr, sizeof( errorStr ), "%m", subtestErr ); err = CFPropertyListAppendFormatted( kCFAllocatorDefault, me->subtestResults, "{" "%kO=%s" // description "%kO=%s" // startTime "%kO=%s" // endTime "%kO=%s" // hostname "%kO=%b" // pass "%kO=[%@]" // GAIResults "%kO=%O" // queryCountStats "%kO=[%@]" // unexpectedResults "%kO=%O" // missingAddrs "%kO=" // error "{" "%kO=%lli" // code "%kO=%s" // description "}" "}", CFSTR( "description" ), me->description, CFSTR( "startTime" ), startTimeStr, CFSTR( "endTime" ), endTimeStr, CFSTR( "hostname" ), me->hostname, CFSTR( "pass" ), !subtestErr, CFSTR( "GAIResults" ), &gaiResultStrings, CFSTR( "queryCountStats" ), me->queryCountStats, CFSTR( "unexpectedResults" ), &unexpectedResultStrings, CFSTR( "missingAddrs" ), me->missingAddrs, CFSTR( "error" ), CFSTR( "code" ), (int64_t) subtestErr, CFSTR( "description" ), errorStr ); require_noerr( err, exit ); n = CFArrayGetCount( me->gaiResults ); for( i = 0; i < n; ++ i ) { CFStringRef resultStr; resultStr = CFStringCreateF( &err, "%@", CFArrayGetValueAtIndex( me->gaiResults, i ) ); require( resultStr, exit ); CFArrayAppendValue( gaiResultStrings, resultStr ); CFForget( &resultStr ); } n = CFArrayGetCountNullSafe( me->unexpectedResults ); for( i = 0; i < n; ++ i ) { CFStringRef resultStr; resultStr = CFStringCreateF( &err, "%@", CFArrayGetValueAtIndex( me->unexpectedResults, i ) ); require( resultStr, exit ); CFArrayAppendValue( unexpectedResultStrings, resultStr ); CFForget( &resultStr ); } if( me->subtestIndex < ( countof( kDNSQuerySubtestParams ) - 1 ) ) { ++me->subtestIndex; } else { me->done = true; } exit: _DNSQueryTestSubtestCleanup( me ); return( err ); } //=========================================================================================================================== // FastRecoveryTestCmd //=========================================================================================================================== typedef struct { dnssd_getaddrinfo_t gai; // GAI object. char * hostname; // Hostname to be resolved. Boolean gotResultIPv4; // True if the IPv4 GAI result was received. Boolean gotResultIPv6; // True if the IPv6 GAI result was received. } FastRecoveryGAI; typedef struct FastRecoveryTest * FastRecoveryTestRef; struct FastRecoveryTest { dispatch_queue_t queue; // Serial queue for test events. dispatch_semaphore_t doneSem; // Semaphore to signal when the test is done. dnssd_getaddrinfo_t gai; // GAI object for probing/suspending and resuming the test DNS server. FastRecoveryGAI gaiArray[ 5 ]; // Array of regular GAI operations to test fast recovery. size_t gaiCount; // Number of regular GAI operations that are in progress. dispatch_source_t timer; // Timer for enforcing time limit on current dnssd_getaddrinfo. CFMutableDictionaryRef report; // Test's report, as a plist. CFMutableArrayRef gaiResults; // Pointer to report's GAI results. char * domain; // High-level domain for test's hostnames. char * probeQNAME; // QNAME used in probe query for test DNS server. char * resumeQNAME; // QNAME used in query to resume the test DNS server. NanoTime64 startTime; // Current subtest's start time. pid_t serverPID; // PID of spawned test DNS server. int32_t refCount; // Test's reference count. OSStatus error; // Current test error. Boolean suspended; // True if the test DNS server is currently suspended. Boolean done; // True if all subtests have completed. }; ulog_define_ex( kDNSSDUtilIdentifier, FastRecoveryTest, kLogLevelInfo, kLogFlags_None, "FastRecoveryTest", NULL ); #define frt_ulog( LEVEL, ... ) ulog( &log_category_from_name( FastRecoveryTest ), (LEVEL), __VA_ARGS__ ) static OSStatus _FastRecoveryTestCreate( FastRecoveryTestRef *outTest ); static OSStatus _FastRecoveryTestRun( FastRecoveryTestRef inTest, Boolean *outPassed ); static void _FastRecoveryTestRetain( FastRecoveryTestRef inTest ); static void _FastRecoveryTestRelease( FastRecoveryTestRef inTest ); static void FastRecoveryTestCmd( void ) { OSStatus err; OutputFormatType outputFormat; FastRecoveryTestRef test = NULL; Boolean passed = false; err = OutputFormatFromArgString( gFastRecoveryTest_OutputFormat, &outputFormat ); require_noerr_quiet( err, exit ); err = _FastRecoveryTestCreate( &test ); require_noerr( err, exit ); err = _FastRecoveryTestRun( test, &passed ); require_noerr( err, exit ); err = OutputPropertyList( test->report, outputFormat, gFastRecoveryTest_OutputFilePath ); require_noerr( err, exit ); exit: if( test ) _FastRecoveryTestRelease( test ); gExitCode = err ? 1 : ( passed ? 0 : 2 ); } //=========================================================================================================================== static OSStatus _FastRecoveryTestCreate( FastRecoveryTestRef * const outTest ) { OSStatus err; FastRecoveryTestRef obj; obj = (FastRecoveryTestRef) calloc( 1, sizeof( *obj ) ); require_action( obj, exit, err = kNoMemoryErr ); obj->refCount = 1; obj->error = kInProgressErr; obj->serverPID = -1; obj->queue = dispatch_queue_create( "com.apple.dnssdutil.fast-recovery-test", DISPATCH_QUEUE_SERIAL ); require_action( obj->queue, exit, err = kNoResourcesErr ); obj->doneSem = dispatch_semaphore_create( 0 ); require_action( obj->doneSem, exit, err = kNoResourcesErr ); obj->report = CFDictionaryCreateMutable( NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks ); require_action( obj->report, exit, err = kNoMemoryErr ); *outTest = obj; obj = NULL; err = kNoErr; exit: if( obj ) _FastRecoveryTestRelease( obj ); return( err ); } //=========================================================================================================================== static void _FastRecoveryTestStart( void *inCtx ); static void _FastRecoveryTestStop( FastRecoveryTestRef inTest, OSStatus inError ); static OSStatus _FastRecoveryTestRun( const FastRecoveryTestRef me, Boolean * const outPassed ) { Boolean passed; dispatch_async_f( me->queue, me, _FastRecoveryTestStart ); dispatch_semaphore_wait( me->doneSem, DISPATCH_TIME_FOREVER ); passed = !me->error; CFDictionarySetBoolean( me->report, CFSTR( "pass" ), passed ); frt_ulog( kLogLevelInfo, "Test result: %s\n", passed ? "pass" : "fail" ); if( outPassed ) *outPassed = passed; return( me->error ); } //=========================================================================================================================== static void _FastRecoveryTestRetain( const FastRecoveryTestRef me ) { atomic_add_32( &me->refCount, 1 ); } //=========================================================================================================================== static void _FastRecoveryTestRelease( const FastRecoveryTestRef me ) { if( atomic_add_and_fetch_32( &me->refCount, -1 ) == 0 ) { check( !me->gai ); check( !me->timer ); check( !me->gaiResults ); check( !me->domain ); check( !me->probeQNAME ); check( !me->resumeQNAME ); check( me->serverPID < 0 ); dispatch_forget( &me->queue ); dispatch_forget( &me->doneSem ); ForgetCF( &me->report ); free( me ); } } //=========================================================================================================================== #define kFastRecoveryTestProbeQueryTimeoutSecs 5 static void _FastRecoveryTestHandleGAIProbeResults( FastRecoveryTestRef inTest, dnssd_getaddrinfo_result_t * inResults, size_t inCount ); static void _FastRecoveryTestProbeQueryTimerHandler( void *inCtx ); static OSStatus _FastRecoveryTestStartGAIRequests( FastRecoveryTestRef inTest ); static void _FastRecoveryTestStart( void * const inCtx ) { OSStatus err; const FastRecoveryTestRef me = (FastRecoveryTestRef) inCtx; char * serverCmd = NULL; dnssd_getaddrinfo_t gai; NanoTime64 startTime; char startTimeStr[ 32 ]; char tag[ 6 + 1 ]; startTime = NanoTimeGetCurrent(); frt_ulog( kLogLevelInfo, "Starting test\n" ); serverCmd = NULL; ASPrintF( &serverCmd, DNSSDUTIL_TEST_DNS_SERVER_COMMAND_PREAMBLE " --loopback --follow %lld --responseDelay 10", (int64_t) getpid() ); require_action_quiet( serverCmd, exit, err = kNoMemoryErr ); err = _SpawnCommand( &me->serverPID, "/dev/null", "/dev/null", "%s", serverCmd ); require_noerr( err, exit ); check( !me->domain ); ASPrintF( &me->domain, "tag-fast-recovery-test-%s.d.test.", _RandomStringExact( kLowerAlphaNumericCharSet, kLowerAlphaNumericCharSetSize, sizeof( tag ) - 1, tag ) ); require_action( me->domain, exit, err = kNoMemoryErr ); check( !me->probeQNAME ); ASPrintF( &me->probeQNAME, "tag-fast-recovery-test-probe.ttl-300.command-suspend.%s", me->domain ); require_action( me->probeQNAME, exit, err = kNoMemoryErr ); check( !me->gai ); me->gai = dnssd_getaddrinfo_create(); require_action( me->gai, exit, err = kNoResourcesErr ); dnssd_getaddrinfo_set_hostname( me->gai, me->probeQNAME ); dnssd_getaddrinfo_set_flags( me->gai, 0 ); dnssd_getaddrinfo_set_interface_index( me->gai, kDNSServiceInterfaceIndexAny ); dnssd_getaddrinfo_set_protocols( me->gai, kDNSServiceProtocol_IPv4 ); dnssd_getaddrinfo_set_queue( me->gai, me->queue ); gai = me->gai; dnssd_retain( gai ); _FastRecoveryTestRetain( me ); dnssd_getaddrinfo_set_result_handler( me->gai, ^( dnssd_getaddrinfo_result_t * const inResults, const size_t inCount ) { require_return( me->gai == gai ); _FastRecoveryTestHandleGAIProbeResults( me, inResults, inCount ); } ); dnssd_getaddrinfo_set_event_handler( me->gai, ^( const dnssd_event_t inEvent, const DNSServiceErrorType inGAIError ) { if( inEvent == dnssd_event_invalidated ) { dnssd_release( gai ); _FastRecoveryTestRelease( me ); } else if( inEvent == dnssd_event_error ) { require_return( me->gai == gai ); frt_ulog( kLogLevelError, "dnssd_getaddrinfo error: %#m\n", inGAIError ); _FastRecoveryTestStop( me, inGAIError ); } } ); dnssd_getaddrinfo_activate( me->gai ); check( !me->timer ); err = DispatchTimerOneShotCreate( dispatch_time_seconds( kFastRecoveryTestProbeQueryTimeoutSecs ), kFastRecoveryTestProbeQueryTimeoutSecs * ( UINT64_C_safe( kNanosecondsPerSecond ) / 10 ), me->queue, _FastRecoveryTestProbeQueryTimerHandler, me, &me->timer ); require_noerr( err, exit ); dispatch_resume( me->timer ); _NanoTime64ToTimestamp( startTime, startTimeStr, sizeof( startTimeStr ) ); err = CFPropertyListAppendFormatted( NULL, me->report, "%kO=%s" // startTime "%kO=%s" // serverCmd "%kO=%s" // probeQNAME "%kO=[%@]", // results CFSTR( "startTime" ), startTimeStr, CFSTR( "serverCmd" ), serverCmd, CFSTR( "probeQNAME" ), me->probeQNAME, CFSTR( "results" ), &me->gaiResults ); require_noerr( err, exit ); exit: FreeNullSafe( serverCmd ); if( err ) _FastRecoveryTestStop( me, err ); } static void _FastRecoveryTestHandleGAIProbeResults( const FastRecoveryTestRef me, dnssd_getaddrinfo_result_t * const inResults, const size_t inCount ) { size_t i; Boolean startGAIRequests = false; for( i = 0; i < inCount; ++i ) { const dnssd_getaddrinfo_result_t result = inResults[ i ]; if( dnssd_getaddrinfo_result_get_type( result ) == dnssd_getaddrinfo_result_type_add ) { frt_ulog( kLogLevelInfo, "Probe GAI got %##a for %s\n", dnssd_getaddrinfo_result_get_address( result ), me->probeQNAME ); startGAIRequests = true; break; } } if( startGAIRequests ) { OSStatus err; me->suspended = true; dispatch_source_forget( &me->timer ); dnssd_getaddrinfo_forget( &me->gai ); err = _FastRecoveryTestStartGAIRequests( me ); if( err ) _FastRecoveryTestStop( me, err ); } } static void _FastRecoveryTestProbeQueryTimerHandler( void * const inCtx ) { const FastRecoveryTestRef me = (FastRecoveryTestRef) inCtx; frt_ulog( kLogLevelInfo, "Probe GAI request for '%s' timed out.\n", me->probeQNAME ); _FastRecoveryTestStop( me, kNotPreparedErr ); } //=========================================================================================================================== static void _FastRecoveryTestStop( const FastRecoveryTestRef me, const OSStatus inError ) { OSStatus err; NanoTime64 endTime; size_t i; char endTimeStr[ 32 ]; endTime = NanoTimeGetCurrent(); me->error = inError; frt_ulog( kLogLevelInfo, "Stopping test with error: %#m\n", me->error ); dnssd_getaddrinfo_forget( &me->gai ); dispatch_source_forget( &me->timer ); for( i = 0; i < countof( me->gaiArray ); ++i ) { FastRecoveryGAI * const frGAI = &me->gaiArray[ i ]; ForgetMem( &frGAI->hostname ); dnssd_getaddrinfo_forget( &frGAI->gai ); } ForgetMem( &me->domain ); ForgetMem( &me->probeQNAME ); ForgetMem( &me->resumeQNAME ); me->gaiResults = NULL; if( me->serverPID >= 0 ) { OSStatus killErr; killErr = kill( me->serverPID, SIGTERM ); killErr = map_global_noerr_errno( killErr ); check_noerr( killErr ); me->serverPID = -1; } _NanoTime64ToTimestamp( endTime, endTimeStr, sizeof( endTimeStr ) ); err = CFPropertyListAppendFormatted( NULL, me->report, "%kO=%s", CFSTR( "endTime" ), endTimeStr ); check_noerr( err ); if( err && !me->error ) me->error = err; dispatch_semaphore_signal( me->doneSem ); } //=========================================================================================================================== static OSStatus _FastRecoveryTestStartQuerier( FastRecoveryTestRef inTest ); #define kFastRecoveryTestQuerierStartIntervalSecs 5 static OSStatus _FastRecoveryTestStartGAIRequests( const FastRecoveryTestRef me ) { OSStatus err; check( !me->timer ); me->timer = dispatch_source_create( DISPATCH_SOURCE_TYPE_TIMER, 0, 0, me->queue ); require_action( me->timer, exit, err = kNoResourcesErr ); dispatch_source_set_timer( me->timer, _dispatch_monotonictime_after_sec( kFastRecoveryTestQuerierStartIntervalSecs ), kFastRecoveryTestQuerierStartIntervalSecs * UINT64_C_safe( kNanosecondsPerSecond ), kFastRecoveryTestQuerierStartIntervalSecs * ( UINT64_C_safe( kNanosecondsPerSecond ) / 20 ) ); dispatch_set_context( me->timer, me ); dispatch_source_set_event_handler( me->timer, ^{ OSStatus startErr; startErr = _FastRecoveryTestStartQuerier( me ); if( startErr ) _FastRecoveryTestStop( me, startErr ); } ); dispatch_activate( me->timer ); err = kNoErr; exit: return( err ); } //=========================================================================================================================== #define kFastRecoveryTestFinalTimeoutSecs 2 static void _FastRecoveryTestHandleGAIResuts( FastRecoveryTestRef inTest, FastRecoveryGAI * inGAI, dnssd_getaddrinfo_result_t * inResults, size_t inCount ); static void _FastRecoveryTestComplete( FastRecoveryTestRef inTest ); static OSStatus _FastRecoveryTestStartQuerier( const FastRecoveryTestRef me ) { OSStatus err; if( me->gaiCount < countof( me->gaiArray ) ) { dnssd_getaddrinfo_t gai; FastRecoveryGAI * const frGAI = &me->gaiArray[ me->gaiCount++ ]; char tag[ 6 + 1 ]; check( !frGAI->hostname ); ASPrintF( &frGAI->hostname, "count-1.tag-%s.%s", _RandomStringExact( kLowerAlphaNumericCharSet, kLowerAlphaNumericCharSetSize, sizeof( tag ) - 1, tag ), me->domain ); require_action( frGAI->hostname, exit, err = kNoMemoryErr ); gai = dnssd_getaddrinfo_create(); require_action( gai, exit, err = kNoResourcesErr ); dnssd_getaddrinfo_set_hostname( gai, frGAI->hostname ); dnssd_getaddrinfo_set_flags( gai, 0 ); dnssd_getaddrinfo_set_interface_index( gai, kDNSServiceInterfaceIndexAny ); dnssd_getaddrinfo_set_protocols( gai, kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6 ); dnssd_getaddrinfo_set_queue( gai, me->queue ); dnssd_retain( gai ); _FastRecoveryTestRetain( me ); dnssd_getaddrinfo_set_result_handler( gai, ^( dnssd_getaddrinfo_result_t * const inResults, const size_t inCount ) { require_return( frGAI->gai == gai ); _FastRecoveryTestHandleGAIResuts( me, frGAI, inResults, inCount ); } ); dnssd_getaddrinfo_set_event_handler( gai, ^( const dnssd_event_t inEvent, const DNSServiceErrorType inGAIError ) { switch( inEvent ) { case dnssd_event_invalidated: dnssd_release( gai ); _FastRecoveryTestRelease( me ); break; case dnssd_event_error: require_return( frGAI->gai == gai ); frt_ulog( kLogLevelError, "dnssd_getaddrinfo error: %#m\n", inGAIError ); _FastRecoveryTestStop( me, inGAIError ); break; default: break; } } ); frGAI->gai = gai; gai = NULL; dnssd_getaddrinfo_activate( frGAI->gai ); } else { dnssd_getaddrinfo_t gai; dispatch_source_forget( &me->timer ); me->timer = dispatch_source_create( DISPATCH_SOURCE_TYPE_TIMER, 0, 0, me->queue ); require_action( me->timer, exit, err = kNoResourcesErr ); dispatch_source_set_timer( me->timer, _dispatch_monotonictime_after_sec( kFastRecoveryTestFinalTimeoutSecs ), kFastRecoveryTestFinalTimeoutSecs * UINT64_C_safe( kNanosecondsPerSecond ), kFastRecoveryTestFinalTimeoutSecs * ( UINT64_C_safe( kNanosecondsPerSecond ) / 20 ) ); dispatch_set_context( me->timer, me ); dispatch_source_set_event_handler( me->timer, ^{ _FastRecoveryTestComplete( me ); } ); dispatch_activate( me->timer ); check( !me->resumeQNAME ); ASPrintF( &me->resumeQNAME, "tag-fast-recovery-test-probe.command-resume.%s", me->domain ); require_action( me->resumeQNAME, exit, err = kNoMemoryErr ); err = CFPropertyListAppendFormatted( NULL, me->report, "%kO=%s", CFSTR( "resumeQNAME" ), me->resumeQNAME ); require_noerr( err, exit ); gai = dnssd_getaddrinfo_create(); require_action( gai, exit, err = kNoResourcesErr ); dnssd_getaddrinfo_set_hostname( gai, me->resumeQNAME ); dnssd_getaddrinfo_set_flags( gai, 0 ); dnssd_getaddrinfo_set_interface_index( gai, kDNSServiceInterfaceIndexAny ); dnssd_getaddrinfo_set_protocols( gai, kDNSServiceProtocol_IPv4 ); dnssd_getaddrinfo_set_queue( gai, me->queue ); dnssd_retain( gai ); _FastRecoveryTestRetain( me ); dnssd_getaddrinfo_set_result_handler( gai, ^( dnssd_getaddrinfo_result_t * const inResults, const size_t inCount ) { size_t i; require_return( me->gai == gai ); for( i = 0; i < inCount; ++i ) { frt_ulog( kLogLevelInfo, "GAI result -- %@\n", inResults[ i ] ); } } ); dnssd_getaddrinfo_set_event_handler( gai, ^( const dnssd_event_t inEvent, const DNSServiceErrorType inGAIError ) { switch( inEvent ) { case dnssd_event_invalidated: dnssd_release( gai ); _FastRecoveryTestRelease( me ); break; case dnssd_event_error: require_return( me->gai == gai ); frt_ulog( kLogLevelError, "dnssd_getaddrinfo error: %#m\n", inGAIError ); _FastRecoveryTestStop( me, inGAIError ); break; default: break; } } ); me->gai = gai; gai = NULL; dnssd_getaddrinfo_activate( me->gai ); me->suspended = false; // The resume command query will resume the DNS server. } err = kNoErr; exit: return( err ); } //=========================================================================================================================== static void _FastRecoveryTestHandleGAIResuts( const FastRecoveryTestRef me, FastRecoveryGAI * const inGAI, dnssd_getaddrinfo_result_t * const inResults, const size_t inCount ) { OSStatus err; size_t i; for( i = 0; i < inCount; ++i ) { const dnssd_getaddrinfo_result_t result = inResults[ i ]; frt_ulog( kLogLevelInfo, "GAI result -- %@\n", result ); if( !me->suspended ) { const sockaddr_ip * sip; const dnssd_getaddrinfo_result_type_t type = dnssd_getaddrinfo_result_get_type( result ); require_action_quiet( type == dnssd_getaddrinfo_result_type_add, exit, err = kTypeErr ); sip = (const sockaddr_ip *) dnssd_getaddrinfo_result_get_address( result ); switch( sip->sa.sa_family ) { case AF_INET: require_action_quiet( !inGAI->gotResultIPv4, exit, err = kUnexpectedErr ); inGAI->gotResultIPv4 = true; break; case AF_INET6: require_action_quiet( !inGAI->gotResultIPv6, exit, err = kUnexpectedErr ); inGAI->gotResultIPv6 = true; break; default: err = kTypeErr; goto exit; } if( inGAI->gotResultIPv4 && inGAI->gotResultIPv6 ) dnssd_getaddrinfo_forget( &inGAI->gai ); } } err = me->suspended ? kUnexpectedErr : kNoErr; exit: if( err ) _FastRecoveryTestStop( me, err ); } //=========================================================================================================================== static void _FastRecoveryTestComplete( const FastRecoveryTestRef me ) { OSStatus err; size_t i; Boolean resultsMissing = false; for( i = 0; i < countof( me->gaiArray ); ++i ) { FastRecoveryGAI * const frGAI = &me->gaiArray[ i ]; err = CFPropertyListAppendFormatted( NULL, me->gaiResults, "{" "%kO=%s" // hostname "%kO=%b" // gotResultIPv4 "%kO=%b" // gotResultIPv6 "}", CFSTR( "hostname" ), frGAI->hostname, CFSTR( "gotResultIPv4" ), frGAI->gotResultIPv4, CFSTR( "gotResultIPv6" ), frGAI->gotResultIPv6 ); require_noerr( err, exit ); if( !frGAI->gotResultIPv4 || !frGAI->gotResultIPv6 ) resultsMissing = true; } err = resultsMissing ? kUnderrunErr : kNoErr; exit: _FastRecoveryTestStop( me, err ); } #endif // MDNSRESPONDER_PROJECT //=========================================================================================================================== // RegistrationTestCmd //=========================================================================================================================== typedef struct RegistrationSubtest RegistrationSubtest; typedef struct { CFMutableArrayRef subtestReports; // Array of subtest reports. dispatch_source_t timer; // Timer to enforce subtest durations. dispatch_source_t sigSourceINT; // SIGINT signal handler for a clean test exit. dispatch_source_t sigSourceTERM; // SIGTERM signal handler for a clean test exit. RegistrationSubtest * subtest; // Current subtest. char * outputFilePath; // Path of test result output file. If NULL, stdout will be used. OutputFormatType outputFormat; // Format of test results output. CFStringRef computerNamePrev; // Previous ComputerName. CFStringRef localHostNamePrev; // Previous LocalHostName. NanoTime64 startTime; // Test's start time. char * computerName; // Temporary ComputerName to set during testing. (malloc'd) char * localHostName; // Temporary LocalHostName to set during testing. (malloc'd) CFStringEncoding computerNamePrevEncoding; // Previous ComputerName's encoding. int subtestIndex; // Index of current subtest. Boolean computerNameSet; // True if a temporary ComputerName was set. Boolean localHostNameSet; // True if a temporary LocalHostName was set. Boolean failed; // True if at least one non-skipped subtest failed. Boolean forBATS; // True if the test is running in a BATS environment. } RegistrationTest; typedef enum { kRegistrationInterfaceSet_Null = 0, kRegistrationInterfaceSet_All = 1, kRegistrationInterfaceSet_AllPlusAWDL = 2, kRegistrationInterfaceSet_LoopbackOnly = 3, kRegistrationInterfaceSet_AWDLOnly = 4 } RegistrationInterfaceSet; typedef struct { RegistrationInterfaceSet interfaceSet; // Interfaces to register the service over. Boolean useDefaultName; // True if registration is to use the default service name. Boolean useLODiscovery; // True if discovery is to use kDNSServiceInterfaceIndexLocalOnly. } RegistrationSubtestParams; static const RegistrationSubtestParams kRegistrationSubtestParams[] = { { kRegistrationInterfaceSet_All, true, false }, { kRegistrationInterfaceSet_All, false, false }, { kRegistrationInterfaceSet_AllPlusAWDL, true, false }, { kRegistrationInterfaceSet_AllPlusAWDL, false, false }, { kRegistrationInterfaceSet_LoopbackOnly, true, false }, { kRegistrationInterfaceSet_LoopbackOnly, false, false }, { kRegistrationInterfaceSet_AWDLOnly, true, false }, { kRegistrationInterfaceSet_AWDLOnly, false, false }, { kRegistrationInterfaceSet_All, true, true }, { kRegistrationInterfaceSet_All, false, true }, { kRegistrationInterfaceSet_AllPlusAWDL, true, true }, { kRegistrationInterfaceSet_AllPlusAWDL, false, true }, { kRegistrationInterfaceSet_LoopbackOnly, true, true }, { kRegistrationInterfaceSet_LoopbackOnly, false, true }, { kRegistrationInterfaceSet_AWDLOnly, true, true }, { kRegistrationInterfaceSet_AWDLOnly, false, true } }; typedef struct { NanoTime64 browseResultTime; // Per-interface browse result time. NanoTime64 querySRVResultTime; // Per-interface SRV record query result time. NanoTime64 queryTXTResultTime; // Per-interface TXT record query result time. } RegistrationResultTimes; typedef struct { MDNSInterfaceItem base; // Underlying MDNSInterface linked-list item. RegistrationResultTimes times; // Per-interface result times. } RegistrationInterfaceItem; struct RegistrationSubtest { DNSServiceRef registration; // DNS-SD service registration. DNSServiceRef connection; // Shared DNS-SD connection. DNSServiceRef browse; // DNS-SD browse for service's type. DNSServiceRef querySRV; // DNS-SD query request for service's SRV record. DNSServiceRef queryTXT; // DNS-SD query request for service's TXT record. CFMutableArrayRef unexpected; // Array of unexpected registration, browse, and query results. #if( TARGET_OS_WATCH ) CFMutableArrayRef ignored; // Array of unexpected, but ignored, browse and query results. #endif const char * serviceName; // Service's name. char * serviceNameCustom; // Service's name if using a custom name. (malloc'd) char * serviceType; // Service's service type. (malloc'd) size_t serviceTypeLen; // C string length of service's service type. char * serviceFQDN; // Service's FQDN, i.e., name of its SRV and TXT records. uint8_t * txtPtr; // Pointer to service's TXT record data. (malloc'd) size_t txtLen; // Length of service's TXT record data. RegistrationInterfaceItem * ifList; // If ifIndex == 0, interfaces that service should register over. RegistrationResultTimes ifTimes; // If ifIndex != 0, result times for interface with that index. RegistrationTest * test; // Pointer to parent test. NanoTime64 startTime; // Subtest's start time. char * description; // Subtest's description. (malloc'd) uint32_t ifIndex; // Interface index used for service registration. uint16_t port; // Service's port number. Boolean useLODiscovery; // True if discovery is to use kDNSServiceInterfaceIndexLocalOnly. Boolean includeAWDL; // True if the IncludeAWDL flag was used during registration. Boolean ifIsAWDL; // True if ifIndex is the index of an AWDL interface. Boolean skipped; // True if this subtest is to be skipped. Boolean registered; // True if the test service was successfully registered. Boolean useDefaultName; // True if the service is to use the default service name. }; static OSStatus _RegistrationTestCreate( RegistrationTest **outTest ); static void _RegistrationTestFree( RegistrationTest *inTest ); static void _RegistrationTestBegin( void *inContext ); static void _RegistrationTestProceed( RegistrationTest *inTest ); static OSStatus _RegistrationTestStart( RegistrationTest *inTest ); static void _RegistrationTestStop( RegistrationTest *inTest ); #define _RegistrationTestForget( X ) ForgetCustomEx( X, _RegistrationTestStop, _RegistrationTestFree ) static OSStatus _RegistrationTestStartSubtest( RegistrationTest * inTest, const RegistrationSubtestParams * inParams, Boolean * outSkipped ); static OSStatus _RegistrationTestEndSubtest( RegistrationTest *inTest ); static void _RegistrationTestEnd( RegistrationTest *inTest ) ATTRIBUTE_NORETURN; static void _RegistrationTestExit( RegistrationTest *inTest, OSStatus inError ) ATTRIBUTE_NORETURN; static OSStatus _RegistrationSubtestCreate( RegistrationSubtest **outSubtest ); static void _RegistrationSubtestStop( RegistrationSubtest *inSubtest ); static void _RegistrationSubtestFree( RegistrationSubtest *inSubtest ); #define _RegistrationSubtestForget( X ) ForgetCustomEx( X, _RegistrationSubtestStop, _RegistrationSubtestFree ) static OSStatus _RegistrationTestInterfaceListCreate( Boolean inIncludeAWDL, RegistrationInterfaceItem **outList ); static OSStatus _RegistrationTestCreateRandomTXTRecord( size_t inMinLen, size_t inMaxLen, uint8_t ** outTXTPtr, size_t * outTXTLen ); static void DNSSD_API _RegistrationSubtestRegisterCallback( DNSServiceRef inSDRef, DNSServiceFlags inFlags, DNSServiceErrorType inError, const char * inName, const char * inType, const char * inDomain, void * inContext ); static void DNSSD_API _RegistrationSubtestBrowseCallback( DNSServiceRef inSDRef, DNSServiceFlags inFlags, uint32_t inIfIndex, DNSServiceErrorType inError, const char * inServiceName, const char * inServiceType, const char * inDomain, void * inContext ); static void DNSSD_API _RegistrationSubtestQueryCallback( DNSServiceRef inSDRef, DNSServiceFlags inFlags, uint32_t inIfIndex, DNSServiceErrorType inError, const char * inName, uint16_t inType, uint16_t inClass, uint16_t inRDataLen, const void * inRDataPtr, uint32_t inTTL, void * inContext ); static Boolean _RegistrationSubtestValidServiceType( const RegistrationSubtest *inSubtest, const char *inServiceType ); static RegistrationResultTimes * _RegistrationSubtestGetInterfaceResultTimes( RegistrationSubtest * inSubtest, uint32_t inIfIndex, Boolean * outIsAWDL ); static void _RegistrationTestTimerHandler( void *inContext ); #if( TARGET_OS_WATCH ) static Boolean _RegistrationTestInterfaceIsWiFi( const char *inIfName ); #endif static void RegistrationTestCmd( void ) { OSStatus err; RegistrationTest * test = NULL; err = _RegistrationTestCreate( &test ); require_noerr( err, exit ); if( gRegistrationTest_BATSEnvironment ) test->forBATS = true; if( gRegistrationTest_OutputFilePath ) { test->outputFilePath = strdup( gRegistrationTest_OutputFilePath ); require_action( test->outputFilePath, exit, err = kNoMemoryErr ); } err = OutputFormatFromArgString( gRegistrationTest_OutputFormat, &test->outputFormat ); require_noerr_quiet( err, exit ); dispatch_async_f( dispatch_get_main_queue(), test, _RegistrationTestBegin ); dispatch_main(); exit: if( test ) _RegistrationTestFree( test ); ErrQuit( 1, "error: %#m\n", err ); } //=========================================================================================================================== static OSStatus _RegistrationTestCreate( RegistrationTest **outTest ) { OSStatus err; RegistrationTest * obj; obj = (RegistrationTest *) calloc( 1, sizeof( *obj ) ); require_action( obj, exit, err = kNoMemoryErr ); obj->subtestReports = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks ); require_action( obj->subtestReports, exit, err = kNoMemoryErr ); *outTest = obj; obj = NULL; err = kNoErr; exit: if( obj ) _RegistrationTestFree( obj ); return( err ); } //=========================================================================================================================== static void _RegistrationTestFree( RegistrationTest *inTest ) { check( !inTest->timer ); check( !inTest->sigSourceINT ); check( !inTest->sigSourceTERM ); check( !inTest->computerNameSet ); check( !inTest->localHostNameSet ); check( !inTest->subtest ); ForgetCF( &inTest->subtestReports ); ForgetMem( &inTest->outputFilePath ); ForgetCF( &inTest->computerNamePrev ); ForgetCF( &inTest->localHostNamePrev ); ForgetMem( &inTest->computerName ); ForgetMem( &inTest->localHostName ); } //=========================================================================================================================== static void _RegistrationTestBegin( void *inContext ) { _RegistrationTestProceed( (RegistrationTest *) inContext ); } //=========================================================================================================================== static void _RegistrationTestProceed( RegistrationTest *inTest ) { OSStatus err; Boolean skippedSubtest; do { int subtestIndex; if( !inTest->startTime ) { err = _RegistrationTestStart( inTest ); require_noerr_quiet( err, exit ); inTest->startTime = NanoTimeGetCurrent(); } else { err = _RegistrationTestEndSubtest( inTest ); require_noerr( err, exit ); ++inTest->subtestIndex; } subtestIndex = inTest->subtestIndex; if( subtestIndex < (int) countof( kRegistrationSubtestParams ) ) { err = _RegistrationTestStartSubtest( inTest, &kRegistrationSubtestParams[ subtestIndex ], &skippedSubtest ); require_noerr_quiet( err, exit ); } else { _RegistrationTestEnd( inTest ); } } while( skippedSubtest ); exit: if( err ) _RegistrationTestExit( inTest, err ); } //=========================================================================================================================== static void _RegistrationTestSignalHandler( void *inContext ); static OSStatus _RegistrationTestStart( RegistrationTest *inTest ) { OSStatus err; char tag[ 6 + 1 ]; // Save original ComputerName and LocalHostName. check( !inTest->computerNamePrev ); inTest->computerNamePrev = SCDynamicStoreCopyComputerName( NULL, &inTest->computerNamePrevEncoding ); err = map_scerror( inTest->computerNamePrev ); require_noerr( err, exit ); check( !inTest->localHostNamePrev ); inTest->localHostNamePrev = SCDynamicStoreCopyLocalHostName( NULL ); err = map_scerror( inTest->localHostNamePrev ); require_noerr( err, exit ); // Generate a unique test ComputerName. check( !inTest->computerName ); ASPrintF( &inTest->computerName, "dnssdutil-regtest-computer-name-%s", _RandomStringExact( kLowerAlphaNumericCharSet, kLowerAlphaNumericCharSetSize, sizeof( tag ) - 1, tag ) ); require_action( inTest->computerName, exit, err = kNoMemoryErr ); // Generate a unique test LocalHostName. check( !inTest->localHostName ); ASPrintF( &inTest->localHostName, "dnssdutil-regtest-local-hostname-%s", _RandomStringExact( kLowerAlphaNumericCharSet, kLowerAlphaNumericCharSetSize, sizeof( tag ) - 1, tag ) ); require_action( inTest->localHostName, exit, err = kNoMemoryErr ); // Set up SIGINT signal handler. signal( SIGINT, SIG_IGN ); check( !inTest->sigSourceINT ); err = DispatchSignalSourceCreate( SIGINT, dispatch_get_main_queue(), _RegistrationTestSignalHandler, inTest, &inTest->sigSourceINT ); require_noerr( err, exit ); dispatch_resume( inTest->sigSourceINT ); // Set up SIGTERM signal handler. signal( SIGTERM, SIG_IGN ); check( !inTest->sigSourceTERM ); err = DispatchSignalSourceCreate( SIGTERM, dispatch_get_main_queue(), _RegistrationTestSignalHandler, inTest, &inTest->sigSourceTERM ); require_noerr( err, exit ); dispatch_resume( inTest->sigSourceTERM ); // Set test ComputerName. check( !inTest->computerNameSet ); err = _SetComputerNameWithUTF8CString( inTest->computerName ); require_noerr( err, exit ); inTest->computerNameSet = true; // Set test LocalHostName. check( !inTest->localHostNameSet ); err = _SetLocalHostNameWithUTF8CString( inTest->localHostName ); require_noerr( err, exit ); inTest->localHostNameSet = true; exit: if( err ) _RegistrationTestStop( inTest ); return( err ); } static void _RegistrationTestSignalHandler( void *inContext ) { RegistrationTest * const test = (RegistrationTest *) inContext; FPrintF( stderr, "Registration test got a SIGINT or SIGTERM signal, exiting..." ); _RegistrationTestExit( test, kCanceledErr ); } //=========================================================================================================================== static void _RegistrationTestStop( RegistrationTest *inTest ) { OSStatus err; dispatch_source_forget( &inTest->timer ); dispatch_source_forget( &inTest->sigSourceINT ); dispatch_source_forget( &inTest->sigSourceTERM ); _RegistrationSubtestForget( &inTest->subtest ); if( inTest->computerNameSet ) { err = _SetComputerName( inTest->computerNamePrev, inTest->computerNamePrevEncoding ); check_noerr( err ); if( !err ) inTest->computerNameSet = false; } if( inTest->localHostNameSet ) { err = _SetLocalHostName( inTest->localHostNamePrev ); check_noerr( err ); if( !err ) inTest->localHostNameSet = false; } } //=========================================================================================================================== #define kRegistrationTestSubtestDurationSecs 5 static OSStatus _RegistrationTestStartSubtest( RegistrationTest * inTest, const RegistrationSubtestParams * inParams, Boolean * outSkipped ) { OSStatus err; RegistrationSubtest * subtest; const char * interfaceDesc; DNSServiceFlags flags; char tag[ 6 + 1 ]; subtest = NULL; err = _RegistrationSubtestCreate( &subtest ); require_noerr( err, exit ); subtest->test = inTest; subtest->useDefaultName = inParams->useDefaultName; subtest->useLODiscovery = inParams->useLODiscovery; // Determine registration interfaces. switch( inParams->interfaceSet ) { case kRegistrationInterfaceSet_All: subtest->ifIndex = kDNSServiceInterfaceIndexAny; if( !subtest->useLODiscovery ) { err = _RegistrationTestInterfaceListCreate( false, &subtest->ifList ); require_noerr( err, exit ); } interfaceDesc = "all interfaces (excluding AWDL)"; break; case kRegistrationInterfaceSet_AllPlusAWDL: subtest->ifIndex = kDNSServiceInterfaceIndexAny; subtest->includeAWDL = true; if( !subtest->useLODiscovery ) { err = _RegistrationTestInterfaceListCreate( true, &subtest->ifList ); require_noerr( err, exit ); } interfaceDesc = "all interfaces (including AWDL)"; break; case kRegistrationInterfaceSet_LoopbackOnly: subtest->ifIndex = if_nametoindex( "lo0" ); if( subtest->ifIndex == 0 ) { FPrintF( stderr, "Failed to get index for loopback interface lo0.\n" ); err = kNoResourcesErr; goto exit; } interfaceDesc = "loopback interface"; break; case kRegistrationInterfaceSet_AWDLOnly: err = _MDNSInterfaceGetAny( kMDNSInterfaceSubset_AWDL, NULL, &subtest->ifIndex ); if( err == kNotFoundErr ) { FPrintF( stderr, "Warning: No mDNS-capable AWDL interface is available.\n" ); subtest->skipped = true; err = kNoErr; } require_noerr( err, exit ); subtest->ifIsAWDL = true; interfaceDesc = "AWDL interface"; break; default: err = kParamErr; goto exit; } // Create description. ASPrintF( &subtest->description, "Service registration over %s using %s service name.%s", interfaceDesc, subtest->useDefaultName ? "default" : "custom", subtest->useLODiscovery ? " (LocalOnly discovery)" : "" ); require_action( subtest->description, exit, err = kNoMemoryErr ); if( subtest->skipped ) { subtest->startTime = NanoTimeGetCurrent(); } else { // Generate a service name. if( subtest->useDefaultName ) { subtest->serviceName = inTest->computerName; } else { ASPrintF( &subtest->serviceNameCustom, "dnssdutil-regtest-service-name-%s", _RandomStringExact( kLowerAlphaNumericCharSet, kLowerAlphaNumericCharSetSize, sizeof( tag ) - 1, tag ) ); require_action( subtest->serviceNameCustom, exit, err = kNoMemoryErr ); subtest->serviceName = subtest->serviceNameCustom; } // Generate a service type. ASPrintF( &subtest->serviceType, "_regtest-%s._udp", _RandomStringExact( kLowerAlphaNumericCharSet, kLowerAlphaNumericCharSetSize, sizeof( tag ) - 1, tag ) ); require_action( subtest->serviceType, exit, err = kNoMemoryErr ); subtest->serviceTypeLen = strlen( subtest->serviceType ); // Create SRV and TXT record name FQDN. ASPrintF( &subtest->serviceFQDN, "%s.%s.local.", subtest->serviceName, subtest->serviceType ); require_action( subtest->serviceFQDN, exit, err = kNoMemoryErr ); // Generate a port number. subtest->port = (uint16_t) RandomRange( 60000, 65535 ); // Generate TXT record data. err = _RegistrationTestCreateRandomTXTRecord( 100, 1000, &subtest->txtPtr, &subtest->txtLen ); require_noerr( err, exit ); // Register service. subtest->startTime = NanoTimeGetCurrent(); flags = kDNSServiceFlagsNoAutoRename; if( subtest->includeAWDL ) flags |= kDNSServiceFlagsIncludeAWDL; err = DNSServiceRegister( &subtest->registration, flags, subtest->ifIndex, subtest->useDefaultName ? NULL : subtest->serviceNameCustom, subtest->serviceType, "local.", NULL, htons( subtest->port ), (uint16_t) subtest->txtLen, subtest->txtPtr, _RegistrationSubtestRegisterCallback, subtest ); require_noerr( err, exit ); err = DNSServiceSetDispatchQueue( subtest->registration, dispatch_get_main_queue() ); require_noerr( err, exit ); // Start timer. check( !inTest->timer ); err = DispatchTimerOneShotCreate( dispatch_time_seconds( kRegistrationTestSubtestDurationSecs ), INT64_C_safe( kRegistrationTestSubtestDurationSecs ) * kNanosecondsPerSecond / 10, dispatch_get_main_queue(), _RegistrationTestTimerHandler, inTest, &inTest->timer ); require_noerr( err, exit ); dispatch_resume( inTest->timer ); } *outSkipped = subtest->skipped; check( !inTest->subtest ); inTest->subtest = subtest; subtest = NULL; exit: _RegistrationSubtestForget( &subtest ); return( err ); } //=========================================================================================================================== #define kRegistrationTestReportKey_ComputerName CFSTR( "computerName" ) // String #define kRegistrationTestReportKey_Description CFSTR( "description" ) // String #define kRegistrationTestReportKey_Domain CFSTR( "domain" ) // String #define kRegistrationTestReportKey_EndTime CFSTR( "endTime" ) // String #define kRegistrationTestReportKey_Error CFSTR( "error" ) // Integer #define kRegistrationTestReportKey_Flags CFSTR( "flags" ) // Integer #define kRegistrationTestReportKey_IgnoredResults CFSTR( "ignoredResults" ) // Array of dictionaries #define kRegistrationTestReportKey_InterfaceIndex CFSTR( "ifIndex" ) // Integer #define kRegistrationTestReportKey_InterfaceName CFSTR( "ifName" ) // String #define kRegistrationTestReportKey_LocalHostName CFSTR( "localHostName" ) // String #define kRegistrationTestReportKey_MissingResults CFSTR( "missingResults" ) // Array of dictionaries #define kRegistrationTestReportKey_Pass CFSTR( "pass" ) // Boolean #define kRegistrationTestReportKey_Port CFSTR( "port" ) // Integer #define kRegistrationTestReportKey_RDataFormatted CFSTR( "rdataFormatted" ) // String #define kRegistrationTestReportKey_RDataHexString CFSTR( "rdataHexString" ) // String #define kRegistrationTestReportKey_RecordClass CFSTR( "recordClass" ) // Integer #define kRegistrationTestReportKey_RecordType CFSTR( "recordType" ) // Integer #define kRegistrationTestReportKey_Registered CFSTR( "registered" ) // Boolean #define kRegistrationTestReportKey_ResultType CFSTR( "resultType" ) // String #define kRegistrationTestReportKey_ServiceFQDN CFSTR( "serviceFQDN" ) // String #define kRegistrationTestReportKey_ServiceName CFSTR( "serviceName" ) // String #define kRegistrationTestReportKey_ServiceType CFSTR( "serviceType" ) // String #define kRegistrationTestReportKey_Skipped CFSTR( "skipped" ) // Boolean #define kRegistrationTestReportKey_StartTime CFSTR( "startTime" ) // String #define kRegistrationTestReportKey_Subtests CFSTR( "subtests" ) // Array of dictionaries #define kRegistrationTestReportKey_Timestamp CFSTR( "timestamp" ) // String #define kRegistrationTestReportKey_TXT CFSTR( "txt" ) // String #define kRegistrationTestReportKey_UnexpectedResults CFSTR( "unexpectedResults" ) // Array of dictionaries #define kRegistrationTestReportKey_UsedDefaultName CFSTR( "usedDefaultName" ) // Boolean #define kRegistrationTestReportKey_UsedLODiscovery CFSTR( "usedLODiscovery" ) // Boolean #define kRegistrationTestResultType_Browse CFSTR( "browse" ) #define kRegistrationTestResultType_Query CFSTR( "query" ) #define kRegistrationTestResultType_QuerySRV CFSTR( "querySRV" ) #define kRegistrationTestResultType_QueryTXT CFSTR( "queryTXT" ) #define kRegistrationTestResultType_Registration CFSTR( "registration" ) static OSStatus _RegistrationTestAppendMissingResults( CFMutableArrayRef inMissingResults, const RegistrationResultTimes * inTimes, uint32_t inIfIndex, const char * inIfName ); static OSStatus _RegistrationTestEndSubtest( RegistrationTest *inTest ) { OSStatus err; RegistrationSubtest * subtest; CFMutableDictionaryRef subtestReport; CFMutableArrayRef missing; char * txtStr; NanoTime64 now; Boolean subtestFailed; char startTime[ 32 ]; char endTime[ 32 ]; char ifNameBuf[ IF_NAMESIZE + 1 ]; now = NanoTimeGetCurrent(); subtest = inTest->subtest; inTest->subtest = NULL; _RegistrationSubtestStop( subtest ); missing = NULL; subtestReport = NULL; txtStr = NULL; if( subtest->txtPtr ) { err = DNSRecordDataToString( subtest->txtPtr, subtest->txtLen, kDNSServiceType_TXT, &txtStr ); require_noerr( err, exit ); } _NanoTime64ToTimestamp( subtest->startTime, startTime, sizeof( startTime ) ); _NanoTime64ToTimestamp( now, endTime, sizeof( endTime ) ); err = CFPropertyListCreateFormatted( kCFAllocatorDefault, &subtestReport, "{" "%kO=%s" // description "%kO=%s" // startTime "%kO=%s" // endTime "%kO=%s" // serviceFQDN "%kO=%lli" // ifIndex "%kO=%s" // ifName "%kO=%lli" // port "%kO=%s" // txt "%kO=%b" // registered "%kO=%b" // usedDefaultName "%kO=%b" // usedLODiscovery "}", kRegistrationTestReportKey_Description, subtest->description, kRegistrationTestReportKey_StartTime, startTime, kRegistrationTestReportKey_EndTime, endTime, kRegistrationTestReportKey_ServiceFQDN, subtest->serviceFQDN, kRegistrationTestReportKey_InterfaceIndex, (int64_t) subtest->ifIndex, kRegistrationTestReportKey_InterfaceName, if_indextoname( subtest->ifIndex, ifNameBuf ), kRegistrationTestReportKey_Port, (int64_t) subtest->port, kRegistrationTestReportKey_TXT, txtStr, kRegistrationTestReportKey_Registered, (int) subtest->registered, kRegistrationTestReportKey_UsedDefaultName, (int) subtest->useDefaultName, kRegistrationTestReportKey_UsedLODiscovery, (int) subtest->useLODiscovery ); ForgetMem( &txtStr ); require_noerr( err, exit ); if( !subtest->skipped && subtest->registered ) { missing = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks ); require_action( missing, exit, err = kNoMemoryErr ); if( subtest->ifList ) { RegistrationInterfaceItem * item; for( item = subtest->ifList; item; item = (RegistrationInterfaceItem *) item->base.next ) { #if( TARGET_OS_WATCH ) if( inTest->forBATS && item->base.isWiFi ) continue; #endif err = _RegistrationTestAppendMissingResults( missing, &item->times, item->base.ifIndex, item->base.ifName ); require_noerr( err, exit ); } } else { err = _RegistrationTestAppendMissingResults( missing, &subtest->ifTimes, subtest->ifIndex, NULL ); require_noerr( err, exit ); } subtestFailed = false; if( CFArrayGetCount( missing ) > 0 ) { subtestFailed = true; CFDictionarySetValue( subtestReport, kRegistrationTestReportKey_MissingResults, missing ); } if( CFArrayGetCount( subtest->unexpected ) > 0 ) { subtestFailed = true; CFDictionarySetValue( subtestReport, kRegistrationTestReportKey_UnexpectedResults, subtest->unexpected ); } #if( TARGET_OS_WATCH ) if( CFArrayGetCount( subtest->ignored ) > 0 ) { CFDictionarySetValue( subtestReport, kRegistrationTestReportKey_IgnoredResults, subtest->ignored ); } #endif } else { subtestFailed = true; } CFDictionarySetBoolean( subtestReport, kRegistrationTestReportKey_Pass, subtestFailed ? false : true ); if( subtestFailed ) { CFDictionarySetBoolean( subtestReport, kRegistrationTestReportKey_Skipped, subtest->skipped ); if( !subtest->skipped ) inTest->failed = true; } CFArrayAppendValue( inTest->subtestReports, subtestReport ); exit: CFReleaseNullSafe( missing ); CFReleaseNullSafe( subtestReport ); _RegistrationSubtestFree( subtest ); return( err ); } static OSStatus _RegistrationTestAppendMissingResult( CFMutableArrayRef inMissingResults, CFStringRef inType, uint32_t inIfIndex, const char * inIfName ); static OSStatus _RegistrationTestAppendMissingResults( CFMutableArrayRef inMissingResults, const RegistrationResultTimes * inTimes, uint32_t inIfIndex, const char * inIfName ) { OSStatus err; if( !inTimes->browseResultTime ) { err = _RegistrationTestAppendMissingResult( inMissingResults, kRegistrationTestResultType_Browse, inIfIndex, inIfName ); require_noerr( err, exit ); } if( !inTimes->querySRVResultTime ) { err = _RegistrationTestAppendMissingResult( inMissingResults, kRegistrationTestResultType_QuerySRV, inIfIndex, inIfName ); require_noerr( err, exit ); } if( !inTimes->queryTXTResultTime ) { err = _RegistrationTestAppendMissingResult( inMissingResults, kRegistrationTestResultType_QueryTXT, inIfIndex, inIfName ); require_noerr( err, exit ); } err = kNoErr; exit: return( err ); } static OSStatus _RegistrationTestAppendMissingResult( CFMutableArrayRef inMissingResults, CFStringRef inType, uint32_t inIfIndex, const char * inIfName ) { OSStatus err; char ifName[ IF_NAMESIZE + 1 ]; err = CFPropertyListAppendFormatted( kCFAllocatorDefault, inMissingResults, "{" "%kO=%O" // resultType "%kO=%lli" // ifIndex "%kO=%s" // ifName "}", kRegistrationTestReportKey_ResultType, inType, kRegistrationTestReportKey_InterfaceIndex, (int64_t) inIfIndex, kRegistrationTestReportKey_InterfaceName, inIfName ? inIfName : if_indextoname( inIfIndex, ifName ) ); return( err ); } //=========================================================================================================================== static void _RegistrationTestEnd( RegistrationTest *inTest ) { OSStatus err; NanoTime64 now; CFPropertyListRef plist; char startTime[ 32 ]; char endTime[ 32 ]; now = NanoTimeGetCurrent(); _NanoTime64ToTimestamp( inTest->startTime, startTime, sizeof( startTime ) ); _NanoTime64ToTimestamp( now, endTime, sizeof( endTime ) ); err = CFPropertyListCreateFormatted( kCFAllocatorDefault, &plist, "{" "%kO=%s" // startTime "%kO=%s" // endTime "%kO=%s" // computerName "%kO=%s" // localHostName "%kO=%O" // subtests "%kO=%b" // pass "}", kRegistrationTestReportKey_StartTime, startTime, kRegistrationTestReportKey_EndTime, endTime, kRegistrationTestReportKey_ComputerName, inTest->computerName, kRegistrationTestReportKey_LocalHostName, inTest->localHostName, kRegistrationTestReportKey_Subtests, inTest->subtestReports, kRegistrationTestReportKey_Pass, inTest->failed ? false : true ); require_noerr( err, exit ); err = OutputPropertyList( plist, inTest->outputFormat, inTest->outputFilePath ); CFRelease( plist ); require_noerr( err, exit ); exit: _RegistrationTestExit( inTest, err ); } //=========================================================================================================================== static void _RegistrationTestExit( RegistrationTest *inTest, OSStatus inError ) { int exitCode; if( inError ) { FPrintF( stderr, "error: %#m\n", inError ); exitCode = 1; } else { exitCode = inTest->failed ? 2 : 0; } _RegistrationTestForget( &inTest ); exit( exitCode ); } //=========================================================================================================================== static OSStatus _RegistrationSubtestCreate( RegistrationSubtest **outSubtest ) { OSStatus err; RegistrationSubtest * obj; obj = (RegistrationSubtest *) calloc( 1, sizeof( *obj ) ); require_action( obj, exit, err = kNoMemoryErr ); obj->unexpected = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks ); require_action( obj->unexpected, exit, err = kNoMemoryErr ); #if( TARGET_OS_WATCH ) obj->ignored = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks ); require_action( obj->ignored, exit, err = kNoMemoryErr ); #endif *outSubtest = obj; obj = NULL; err = kNoErr; exit: if( obj ) _RegistrationSubtestFree( obj ); return( err ); } //=========================================================================================================================== static void _RegistrationSubtestFree( RegistrationSubtest *inSubtest ) { check( !inSubtest->registration ); check( !inSubtest->browse ); check( !inSubtest->querySRV ); check( !inSubtest->queryTXT ); check( !inSubtest->connection ); ForgetMem( &inSubtest->serviceNameCustom ); ForgetMem( &inSubtest->serviceType ); ForgetMem( &inSubtest->serviceFQDN ); ForgetMem( &inSubtest->txtPtr ); ForgetCF( &inSubtest->unexpected ); #if( TARGET_OS_WATCH ) ForgetCF( &inSubtest->ignored ); #endif _MDNSInterfaceListForget( (MDNSInterfaceItem **) &inSubtest->ifList ); ForgetMem( &inSubtest->description ); free( inSubtest ); } //=========================================================================================================================== static void _RegistrationSubtestStop( RegistrationSubtest *inSubtest ) { DNSServiceForget( &inSubtest->registration ); DNSServiceForget( &inSubtest->browse ); DNSServiceForget( &inSubtest->querySRV ); DNSServiceForget( &inSubtest->queryTXT ); DNSServiceForget( &inSubtest->connection ); } //=========================================================================================================================== static OSStatus _RegistrationTestInterfaceListCreate( Boolean inIncludeAWDL, RegistrationInterfaceItem **outList ) { OSStatus err; RegistrationInterfaceItem * list; const MDNSInterfaceSubset subset = inIncludeAWDL ? kMDNSInterfaceSubset_All : kMDNSInterfaceSubset_NonAWDL; err = _MDNSInterfaceListCreate( subset, sizeof( *list ), (MDNSInterfaceItem **) &list ); require_noerr( err, exit ); *outList = list; exit: return( err ); } //=========================================================================================================================== static OSStatus _RegistrationTestCreateRandomTXTRecord( size_t inMinLen, size_t inMaxLen, uint8_t ** outTXTPtr, size_t * outTXTLen ) { OSStatus err; uint8_t * ptr; const uint8_t * txtEnd; uint8_t * txtPtr = NULL; size_t txtLen; require_action_quiet( inMinLen <= inMaxLen, exit, err = kSizeErr ); txtLen = RandomRange( inMinLen, inMaxLen ); txtPtr = (uint8_t *) malloc( txtLen + 1 ); require_action( txtPtr, exit, err = kNoMemoryErr ); _RandomStringExact( kAlphaNumericCharSet, sizeof_string( kAlphaNumericCharSet ), txtLen, (char *)txtPtr ); ptr = txtPtr; txtEnd = &txtPtr[ txtLen ]; while( ptr < txtEnd ) { size_t maxLen, len; maxLen = ( (size_t)( txtEnd - ptr ) ) - 1; len = RandomRange( 1, 255 ); if( len > maxLen ) len = maxLen; *ptr = (uint8_t) len; ptr += ( 1 + len ); } check( ptr == txtEnd ); if( outTXTPtr ) { *outTXTPtr = txtPtr; txtPtr = NULL; } if( outTXTLen ) *outTXTLen = txtLen; err = kNoErr; exit: FreeNullSafe( txtPtr ); return( err ); } //=========================================================================================================================== static void DNSSD_API _RegistrationSubtestRegisterCallback( DNSServiceRef inSDRef, DNSServiceFlags inFlags, DNSServiceErrorType inError, const char * inServiceName, const char * inServiceType, const char * inDomain, void * inContext ) { OSStatus err; const NanoTime64 now = NanoTimeGetCurrent(); RegistrationSubtest * const subtest = (RegistrationSubtest *) inContext; Unused( inSDRef ); if( ( inFlags & kDNSServiceFlagsAdd ) && !inError && ( strcasecmp( inServiceName, subtest->serviceName ) == 0 ) && _RegistrationSubtestValidServiceType( subtest, inServiceType ) && ( strcasecmp( inDomain, "local." ) == 0 ) ) { if( !subtest->registered ) { DNSServiceRef sdRef; const DNSServiceFlags flags = kDNSServiceFlagsShareConnection | kDNSServiceFlagsIncludeAWDL; subtest->registered = true; // Create shared connection. check( !subtest->connection ); err = DNSServiceCreateConnection( &subtest->connection ); require_noerr( err, exit ); err = DNSServiceSetDispatchQueue( subtest->connection, dispatch_get_main_queue() ); require_noerr( err, exit ); // Start browse. check( !subtest->browse ); sdRef = subtest->connection; err = DNSServiceBrowse( &sdRef, flags, subtest->useLODiscovery ? kDNSServiceInterfaceIndexLocalOnly : kDNSServiceInterfaceIndexAny, subtest->serviceType, "local.", _RegistrationSubtestBrowseCallback, subtest ); require_noerr( err, exit ); subtest->browse = sdRef; } } else { char timestamp[ 32 ]; err = CFPropertyListAppendFormatted( kCFAllocatorDefault, subtest->unexpected, "{" "%kO=%O" // resultType "%kO=%s" // timestamp "%kO=%lli" // flags "%kO=%lli" // error "%kO=%s" // serviceName "%kO=%s" // serviceType "%kO=%s" // domain "}", kRegistrationTestReportKey_ResultType, kRegistrationTestResultType_Registration, kRegistrationTestReportKey_Timestamp, _NanoTime64ToTimestamp( now, timestamp, sizeof( timestamp ) ), kRegistrationTestReportKey_Flags, (int64_t) inFlags, kRegistrationTestReportKey_Error, (int64_t) inError, kRegistrationTestReportKey_ServiceName, inServiceName, kRegistrationTestReportKey_ServiceType, inServiceType, kRegistrationTestReportKey_Domain, inDomain ); require_noerr( err, exit ); } err = kNoErr; exit: if( err ) _RegistrationTestExit( subtest->test, err ); } //=========================================================================================================================== static void DNSSD_API _RegistrationSubtestBrowseCallback( DNSServiceRef inSDRef, DNSServiceFlags inFlags, uint32_t inIfIndex, DNSServiceErrorType inError, const char * inServiceName, const char * inServiceType, const char * inDomain, void * inContext ) { OSStatus err; NanoTime64 now; RegistrationSubtest * const subtest = (RegistrationSubtest *) inContext; Boolean serviceIsCorrect, resultIsExpected; Unused( inSDRef ); now = NanoTimeGetCurrent(); if( !inError && ( strcasecmp( inServiceName, subtest->serviceName ) == 0 ) && _RegistrationSubtestValidServiceType( subtest, inServiceType ) && ( strcasecmp( inDomain, "local." ) == 0 ) ) { serviceIsCorrect = true; } else { serviceIsCorrect = false; } resultIsExpected = false; if( serviceIsCorrect && ( inFlags & kDNSServiceFlagsAdd ) ) { RegistrationResultTimes * times; times = _RegistrationSubtestGetInterfaceResultTimes( subtest, inIfIndex, NULL ); if( times ) { DNSServiceRef sdRef; const DNSServiceFlags flags = kDNSServiceFlagsShareConnection | kDNSServiceFlagsIncludeAWDL; uint32_t ifIndex; resultIsExpected = true; if( !times->browseResultTime ) times->browseResultTime = now; ifIndex = subtest->useLODiscovery ? kDNSServiceInterfaceIndexLocalOnly : kDNSServiceInterfaceIndexAny; if( !subtest->querySRV ) { // Start SRV record query. sdRef = subtest->connection; err = DNSServiceQueryRecord( &sdRef, flags, ifIndex, subtest->serviceFQDN, kDNSServiceType_SRV, kDNSServiceClass_IN, _RegistrationSubtestQueryCallback, subtest ); require_noerr( err, exit ); subtest->querySRV = sdRef; } if( !subtest->queryTXT ) { // Start TXT record query. sdRef = subtest->connection; err = DNSServiceQueryRecord( &sdRef, flags, ifIndex, subtest->serviceFQDN, kDNSServiceType_TXT, kDNSServiceClass_IN, _RegistrationSubtestQueryCallback, subtest ); require_noerr( err, exit ); subtest->queryTXT = sdRef; } } } if( !resultIsExpected ) { CFMutableArrayRef resultArray; char timestamp[ 32 ]; const char * ifNamePtr; char ifNameBuf[ IF_NAMESIZE + 1 ]; ifNamePtr = if_indextoname( inIfIndex, ifNameBuf ); resultArray = subtest->unexpected; #if( TARGET_OS_WATCH ) if( subtest->test->forBATS && ( subtest->ifIndex == kDNSServiceInterfaceIndexAny ) && ifNamePtr && _RegistrationTestInterfaceIsWiFi( ifNamePtr ) && serviceIsCorrect ) { resultArray = subtest->ignored; } #endif err = CFPropertyListAppendFormatted( kCFAllocatorDefault, resultArray, "{" "%kO=%O" // resultType "%kO=%s" // timestamp "%kO=%lli" // flags "%kO=%lli" // ifIndex "%kO=%s" // ifName "%kO=%lli" // error "%kO=%s" // serviceName "%kO=%s" // serviceType "%kO=%s" // domain "}", kRegistrationTestReportKey_ResultType, kRegistrationTestResultType_Browse, kRegistrationTestReportKey_Timestamp, _NanoTime64ToTimestamp( now, timestamp, sizeof( timestamp ) ), kRegistrationTestReportKey_Flags, (int64_t) inFlags, kRegistrationTestReportKey_InterfaceIndex, (int64_t) inIfIndex, kRegistrationTestReportKey_InterfaceName, ifNamePtr, kRegistrationTestReportKey_Error, (int64_t) inError, kRegistrationTestReportKey_ServiceName, inServiceName, kRegistrationTestReportKey_ServiceType, inServiceType, kRegistrationTestReportKey_Domain, inDomain ); require_noerr( err, exit ); } err = kNoErr; exit: if( err ) _RegistrationTestExit( subtest->test, err ); } //=========================================================================================================================== static Boolean _RegistrationSubtestIsSRVRecordDataValid( RegistrationSubtest * inSubtest, const uint8_t * inRDataPtr, size_t inRDataLen, Boolean inExpectRandHostname ); static void DNSSD_API _RegistrationSubtestQueryCallback( DNSServiceRef inSDRef, DNSServiceFlags inFlags, uint32_t inIfIndex, DNSServiceErrorType inError, const char * inName, uint16_t inType, uint16_t inClass, uint16_t inRDataLen, const void * inRDataPtr, uint32_t inTTL, void * inContext ) { OSStatus err; NanoTime64 now; RegistrationSubtest * const subtest = (RegistrationSubtest *) inContext; Boolean resultIsExpected; Unused( inSDRef ); Unused( inTTL ); now = NanoTimeGetCurrent(); resultIsExpected = false; if( ( inFlags & kDNSServiceFlagsAdd ) && !inError && ( strcasecmp( inName, subtest->serviceFQDN ) == 0 ) && ( inClass == kDNSServiceClass_IN ) ) { RegistrationResultTimes * times; Boolean isAWDL; times = _RegistrationSubtestGetInterfaceResultTimes( subtest, inIfIndex, &isAWDL ); if( times ) { if( inType == kDNSServiceType_SRV ) { Boolean expectRandHostname; if( isAWDL || ( ( subtest->ifIndex == kDNSServiceInterfaceIndexAny ) && subtest->includeAWDL ) ) { expectRandHostname = true; } else { expectRandHostname = false; } if( _RegistrationSubtestIsSRVRecordDataValid( subtest, inRDataPtr, inRDataLen, expectRandHostname ) ) { resultIsExpected = true; if( !times->querySRVResultTime ) times->querySRVResultTime = now; } } else if( inType == kDNSServiceType_TXT ) { if( MemEqual( inRDataPtr, inRDataLen, subtest->txtPtr, subtest->txtLen ) ) { resultIsExpected = true; if( !times->queryTXTResultTime ) times->queryTXTResultTime = now; } } } } if( !resultIsExpected ) { CFMutableArrayRef resultArray; CFMutableDictionaryRef resultDict; CFStringRef rdataKey; char * rdataStr; const char * ifNamePtr; char timestamp[ 32 ]; char ifNameBuf[ IF_NAMESIZE + 1 ]; ifNamePtr = if_indextoname( inIfIndex, ifNameBuf ); resultArray = subtest->unexpected; #if( TARGET_OS_WATCH ) if( subtest->test->forBATS && ( subtest->ifIndex == kDNSServiceInterfaceIndexAny ) && ifNamePtr && _RegistrationTestInterfaceIsWiFi( ifNamePtr ) && !inError && ( strcasecmp( inName, subtest->serviceFQDN ) == 0 ) ) { if( inType == kDNSServiceType_SRV ) { const Boolean expectRandHostname = subtest->includeAWDL ? true : false; if( _RegistrationSubtestIsSRVRecordDataValid( subtest, inRDataPtr, inRDataLen, expectRandHostname ) ) { resultArray = subtest->ignored; } } else if( inType == kDNSServiceType_TXT ) { if( MemEqual( inRDataPtr, inRDataLen, subtest->txtPtr, subtest->txtLen ) ) { resultArray = subtest->ignored; } } } #endif err = CFPropertyListCreateFormatted( kCFAllocatorDefault, &resultDict, "{" "%kO=%O" // resultType "%kO=%s" // timestamp "%kO=%lli" // flags "%kO=%lli" // ifIndex "%kO=%s" // ifName "%kO=%lli" // error "%kO=%s" // serviceFQDN "%kO=%lli" // recordType "%kO=%lli" // class "}", kRegistrationTestReportKey_ResultType, kRegistrationTestResultType_Query, kRegistrationTestReportKey_Timestamp, _NanoTime64ToTimestamp( now, timestamp, sizeof( timestamp ) ), kRegistrationTestReportKey_Flags, (int64_t) inFlags, kRegistrationTestReportKey_InterfaceIndex, (int64_t) inIfIndex, kRegistrationTestReportKey_InterfaceName, ifNamePtr, kRegistrationTestReportKey_Error, (int64_t) inError, kRegistrationTestReportKey_ServiceFQDN, inName, kRegistrationTestReportKey_RecordType, (int64_t) inType, kRegistrationTestReportKey_RecordClass, (int64_t) inClass ); require_noerr( err, exit ); rdataStr = NULL; DNSRecordDataToString( inRDataPtr, inRDataLen, inType, &rdataStr ); if( rdataStr ) { rdataKey = kRegistrationTestReportKey_RDataFormatted; } else { ASPrintF( &rdataStr, "%#H", inRDataPtr, inRDataLen, inRDataLen ); require_action( rdataStr, exit, err = kNoMemoryErr ); rdataKey = kRegistrationTestReportKey_RDataHexString; } err = CFDictionarySetCString( resultDict, rdataKey, rdataStr, kSizeCString ); ForgetMem( &rdataStr ); if( err ) CFRelease( resultDict ); require_noerr( err, exit ); CFArrayAppendValue( resultArray, resultDict ); CFRelease( resultDict ); } err = kNoErr; exit: if( err ) _RegistrationTestExit( subtest->test, err ); } static Boolean _RegistrationSubtestIsSRVRecordDataValid( RegistrationSubtest * inSubtest, const uint8_t * inRDataPtr, size_t inRDataLen, Boolean inExpectRandHostname ) { const dns_fixed_fields_srv * fields; const uint8_t * const end = &inRDataPtr[ inRDataLen ]; const uint8_t * label; size_t len; uint16_t port; Boolean isValid; isValid = false; require_quiet( inRDataLen >= sizeof( dns_fixed_fields_srv ), exit ); fields = (const dns_fixed_fields_srv *) inRDataPtr; port = dns_fixed_fields_srv_get_port( fields ); require_quiet( port == inSubtest->port, exit ); // First target label should be a UUID string for the AWDL interface. label = (const uint8_t *) &fields[ 1 ]; require_quiet( ( end - label ) >= 1, exit ); len = label[ 0 ]; require_quiet( ( (size_t)( end - label ) ) >= ( 1 + len ), exit ); if( inExpectRandHostname ) { if( StringToUUID( (const char *) &label[ 1 ], len, false, NULL ) != kNoErr ) goto exit; } else { if( strnicmpx( &label[ 1 ], len, inSubtest->test->localHostName ) != 0 ) goto exit; } // Second target label should be "local". label = &label[ 1 + len ]; require_quiet( ( end - label ) >= 1, exit ); len = label[ 0 ]; require_quiet( ( (size_t)( end - label ) ) >= ( 1 + len ), exit ); if( ( len != kLocalLabel[ 0 ] ) || ( _memicmp( &label[ 1 ], &kLocalLabel[ 1 ], kLocalLabel[ 0 ] ) != 0 ) ) goto exit; // Third target label should be the root label. label = &label[ 1 + len ]; require_quiet( ( end - label ) >= 1, exit ); len = label[ 0 ]; if( len != 0 ) goto exit; isValid = true; exit: return( isValid ); } //=========================================================================================================================== static Boolean _RegistrationSubtestValidServiceType( const RegistrationSubtest *inSubtest, const char *inServiceType ) { if( stricmp_prefix( inServiceType, inSubtest->serviceType ) == 0 ) { const char * const ptr = &inServiceType[ inSubtest->serviceTypeLen ]; if( ( ptr[ 0 ] == '\0' ) || ( ( ptr[ 0 ] == '.' ) && ( ptr[ 1 ] == '\0' ) ) ) return( true ); } return( false ); } //=========================================================================================================================== static RegistrationResultTimes * _RegistrationSubtestGetInterfaceResultTimes( RegistrationSubtest * inSubtest, uint32_t inIfIndex, Boolean * outIsAWDL ) { if( inSubtest->ifList ) { RegistrationInterfaceItem * item; for( item = inSubtest->ifList; item; item = (RegistrationInterfaceItem *) item->base.next ) { if( inIfIndex == item->base.ifIndex ) { if( outIsAWDL ) *outIsAWDL = item->base.isAWDL ? true : false; return( &item->times ); } } } else { if( inIfIndex == inSubtest->ifIndex ) { if( outIsAWDL ) *outIsAWDL = inSubtest->ifIsAWDL ? true : false; return( &inSubtest->ifTimes ); } } return( NULL ); } //=========================================================================================================================== static void _RegistrationTestTimerHandler( void *inContext ) { RegistrationTest * const test = (RegistrationTest *) inContext; dispatch_source_forget( &test->timer ); _RegistrationTestProceed( test ); } //=========================================================================================================================== #if( TARGET_OS_WATCH ) static Boolean _RegistrationTestInterfaceIsWiFi( const char *inIfName ) { NetTransportType type = kNetTransportType_Undefined; SocketGetInterfaceInfo( kInvalidSocketRef, inIfName, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &type ); return( ( type == kNetTransportType_WiFi ) ? true : false ); } #endif #if( TARGET_OS_DARWIN ) //=========================================================================================================================== // KeepAliveTestCmd //=========================================================================================================================== typedef enum { kKeepAliveCallVariant_Null = 0, kKeepAliveCallVariant_TakesSocket = 1, // DNSServiceSleepKeepalive(), which takes a connected socket. kKeepAliveCallVariant_TakesSockAddrs = 2, // DNSServiceSleepKeepalive_sockaddr(), which takes connection's sockaddrs. } KeepAliveCallVariant; typedef struct { int family; // TCP connection's address family. KeepAliveCallVariant callVariant; // Describes which DNSServiceSleepKeepalive* call to use. const char * description; // Subtest description. } KeepAliveSubtestParams; const KeepAliveSubtestParams kKeepAliveSubtestParams[] = { { AF_INET, kKeepAliveCallVariant_TakesSocket, "Calls DNSServiceSleepKeepalive() for IPv4 TCP connection." }, { AF_INET, kKeepAliveCallVariant_TakesSockAddrs, "Calls DNSServiceSleepKeepalive_sockaddr() for IPv4 TCP connection." }, { AF_INET6, kKeepAliveCallVariant_TakesSocket, "Calls DNSServiceSleepKeepalive() for IPv6 TCP connection." }, { AF_INET6, kKeepAliveCallVariant_TakesSockAddrs, "Calls DNSServiceSleepKeepalive_sockaddr() for IPv6 TCP connection." } }; typedef struct { sockaddr_ip local; // TCP connection's local address and port. sockaddr_ip remote; // TCP connection's remote address and port. NanoTime64 startTime; // Subtest's start time. NanoTime64 endTime; // Subtest's end time. SocketRef clientSock; // Socket for client-side of TCP connection. SocketRef serverSock; // Socket for server-side of TCP connection. char * recordName; // Keepalive record's name. char * dataStr; // Data expected to be contained in keepalive record's data. const char * description; // Subtests's description. unsigned int timeoutKA; // Randomly-generated timeout value that gets put in keepalive record's rdata. OSStatus error; // Subtest's error. } KeepAliveSubtest; typedef struct KeepAliveTest * KeepAliveTestRef; typedef struct { KeepAliveTestRef test; // Weak back pointer to test. } KeepAliveTestConnectionContext; struct KeepAliveTest { dispatch_queue_t queue; // Serial queue for test events. dispatch_semaphore_t doneSem; // Semaphore to signal when the test is done. dispatch_source_t readSource; // Read source for TCP listener socket. DNSServiceRef keepalive; // DNSServiceSleepKeepalive{,sockaddr} operation. DNSServiceRef query; // Query to verify registered keepalive record. dispatch_source_t timer; // Timer to put time limit on query. AsyncConnectionRef connection; // Establishes current subtest's TCP connection. KeepAliveTestConnectionContext * connectionCtx; // Weak pointer to connection's context. NanoTime64 startTime; // Test's start time. NanoTime64 endTime; // Test's end time. OSStatus error; // Test's error. size_t subtestIdx; // Index of current subtest. KeepAliveSubtest subtests[ 4 ]; // Subtest array. }; check_compile_time( countof_field( struct KeepAliveTest, subtests ) == countof( kKeepAliveSubtestParams ) ); ulog_define_ex( kDNSSDUtilIdentifier, KeepAliveTest, kLogLevelInfo, kLogFlags_None, "KeepAliveTest", NULL ); #define kat_ulog( LEVEL, ... ) ulog( &log_category_from_name( KeepAliveTest ), (LEVEL), __VA_ARGS__ ) static OSStatus _KeepAliveTestCreate( KeepAliveTestRef *outTest ); static OSStatus _KeepAliveTestRun( KeepAliveTestRef inTest ); static void _KeepAliveTestFree( KeepAliveTestRef inTest ); static void KeepAliveTestCmd( void ) { OSStatus err; OutputFormatType outputFormat; KeepAliveTestRef test = NULL; CFPropertyListRef plist = NULL; CFMutableArrayRef subtests; size_t i; size_t subtestFailCount; Boolean testPassed = false; char startTime[ 32 ]; char endTime[ 32 ]; err = OutputFormatFromArgString( gKeepAliveTest_OutputFormat, &outputFormat ); require_noerr_quiet( err, exit ); err = _KeepAliveTestCreate( &test ); require_noerr( err, exit ); err = _KeepAliveTestRun( test ); require_noerr( err, exit ); _NanoTime64ToTimestamp( test->startTime, startTime, sizeof( startTime ) ); _NanoTime64ToTimestamp( test->endTime, endTime, sizeof( endTime ) ); err = CFPropertyListCreateFormatted( kCFAllocatorDefault, &plist, "{" "%kO=%s" // startTime "%kO=%s" // endTime "%kO=[%@]" // subtests "}", CFSTR( "startTime" ), startTime, CFSTR( "endTime" ), endTime, CFSTR( "subtests" ), &subtests ); require_noerr( err, exit ); subtestFailCount = 0; check( test->subtestIdx == countof( test->subtests ) ); for( i = 0; i < countof( test->subtests ); ++i ) { KeepAliveSubtest * const subtest = &test->subtests[ i ]; char errorDesc[ 128 ]; _NanoTime64ToTimestamp( subtest->startTime, startTime, sizeof( startTime ) ); _NanoTime64ToTimestamp( subtest->endTime, endTime, sizeof( endTime ) ); SNPrintF( errorDesc, sizeof( errorDesc ), "%m", subtest->error ); err = CFPropertyListAppendFormatted( kCFAllocatorDefault, subtests, "{" "%kO=%s" // startTime "%kO=%s" // endTime "%kO=%s" // description "%kO=%##a" // localAddr "%kO=%##a" // remoteAddr "%kO=%s" // recordName "%kO=%s" // expectedRData "%kO=" // error "{" "%kO=%lli" // code "%kO=%s" // description "}" "}", CFSTR( "startTime" ), startTime, CFSTR( "endTime" ), endTime, CFSTR( "description" ), subtest->description, CFSTR( "localAddr" ), &subtest->local.sa, CFSTR( "remoteAddr" ), &subtest->remote.sa, CFSTR( "recordName" ), subtest->recordName, CFSTR( "expectedRData" ), subtest->dataStr, CFSTR( "error" ), CFSTR( "code" ), (int64_t) subtest->error, CFSTR( "description" ), errorDesc ); require_noerr( err, exit ); if( subtest->error ) ++subtestFailCount; } if( subtestFailCount == 0 ) testPassed = true; CFPropertyListAppendFormatted( kCFAllocatorDefault, plist, "%kO=%b", CFSTR( "pass" ), testPassed ); err = OutputPropertyList( plist, outputFormat, gKeepAliveTest_OutputFilePath ); require_noerr( err, exit ); exit: if( test ) _KeepAliveTestFree( test ); CFReleaseNullSafe( plist ); gExitCode = err ? 1 : ( testPassed ? 0 : 2 ); } //=========================================================================================================================== static void _KeepAliveTestStart( void *inCtx ); static void _KeepAliveTestStop( KeepAliveTestRef inTest, OSStatus inError ); static OSStatus _KeepAliveTestStartSubtest( KeepAliveTestRef inTest ); static void _KeepAliveTestStopSubtest( KeepAliveTestRef inTest ); static KeepAliveSubtest * _KeepAliveTestGetSubtest( KeepAliveTestRef inTest ); static const char * _KeepAliveTestGetSubtestLogPrefix( KeepAliveTestRef inTest, char *inBufPtr, size_t inBufLen ); static OSStatus _KeepAliveTestContinue( KeepAliveTestRef inTest, OSStatus inSubtestError, Boolean *outDone ); static void _KeepAliveTestTCPAcceptHandler( void *inCtx ); static void _KeepAliveTestConnectionHandler( SocketRef inSock, OSStatus inError, void *inArg ); static void _KeepAliveTestHandleConnection( KeepAliveTestRef inTest, SocketRef inSock, OSStatus inError ); static void _KeepAliveTestForgetConnection( KeepAliveTestRef inTest ); static void DNSSD_API _KeepAliveTestKeepaliveCallback( DNSServiceRef inSDRef, DNSServiceErrorType inErr, void *inCtx ); static void _KeepAliveTestQueryTimerHandler( void *inCtx ); static void DNSSD_API _KeepAliveTestQueryRecordCallback( 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 * inCtx ); static OSStatus _KeepAliveTestCreate( KeepAliveTestRef *outTest ) { OSStatus err; KeepAliveTestRef test; size_t i; test = (KeepAliveTestRef) calloc( 1, sizeof( *test ) ); require_action( test, exit, err = kNoMemoryErr ); test->error = kInProgressErr; for( i = 0; i < countof( test->subtests ); ++i ) { KeepAliveSubtest * const subtest = &test->subtests[ i ]; subtest->local.sa.sa_family = AF_UNSPEC; subtest->remote.sa.sa_family = AF_UNSPEC; subtest->clientSock = kInvalidSocketRef; subtest->serverSock = kInvalidSocketRef; } test->queue = dispatch_queue_create( "com.apple.dnssdutil.keepalive-test", DISPATCH_QUEUE_SERIAL ); require_action( test->queue, exit, err = kNoResourcesErr ); test->doneSem = dispatch_semaphore_create( 0 ); require_action( test->doneSem, exit, err = kNoResourcesErr ); *outTest = test; test = NULL; err = kNoErr; exit: if( test ) _KeepAliveTestFree( test ); return( err ); } //=========================================================================================================================== static OSStatus _KeepAliveTestRun( KeepAliveTestRef inTest ) { dispatch_async_f( inTest->queue, inTest, _KeepAliveTestStart ); dispatch_semaphore_wait( inTest->doneSem, DISPATCH_TIME_FOREVER ); return( inTest->error ); } //=========================================================================================================================== static void _KeepAliveTestFree( KeepAliveTestRef inTest ) { size_t i; check( !inTest->readSource ); check( !inTest->query ); check( !inTest->timer ); check( !inTest->keepalive ); check( !inTest->connection ); check( !inTest->connectionCtx ); dispatch_forget( &inTest->queue ); dispatch_forget( &inTest->doneSem ); for( i = 0; i < countof( inTest->subtests ); ++i ) { KeepAliveSubtest * const subtest = &inTest->subtests[ i ]; check( !IsValidSocket( subtest->clientSock ) ); check( !IsValidSocket( subtest->serverSock ) ); ForgetMem( &subtest->recordName ); ForgetMem( &subtest->dataStr ); } free( inTest ); } //=========================================================================================================================== static void _KeepAliveTestStart( void *inCtx ) { OSStatus err; const KeepAliveTestRef test = (KeepAliveTestRef) inCtx; test->error = kInProgressErr; test->startTime = NanoTimeGetCurrent(); err = _KeepAliveTestStartSubtest( test ); require_noerr( err, exit ); exit: if( err ) _KeepAliveTestStop( test, err ); } //=========================================================================================================================== static void _KeepAliveTestStop( KeepAliveTestRef inTest, OSStatus inError ) { size_t i; inTest->error = inError; inTest->endTime = NanoTimeGetCurrent(); _KeepAliveTestStopSubtest( inTest ); for( i = 0; i < countof( inTest->subtests ); ++i ) { KeepAliveSubtest * const subtest = &inTest->subtests[ i ]; ForgetSocket( &subtest->clientSock ); ForgetSocket( &subtest->serverSock ); } dispatch_semaphore_signal( inTest->doneSem ); } //=========================================================================================================================== static OSStatus _KeepAliveTestStartSubtest( KeepAliveTestRef inTest ) { OSStatus err; KeepAliveSubtest * const subtest = _KeepAliveTestGetSubtest( inTest ); const KeepAliveSubtestParams * const params = &kKeepAliveSubtestParams[ inTest->subtestIdx ]; int port; SocketRef sock = kInvalidSocketRef; const uint32_t loopbackV4 = htonl( INADDR_LOOPBACK ); SocketContext * sockCtx = NULL; KeepAliveTestConnectionContext * cnxCtx = NULL; Boolean useIPv4; char serverAddrStr[ 64 ]; char prefix[ 64 ]; subtest->error = kInProgressErr; subtest->startTime = NanoTimeGetCurrent(); subtest->description = params->description; require_action( ( params->family == AF_INET ) || ( params->family == AF_INET6 ), exit, err = kInternalErr ); // Create TCP listener socket. useIPv4 = ( params->family == AF_INET ) ? true : false; err = ServerSocketOpenEx( params->family, SOCK_STREAM, IPPROTO_TCP, useIPv4 ? ( (const void *) &loopbackV4 ) : ( (const void *) &in6addr_loopback ), kSocketPort_Auto, &port, kSocketBufferSize_DontSet, &sock ); require_noerr( err, exit ); if( useIPv4 ) SNPrintF( serverAddrStr, sizeof( serverAddrStr ), "%.4a:%d", &loopbackV4, port ); else SNPrintF( serverAddrStr, sizeof( serverAddrStr ), "[%.16a]:%d", in6addr_loopback.s6_addr, port ); _KeepAliveTestGetSubtestLogPrefix( inTest, prefix, sizeof( prefix ) ); kat_ulog( kLogLevelInfo, "%s: Will listen for connections on %s\n", prefix, serverAddrStr ); sockCtx = SocketContextCreate( sock, inTest, &err ); require_noerr( err, exit ); sock = kInvalidSocketRef; // Create read source for TCP listener socket. check( !inTest->readSource ); err = DispatchReadSourceCreate( sockCtx->sock, inTest->queue, _KeepAliveTestTCPAcceptHandler, SocketContextCancelHandler, sockCtx, &inTest->readSource ); require_noerr( err, exit ); sockCtx = NULL; dispatch_resume( inTest->readSource ); cnxCtx = (KeepAliveTestConnectionContext *) calloc( 1, sizeof( *cnxCtx ) ); require_action( cnxCtx, exit, err = kNoMemoryErr ); // Start asynchronous connection to listener socket. kat_ulog( kLogLevelInfo, "%s: Will connect to %s\n", prefix, serverAddrStr ); check( !inTest->connection ); err = AsyncConnection_Connect( &inTest->connection, serverAddrStr, 0, kAsyncConnectionFlags_None, 5 * UINT64_C_safe( kNanosecondsPerSecond ), kSocketBufferSize_DontSet, kSocketBufferSize_DontSet, NULL, NULL, _KeepAliveTestConnectionHandler, cnxCtx, inTest->queue ); require_noerr( err, exit ); cnxCtx->test = inTest; check( !inTest->connectionCtx ); inTest->connectionCtx = cnxCtx; cnxCtx = NULL; exit: ForgetSocket( &sock ); if( sockCtx ) SocketContextRelease( sockCtx ); FreeNullSafe( cnxCtx ); return( err ); } //=========================================================================================================================== static void _KeepAliveTestStopSubtest( KeepAliveTestRef inTest ) { dispatch_source_forget( &inTest->readSource ); DNSServiceForget( &inTest->keepalive ); DNSServiceForget( &inTest->query ); dispatch_source_forget( &inTest->timer ); _KeepAliveTestForgetConnection( inTest ); } //=========================================================================================================================== static KeepAliveSubtest * _KeepAliveTestGetSubtest( KeepAliveTestRef inTest ) { return( ( inTest->subtestIdx < countof( inTest->subtests ) ) ? &inTest->subtests[ inTest->subtestIdx ] : NULL ); } //=========================================================================================================================== static const char * _KeepAliveTestGetSubtestLogPrefix( KeepAliveTestRef inTest, char *inBufPtr, size_t inBufLen ) { SNPrintF( inBufPtr, inBufLen, "Subtest %zu/%zu", inTest->subtestIdx + 1, countof( inTest->subtests ) ); return( inBufPtr ); } //=========================================================================================================================== static OSStatus _KeepAliveTestContinue( KeepAliveTestRef inTest, OSStatus inSubtestError, Boolean *outDone ) { OSStatus err; KeepAliveSubtest * subtest; require_action( inTest->subtestIdx <= countof( inTest->subtests ), exit, err = kInternalErr ); if( inTest->subtestIdx < countof( inTest->subtests ) ) { subtest = _KeepAliveTestGetSubtest( inTest ); _KeepAliveTestStopSubtest( inTest ); subtest->endTime = NanoTimeGetCurrent(); subtest->error = inSubtestError; if( ++inTest->subtestIdx < countof( inTest->subtests ) ) { err = _KeepAliveTestStartSubtest( inTest ); require_noerr_quiet( err, exit ); } } err = kNoErr; exit: if( outDone ) *outDone = ( !err && ( inTest->subtestIdx == countof( inTest->subtests ) ) ) ? true : false; return( err ); } //=========================================================================================================================== static void _KeepAliveTestTCPAcceptHandler( void *inCtx ) { OSStatus err; const SocketContext * const sockCtx = (SocketContext *) inCtx; const KeepAliveTestRef test = (KeepAliveTestRef) sockCtx->userContext; KeepAliveSubtest * const subtest = _KeepAliveTestGetSubtest( test ); sockaddr_ip peer; socklen_t len; char prefix[ 64 ]; check( !IsValidSocket( subtest->serverSock ) ); len = (socklen_t) sizeof( peer ); subtest->serverSock = accept( sockCtx->sock, &peer.sa, &len ); err = map_socket_creation_errno( subtest->serverSock ); require_noerr( err, exit ); _KeepAliveTestGetSubtestLogPrefix( test, prefix, sizeof( prefix ) ); kat_ulog( kLogLevelInfo, "%s: Accepted connection from %##a\n", prefix, &peer.sa ); dispatch_source_forget( &test->readSource ); exit: if( err ) _KeepAliveTestStop( test, err ); } //=========================================================================================================================== static void _KeepAliveTestConnectionHandler( SocketRef inSock, OSStatus inError, void *inArg ) { KeepAliveTestConnectionContext * ctx = (KeepAliveTestConnectionContext *) inArg; const KeepAliveTestRef test = ctx->test; if( test ) { _KeepAliveTestForgetConnection( test ); _KeepAliveTestHandleConnection( test, inSock, inError ); inSock = kInvalidSocketRef; } ForgetSocket( &inSock ); free( ctx ); } //=========================================================================================================================== #define kKeepAliveTestQueryTimeoutSecs 5 static void _KeepAliveTestHandleConnection( KeepAliveTestRef inTest, SocketRef inSock, OSStatus inError ) { OSStatus err; KeepAliveSubtest * const subtest = _KeepAliveTestGetSubtest( inTest ); const KeepAliveSubtestParams * const params = &kKeepAliveSubtestParams[ inTest->subtestIdx ]; socklen_t len; uint32_t value; int family, i; Boolean subtestFailed = false; Boolean done; char prefix[ 64 ]; require_noerr_action( inError, exit, err = inError ); check( !IsValidSocket( subtest->clientSock ) ); subtest->clientSock = inSock; inSock = kInvalidSocketRef; // Get local and remote IP addresses. len = (socklen_t) sizeof( subtest->local ); err = getsockname( subtest->clientSock, &subtest->local.sa, &len ); err = map_global_noerr_errno( err ); require_noerr( err, exit ); len = (socklen_t) sizeof( subtest->remote ); err = getpeername( subtest->clientSock, &subtest->remote.sa, &len ); err = map_global_noerr_errno( err ); require_noerr( err, exit ); _KeepAliveTestGetSubtestLogPrefix( inTest, prefix, sizeof( prefix ) ); kat_ulog( kLogLevelInfo, "%s: Connection established: %##a <-> %##a\n", prefix, &subtest->local.sa, &subtest->remote.sa ); // Call either DNSServiceSleepKeepalive() or DNSServiceSleepKeepalive_sockaddr(). check( subtest->timeoutKA == 0 ); subtest->timeoutKA = (unsigned int) RandomRange( 1, UINT_MAX ); switch( params->callVariant ) { case kKeepAliveCallVariant_TakesSocket: kat_ulog( kLogLevelInfo, "%s: Will call DNSServiceSleepKeepalive() for client-side socket\n", prefix ); check( !inTest->keepalive ); err = DNSServiceSleepKeepalive( &inTest->keepalive, 0, subtest->clientSock, subtest->timeoutKA, _KeepAliveTestKeepaliveCallback, inTest ); require_noerr( err, exit ); err = DNSServiceSetDispatchQueue( inTest->keepalive, inTest->queue ); require_noerr( err, exit ); break; case kKeepAliveCallVariant_TakesSockAddrs: kat_ulog( kLogLevelInfo, "%s: Will call DNSServiceSleepKeepalive_sockaddr() for local and remote sockaddrs\n", prefix ); check( !inTest->keepalive ); err = DNSServiceSleepKeepalive_sockaddr( &inTest->keepalive, 0, &subtest->local.sa, &subtest->remote.sa, subtest->timeoutKA, _KeepAliveTestKeepaliveCallback, inTest ); require_noerr( err, exit ); err = DNSServiceSetDispatchQueue( inTest->keepalive, inTest->queue ); require_noerr( err, exit ); break; default: kat_ulog( kLogLevelError, "%s: Invalid KeepAliveCallVariant value %d\n", prefix, (int) params->callVariant ); err = kInternalErr; goto exit; } // Use the same logic that the DNSServiceSleepKeepalive functions use to derive a record name and rdata. value = 0; family = subtest->local.sa.sa_family; if( family == AF_INET ) { const struct sockaddr_in * const sin = &subtest->local.v4; const uint8_t * ptr; check_compile_time_code( sizeof( sin->sin_addr.s_addr ) == 4 ); ptr = (const uint8_t *) &sin->sin_addr.s_addr; for( i = 0; i < 4; ++i ) value += ptr[ i ]; value += sin->sin_port; // Note: No ntohl(). This is what DNSServiceSleepKeepalive does. check( subtest->remote.sa.sa_family == AF_INET ); ASPrintF( &subtest->dataStr, "t=%u h=%.4a d=%.4a l=%u r=%u", subtest->timeoutKA, &subtest->local.v4.sin_addr.s_addr, &subtest->remote.v4.sin_addr.s_addr, ntohs( subtest->local.v4.sin_port ), ntohs( subtest->remote.v4.sin_port ) ); require_action( subtest->dataStr, exit, err = kNoMemoryErr ); } else if( family == AF_INET6 ) { const struct sockaddr_in6 * const sin6 = &subtest->local.v6; check_compile_time_code( countof( sin6->sin6_addr.s6_addr ) == 16 ); for( i = 0; i < 16; ++i ) value += sin6->sin6_addr.s6_addr[ i ]; value += sin6->sin6_port; // Note: No ntohl(). This is what DNSServiceSleepKeepalive does. check( subtest->remote.sa.sa_family == AF_INET6 ); ASPrintF( &subtest->dataStr, "t=%u H=%.16a D=%.16a l=%u r=%u", subtest->timeoutKA, subtest->local.v6.sin6_addr.s6_addr, subtest->remote.v6.sin6_addr.s6_addr, ntohs( subtest->local.v6.sin6_port ), ntohs( subtest->remote.v6.sin6_port ) ); require_action( subtest->dataStr, exit, err = kNoMemoryErr ); } else { kat_ulog( kLogLevelError, "%s: Unexpected local address family %d\n", prefix, family ); err = kInternalErr; goto exit; } // Start query for the new keepalive record. check( !subtest->recordName ); ASPrintF( &subtest->recordName, "%u._keepalive._dns-sd._udp.local.", value ); require_action( subtest->recordName, exit, err = kNoMemoryErr ); kat_ulog( kLogLevelInfo, "%s: Will query for %s NULL record\n", prefix, subtest->recordName ); check( !inTest->query ); err = DNSServiceQueryRecord( &inTest->query, 0, kDNSServiceInterfaceIndexLocalOnly, subtest->recordName, kDNSServiceType_NULL, kDNSServiceClass_IN, _KeepAliveTestQueryRecordCallback, inTest ); require_noerr( err, exit ); err = DNSServiceSetDispatchQueue( inTest->query, inTest->queue ); require_noerr( err, exit ); // Start timer to enforce a time limit on the query. check( !inTest->timer ); err = DispatchTimerOneShotCreate( dispatch_time_seconds( kKeepAliveTestQueryTimeoutSecs ), kKeepAliveTestQueryTimeoutSecs * ( INT64_C_safe( kNanosecondsPerSecond ) / 20 ), inTest->queue, _KeepAliveTestQueryTimerHandler, inTest, &inTest->timer ); require_noerr( err, exit ); dispatch_resume( inTest->timer ); exit: ForgetSocket( &inSock ); if( subtestFailed ) { err = _KeepAliveTestContinue( inTest, err, &done ); check_noerr( err ); } else { done = false; } if( err || done ) _KeepAliveTestStop( inTest, err ); } //=========================================================================================================================== static void _KeepAliveTestForgetConnection( KeepAliveTestRef inTest ) { if( inTest->connection ) { check( inTest->connectionCtx ); inTest->connectionCtx->test = NULL; // Unset the connection's back pointer to test. inTest->connectionCtx = NULL; // Context will be freed by the connection's handler. AsyncConnection_Forget( &inTest->connection ); } } //=========================================================================================================================== static void DNSSD_API _KeepAliveTestKeepaliveCallback( DNSServiceRef inSDRef, DNSServiceErrorType inError, void *inCtx ) { OSStatus err; const KeepAliveTestRef test = (KeepAliveTestRef) inCtx; char prefix[ 64 ]; Unused( inSDRef ); _KeepAliveTestGetSubtestLogPrefix( test, prefix, sizeof( prefix ) ); kat_ulog( kLogLevelInfo, "%s: Keepalive callback error: %#m\n", prefix, inError ); if( inError ) { Boolean done; err = _KeepAliveTestContinue( test, inError, &done ); check_noerr( err ); if( err || done ) _KeepAliveTestStop( test, err ); } } //=========================================================================================================================== static void _KeepAliveTestQueryTimerHandler( void *inCtx ) { OSStatus err; const KeepAliveTestRef test = (KeepAliveTestRef) inCtx; KeepAliveSubtest * const subtest = _KeepAliveTestGetSubtest( test ); Boolean done; char prefix[ 64 ]; _KeepAliveTestGetSubtestLogPrefix( test, prefix, sizeof( prefix ) ); kat_ulog( kLogLevelInfo, "%s: Query for \"%s\" timed out.\n", prefix, subtest->recordName ); err = _KeepAliveTestContinue( test, kTimeoutErr, &done ); check_noerr( err ); if( err || done ) _KeepAliveTestStop( test, err ); } //=========================================================================================================================== static void DNSSD_API _KeepAliveTestQueryRecordCallback( 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 * inCtx ) { OSStatus err; const KeepAliveTestRef test = (KeepAliveTestRef) inCtx; KeepAliveSubtest * const subtest = _KeepAliveTestGetSubtest( test ); const uint8_t * ptr; size_t dataStrLen, minLen; Boolean done; char prefix[ 64 ]; Unused( inSDRef ); Unused( inInterfaceIndex ); Unused( inTTL ); _KeepAliveTestGetSubtestLogPrefix( test, prefix, sizeof( prefix ) ); if( strcasecmp( inFullName, subtest->recordName ) != 0 ) { kat_ulog( kLogLevelError, "%s: QueryRecord(%s) result: Got unexpected record name \"%s\".\n", prefix, subtest->recordName, inFullName ); err = kUnexpectedErr; goto exit; } if( inType != kDNSServiceType_NULL ) { kat_ulog( kLogLevelError, "%s: QueryRecord(%s) result: Got unexpected record type %d (%s) != %d (NULL).\n", prefix, subtest->recordName, inType, RecordTypeToString( inType ), kDNSServiceType_NULL ); err = kUnexpectedErr; goto exit; } if( inClass != kDNSServiceClass_IN ) { kat_ulog( kLogLevelError, "%s: QueryRecord(%s) result: Got unexpected record class %d != %d (IN).\n", prefix, subtest->recordName, inClass, kDNSServiceClass_IN ); err = kUnexpectedErr; goto exit; } if( inError ) { kat_ulog( kLogLevelError, "%s: QueryRecord(%s) result: Got unexpected error %#m.\n", prefix, subtest->recordName, inError ); err = inError; goto exit; } if( ( inFlags & kDNSServiceFlagsAdd ) == 0 ) { kat_ulog( kLogLevelError, "%s: QueryRecord(%s) result: Missing Add flag.\n", prefix, subtest->recordName ); err = kUnexpectedErr; goto exit; } kat_ulog( kLogLevelInfo, "%s: QueryRecord(%s) result rdata: %#H\n", prefix, subtest->recordName, inRDataPtr, inRDataLen, inRDataLen ); dataStrLen = strlen( subtest->dataStr ) + 1; // There's a NUL terminator at the end of the rdata. minLen = 1 + dataStrLen; // The first byte of the rdata is a length byte. if( inRDataLen < minLen ) { kat_ulog( kLogLevelError, "%s: QueryRecord(%s) result: rdata length (%d) is less than expected minimum (%zu).\n", prefix, subtest->recordName, inRDataLen, minLen ); err = kUnexpectedErr; goto exit; } ptr = (const uint8_t *) inRDataPtr; if( ptr[ 0 ] < dataStrLen ) { kat_ulog( kLogLevelError, "%s: QueryRecord(%s) result: rdata length byte value (%d) is less than expected minimum (%zu).\n", prefix, subtest->recordName, ptr[ 0 ], dataStrLen ); err = kUnexpectedErr; goto exit; } if( memcmp( &ptr[ 1 ], subtest->dataStr, dataStrLen - 1 ) != 0 ) { kat_ulog( kLogLevelError, "%s: QueryRecord(%s) result: rdata body doesn't contain '%s'.\n", prefix, subtest->recordName, subtest->dataStr ); } err = kNoErr; exit: err = _KeepAliveTestContinue( test, err, &done ); check_noerr( err ); if( err || done ) _KeepAliveTestStop( test, kNoErr ); } #endif // TARGET_OS_DARWIN #if ( ENABLE_DNSSDUTIL_DNSSEC_TEST == 1 ) //=========================================================================================================================== // DNSSECTestCmd //=========================================================================================================================== #define kDNSSECTestQueryTimeoutSecs 4 typedef struct DNSSECTest_BasicValidationContext { uint32_t number_of_answers; // The number of answers expected to receive in the "basic validation" test } DNSSECTest_BasicValidationContext; //=========================================================================================================================== typedef struct { DNSServiceRef query; dispatch_source_t queryTimer; // Used to setup timeout timer, to prevent from waiting forever. Boolean testStarted; const char * testName; // The name of the curreent running test case. const char * testCaseName; // The query name in the subtest of the test case. union { DNSSECTest_BasicValidationContext basicValidation; } testCaseContext; // Contains different customized context pointer, which can be used by different test cases. const char * subtestQueryName; // The query name that is passed to mDNSResponder API int subtestIndex; // The index of the current case in the test input array. pid_t localServerPID; // The pid of the dnssdutil server NanoTime64 startTime; // The time when the DNSSEC test case starts. CFMutableArrayRef subtestReport; // The reference to the CFMutableArrayRef, which contains subtest reports for different subtests NanoTime64 subtestStartTime; // The time when the subtest starts Boolean subtestFailed; // Indicate if any subtest failed before. char * outputFilePath; // File to write test results to. If NULL, then write to stdout. (malloced) OutputFormatType outputFormat; // Format of test report output. } DNSSECTestContext; //=========================================================================================================================== typedef struct { const char * testCaseName; // The name of the test case that will be run. void (*testCaseHandler)( DNSSECTestContext * context ); // The main function of the test case. } DNSSECTestTestCase; //=========================================================================================================================== static void DNSSECTestSetupLocalDNSServer( DNSSECTestContext *context ); static void DNSSECTestStartTestCase( DNSSECTestContext *context ); //=========================================================================================================================== // DNSSEC test cases static void DNSSECTest_BasicValidation( DNSSECTestContext *context ); static DNSSECTestTestCase DNSSECTestTestCases[] = { { "basic validation", DNSSECTest_BasicValidation } }; //=========================================================================================================================== // The main function to start the DNSSEC test static void DNSSECTestCmd( void ) { OSStatus err; dispatch_source_t signalSource = NULL; DNSSECTestContext * context = NULL; // Set up SIGINT handler. signal( SIGINT, SIG_IGN ); err = DispatchSignalSourceCreate( SIGINT, dispatch_get_main_queue(), Exit, kExitReason_SIGINT, &signalSource ); require_noerr( err, exit ); dispatch_resume( signalSource ); // Create the test context. context = (DNSSECTestContext *) calloc( 1, sizeof( *context ) ); require_action( context, exit, err = kNoMemoryErr ); context->testStarted = false; // Get the command line option. context->testCaseName = strdup( gDNSSECTest_TestCaseName ); require( context->testCaseName , exit ); // Start the subtest from index 0 context->subtestIndex = 0; // Get the output format. err = OutputFormatFromArgString( gDNSSECTest_OutputFormat, &context->outputFormat ); require_noerr_quiet( err, exit ); // Get the output file path. if( gDNSSECTest_OutputFilePath ) { context->outputFilePath = strdup( gDNSSECTest_OutputFilePath ); require_noerr_quiet( context->outputFilePath , exit ); } // Initialize the CFArray to store the test result. context->subtestReport = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks ); context->startTime = NanoTimeGetCurrent(); // Start the local dnssdutil server. DNSSECTestSetupLocalDNSServer( context ); // Start the test. DNSSECTestStartTestCase( context ); exit: exit( 1 ); } //=========================================================================================================================== // Start the local DNS server with dnssdutil server command. static void DNSSECTestSetupLocalDNSServer( DNSSECTestContext *context ) { Unused( context ); pid_t pid = getpid(); OSStatus err = _SpawnCommand( &context->localServerPID, NULL, NULL, DNSSDUTIL_TEST_DNS_SERVER_COMMAND_PREAMBLE " --loopback --follow %lld", (int64_t) pid ); require_noerr_action( err, exit, FPrintF( stderr, DNSSDUTIL_TEST_DNS_SERVER_COMMAND_PREAMBLE " --loopback --follow %lld failed, error: %d\n", (int64_t) pid, err ) ); // Wait long enough to allow the DNS server being setup. sleep( 2 ); return; exit: exit( 1 ); } //=========================================================================================================================== // Start the test case that user specifies. static void DNSSECTestStartTestCase( DNSSECTestContext *context ) { Boolean findTestCase = false; for ( int i = 0; i < (int)countof( DNSSECTestTestCases ); i++ ) { if( strcmp( context->testCaseName, DNSSECTestTestCases[i].testCaseName ) == 0 ) { DNSSECTestTestCases[ i ].testCaseHandler( context ); findTestCase = true; } } if( !findTestCase ) { FPrintF( stdout, "Unknown test case \"%s\"\n", context->testCaseName ); exit( 1 ); } } //=========================================================================================================================== // Write the current subtest status into the report list. When this function is called, either some error occurs or the // subtest finishes. static void _DNSSECTestQueryWriteSubtestReport( DNSSECTestContext *inContext, const char *errorDescription ) { OSStatus err; NanoTime64 now; char startTime[ 32 ]; char endTime[ 32 ]; now = NanoTimeGetCurrent(); _NanoTime64ToTimestamp( inContext->subtestStartTime, startTime, sizeof( startTime ) ); _NanoTime64ToTimestamp( now, endTime, sizeof( endTime ) ); err = CFPropertyListAppendFormatted( kCFAllocatorDefault, inContext->subtestReport, "{" "%kO=%s" "%kO=%s" "%kO=%s" "%kO=%s" "%kO=%s" "}", CFSTR( "Start Time" ), startTime, CFSTR( "End Time" ), endTime, CFSTR( "Subtest Query Name" ), inContext->subtestQueryName, CFSTR( "Result" ), errorDescription ? "Fail" : "Pass", CFSTR( "Error Description" ), errorDescription ? errorDescription : "No Error" ); require_noerr( err, exit ); return; exit: ErrQuit( 1, "error: %#m\n", err ); } //=========================================================================================================================== // Output the final test result to the file (path specified by the user) in the disk. static void _DNSSECTestQueryOutputFinalReport( DNSSECTestContext *inContext ) { OSStatus err; CFPropertyListRef plist; NanoTime64 now; char startTime[ 32 ]; char endTime[ 32 ]; now = NanoTimeGetCurrent(); _NanoTime64ToTimestamp( inContext->startTime, startTime, sizeof( startTime ) ); _NanoTime64ToTimestamp( now, endTime, sizeof( endTime ) ); err = CFPropertyListCreateFormatted( kCFAllocatorDefault, &plist, "{" "%kO=%s" "%kO=%s" "%kO=%s" "%kO=%b" "%kO=%O" "}", CFSTR( "Start Time" ), startTime, CFSTR( "End Time" ), endTime, CFSTR( "Test Case Name" ), inContext->testCaseName, CFSTR( "All Passed" ), !inContext->subtestFailed, CFSTR( "Subtest Reports" ), inContext->subtestReport ); require_noerr( err, exit ); ForgetCF( &inContext->subtestReport ); err = OutputPropertyList( plist, inContext->outputFormat, inContext->outputFilePath ); CFRelease( plist ); require_noerr( err, exit ); return; exit: ErrQuit( 1, "error: %#m\n", err ); } //=========================================================================================================================== // The handler that will be called when timeout happens. When timeout happens it always means something bad happen, and // it might be possible that all the remaning tests timeout too, so the function exits directly instead of continuing. static void _DNSSECTestQueryTimeoutHandler( void *inContext ) { DNSSECTestContext * const context = (DNSSECTestContext *) inContext; DNSServiceForget( &context->query ); dispatch_source_forget( &context->queryTimer ); context->subtestFailed = true; _DNSSECTestQueryWriteSubtestReport( context, "Query for DNSSEC-related records timed out" ); _DNSSECTestQueryOutputFinalReport( context ); exit( 1 ); } //=========================================================================================================================== #pragma mark - Test case "basic validation" //=========================================================================================================================== typedef struct DNSSECTest_BasicValidationTestCase { const char * queryName; // The query name that controls the behavior of the local DNS server. uint16_t queryType; // The DNS record being queried. uint32_t num_of_answers; // How many anwers are expected to be returned. } DNSSECTest_BasicValidationTestCase; //=========================================================================================================================== DNSSECTest_BasicValidationTestCase DNSSECTest_BasicValidationTestCases[] = { // kDNSSECAlgorithm_RSASHA1 5 { "count-1.z-5-1.z-5-2.z-5-3.dnssec.test.", kDNSServiceType_A, 1 }, { "count-2.z-5-1.z-5-2.z-5-3.dnssec.test.", kDNSServiceType_A, 2 }, { "count-1.z-5-1.z-5-2.z-5-3.dnssec.test.", kDNSServiceType_AAAA, 1 }, { "count-2.z-5-1.z-5-2.z-5-3.dnssec.test.", kDNSServiceType_AAAA, 2 }, { "count-10.z-5-1.z-5-2.z-5-3.dnssec.test.", kDNSServiceType_A, 10 }, // kDNSSECAlgorithm_RSASHA256 8 { "count-1.z-8-1.z-8-2.z-8-3.dnssec.test.", kDNSServiceType_A, 1 }, { "count-2.z-8-1.z-8-2.z-8-3.dnssec.test.", kDNSServiceType_A, 2 }, { "count-1.z-8-1.z-8-2.z-8-3.dnssec.test.", kDNSServiceType_AAAA, 1 }, { "count-2.z-8-1.z-8-2.z-8-3.dnssec.test.", kDNSServiceType_AAAA, 2 }, { "count-10.z-8-1.z-8-2.z-8-3.dnssec.test.", kDNSServiceType_A, 10 }, // kDNSSECAlgorithm_RSASHA512 10 { "count-1.z-10-1.z-10-2.z-10-3.dnssec.test.", kDNSServiceType_A, 1 }, { "count-2.z-10-1.z-10-2.z-10-3.dnssec.test.", kDNSServiceType_A, 2 }, { "count-1.z-10-1.z-10-2.z-10-3.dnssec.test.", kDNSServiceType_AAAA, 1 }, { "count-2.z-10-1.z-10-2.z-10-3.dnssec.test.", kDNSServiceType_AAAA, 2 }, { "count-10.z-10-1.z-10-2.z-10-3.dnssec.test.", kDNSServiceType_A, 10 }, // kDNSSECAlgorithm_ECDSAP256SHA256 13 { "count-1.z-13-1.z-13-2.z-13-3.dnssec.test.", kDNSServiceType_A, 1 }, { "count-2.z-13-1.z-13-2.z-13-3.dnssec.test.", kDNSServiceType_A, 2 }, { "count-1.z-13-1.z-13-2.z-13-3.dnssec.test.", kDNSServiceType_AAAA, 1 }, { "count-2.z-13-1.z-13-2.z-13-3.dnssec.test.", kDNSServiceType_AAAA, 2 }, { "count-10.z-13-1.z-13-2.z-13-3.dnssec.test.", kDNSServiceType_A, 10 }, // kDNSSECAlgorithm_ECDSAP384SHA384 14 { "count-1.z-14-1.z-14-2.z-14-3.dnssec.test.", kDNSServiceType_A, 1 }, { "count-2.z-14-1.z-14-2.z-14-3.dnssec.test.", kDNSServiceType_A, 2 }, { "count-1.z-14-1.z-14-2.z-14-3.dnssec.test.", kDNSServiceType_AAAA, 1 }, { "count-2.z-14-1.z-14-2.z-14-3.dnssec.test.", kDNSServiceType_AAAA, 2 }, { "count-10.z-14-1.z-14-2.z-14-3.dnssec.test.", kDNSServiceType_A, 10 }, // Mixed use of mutiple DNSKEY algorithms { "count-1.z-5-1.z-8-2.z-10-3.dnssec.test.", kDNSServiceType_A, 1 }, { "count-2.z-5-1.z-5-2.z-8-3.dnssec.test.", kDNSServiceType_A, 2 }, { "count-1.z-8-1.z-5-2.z-5-3.dnssec.test.", kDNSServiceType_AAAA, 1 }, { "count-2.z-10-1.z-8-2.z-8-3.dnssec.test.", kDNSServiceType_AAAA, 2 }, { "count-10.z-10-1.z-8-2.z-5-3.dnssec.test.", kDNSServiceType_A, 10 }, { "count-1.z-13-1.z-14-2.z-14-3.dnssec.test.", kDNSServiceType_A, 1 }, { "count-2.z-14-1.z-13-2.z-8-3.dnssec.test.", kDNSServiceType_A, 2 }, { "count-1.z-5-1.z-14-2.z-8-3.dnssec.test.", kDNSServiceType_AAAA, 1 }, { "count-2.z-10-1.z-8-2.z-13-3.dnssec.test.", kDNSServiceType_AAAA, 2 }, { "count-10.z-14-1.z-13-2.z-5-3.dnssec.test.", kDNSServiceType_A, 10 } }; //=========================================================================================================================== static void DNSSD_API _DNSSECTest_BasicValidationCallBack( 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 Boolean _DNSSECTest_BasicValidationVerifyTheResponse( DNSSECTestContext * inContext, 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, char * inStrBuffer, uint32_t inBufferLen, Boolean * outExpectMore ); //=========================================================================================================================== static void DNSSECTest_BasicValidation( DNSSECTestContext *inContext ) { OSStatus err; const char * error_description = NULL; // Get the current subtest DNSSECTest_BasicValidationTestCase * testCases = &DNSSECTest_BasicValidationTestCases[ inContext->subtestIndex ]; inContext->subtestQueryName = testCases->queryName; inContext->testCaseContext.basicValidation.number_of_answers = testCases->num_of_answers; // Issue the query. err = DNSServiceQueryRecord( &inContext->query, kDNSServiceFlagsEnableDNSSEC, 0, testCases->queryName, testCases->queryType, kDNSServiceClass_IN, _DNSSECTest_BasicValidationCallBack, inContext ); require_noerr_action( err, exit, error_description = "DNSServiceQueryRecord failed" ); // Set the dispatch queue. err = DNSServiceSetDispatchQueue( inContext->query, dispatch_get_main_queue() ); require_noerr_action( err, exit, error_description = "DNSServiceSetDispatchQueue failed" ); // Create a timer to handle timeout. err = DispatchTimerCreate( dispatch_time_seconds( kDNSSECTestQueryTimeoutSecs ), DISPATCH_TIME_FOREVER, UINT64_C_safe( kDNSSECTestQueryTimeoutSecs ) * kNanosecondsPerSecond / 10, NULL, _DNSSECTestQueryTimeoutHandler, NULL, inContext, &inContext->queryTimer ); require_noerr_action( err, exit, error_description = "DispatchTimerCreate failed" ); dispatch_resume( inContext->queryTimer ); // start the current subtest, and record the start time. inContext->subtestStartTime = NanoTimeGetCurrent(); if( !inContext->testStarted ) { // Only call dispatch_main once, when we first start the test. inContext->testStarted = true; dispatch_main(); } return; exit: _DNSSECTestQueryWriteSubtestReport( inContext, error_description ); _DNSSECTestQueryOutputFinalReport( inContext ); exit( 1 ); } //=========================================================================================================================== static void DNSSD_API _DNSSECTest_BasicValidationCallBack( 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 ) { char err_desp_str[1024]; Boolean valid; Boolean expectMore; DNSSECTestContext * const context = (DNSSECTestContext *)inContext; Unused( inSDRef ); err_desp_str[0] = '\0'; // Verify if the response is expected valid = _DNSSECTest_BasicValidationVerifyTheResponse(context, inFlags, inInterfaceIndex, inError, inFullName, inType, inClass, inRDataLen, inRDataPtr, inTTL, err_desp_str, sizeof( err_desp_str ), &expectMore); // If more answers are expected to be returned. if( expectMore ) { return; } // Cancel the current request and its corresponding timer. DNSServiceForget( &context->query ); dispatch_source_forget( &context->queryTimer ); // Check if the test fails. if( !valid ) { context->subtestFailed = true; } _DNSSECTestQueryWriteSubtestReport( context, valid ? NULL : err_desp_str ); // Increment the index. context->subtestIndex++; if( context->subtestIndex == countof( DNSSECTest_BasicValidationTestCases ) ) { // All test cases have been run _DNSSECTestQueryOutputFinalReport( context ); exit( context->subtestFailed ? 2 : 0 ); } // Start the next subtest. DNSSECTest_BasicValidation( context ); } //=========================================================================================================================== // Check if the response is expected. Return true if the response is expected, otherwise return false. static Boolean _DNSSECTest_BasicValidationVerifyTheResponse( DNSSECTestContext * inContext, 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, char * inStrBuffer, uint32_t inBufferLen, Boolean * outExpectMore ) { const char * const queryName = inContext->subtestQueryName; DNSSECTest_BasicValidationTestCase * testCases = &DNSSECTest_BasicValidationTestCases[ inContext->subtestIndex ]; DNSSECTest_BasicValidationContext * subContext = &inContext->testCaseContext.basicValidation; Boolean expectMore = false; Unused( inClass ); Unused( inRDataLen ); Unused( inRDataPtr ); Unused( inTTL ); require_noerr_action( inError, exit, SNPrintF( inStrBuffer, inBufferLen, "Unexpected DNSServiceErrorType: %d", inError ) ); require_action( inFlags & kDNSServiceFlagsAdd, exit, SNPrintF( inStrBuffer, inBufferLen, "Unexpected add/remove event: %#{flags}", inFlags, kDNSServiceFlagsDescriptors ) ); require_action( inInterfaceIndex == kDNSServiceInterfaceIndexAny, exit, SNPrintF( inStrBuffer, inBufferLen, "Non-local-only interface is used: %u", inInterfaceIndex ) ); require_action( strcmp( queryName, inFullName ) == 0, exit, SNPrintF( inStrBuffer, inBufferLen, "Mismatched name: %s", inFullName ) ); require_action( inType == testCases->queryType, exit, SNPrintF( inStrBuffer, inBufferLen, "Mismatched query type: %s", RecordTypeToString( inType ) ) ); require_action( (inFlags & kDNSServiceFlagsSecure) == kDNSServiceFlagsSecure, exit, SNPrintF( inStrBuffer, inBufferLen, "Unexpected DNSSEC result: %#{flags}", (inFlags & kDNSServiceFlagsSecure), kDNSServiceFlagsDescriptors ) ); if( --subContext->number_of_answers != 0 ) { expectMore = true; } exit: if( outExpectMore ) { *outExpectMore = expectMore; } return inStrBuffer[0] != '\0' ? false : true; } #else static void DNSSECTestCmd( void ) { exit( 0 ); } #endif // #if ( ENABLE_DNSSDUTIL_DNSSEC_TEST == 1 ) //=========================================================================================================================== // OptimisticDNSTestCommand //=========================================================================================================================== typedef struct OptimisticDNSTest * OptimisticDNSTestRef; struct OptimisticDNSTest { dispatch_queue_t queue; // Serial queue for test events. dispatch_semaphore_t doneSem; // Semaphore to signal when the test is done. dnssd_getaddrinfo_t gai; // For GAI operations. dispatch_source_t timer; // Timer for enforcing time limits. mdns_domain_name_t domain; // High-level domain for test's hostnames. char * hostname; // Hostname used in current GAI operation. CFPropertyListRef savedAASDPref; // The saved AlwaysAppendSearchDomains preference value. [1] pid_t serverPID; // PID of spawned test DNS server. int32_t refCount; // Test's reference count. OSStatus error; // Test's error code. sockaddr_ip expectedAddr; // The expected IP address from a GAI operation. Boolean modifiedDaemon; // True if the test needs to undo mDNSResponder modifications. [2] Boolean fullTest; // Run full test, even if mDNSResponder needs to be killed. uint8_t expectedHostname[ kDomainNameLengthMax ]; }; // Notes: // 1. The saved AlwaysAppendSearchDomains preference will be restored after the test is done testing with its own // AlwaysAppendSearchDomains value. // 2. For example, the test might have set a preference and restarted mDNSResponder for the preference to take effect. // Cleanup would consist of restoring the preference and restarting mDNSResponder again. static OptimisticDNSTestRef _OptimisticDNSTestCreate( OSStatus *outError, Boolean inFullTest ); static OSStatus _OptimisticDNSTestRun( OptimisticDNSTestRef inTest ); static void _OptimisticDNSTestRelease( OptimisticDNSTestRef inTest ); static void OptimisticDNSTestCommand( void ) { OSStatus err; OptimisticDNSTestRef test = NULL; const Boolean fullTest = ( gOptimisticDNSTest_FullTest != 0 ); if( fullTest ) { err = CheckRootUser(); require_noerr_quiet( err, exit ); } test = _OptimisticDNSTestCreate( &err, fullTest ); require_noerr( err, exit ); err = _OptimisticDNSTestRun( test ); require_noerr( err, exit ); exit: if( test ) _OptimisticDNSTestRelease( test ); gExitCode = err ? 1 : 0; } //=========================================================================================================================== static OptimisticDNSTestRef _OptimisticDNSTestCreate( OSStatus * const outError, const Boolean inFullTest ) { OSStatus err; OptimisticDNSTestRef test = NULL; OptimisticDNSTestRef obj = (OptimisticDNSTestRef) calloc( 1, sizeof( *obj ) ); require_action( obj, exit, err = kNoMemoryErr ); obj->refCount = 1; obj->error = kInProgressErr; obj->serverPID = -1; obj->fullTest = inFullTest; obj->queue = dispatch_queue_create( "com.apple.dnssdutil.optimistic-dns-test", DISPATCH_QUEUE_SERIAL ); require_action( obj->queue, exit, err = kNoResourcesErr ); obj->doneSem = dispatch_semaphore_create( 0 ); require_action( obj->doneSem, exit, err = kNoResourcesErr ); test = obj; obj = NULL; err = kNoErr; exit: if( outError ) *outError = err; if( obj ) _OptimisticDNSTestRelease( obj ); return( test ); } //=========================================================================================================================== static void _OptimisticDNSTestRetain( const OptimisticDNSTestRef me ) { atomic_add_32( &me->refCount, 1 ); } //=========================================================================================================================== static void _OptimisticDNSTestStop( const OptimisticDNSTestRef me, const OSStatus inError ) { me->error = inError; if( !me->error ) { FPrintF( stdout, "%{du:time} Test PASSED\n", NULL ); } else { FPrintF( stdout, "%{du:time} Test FAILED: %#m\n", NULL, me->error ); } dnssd_getaddrinfo_forget( &me->gai ); dispatch_source_forget( &me->timer ); mdns_forget( &me->domain ); ForgetMem( &me->hostname ); if( me->serverPID >= 0 ) { OSStatus killErr; killErr = kill( me->serverPID, SIGTERM ); killErr = map_global_noerr_errno( killErr ); check_noerr( killErr ); me->serverPID = -1; } if( me->modifiedDaemon ) { const CFStringRef keyAASD = CFSTR( kMDNSResponderPrefStr_AlwaysAppendSearchDomains ); FPrintF( stdout, "\n%{du:time} Cleanup:\n", NULL ); FPrintF( stdout, "%{du:time} ⚒ Restoring %@ preference to '%@'\n", NULL, keyAASD, me->savedAASDPref ); const CFStringRef appID = CFSTR( kMDNSResponderPrefAppIDStr ); CFPreferencesSetValue( keyAASD, me->savedAASDPref, appID, kCFPreferencesAnyUser, kCFPreferencesCurrentHost ); FPrintF( stdout, "%{du:time} ↻ Restarting mDNSResponder\n", NULL ); const OSStatus killErr = systemf( NULL, "killall -KILL mDNSResponder" ); if( killErr ) { FPrintF( stdout, "%{du:time} x Error killing mDNSResponder: %#m\n", NULL, killErr ); } } CFForget( &me->savedAASDPref ); dispatch_semaphore_signal( me->doneSem ); } //=========================================================================================================================== static dnssd_getaddrinfo_t _OptimisticDNSTestCreateGAI( const OptimisticDNSTestRef me, const char * const inHostname, const DNSServiceFlags inFlags ) { const dnssd_getaddrinfo_t gai = dnssd_getaddrinfo_create(); require_quiet( gai, exit ); dnssd_getaddrinfo_set_hostname( gai, inHostname ); dnssd_getaddrinfo_set_flags( gai, inFlags ); dnssd_getaddrinfo_set_interface_index( gai, kDNSServiceInterfaceIndexAny ); dnssd_getaddrinfo_set_protocols( gai, kDNSServiceProtocol_IPv4 ); dnssd_getaddrinfo_set_queue( gai, me->queue ); dnssd_retain( gai ); _OptimisticDNSTestRetain( me ); dnssd_getaddrinfo_set_event_handler( gai, ^( const dnssd_event_t inEvent, const DNSServiceErrorType inGAIError ) { switch( inEvent ) { case dnssd_event_invalidated: dnssd_release( gai ); _OptimisticDNSTestRelease( me ); break; case dnssd_event_error: require_return( me->gai == gai ); FPrintF( stdout, "dnssd_getaddrinfo error: %#m\n", inGAIError ); _OptimisticDNSTestStop( me, inGAIError ); break; case dnssd_event_remove_all: break; } } ); exit: return( gai ); } //=========================================================================================================================== static OSStatus _OptimisticDNSTestStartSubtest7_2( const OptimisticDNSTestRef me ) { OSStatus err; __block Boolean gotExpiredResult = false; __block Boolean gotAddResult = false; FPrintF( stdout, "%{du:time} Subtest 7.2: Second multi-label PQDN GAI for hostname that has one CNAME record and that doesn't" " depend on a search domain (CNAME record expired, AlwaysAppendSearchDomains enabled)\n", NULL ); // Start the GAI operation. check( me->hostname ); const unsigned int timeLimitSecs = 5; FPrintF( stdout, "%{du:time} * Starting GAI for multi-label PQDN '%s' with %u second time limit\n", NULL, me->hostname, timeLimitSecs ); const DNSServiceFlags flags = kDNSServiceFlagsReturnIntermediates | kDNSServiceFlagsAllowExpiredAnswers; me->gai = _OptimisticDNSTestCreateGAI( me, me->hostname, flags ); require_action( me->gai, exit, err = kNoResourcesErr ); const dnssd_getaddrinfo_t gai = me->gai; dnssd_getaddrinfo_set_result_handler( me->gai, ^( dnssd_getaddrinfo_result_t * const inResults, const size_t inCount ) { require_return( me->gai == gai ); size_t unexpectedResultCount = 0; for( size_t i = 0; i < inCount; ++i ) { Boolean resultIsExpected = false; const dnssd_getaddrinfo_result_t result = inResults[ i ]; const dnssd_getaddrinfo_result_type_t resultType = dnssd_getaddrinfo_result_get_type( result ); const char * const actualHostnameStr = dnssd_getaddrinfo_result_get_actual_hostname( result ); const bool fromCache = dnssd_getaddrinfo_result_is_from_cache( result ); if( SockAddrCompareAddr( dnssd_getaddrinfo_result_get_address( result ), &me->expectedAddr.sa ) == 0 ) { // Since this is a GAI for a single-label instead of a FQDN, to make sure that appropriate search domain // was appended, check the actual hostname. uint8_t actualHostname[ kDomainNameLengthMax ]; OSStatus nameErr = DomainNameFromString( actualHostname, actualHostnameStr, NULL ); if( !nameErr && DomainNameEqual( actualHostname, me->expectedHostname ) ) { if( !gotExpiredResult ) { if( ( resultType == dnssd_getaddrinfo_result_type_expired ) && fromCache ) { gotExpiredResult = true; resultIsExpected = true; } } else if ( !gotAddResult ) { if( ( resultType == dnssd_getaddrinfo_result_type_add ) && !fromCache ) { gotAddResult = true; resultIsExpected = true; } } } } if( !resultIsExpected ) ++unexpectedResultCount; FPrintF( stdout, "%{du:time} %s Got %sexpected result -- type: %s, hostname: %s, address: %##a, actual hostname: %s, " "from cache: %s\n", NULL, resultIsExpected ? "✓" : "x", resultIsExpected ? "" : "un", dnssd_getaddrinfo_result_type_to_string( resultType ), dnssd_getaddrinfo_result_get_hostname( result ), dnssd_getaddrinfo_result_get_address( result ), actualHostnameStr, YesNoStr( fromCache ) ); } if( unexpectedResultCount > 0 ) _OptimisticDNSTestStop( me, kUnexpectedErr ); } ); dnssd_getaddrinfo_activate( me->gai ); // Start the time limit timer. check( !me->timer ); me->timer = dispatch_source_create( DISPATCH_SOURCE_TYPE_TIMER, 0, 0, me->queue ); require_action_quiet( me->timer, exit, err = kNoResourcesErr ); dispatch_source_set_timer( me->timer, dispatch_time_seconds( timeLimitSecs ), DISPATCH_TIME_FOREVER, 0 ); dispatch_source_set_event_handler( me->timer, ^{ dispatch_source_forget( &me->timer ); dnssd_getaddrinfo_forget( &me->gai ); OSStatus finalErr; if( !gotExpiredResult || !gotAddResult ) { if( !gotExpiredResult ) { FPrintF( stdout, "%{du:time} Failed to get EXPIRED GAI result for '%s' after %u seconds.\n", NULL, me->hostname, timeLimitSecs ); } if( !gotAddResult ) { FPrintF( stdout, "%{du:time} Failed to get ADD GAI result for '%s' after %u seconds.\n", NULL, me->hostname, timeLimitSecs ); } finalErr = kTimeoutErr; } else { finalErr = kNoErr; } _OptimisticDNSTestStop( me, finalErr ); } ); dispatch_activate( me->timer ); err = kNoErr; exit: return( err ); } //=========================================================================================================================== static OSStatus _OptimisticDNSTestStartSubtest7_1( const OptimisticDNSTestRef me ) { OSStatus err; __block Boolean gotAddResult = false; FPrintF( stdout, "%{du:time} Subtest 7.1: First multi-label PQDN GAI for hostname that has one CNAME record and that doesn't" " depend on a search domain (AlwaysAppendSearchDomains enabled)\n", NULL ); // Create the hostname. const uint8_t offset = 70; char middleLabels[ 512 ]; SNPrintF( middleLabels, sizeof( middleLabels ), "ttl-600.offset-%u", offset ); ForgetMem( &me->hostname ); const char * const domainStr = mdns_domain_name_get_presentation( me->domain ); ASPrintF( &me->hostname, "alias-ttl-1.%s.%s", middleLabels, domainStr ); require_action( me->hostname, exit, err = kNoMemoryErr ); // Make sure to remove the trailing dot to make the hostname a PQDN. const size_t len = strlen( me->hostname ); me->hostname[ len - 1 ] = '\0'; // Set the expected IPv4 address. char ipv4AddrStr[ kSockAddrStringMaxSize ]; SNPrintF( ipv4AddrStr, sizeof( ipv4AddrStr ), "203.0.113.%u", offset + 1 ); err = StringToSockAddr( ipv4AddrStr, &me->expectedAddr, sizeof( me->expectedAddr ), NULL ); require_noerr( err, exit ); // Since this is a GAI for a PQDN instead of a FQDN, and it doesn't depend on a search domain, to make sure that no // search domain was appended, check the actual hostname, which should be a child of the domain that we used for the // test DNS server. Specifically, the actual hostname should be of the form ttl-600.offset-60.. // // Note: We don't want results from another of the DNS server's search domains, such as dnssec.test. The domain // specified with --domain should come first in the list of search domains. err = DomainNameFromString( me->expectedHostname, middleLabels, NULL ); require_noerr( err, exit ); err = DomainNameAppendString( me->expectedHostname, domainStr, NULL ); require_noerr( err, exit ); // Start the GAI operation. const unsigned int timeLimitSecs = 5; FPrintF( stdout, "%{du:time} * Starting GAI for multi-label PQDN '%s' with %u second time limit\n", NULL, me->hostname, timeLimitSecs ); const DNSServiceFlags flags = kDNSServiceFlagsReturnIntermediates | kDNSServiceFlagsAllowExpiredAnswers; me->gai = _OptimisticDNSTestCreateGAI( me, me->hostname, flags ); require_action( me->gai, exit, err = kNoResourcesErr ); const dnssd_getaddrinfo_t gai = me->gai; dnssd_getaddrinfo_set_result_handler( me->gai, ^( dnssd_getaddrinfo_result_t * const inResults, const size_t inCount ) { require_return( me->gai == gai ); size_t unexpectedResultCount = 0; for( size_t i = 0; i < inCount; ++i ) { Boolean resultIsExpected = false; const dnssd_getaddrinfo_result_t result = inResults[ i ]; const dnssd_getaddrinfo_result_type_t resultType = dnssd_getaddrinfo_result_get_type( result ); const char * const actualHostnameStr = dnssd_getaddrinfo_result_get_actual_hostname( result ); const bool fromCache = dnssd_getaddrinfo_result_is_from_cache( result ); if( SockAddrCompareAddr( dnssd_getaddrinfo_result_get_address( result ), &me->expectedAddr.sa ) == 0 ) { // Since this is a GAI for a single-label instead of a FQDN, to make sure that appropriate search domain // was appended, check the actual hostname, which should be equal to the domain that we used for the // test DNS server: alias-ttl-1. → 203.0.113.1 // // Note: We don't want results from another of the DNS server's search domains, such as dnssec.test. The // domain specified with --domain should come first in the list of search domains. uint8_t actualHostname[ kDomainNameLengthMax ]; OSStatus nameErr = DomainNameFromString( actualHostname, actualHostnameStr, NULL ); if( !nameErr && DomainNameEqual( actualHostname, me->expectedHostname ) ) { if( !gotAddResult ) { if( ( resultType == dnssd_getaddrinfo_result_type_add ) && !fromCache ) { gotAddResult = true; resultIsExpected = true; } } } } if( !resultIsExpected ) ++unexpectedResultCount; FPrintF( stdout, "%{du:time} %s Got %sexpected result -- type: %s, hostname: %s, address: %##a, actual hostname: %s, " "from cache: %s\n", NULL, resultIsExpected ? "✓" : "x", resultIsExpected ? "" : "un", dnssd_getaddrinfo_result_type_to_string( resultType ), dnssd_getaddrinfo_result_get_hostname( result ), dnssd_getaddrinfo_result_get_address( result ), actualHostnameStr, YesNoStr( fromCache ) ); } if( unexpectedResultCount > 0 ) _OptimisticDNSTestStop( me, kUnexpectedErr ); } ); dnssd_getaddrinfo_activate( me->gai ); // Start the time limit timer. check( !me->timer ); me->timer = dispatch_source_create( DISPATCH_SOURCE_TYPE_TIMER, 0, 0, me->queue ); require_action_quiet( me->timer, exit, err = kNoResourcesErr ); dispatch_source_set_timer( me->timer, dispatch_time_seconds( timeLimitSecs ), DISPATCH_TIME_FOREVER, 0 ); dispatch_source_set_event_handler( me->timer, ^{ dispatch_source_forget( &me->timer ); dnssd_getaddrinfo_forget( &me->gai ); if( !gotAddResult ) { FPrintF( stdout, "%{du:time} Failed to get ADD GAI result for '%s' after %u seconds.\n", NULL, me->hostname, timeLimitSecs ); _OptimisticDNSTestStop( me, kTimeoutErr ); } else { const unsigned int waitSecs = 15; FPrintF( stdout, "%{du:time} ⧖ Waiting %u seconds for CNAME record to expire\n", NULL, waitSecs ); usleep( waitSecs * kMicrosecondsPerSecond ); const OSStatus startErr = _OptimisticDNSTestStartSubtest7_2( me ); if( startErr ) _OptimisticDNSTestStop( me, startErr ); } } ); dispatch_activate( me->timer ); err = kNoErr; exit: return( err ); } //=========================================================================================================================== static OSStatus _OptimisticDNSTestStartSubtest6_2( const OptimisticDNSTestRef me ) { OSStatus err; __block Boolean gotExpiredResult = false; __block Boolean gotAddResult = false; FPrintF( stdout, "%{du:time} Subtest 6.2: Second multi-label PQDN GAI for hostname that has one CNAME record and that depends on a" " search domain (CNAME record expired, AlwaysAppendSearchDomains enabled)\n", NULL ); // Start the GAI operation. check( me->hostname ); const unsigned int timeLimitSecs = 5; FPrintF( stdout, "%{du:time} * Starting GAI for multi-label PQDN '%s' with %u second time limit\n", NULL, me->hostname, timeLimitSecs ); const DNSServiceFlags flags = kDNSServiceFlagsReturnIntermediates | kDNSServiceFlagsAllowExpiredAnswers; me->gai = _OptimisticDNSTestCreateGAI( me, me->hostname, flags ); require_action( me->gai, exit, err = kNoResourcesErr ); const dnssd_getaddrinfo_t gai = me->gai; dnssd_getaddrinfo_set_result_handler( me->gai, ^( dnssd_getaddrinfo_result_t * const inResults, const size_t inCount ) { require_return( me->gai == gai ); size_t unexpectedResultCount = 0; for( size_t i = 0; i < inCount; ++i ) { Boolean resultIsExpected = false; const dnssd_getaddrinfo_result_t result = inResults[ i ]; const dnssd_getaddrinfo_result_type_t resultType = dnssd_getaddrinfo_result_get_type( result ); const char * const actualHostnameStr = dnssd_getaddrinfo_result_get_actual_hostname( result ); const bool fromCache = dnssd_getaddrinfo_result_is_from_cache( result ); if( SockAddrCompareAddr( dnssd_getaddrinfo_result_get_address( result ), &me->expectedAddr.sa ) == 0 ) { uint8_t actualHostname[ kDomainNameLengthMax ]; OSStatus nameErr = DomainNameFromString( actualHostname, actualHostnameStr, NULL ); if( !nameErr && DomainNameEqual( actualHostname, me->expectedHostname ) ) { if( !gotExpiredResult ) { if( ( resultType == dnssd_getaddrinfo_result_type_expired ) && fromCache ) { gotExpiredResult = true; resultIsExpected = true; } } else if ( !gotAddResult ) { if( ( resultType == dnssd_getaddrinfo_result_type_add ) && !fromCache ) { gotAddResult = true; resultIsExpected = true; } } } } if( !resultIsExpected ) ++unexpectedResultCount; FPrintF( stdout, "%{du:time} %s Got %sexpected result -- type: %s, hostname: %s, address: %##a, actual hostname: %s, " "from cache: %s\n", NULL, resultIsExpected ? "✓" : "x", resultIsExpected ? "" : "un", dnssd_getaddrinfo_result_type_to_string( resultType ), dnssd_getaddrinfo_result_get_hostname( result ), dnssd_getaddrinfo_result_get_address( result ), actualHostnameStr, YesNoStr( fromCache ) ); } if( unexpectedResultCount > 0 ) _OptimisticDNSTestStop( me, kUnexpectedErr ); } ); dnssd_getaddrinfo_activate( me->gai ); // Start the time limit timer. check( !me->timer ); me->timer = dispatch_source_create( DISPATCH_SOURCE_TYPE_TIMER, 0, 0, me->queue ); require_action_quiet( me->timer, exit, err = kNoResourcesErr ); dispatch_source_set_timer( me->timer, dispatch_time_seconds( timeLimitSecs ), DISPATCH_TIME_FOREVER, 0 ); dispatch_source_set_event_handler( me->timer, ^{ dispatch_source_forget( &me->timer ); dnssd_getaddrinfo_forget( &me->gai ); if( !gotExpiredResult || !gotAddResult ) { if( !gotExpiredResult ) { FPrintF( stdout, "%{du:time} Failed to get EXPIRED GAI result for '%s' after %u seconds.\n", NULL, me->hostname, timeLimitSecs ); } if( !gotAddResult ) { FPrintF( stdout, "%{du:time} Failed to get ADD GAI result for '%s' after %u seconds.\n", NULL, me->hostname, timeLimitSecs ); } _OptimisticDNSTestStop( me, kTimeoutErr ); } else { const OSStatus startErr = _OptimisticDNSTestStartSubtest7_1( me ); if( startErr ) _OptimisticDNSTestStop( me, startErr ); } } ); dispatch_activate( me->timer ); err = kNoErr; exit: return( err ); } //=========================================================================================================================== static OSStatus _OptimisticDNSTestStartSubtest6_1( const OptimisticDNSTestRef me ) { OSStatus err; __block Boolean gotAddResult = false; // Check if the test should proceed with subtests that require that mDNSResponder be killed. if( !me->fullTest ) { FPrintF( stdout, "%{du:time} ⚠ Not proceeding with subtests that require mDNSResponder to be killed\n", NULL ); _OptimisticDNSTestRetain( me ); dispatch_async( me->queue, ^{ _OptimisticDNSTestStop( me, kNoErr ); _OptimisticDNSTestRelease( me ); } ); err = kNoErr; goto exit; } FPrintF( stdout, "%{du:time} Subtest 6.1: First multi-label PQDN GAI for hostname that has one CNAME record and that depends on a" " search domain (AlwaysAppendSearchDomains enabled)\n", NULL ); // Set AlwaysAppendSearchDomains to true. const CFStringRef appID = CFSTR( kMDNSResponderPrefAppIDStr ); const CFStringRef keyAASD = CFSTR( kMDNSResponderPrefStr_AlwaysAppendSearchDomains ); const CFPropertyListRef valueAASD = kCFBooleanTrue; me->savedAASDPref = CFPreferencesCopyValue( keyAASD, appID, kCFPreferencesAnyUser, kCFPreferencesCurrentHost ); FPrintF( stdout, "%{du:time} ⚒ Changing %@ preference: '%@' → '%@'\n", NULL, keyAASD, me->savedAASDPref, valueAASD ); CFPreferencesSetValue( keyAASD, valueAASD, appID, kCFPreferencesAnyUser, kCFPreferencesCurrentHost ); me->modifiedDaemon = true; // Restart mDNSResponder. FPrintF( stdout, "%{du:time} ↻ Restarting mDNSResponder\n", NULL ); err = systemf( NULL, "killall -KILL mDNSResponder" ); require_noerr( err, exit ); // Give mDNSResponder some time to respawn. const unsigned int delaySecs = 15; FPrintF( stdout, "%{du:time} ⧖ Waiting %u seconds to allow mDNSResponder to respawn\n", NULL, delaySecs ); usleep( delaySecs * kMicrosecondsPerSecond ); // Create the hostname. const uint8_t offset = 60; char offsetLabel[ 512 ]; SNPrintF( offsetLabel, sizeof( offsetLabel ), "offset-%u", offset ); ForgetMem( &me->hostname ); ASPrintF( &me->hostname, "alias-ttl-1.%s", offsetLabel ); require_action( me->hostname, exit, err = kNoMemoryErr ); // Set the expected IPv4 address. char ipv4AddrStr[ kSockAddrStringMaxSize ]; SNPrintF( ipv4AddrStr, sizeof( ipv4AddrStr ), "203.0.113.%u", offset + 1 ); err = StringToSockAddr( ipv4AddrStr, &me->expectedAddr, sizeof( me->expectedAddr ), NULL ); require_noerr( err, exit ); // Since this is a GAI for a PQDN instead of a FQDN, and it depends on a search domain, to make sure that // the appropriate search domain was appended, check the actual hostname, which should be a child of the domain // that we used for the test DNS server. Specifically, the actual hostname should be of the form offset-50.. // // Note: We don't want results from another of the DNS server's search domains, such as dnssec.test. The domain // specified with --domain should come first in the list of search domains. err = DomainNameFromString( me->expectedHostname, offsetLabel, NULL ); require_noerr( err, exit ); err = DomainNameAppendString( me->expectedHostname, mdns_domain_name_get_presentation( me->domain ), NULL ); require_noerr( err, exit ); // Start the GAI operation. const unsigned int timeLimitSecs = 5; FPrintF( stdout, "%{du:time} * Starting GAI for multi-label PQDN '%s' with %u second time limit\n", NULL, me->hostname, timeLimitSecs ); const DNSServiceFlags flags = kDNSServiceFlagsReturnIntermediates | kDNSServiceFlagsAllowExpiredAnswers; me->gai = _OptimisticDNSTestCreateGAI( me, me->hostname, flags ); require_action( me->gai, exit, err = kNoResourcesErr ); const dnssd_getaddrinfo_t gai = me->gai; dnssd_getaddrinfo_set_result_handler( me->gai, ^( dnssd_getaddrinfo_result_t * const inResults, const size_t inCount ) { require_return( me->gai == gai ); size_t unexpectedResultCount = 0; for( size_t i = 0; i < inCount; ++i ) { Boolean resultIsExpected = false; const dnssd_getaddrinfo_result_t result = inResults[ i ]; const dnssd_getaddrinfo_result_type_t resultType = dnssd_getaddrinfo_result_get_type( result ); const char * const actualHostnameStr = dnssd_getaddrinfo_result_get_actual_hostname( result ); const bool fromCache = dnssd_getaddrinfo_result_is_from_cache( result ); if( SockAddrCompareAddr( dnssd_getaddrinfo_result_get_address( result ), &me->expectedAddr.sa ) == 0 ) { uint8_t actualHostname[ kDomainNameLengthMax ]; OSStatus nameErr = DomainNameFromString( actualHostname, actualHostnameStr, NULL ); if( !nameErr && DomainNameEqual( actualHostname, me->expectedHostname ) ) { if( !gotAddResult ) { if( ( resultType == dnssd_getaddrinfo_result_type_add ) && !fromCache ) { gotAddResult = true; resultIsExpected = true; } } } } if( !resultIsExpected ) ++unexpectedResultCount; FPrintF( stdout, "%{du:time} %s Got %sexpected result -- type: %s, hostname: %s, address: %##a, actual hostname: %s, " "from cache: %s\n", NULL, resultIsExpected ? "✓" : "x", resultIsExpected ? "" : "un", dnssd_getaddrinfo_result_type_to_string( resultType ), dnssd_getaddrinfo_result_get_hostname( result ), dnssd_getaddrinfo_result_get_address( result ), actualHostnameStr, YesNoStr( fromCache ) ); } if( unexpectedResultCount > 0 ) _OptimisticDNSTestStop( me, kUnexpectedErr ); } ); dnssd_getaddrinfo_activate( me->gai ); // Start the time limit timer. check( !me->timer ); me->timer = dispatch_source_create( DISPATCH_SOURCE_TYPE_TIMER, 0, 0, me->queue ); require_action_quiet( me->timer, exit, err = kNoResourcesErr ); dispatch_source_set_timer( me->timer, dispatch_time_seconds( timeLimitSecs ), DISPATCH_TIME_FOREVER, 0 ); dispatch_source_set_event_handler( me->timer, ^{ dispatch_source_forget( &me->timer ); dnssd_getaddrinfo_forget( &me->gai ); if( !gotAddResult ) { FPrintF( stdout, "%{du:time} Failed to get ADD GAI result for '%s' after %u seconds.\n", NULL, me->hostname, timeLimitSecs ); _OptimisticDNSTestStop( me, kTimeoutErr ); } else { const unsigned int waitSecs = 15; FPrintF( stdout, "%{du:time} ⧖ Waiting %u seconds for CNAME record to expire\n", NULL, waitSecs ); usleep( waitSecs * kMicrosecondsPerSecond ); const OSStatus startErr = _OptimisticDNSTestStartSubtest6_2( me ); if( startErr ) _OptimisticDNSTestStop( me, startErr ); } } ); dispatch_activate( me->timer ); err = kNoErr; exit: return( err ); } //=========================================================================================================================== static OSStatus _OptimisticDNSTestStartSubtest5_2( const OptimisticDNSTestRef me ) { OSStatus err; __block Boolean gotExpiredResult = false; __block Boolean gotAddResult = false; FPrintF( stdout, "%{du:time} Subtest 5.2: Second single-label GAI for hostname with CNAME record" " (appends search domain, CNAME record expired)\n", NULL ); check( me->hostname ); const unsigned int timeLimitSecs = 5; FPrintF( stdout, "%{du:time} * Starting GAI for single-label '%s' with %u second time limit\n", NULL, me->hostname, timeLimitSecs ); const DNSServiceFlags flags = kDNSServiceFlagsReturnIntermediates | kDNSServiceFlagsAllowExpiredAnswers; me->gai = _OptimisticDNSTestCreateGAI( me, me->hostname, flags ); require_action( me->gai, exit, err = kNoResourcesErr ); const dnssd_getaddrinfo_t gai = me->gai; dnssd_getaddrinfo_set_result_handler( me->gai, ^( dnssd_getaddrinfo_result_t * const inResults, const size_t inCount ) { require_return( me->gai == gai ); size_t unexpectedResultCount = 0; for( size_t i = 0; i < inCount; ++i ) { Boolean resultIsExpected = false; const dnssd_getaddrinfo_result_t result = inResults[ i ]; const dnssd_getaddrinfo_result_type_t resultType = dnssd_getaddrinfo_result_get_type( result ); const char * const actualHostnameStr = dnssd_getaddrinfo_result_get_actual_hostname( result ); const bool fromCache = dnssd_getaddrinfo_result_is_from_cache( result ); if( SockAddrCompareAddr( dnssd_getaddrinfo_result_get_address( result ), &me->expectedAddr.sa ) == 0 ) { uint8_t actualHostname[ kDomainNameLengthMax ]; OSStatus nameErr = DomainNameFromString( actualHostname, actualHostnameStr, NULL ); if( !nameErr && DomainNameEqual( actualHostname, me->expectedHostname ) ) { if( !gotExpiredResult ) { if( ( resultType == dnssd_getaddrinfo_result_type_expired ) && fromCache ) { gotExpiredResult = true; resultIsExpected = true; } } else if ( !gotAddResult ) { if( ( resultType == dnssd_getaddrinfo_result_type_add ) && !fromCache ) { gotAddResult = true; resultIsExpected = true; } } } } if( !resultIsExpected ) ++unexpectedResultCount; FPrintF( stdout, "%{du:time} %s Got %sexpected result -- type: %s, hostname: %s, address: %##a, actual hostname: %s, " "from cache: %s\n", NULL, resultIsExpected ? "✓" : "x", resultIsExpected ? "" : "un", dnssd_getaddrinfo_result_type_to_string( resultType ), dnssd_getaddrinfo_result_get_hostname( result ), dnssd_getaddrinfo_result_get_address( result ), actualHostnameStr, YesNoStr( fromCache ) ); } if( unexpectedResultCount > 0 ) _OptimisticDNSTestStop( me, kUnexpectedErr ); } ); dnssd_getaddrinfo_activate( me->gai ); check( !me->timer ); me->timer = dispatch_source_create( DISPATCH_SOURCE_TYPE_TIMER, 0, 0, me->queue ); require_action_quiet( me->timer, exit, err = kNoResourcesErr ); dispatch_source_set_timer( me->timer, dispatch_time_seconds( timeLimitSecs ), DISPATCH_TIME_FOREVER, 0 ); dispatch_source_set_event_handler( me->timer, ^{ dispatch_source_forget( &me->timer ); dnssd_getaddrinfo_forget( &me->gai ); if( !gotExpiredResult || !gotAddResult ) { if( !gotExpiredResult ) { FPrintF( stdout, "%{du:time} Failed to get EXPIRED GAI result for '%s' after %u seconds.\n", NULL, me->hostname, timeLimitSecs ); } if( !gotAddResult ) { FPrintF( stdout, "%{du:time} Failed to get ADD GAI result for '%s' after %u seconds.\n", NULL, me->hostname, timeLimitSecs ); } _OptimisticDNSTestStop( me, kTimeoutErr ); } else { const OSStatus startErr = _OptimisticDNSTestStartSubtest6_1( me ); if( startErr ) _OptimisticDNSTestStop( me, startErr ); } } ); dispatch_activate( me->timer ); err = kNoErr; exit: return( err ); } //=========================================================================================================================== static OSStatus _OptimisticDNSTestStartSubtest5_1( const OptimisticDNSTestRef me ) { OSStatus err; __block Boolean gotAddResult = false; FPrintF( stdout, "%{du:time} Subtest 5.1: First single-label GAI for hostname with CNAME record (appends search domain)\n", NULL ); ForgetMem( &me->hostname ); ASPrintF( &me->hostname, "%s", "alias-ttl-1" ); require_action( me->hostname, exit, err = kNoMemoryErr ); // Since this is a GAI for a single-label PQDN instead of a FQDN, and it depends on a search domain, to make sure // that the appropriate search domain was appended, check the actual hostname, which should be a child of the domain // that we used for the test DNS server. Specifically, the actual hostname should be . // // Note: We don't want results from another of the DNS server's search domains, such as dnssec.test. The domain // specified with --domain should come first in the list of search domains. err = DomainNameFromString( me->expectedHostname, mdns_domain_name_get_presentation( me->domain ), NULL ); require_noerr( err, exit ); // Set the expected IPv4 address. err = StringToSockAddr( "203.0.113.1", &me->expectedAddr, sizeof( me->expectedAddr ), NULL ); require_noerr( err, exit ); const unsigned int timeLimitSecs = 5; FPrintF( stdout, "%{du:time} * Starting GAI for single-label '%s' with %u second time limit\n", NULL, me->hostname, timeLimitSecs ); const DNSServiceFlags flags = kDNSServiceFlagsReturnIntermediates | kDNSServiceFlagsAllowExpiredAnswers; me->gai = _OptimisticDNSTestCreateGAI( me, me->hostname, flags ); require_action( me->gai, exit, err = kNoResourcesErr ); const dnssd_getaddrinfo_t gai = me->gai; dnssd_getaddrinfo_set_result_handler( me->gai, ^( dnssd_getaddrinfo_result_t * const inResults, const size_t inCount ) { require_return( me->gai == gai ); size_t unexpectedResultCount = 0; for( size_t i = 0; i < inCount; ++i ) { Boolean resultIsExpected = false; const dnssd_getaddrinfo_result_t result = inResults[ i ]; const dnssd_getaddrinfo_result_type_t resultType = dnssd_getaddrinfo_result_get_type( result ); const char * const actualHostnameStr = dnssd_getaddrinfo_result_get_actual_hostname( result ); const bool fromCache = dnssd_getaddrinfo_result_is_from_cache( result ); if( SockAddrCompareAddr( dnssd_getaddrinfo_result_get_address( result ), &me->expectedAddr.sa ) == 0 ) { uint8_t actualHostname[ kDomainNameLengthMax ]; OSStatus nameErr = DomainNameFromString( actualHostname, actualHostnameStr, NULL ); if( !nameErr && DomainNameEqual( actualHostname, me->expectedHostname ) ) { if( !gotAddResult ) { if( ( resultType == dnssd_getaddrinfo_result_type_add ) && !fromCache ) { gotAddResult = true; resultIsExpected = true; } } } } if( !resultIsExpected ) ++unexpectedResultCount; FPrintF( stdout, "%{du:time} %s Got %sexpected result -- type: %s, hostname: %s, address: %##a, actual hostname: %s, " "from cache: %s\n", NULL, resultIsExpected ? "✓" : "x", resultIsExpected ? "" : "un", dnssd_getaddrinfo_result_type_to_string( resultType ), dnssd_getaddrinfo_result_get_hostname( result ), dnssd_getaddrinfo_result_get_address( result ), actualHostnameStr, YesNoStr( fromCache ) ); } if( unexpectedResultCount > 0 ) _OptimisticDNSTestStop( me, kUnexpectedErr ); } ); dnssd_getaddrinfo_activate( me->gai ); check( !me->timer ); me->timer = dispatch_source_create( DISPATCH_SOURCE_TYPE_TIMER, 0, 0, me->queue ); require_action_quiet( me->timer, exit, err = kNoResourcesErr ); dispatch_source_set_timer( me->timer, dispatch_time_seconds( timeLimitSecs ), DISPATCH_TIME_FOREVER, 0 ); dispatch_source_set_event_handler( me->timer, ^{ dispatch_source_forget( &me->timer ); dnssd_getaddrinfo_forget( &me->gai ); if( !gotAddResult ) { FPrintF( stdout, "%{du:time} Failed to get ADD GAI result for '%s' after %u seconds.\n", NULL, me->hostname, timeLimitSecs ); _OptimisticDNSTestStop( me, kTimeoutErr ); } else { const unsigned int waitSecs = 15; FPrintF( stdout, "%{du:time} ⧖ Waiting %u seconds for CNAME record to expire\n", NULL, waitSecs ); usleep( waitSecs * kMicrosecondsPerSecond ); const OSStatus startErr = _OptimisticDNSTestStartSubtest5_2( me ); if( startErr ) _OptimisticDNSTestStop( me, startErr ); } } ); dispatch_activate( me->timer ); err = kNoErr; exit: return( err ); } //=========================================================================================================================== static OSStatus _OptimisticDNSTestStartSubtest4_2( const OptimisticDNSTestRef me ) { OSStatus err; __block Boolean gotExpiredResult = false; __block Boolean gotAddResult = false; FPrintF( stdout, "%{du:time} Subtest 4.2: Second GAI for hostname with one CNAME record (A record expired)\n", NULL ); check( me->hostname ); const unsigned int timeLimitSecs = 5; FPrintF( stdout, "%{du:time} * Starting GAI for '%s' with %u second time limit\n", NULL, me->hostname, timeLimitSecs ); const DNSServiceFlags flags = kDNSServiceFlagsReturnIntermediates | kDNSServiceFlagsAllowExpiredAnswers; me->gai = _OptimisticDNSTestCreateGAI( me, me->hostname, flags ); require_action( me->gai, exit, err = kNoResourcesErr ); const dnssd_getaddrinfo_t gai = me->gai; dnssd_getaddrinfo_set_result_handler( me->gai, ^( dnssd_getaddrinfo_result_t * const inResults, const size_t inCount ) { require_return( me->gai == gai ); size_t unexpectedResultCount = 0; for( size_t i = 0; i < inCount; ++i ) { Boolean resultIsExpected = false; const dnssd_getaddrinfo_result_t result = inResults[ i ]; const dnssd_getaddrinfo_result_type_t resultType = dnssd_getaddrinfo_result_get_type( result ); const bool fromCache = dnssd_getaddrinfo_result_is_from_cache( result ); if( SockAddrCompareAddr( dnssd_getaddrinfo_result_get_address( result ), &me->expectedAddr.sa ) == 0 ) { if( !gotExpiredResult ) { if( ( resultType == dnssd_getaddrinfo_result_type_expired ) && fromCache ) { gotExpiredResult = true; resultIsExpected = true; } } else if ( !gotAddResult ) { if( ( resultType == dnssd_getaddrinfo_result_type_add ) && !fromCache ) { gotAddResult = true; resultIsExpected = true; } } } if( !resultIsExpected ) ++unexpectedResultCount; FPrintF( stdout, "%{du:time} %s Got %sexpected result -- type: %s, hostname: %s, address: %##a, from cache: %s\n", NULL, resultIsExpected ? "✓" : "x", resultIsExpected ? "" : "un", dnssd_getaddrinfo_result_type_to_string( resultType ), dnssd_getaddrinfo_result_get_hostname( result ), dnssd_getaddrinfo_result_get_address( result ), YesNoStr( fromCache ) ); } if( unexpectedResultCount > 0 ) _OptimisticDNSTestStop( me, kUnexpectedErr ); } ); dnssd_getaddrinfo_activate( me->gai ); check( !me->timer ); me->timer = dispatch_source_create( DISPATCH_SOURCE_TYPE_TIMER, 0, 0, me->queue ); require_action_quiet( me->timer, exit, err = kNoResourcesErr ); dispatch_source_set_timer( me->timer, dispatch_time_seconds( timeLimitSecs ), DISPATCH_TIME_FOREVER, 0 ); dispatch_source_set_event_handler( me->timer, ^{ dispatch_source_forget( &me->timer ); dnssd_getaddrinfo_forget( &me->gai ); if( !gotExpiredResult || !gotAddResult ) { if( !gotExpiredResult ) { FPrintF( stdout, "%{du:time} Failed to get EXPIRED GAI result for '%s' after %u seconds.\n", NULL, me->hostname, timeLimitSecs ); } if( !gotAddResult ) { FPrintF( stdout, "%{du:time} Failed to get ADD GAI result for '%s' after %u seconds.\n", NULL, me->hostname, timeLimitSecs ); } _OptimisticDNSTestStop( me, kTimeoutErr ); } else { const OSStatus startErr = _OptimisticDNSTestStartSubtest5_1( me ); if( startErr ) _OptimisticDNSTestStop( me, startErr ); } } ); dispatch_activate( me->timer ); err = kNoErr; exit: return( err ); } //=========================================================================================================================== static OSStatus _OptimisticDNSTestStartSubtest4_1( const OptimisticDNSTestRef me ) { OSStatus err; __block Boolean gotAddResult = false; FPrintF( stdout, "%{du:time} Subtest 4.1: First GAI for hostname with one CNAME record\n", NULL ); // Create the hostname. const uint8_t offset = 40; ForgetMem( &me->hostname ); ASPrintF( &me->hostname, "alias-ttl-600.tag-address-record-expires-first.ttl-1.offset-%u.%s", offset, mdns_domain_name_get_presentation( me->domain ) ); require_action( me->hostname, exit, err = kNoMemoryErr ); // Set the expected IPv4 address. char ipv4AddrStr[ kSockAddrStringMaxSize ]; SNPrintF( ipv4AddrStr, sizeof( ipv4AddrStr ), "203.0.113.%u", offset + 1 ); err = StringToSockAddr( ipv4AddrStr, &me->expectedAddr, sizeof( me->expectedAddr ), NULL ); require_noerr( err, exit ); const unsigned int timeLimitSecs = 5; FPrintF( stdout, "%{du:time} * Starting GAI for '%s' with %u second time limit\n", NULL, me->hostname, timeLimitSecs ); const DNSServiceFlags flags = kDNSServiceFlagsReturnIntermediates | kDNSServiceFlagsAllowExpiredAnswers; me->gai = _OptimisticDNSTestCreateGAI( me, me->hostname, flags ); require_action( me->gai, exit, err = kNoResourcesErr ); const dnssd_getaddrinfo_t gai = me->gai; dnssd_getaddrinfo_set_result_handler( me->gai, ^( dnssd_getaddrinfo_result_t * const inResults, const size_t inCount ) { require_return( me->gai == gai ); size_t unexpectedResultCount = 0; for( size_t i = 0; i < inCount; ++i ) { Boolean resultIsExpected = false; const dnssd_getaddrinfo_result_t result = inResults[ i ]; const dnssd_getaddrinfo_result_type_t resultType = dnssd_getaddrinfo_result_get_type( result ); const bool fromCache = dnssd_getaddrinfo_result_is_from_cache( result ); if( SockAddrCompareAddr( dnssd_getaddrinfo_result_get_address( result ), &me->expectedAddr.sa ) == 0 ) { if( !gotAddResult ) { if( ( resultType == dnssd_getaddrinfo_result_type_add ) && !fromCache ) { gotAddResult = true; resultIsExpected = true; } } } if( !resultIsExpected ) ++unexpectedResultCount; FPrintF( stdout, "%{du:time} %s Got %sexpected result -- type: %s, hostname: %s, address: %##a, from cache: %s\n", NULL, resultIsExpected ? "✓" : "x", resultIsExpected ? "" : "un", dnssd_getaddrinfo_result_type_to_string( resultType ), dnssd_getaddrinfo_result_get_hostname( result ), dnssd_getaddrinfo_result_get_address( result ), YesNoStr( fromCache ) ); } if( unexpectedResultCount > 0 ) _OptimisticDNSTestStop( me, kUnexpectedErr ); } ); dnssd_getaddrinfo_activate( me->gai ); check( !me->timer ); me->timer = dispatch_source_create( DISPATCH_SOURCE_TYPE_TIMER, 0, 0, me->queue ); require_action_quiet( me->timer, exit, err = kNoResourcesErr ); dispatch_source_set_timer( me->timer, dispatch_time_seconds( timeLimitSecs ), DISPATCH_TIME_FOREVER, 0 ); dispatch_source_set_event_handler( me->timer, ^{ dispatch_source_forget( &me->timer ); dnssd_getaddrinfo_forget( &me->gai ); if( !gotAddResult ) { FPrintF( stdout, "%{du:time} Failed to get ADD GAI result for '%s' after %u seconds.\n", NULL, me->hostname, timeLimitSecs ); _OptimisticDNSTestStop( me, kTimeoutErr ); } else { const unsigned int waitSecs = 15; FPrintF( stdout, "%{du:time} ⧖ Waiting %u seconds for A record to expire\n", NULL, waitSecs ); usleep( waitSecs * kMicrosecondsPerSecond ); const OSStatus startErr = _OptimisticDNSTestStartSubtest4_2( me ); if( startErr ) _OptimisticDNSTestStop( me, startErr ); } } ); dispatch_activate( me->timer ); err = kNoErr; exit: return( err ); } //=========================================================================================================================== static OSStatus _OptimisticDNSTestStartSubtest3_2( const OptimisticDNSTestRef me ) { OSStatus err; __block Boolean gotExpiredResult = false; __block Boolean gotAddResult = false; FPrintF( stdout, "%{du:time} Subtest 3.2: Second GAI for hostname with two CNAME records (2nd CNAME record expired)\n", NULL ); check( me->hostname ); const unsigned int timeLimitSecs = 5; FPrintF( stdout, "%{du:time} * Starting GAI for '%s' with %u second time limit\n", NULL, me->hostname, timeLimitSecs ); const DNSServiceFlags flags = kDNSServiceFlagsReturnIntermediates | kDNSServiceFlagsAllowExpiredAnswers; me->gai = _OptimisticDNSTestCreateGAI( me, me->hostname, flags ); require_action( me->gai, exit, err = kNoResourcesErr ); const dnssd_getaddrinfo_t gai = me->gai; dnssd_getaddrinfo_set_result_handler( me->gai, ^( dnssd_getaddrinfo_result_t * const inResults, const size_t inCount ) { require_return( me->gai == gai ); size_t unexpectedResultCount = 0; for( size_t i = 0; i < inCount; ++i ) { Boolean resultIsExpected = false; const dnssd_getaddrinfo_result_t result = inResults[ i ]; const dnssd_getaddrinfo_result_type_t resultType = dnssd_getaddrinfo_result_get_type( result ); const bool fromCache = dnssd_getaddrinfo_result_is_from_cache( result ); if( SockAddrCompareAddr( dnssd_getaddrinfo_result_get_address( result ), &me->expectedAddr.sa ) == 0 ) { if( !gotExpiredResult ) { if( ( resultType == dnssd_getaddrinfo_result_type_expired ) && fromCache ) { gotExpiredResult = true; resultIsExpected = true; } } else if ( !gotAddResult ) { if( ( resultType == dnssd_getaddrinfo_result_type_add ) && !fromCache ) { gotAddResult = true; resultIsExpected = true; } } } if( !resultIsExpected ) ++unexpectedResultCount; FPrintF( stdout, "%{du:time} %s Got %sexpected result -- type: %s, hostname: %s, address: %##a, from cache: %s\n", NULL, resultIsExpected ? "✓" : "x", resultIsExpected ? "" : "un", dnssd_getaddrinfo_result_type_to_string( resultType ), dnssd_getaddrinfo_result_get_hostname( result ), dnssd_getaddrinfo_result_get_address( result ), YesNoStr( fromCache ) ); } if( unexpectedResultCount > 0 ) _OptimisticDNSTestStop( me, kUnexpectedErr ); } ); dnssd_getaddrinfo_activate( me->gai ); check( !me->timer ); me->timer = dispatch_source_create( DISPATCH_SOURCE_TYPE_TIMER, 0, 0, me->queue ); require_action_quiet( me->timer, exit, err = kNoResourcesErr ); dispatch_source_set_timer( me->timer, dispatch_time_seconds( timeLimitSecs ), DISPATCH_TIME_FOREVER, 0 ); dispatch_source_set_event_handler( me->timer, ^{ dispatch_source_forget( &me->timer ); dnssd_getaddrinfo_forget( &me->gai ); if( !gotExpiredResult || !gotAddResult ) { if( !gotExpiredResult ) { FPrintF( stdout, "%{du:time} Failed to get EXPIRED GAI result for '%s' after %u seconds.\n", NULL, me->hostname, timeLimitSecs ); } if( !gotAddResult ) { FPrintF( stdout, "%{du:time} Failed to get ADD GAI result for '%s' after %u seconds.\n", NULL, me->hostname, timeLimitSecs ); } _OptimisticDNSTestStop( me, kTimeoutErr ); } else { const OSStatus startErr = _OptimisticDNSTestStartSubtest4_1( me ); if( startErr ) _OptimisticDNSTestStop( me, startErr ); } } ); dispatch_activate( me->timer ); err = kNoErr; exit: return( err ); } //=========================================================================================================================== static OSStatus _OptimisticDNSTestStartSubtest3_1( const OptimisticDNSTestRef me ) { OSStatus err; __block Boolean gotAddResult = false; FPrintF( stdout, "%{du:time} Subtest 3.1: First GAI for hostname with two CNAME records\n", NULL ); // Create the hostname. const uint8_t offset = 30; ForgetMem( &me->hostname ); ASPrintF( &me->hostname, "alias-ttl-600-1.tag-second-cname-expires-first.ttl-600.offset-%u.%s", offset, mdns_domain_name_get_presentation( me->domain ) ); require_action( me->hostname, exit, err = kNoMemoryErr ); // Set the expected IPv4 address. char ipv4AddrStr[ kSockAddrStringMaxSize ]; SNPrintF( ipv4AddrStr, sizeof( ipv4AddrStr ), "203.0.113.%u", offset + 1 ); err = StringToSockAddr( ipv4AddrStr, &me->expectedAddr, sizeof( me->expectedAddr ), NULL ); require_noerr( err, exit ); const unsigned int timeLimitSecs = 5; FPrintF( stdout, "%{du:time} * Starting GAI for '%s' with %u second time limit\n", NULL, me->hostname, timeLimitSecs ); const DNSServiceFlags flags = kDNSServiceFlagsReturnIntermediates | kDNSServiceFlagsAllowExpiredAnswers; me->gai = _OptimisticDNSTestCreateGAI( me, me->hostname, flags ); require_action( me->gai, exit, err = kNoResourcesErr ); const dnssd_getaddrinfo_t gai = me->gai; dnssd_getaddrinfo_set_result_handler( me->gai, ^( dnssd_getaddrinfo_result_t * const inResults, const size_t inCount ) { require_return( me->gai == gai ); size_t unexpectedResultCount = 0; for( size_t i = 0; i < inCount; ++i ) { Boolean resultIsExpected = false; const dnssd_getaddrinfo_result_t result = inResults[ i ]; const dnssd_getaddrinfo_result_type_t resultType = dnssd_getaddrinfo_result_get_type( result ); const bool fromCache = dnssd_getaddrinfo_result_is_from_cache( result ); if( SockAddrCompareAddr( dnssd_getaddrinfo_result_get_address( result ), &me->expectedAddr.sa ) == 0 ) { if( !gotAddResult ) { if( ( resultType == dnssd_getaddrinfo_result_type_add ) && !fromCache ) { gotAddResult = true; resultIsExpected = true; } } } if( !resultIsExpected ) ++unexpectedResultCount; FPrintF( stdout, "%{du:time} %s Got %sexpected result -- type: %s, hostname: %s, address: %##a, from cache: %s\n", NULL, resultIsExpected ? "✓" : "x", resultIsExpected ? "" : "un", dnssd_getaddrinfo_result_type_to_string( resultType ), dnssd_getaddrinfo_result_get_hostname( result ), dnssd_getaddrinfo_result_get_address( result ), YesNoStr( fromCache ) ); } if( unexpectedResultCount > 0 ) _OptimisticDNSTestStop( me, kUnexpectedErr ); } ); dnssd_getaddrinfo_activate( me->gai ); check( !me->timer ); me->timer = dispatch_source_create( DISPATCH_SOURCE_TYPE_TIMER, 0, 0, me->queue ); require_action_quiet( me->timer, exit, err = kNoResourcesErr ); dispatch_source_set_timer( me->timer, dispatch_time_seconds( timeLimitSecs ), DISPATCH_TIME_FOREVER, 0 ); dispatch_source_set_event_handler( me->timer, ^{ dispatch_source_forget( &me->timer ); dnssd_getaddrinfo_forget( &me->gai ); if( !gotAddResult ) { FPrintF( stdout, "%{du:time} Failed to get ADD GAI result for '%s' after %u seconds.\n", NULL, me->hostname, timeLimitSecs ); _OptimisticDNSTestStop( me, kTimeoutErr ); } else { const unsigned int waitSecs = 15; FPrintF( stdout, "%{du:time} ⧖ Waiting %u seconds for CNAME record to expire\n", NULL, waitSecs ); usleep( waitSecs * kMicrosecondsPerSecond ); const OSStatus startErr = _OptimisticDNSTestStartSubtest3_2( me ); if( startErr ) _OptimisticDNSTestStop( me, startErr ); } } ); dispatch_activate( me->timer ); err = kNoErr; exit: return( err ); } //=========================================================================================================================== static OSStatus _OptimisticDNSTestStartSubtest2_2( const OptimisticDNSTestRef me ) { OSStatus err; __block Boolean gotExpiredResult = false; __block Boolean gotAddResult = false; FPrintF( stdout, "%{du:time} Subtest 2.2: Second GAI for hostname with one CNAME record (CNAME record expired)\n", NULL ); check( me->hostname ); const unsigned int timeLimitSecs = 5; FPrintF( stdout, "%{du:time} * Starting GAI for '%s' with %u second time limit\n", NULL, me->hostname, timeLimitSecs ); const DNSServiceFlags flags = kDNSServiceFlagsReturnIntermediates | kDNSServiceFlagsAllowExpiredAnswers; me->gai = _OptimisticDNSTestCreateGAI( me, me->hostname, flags ); require_action( me->gai, exit, err = kNoResourcesErr ); const dnssd_getaddrinfo_t gai = me->gai; dnssd_getaddrinfo_set_result_handler( me->gai, ^( dnssd_getaddrinfo_result_t * const inResults, const size_t inCount ) { require_return( me->gai == gai ); size_t unexpectedResultCount = 0; for( size_t i = 0; i < inCount; ++i ) { Boolean resultIsExpected = false; const dnssd_getaddrinfo_result_t result = inResults[ i ]; const dnssd_getaddrinfo_result_type_t resultType = dnssd_getaddrinfo_result_get_type( result ); const bool fromCache = dnssd_getaddrinfo_result_is_from_cache( result ); if( SockAddrCompareAddr( dnssd_getaddrinfo_result_get_address( result ), &me->expectedAddr.sa ) == 0 ) { if( !gotExpiredResult ) { if( ( resultType == dnssd_getaddrinfo_result_type_expired ) && fromCache ) { gotExpiredResult = true; resultIsExpected = true; } } else if ( !gotAddResult ) { if( ( resultType == dnssd_getaddrinfo_result_type_add ) && !fromCache ) { gotAddResult = true; resultIsExpected = true; } } } if( !resultIsExpected ) ++unexpectedResultCount; FPrintF( stdout, "%{du:time} %s Got %sexpected result -- type: %s, hostname: %s, address: %##a, from cache: %s\n", NULL, resultIsExpected ? "✓" : "x", resultIsExpected ? "" : "un", dnssd_getaddrinfo_result_type_to_string( resultType ), dnssd_getaddrinfo_result_get_hostname( result ), dnssd_getaddrinfo_result_get_address( result ), YesNoStr( fromCache ) ); } if( unexpectedResultCount > 0 ) _OptimisticDNSTestStop( me, kUnexpectedErr ); } ); dnssd_getaddrinfo_activate( me->gai ); check( !me->timer ); me->timer = dispatch_source_create( DISPATCH_SOURCE_TYPE_TIMER, 0, 0, me->queue ); require_action_quiet( me->timer, exit, err = kNoResourcesErr ); dispatch_source_set_timer( me->timer, dispatch_time_seconds( timeLimitSecs ), DISPATCH_TIME_FOREVER, 0 ); dispatch_source_set_event_handler( me->timer, ^{ dispatch_source_forget( &me->timer ); dnssd_getaddrinfo_forget( &me->gai ); if( !gotExpiredResult || !gotAddResult ) { if( !gotExpiredResult ) { FPrintF( stdout, "%{du:time} Failed to get EXPIRED GAI result for '%s' after %u seconds.\n", NULL, me->hostname, timeLimitSecs ); } if( !gotAddResult ) { FPrintF( stdout, "%{du:time} Failed to get ADD GAI result for '%s' after %u seconds.\n", NULL, me->hostname, timeLimitSecs ); } _OptimisticDNSTestStop( me, kTimeoutErr ); } else { const OSStatus startErr = _OptimisticDNSTestStartSubtest3_1( me ); if( startErr ) _OptimisticDNSTestStop( me, startErr ); } } ); dispatch_activate( me->timer ); err = kNoErr; exit: return( err ); } //=========================================================================================================================== static OSStatus _OptimisticDNSTestStartSubtest2_1( const OptimisticDNSTestRef me ) { OSStatus err; __block Boolean gotAddResult = false; FPrintF( stdout, "%{du:time} Subtest 2.1: First GAI for hostname with one CNAME record\n", NULL ); // Create the hostname. const uint8_t offset = 20; ForgetMem( &me->hostname ); ASPrintF( &me->hostname, "alias-ttl-1.tag-cname-expires-first.ttl-600.offset-%u.%s", offset, mdns_domain_name_get_presentation( me->domain ) ); require_action( me->hostname, exit, err = kNoMemoryErr ); // Set the expected IPv4 address. char ipv4AddrStr[ kSockAddrStringMaxSize ]; SNPrintF( ipv4AddrStr, sizeof( ipv4AddrStr ), "203.0.113.%u", offset + 1 ); err = StringToSockAddr( ipv4AddrStr, &me->expectedAddr, sizeof( me->expectedAddr ), NULL ); require_noerr( err, exit ); const unsigned int timeLimitSecs = 5; FPrintF( stdout, "%{du:time} * Starting GAI for '%s' with %u second time limit\n", NULL, me->hostname, timeLimitSecs ); const DNSServiceFlags flags = kDNSServiceFlagsReturnIntermediates | kDNSServiceFlagsAllowExpiredAnswers; me->gai = _OptimisticDNSTestCreateGAI( me, me->hostname, flags ); require_action( me->gai, exit, err = kNoResourcesErr ); const dnssd_getaddrinfo_t gai = me->gai; dnssd_getaddrinfo_set_result_handler( me->gai, ^( dnssd_getaddrinfo_result_t * const inResults, const size_t inCount ) { require_return( me->gai == gai ); size_t unexpectedResultCount = 0; for( size_t i = 0; i < inCount; ++i ) { Boolean resultIsExpected = false; const dnssd_getaddrinfo_result_t result = inResults[ i ]; const dnssd_getaddrinfo_result_type_t resultType = dnssd_getaddrinfo_result_get_type( result ); const bool fromCache = dnssd_getaddrinfo_result_is_from_cache( result ); if( SockAddrCompareAddr( dnssd_getaddrinfo_result_get_address( result ), &me->expectedAddr.sa ) == 0 ) { if( !gotAddResult ) { if( ( resultType == dnssd_getaddrinfo_result_type_add ) && !fromCache ) { gotAddResult = true; resultIsExpected = true; } } } if( !resultIsExpected ) ++unexpectedResultCount; FPrintF( stdout, "%{du:time} %s Got %sexpected result -- type: %s, hostname: %s, address: %##a, from cache: %s\n", NULL, resultIsExpected ? "✓" : "x", resultIsExpected ? "" : "un", dnssd_getaddrinfo_result_type_to_string( resultType ), dnssd_getaddrinfo_result_get_hostname( result ), dnssd_getaddrinfo_result_get_address( result ), YesNoStr( fromCache ) ); } if( unexpectedResultCount > 0 ) _OptimisticDNSTestStop( me, kUnexpectedErr ); } ); dnssd_getaddrinfo_activate( me->gai ); check( !me->timer ); me->timer = dispatch_source_create( DISPATCH_SOURCE_TYPE_TIMER, 0, 0, me->queue ); require_action_quiet( me->timer, exit, err = kNoResourcesErr ); dispatch_source_set_timer( me->timer, dispatch_time_seconds( timeLimitSecs ), DISPATCH_TIME_FOREVER, 0 ); dispatch_source_set_event_handler( me->timer, ^{ dispatch_source_forget( &me->timer ); dnssd_getaddrinfo_forget( &me->gai ); if( !gotAddResult ) { FPrintF( stdout, "%{du:time} Failed to get ADD GAI result for '%s' after %u seconds.\n", NULL, me->hostname, timeLimitSecs ); _OptimisticDNSTestStop( me, kTimeoutErr ); } else { const unsigned int waitSecs = 15; FPrintF( stdout, "%{du:time} ⧖ Waiting %u seconds for CNAME record to expire\n", NULL, waitSecs ); usleep( waitSecs * kMicrosecondsPerSecond ); const OSStatus startErr = _OptimisticDNSTestStartSubtest2_2( me ); if( startErr ) _OptimisticDNSTestStop( me, startErr ); } } ); dispatch_activate( me->timer ); err = kNoErr; exit: return( err ); } //=========================================================================================================================== static OSStatus _OptimisticDNSTestStartSubtest1_2( const OptimisticDNSTestRef me ) { OSStatus err; __block Boolean gotExpiredResult = false; __block Boolean gotAddResult = false; FPrintF( stdout, "%{du:time} Subtest 1.2: Second GAI for hostname without CNAME records (A record expired)\n", NULL ); check( me->hostname ); const unsigned int timeLimitSecs = 5; FPrintF( stdout, "%{du:time} * Starting GAI for '%s' with %u second time limit\n", NULL, me->hostname, timeLimitSecs ); const DNSServiceFlags flags = kDNSServiceFlagsReturnIntermediates | kDNSServiceFlagsAllowExpiredAnswers; me->gai = _OptimisticDNSTestCreateGAI( me, me->hostname, flags ); require_action( me->gai, exit, err = kNoResourcesErr ); const dnssd_getaddrinfo_t gai = me->gai; dnssd_getaddrinfo_set_result_handler( me->gai, ^( dnssd_getaddrinfo_result_t * const inResults, const size_t inCount ) { require_return( me->gai == gai ); size_t unexpectedResultCount = 0; for( size_t i = 0; i < inCount; ++i ) { Boolean resultIsExpected = false; const dnssd_getaddrinfo_result_t result = inResults[ i ]; const dnssd_getaddrinfo_result_type_t resultType = dnssd_getaddrinfo_result_get_type( result ); const bool fromCache = dnssd_getaddrinfo_result_is_from_cache( result ); if( SockAddrCompareAddr( dnssd_getaddrinfo_result_get_address( result ), &me->expectedAddr.sa ) == 0 ) { if( !gotExpiredResult ) { if( ( resultType == dnssd_getaddrinfo_result_type_expired ) && fromCache ) { gotExpiredResult = true; resultIsExpected = true; } } else if ( !gotAddResult ) { if( ( resultType == dnssd_getaddrinfo_result_type_add ) && !fromCache ) { gotAddResult = true; resultIsExpected = true; } } } if( !resultIsExpected ) ++unexpectedResultCount; FPrintF( stdout, "%{du:time} %s Got %sexpected result -- type: %s, hostname: %s, address: %##a, from cache: %s\n", NULL, resultIsExpected ? "✓" : "x", resultIsExpected ? "" : "un", dnssd_getaddrinfo_result_type_to_string( resultType ), dnssd_getaddrinfo_result_get_hostname( result ), dnssd_getaddrinfo_result_get_address( result ), YesNoStr( fromCache ) ); } if( unexpectedResultCount > 0 ) _OptimisticDNSTestStop( me, kUnexpectedErr ); } ); dnssd_getaddrinfo_activate( me->gai ); check( !me->timer ); me->timer = dispatch_source_create( DISPATCH_SOURCE_TYPE_TIMER, 0, 0, me->queue ); require_action_quiet( me->timer, exit, err = kNoResourcesErr ); dispatch_source_set_timer( me->timer, dispatch_time_seconds( timeLimitSecs ), DISPATCH_TIME_FOREVER, 0 ); dispatch_source_set_event_handler( me->timer, ^{ dispatch_source_forget( &me->timer ); dnssd_getaddrinfo_forget( &me->gai ); if( !gotExpiredResult || !gotAddResult ) { if( !gotExpiredResult ) { FPrintF( stdout, "%{du:time} Failed to get EXPIRED GAI result for '%s' after %u seconds.\n", NULL, me->hostname, timeLimitSecs ); } if( !gotAddResult ) { FPrintF( stdout, "%{du:time} Failed to get ADD GAI result for '%s' after %u seconds.\n", NULL, me->hostname, timeLimitSecs ); } _OptimisticDNSTestStop( me, kTimeoutErr ); } else { const OSStatus startErr = _OptimisticDNSTestStartSubtest2_1( me ); if( startErr ) _OptimisticDNSTestStop( me, startErr ); } } ); dispatch_activate( me->timer ); err = kNoErr; exit: return( err ); } //=========================================================================================================================== static OSStatus _OptimisticDNSTestStartSubtest1_1( const OptimisticDNSTestRef me ) { OSStatus err; __block Boolean gotAddResult = false; FPrintF( stdout, "%{du:time} Subtest 1.1: First GAI for hostname without CNAME records\n", NULL ); // Create the hostname. const uint8_t offset = 10; ForgetMem( &me->hostname ); ASPrintF( &me->hostname, "ttl-1.offset-%u.%s", offset, mdns_domain_name_get_presentation( me->domain ) ); require_action( me->hostname, exit, err = kNoMemoryErr ); // Set the expected IPv4 address. char ipv4AddrStr[ kSockAddrStringMaxSize ]; SNPrintF( ipv4AddrStr, sizeof( ipv4AddrStr ), "203.0.113.%u", offset + 1 ); err = StringToSockAddr( ipv4AddrStr, &me->expectedAddr, sizeof( me->expectedAddr ), NULL ); require_noerr( err, exit ); const unsigned int timeLimitSecs = 5; FPrintF( stdout, "%{du:time} * Starting GAI for '%s' with %u second time limit\n", NULL, me->hostname, timeLimitSecs ); const DNSServiceFlags flags = kDNSServiceFlagsReturnIntermediates | kDNSServiceFlagsAllowExpiredAnswers; me->gai = _OptimisticDNSTestCreateGAI( me, me->hostname, flags ); require_action( me->gai, exit, err = kNoResourcesErr ); const dnssd_getaddrinfo_t gai = me->gai; dnssd_getaddrinfo_set_result_handler( me->gai, ^( dnssd_getaddrinfo_result_t * const inResults, const size_t inCount ) { require_return( me->gai == gai ); size_t unexpectedResultCount = 0; for( size_t i = 0; i < inCount; ++i ) { Boolean resultIsExpected = false; const dnssd_getaddrinfo_result_t result = inResults[ i ]; const dnssd_getaddrinfo_result_type_t resultType = dnssd_getaddrinfo_result_get_type( result ); const bool fromCache = dnssd_getaddrinfo_result_is_from_cache( result ); if( !gotAddResult ) { if( ( resultType == dnssd_getaddrinfo_result_type_add ) && !fromCache ) { if( SockAddrCompareAddr( dnssd_getaddrinfo_result_get_address( result ), &me->expectedAddr.sa ) == 0 ) { gotAddResult = true; resultIsExpected = true; } } } if( !resultIsExpected ) ++unexpectedResultCount; FPrintF( stdout, "%{du:time} %s Got %sexpected result -- type: %s, hostname: %s, address: %##a, from cache: %s\n", NULL, resultIsExpected ? "✓" : "x", resultIsExpected ? "" : "un", dnssd_getaddrinfo_result_type_to_string( resultType ), dnssd_getaddrinfo_result_get_hostname( result ), dnssd_getaddrinfo_result_get_address( result ), YesNoStr( fromCache ) ); } if( unexpectedResultCount > 0 ) _OptimisticDNSTestStop( me, kUnexpectedErr ); } ); dnssd_getaddrinfo_activate( me->gai ); check( !me->timer ); me->timer = dispatch_source_create( DISPATCH_SOURCE_TYPE_TIMER, 0, 0, me->queue ); require_action_quiet( me->timer, exit, err = kNoResourcesErr ); dispatch_source_set_timer( me->timer, dispatch_time_seconds( timeLimitSecs ), DISPATCH_TIME_FOREVER, 0 ); dispatch_source_set_event_handler( me->timer, ^{ dispatch_source_forget( &me->timer ); dnssd_getaddrinfo_forget( &me->gai ); if( !gotAddResult ) { FPrintF( stdout, "%{du:time} Failed to get ADD GAI result for '%s' after %u seconds.\n", NULL, me->hostname, timeLimitSecs ); _OptimisticDNSTestStop( me, kTimeoutErr ); } else { const unsigned int waitSecs = 15; FPrintF( stdout, "%{du:time} ⧖ Waiting %u seconds for A record to expire\n", NULL, waitSecs ); usleep( waitSecs * kMicrosecondsPerSecond ); const OSStatus startErr = _OptimisticDNSTestStartSubtest1_2( me ); if( startErr ) _OptimisticDNSTestStop( me, startErr ); } } ); dispatch_activate( me->timer ); err = kNoErr; exit: return( err ); } //=========================================================================================================================== static void _OptimisticDNSTestStart( void * const inCtx ) { OSStatus err; char *domainStr = NULL; char *serverCmd = NULL; const OptimisticDNSTestRef me = (OptimisticDNSTestRef) inCtx; char tag[ 6 + 1 ]; ASPrintF( &domainStr, "optimistic-dns-test-%s.test.", _RandomStringExact( kLowerAlphaNumericCharSet, kLowerAlphaNumericCharSetSize, sizeof( tag ) - 1, tag ) ); require_action( domainStr, exit, err = kNoMemoryErr ); me->domain = mdns_domain_name_create( domainStr, mdns_domain_name_create_opts_none, &err ); require_noerr( err, exit ); // Use the --registerSC option because this test uses GAI operations that only specify a single label as the // hostname. The test depends on a search domain equal to a match domain for the test DNS server being appended to // PQDNs. SystemConfiguration will set up a search domain for each of the DNS service's match domains. // // Also use --default to make the DNS server act as a low-priority default DNS service. Currently, a negative // response from a server is required to iterate to the next search domain in the search domain list. If a test // device happens to not be connected to any network, then it won't have a DHCP-assigned DNS service to act as a // default DNS service that could provide a potentially negative response to move things along. ASPrintF( &serverCmd, DNSSDUTIL_TEST_DNS_SERVER_COMMAND_PREAMBLE " --loopback --registerSC --default --follow %lld --responseDelay 10" " --domain %s", (int64_t) getpid(), mdns_domain_name_get_presentation( me->domain ) ); require_action_quiet( serverCmd, exit, err = kNoMemoryErr ); FPrintF( stdout, "%{du:time} Starting DNS server with command: %s\n", NULL, serverCmd ); err = _SpawnCommand( &me->serverPID, "/dev/null", "/dev/null", "%s", serverCmd ); require_noerr( err, exit ); ASPrintF( &me->hostname, "tag-probe.offset-250.%s", mdns_domain_name_get_presentation( me->domain ) ); require_action( me->hostname, exit, err = kNoMemoryErr ); me->gai = _OptimisticDNSTestCreateGAI( me, me->hostname, 0 ); require_action( me->gai, exit, err = kNoResourcesErr ); const unsigned int timeoutSecs = 5; FPrintF( stdout, "%{du:time} Starting GAI to detect DNS server readiness with probe hostname '%s' and %u second timeout\n", NULL, me->hostname, timeoutSecs ); const dnssd_getaddrinfo_t gai = me->gai; dnssd_getaddrinfo_set_result_handler( me->gai, ^( dnssd_getaddrinfo_result_t * const inResults, const size_t inCount ) { require_return( me->gai == gai ); Boolean proceed = false; for( size_t i = 0; i < inCount; ++i ) { const dnssd_getaddrinfo_result_t result = inResults[ i ]; if( dnssd_getaddrinfo_result_get_type( result ) == dnssd_getaddrinfo_result_type_add ) { proceed = true; break; } } if( proceed ) { dispatch_source_forget( &me->timer ); dnssd_getaddrinfo_forget( &me->gai ); FPrintF( stdout, "%{du:time} Starting subtests...\n", NULL ); const OSStatus startErr = _OptimisticDNSTestStartSubtest1_1( me ); if( startErr ) _OptimisticDNSTestStop( me, startErr ); } } ); dnssd_getaddrinfo_activate( me->gai ); me->timer = dispatch_source_create( DISPATCH_SOURCE_TYPE_TIMER, 0, 0, me->queue ); require_action_quiet( me->timer, exit, err = kNoResourcesErr ); dispatch_source_set_timer( me->timer, dispatch_time_seconds( timeoutSecs ), DISPATCH_TIME_FOREVER, 0 ); dispatch_source_set_event_handler( me->timer, ^{ FPrintF( stdout, "%{du:time} Probe GAI request for '%s' timed out after %u seconds.\n", NULL, me->hostname, timeoutSecs ); _OptimisticDNSTestStop( me, kNotPreparedErr ); } ); dispatch_activate( me->timer ); exit: ForgetMem( &domainStr ); ForgetMem( &serverCmd ); if( err ) _OptimisticDNSTestStop( me, err ); } //=========================================================================================================================== static OSStatus _OptimisticDNSTestRun( const OptimisticDNSTestRef me ) { dispatch_async_f( me->queue, me, _OptimisticDNSTestStart ); dispatch_semaphore_wait( me->doneSem, DISPATCH_TIME_FOREVER ); return( me->error ); } //=========================================================================================================================== static void _OptimisticDNSTestRelease( const OptimisticDNSTestRef me ) { if( atomic_add_and_fetch_32( &me->refCount, -1 ) == 0 ) { check( !me->gai ); check( !me->timer ); check( !me->domain ); check( !me->hostname ); check( me->serverPID < 0 ); dispatch_forget( &me->queue ); dispatch_forget( &me->doneSem ); free( me ); } } //=========================================================================================================================== // RecordRegistrationTestCommand //=========================================================================================================================== // The RRSet maximum member count is currently 32. This allows the use of 32-bit bitmaps for keeping track of RRSet // membership. #define kRecordRegistrationTestRRSetMemberCountMax 32 typedef struct RecordRegistrationTest * RecordRegistrationTestRef; struct RecordRegistrationTest { dispatch_queue_t queue; // Serial queue for test events. dispatch_semaphore_t doneSem; // Semaphore to signal when the test is done. DNSServiceRef query; // Reference for DNSServiceQueryRecord() operation. DNSServiceRef connection; // Connection for record registrations. DNSRecordRef recordRefs[ 32 ]; // Record registration references. dispatch_source_t timer; // Timer for enforcing time limits. char * rrSetName; // Name of current RRSet. MDNSColliderRef collider; // Creates an mDNS record conflict. [1] int32_t refCount; // Test's reference count. OSStatus error; // Test's error code. size_t rrSetBitmapIndex; // The current RRSet bitmap index. uint32_t rrSetBitmapObserved; // Keeps track of RRSet members seen by DNSServiceQueryRecord(). size_t subtestIndex; // Current subtest index into kRegisterRecordSubtests array. size_t interfaceSetIndex; // Current subtest interface set index. uint32_t registrationIfIndex; // Current interface index to pass to DNSServiceRegisterRecord(). DNSServiceFlags registrationFlags; // Current flags to pass to DNSServiceRegisterRecord(). unsigned int rrSetChangeIntervalMs; // The time to wait in between RRSet changes. uint8_t rdataBase[ 4 ]; // The randomly-generated A record RDATA base for the RRSet. Boolean conflictOccurred; // True if the subtest's expected record conflict has occurred. [1] }; // Notes: // 1. Exclusively For conflict subtests. check_compile_time( countof_field( struct RecordRegistrationTest, recordRefs ) == kRecordRegistrationTestRRSetMemberCountMax ); typedef enum { kRecordRegistrationTestInterfaceSet_Any = 1, // "Any" interface, except AWDL and P2P interfaces. [1] kRecordRegistrationTestInterfaceSet_Loopback = 2, // The loopback interface. kRecordRegistrationTestInterfaceSet_LocalOnly = 3, // The LocalOnly pseudo-interface. kRecordRegistrationTestInterfaceSet_AnyPlusAWDL = 4, // "Any" interface plus AWDL interface. [2] kRecordRegistrationTestInterfaceSet_AnyPlusP2P = 5, // "Any" interface plus P2P interface. [3] kRecordRegistrationTestInterfaceSet_AnyPlusAWDLAndP2P = 6, // "Any" interface plus AWDL and P2P interfaces. [4] } RecordRegistrationTestInterfaceSet; // Notes: // 1. Uses kDNSServiceInterfaceIndexAny. // 2. Uses kDNSServiceInterfaceIndexAny along with kDNSServiceFlagsIncludeAWDL. // 3. Uses kDNSServiceInterfaceIndexAny along with kDNSServiceFlagsIncludeP2P. // 4. Uses kDNSServiceInterfaceIndexAny along with kDNSServiceFlagsIncludeAWDL and kDNSServiceFlagsIncludeP2P. typedef enum { kRecordRegistrationTestRegistrationType_Shared = 1, // Shared record (use kDNSServiceFlagsShared). kRecordRegistrationTestRegistrationType_Unique = 2, // Unique record (use kDNSServiceFlagsUnique). kRecordRegistrationTestRegistrationType_KnownUnique = 3, // KnownUnique record (use kDNSServiceFlagsKnownUnique). } RecordRegistrationTestRegistrationType; typedef enum { kRecordRegistrationSubtestType_ChangingRRSet = 1, // Subtest that deals with an RRSet that changes over time. kRecordRegistrationSubtestType_Conflict = 2, // Subtest that deals with an RRSet that experiences a conflict. } RecordRegistrationSubtestType; typedef struct { RecordRegistrationTestRegistrationType registrationType; // Type of record registrations. const RecordRegistrationTestInterfaceSet * interfaceSets; // Interface sets to use for registrations. size_t interfaceSetCount; // Number of interface sets. const uint32_t * rrSetBitmaps; // The different instances of the RRSet to register. size_t rrSetBitmapCount; // Number of RRSet bitmaps. RecordRegistrationSubtestType type; // Type of subtest. } RegisterRecordSubtest; static const RecordRegistrationTestInterfaceSet kRecordRegistrationTestAllInterfaceSets[] = { kRecordRegistrationTestInterfaceSet_Any, kRecordRegistrationTestInterfaceSet_Loopback, kRecordRegistrationTestInterfaceSet_LocalOnly, kRecordRegistrationTestInterfaceSet_AnyPlusAWDL, kRecordRegistrationTestInterfaceSet_AnyPlusP2P, kRecordRegistrationTestInterfaceSet_AnyPlusAWDLAndP2P, }; static const RecordRegistrationTestInterfaceSet kRecordRegistrationTestAllInterfaceSetsExceptLocalOnly[] = { kRecordRegistrationTestInterfaceSet_Any, kRecordRegistrationTestInterfaceSet_Loopback, kRecordRegistrationTestInterfaceSet_AnyPlusAWDL, kRecordRegistrationTestInterfaceSet_AnyPlusP2P, kRecordRegistrationTestInterfaceSet_AnyPlusAWDLAndP2P, }; #define _BitU32( N ) ( UINT32_C( 1 ) << (N) ) // The following array defines the different instances of a test RRSet. Each bit represents the presence or absence of a // member of the RRSet (1 means present, 0 means absent). The test RRSet consists of A records with the same name and // class. If the RRSet's name is register-record-test-wfpx79.local, and the A records are offsets from a base IPv4 // address of 203.0.113.0, then a bitmap of 0x0000002A represents the RRSet with three members (1, 3, and 5): // // register-record-test-wfpx79.local IN A 203.0.113.1 // register-record-test-wfpx79.local IN A 203.0.113.3 // register-record-test-wfpx79.local IN A 203.0.113.5 // // The test actualizes each RRSet instance by making calls to DNSServiceRegisterRecord() and DNSServiceRemoveRecord() to // transform the previous RRSet instance. The first RRSet instance is simply the product of DNSServiceRegisterRecord() // calls. static const uint32_t kRecordRegistrationTestRRSetBitmaps_ChanginRRSet[] = { _BitU32( 1 ), // RRSet with members {1} _BitU32( 1 ) | _BitU32( 2 ), // RRSet with members {1, 2} (Add one member) _BitU32( 1 ) | _BitU32( 2 ) | _BitU32( 3 ), // RRSet with members {1, 2, 3} (Add one member) _BitU32( 2 ) | _BitU32( 3 ), // RRSet with members {2, 3} (Remove one member) _BitU32( 2 ) | _BitU32( 3 ) | _BitU32( 4 ) | _BitU32( 5 ), // RRSet with members {2, 3, 4, 5} (Add two members) _BitU32( 3 ) | _BitU32( 5 ), // RRSet with members {3, 5} (Remove two members) _BitU32( 3 ) | _BitU32( 6 ), // RRSet with members {3, 6} (Add one, Remove one) _BitU32( 7 ) | _BitU32( 8 ), // RRSet with members {7, 8} (Add two, Remove two) _BitU32( 7 ), // RRSet with members {7} (Remove one member) 0, // RRSet with members {} (Remove one → empty set) _BitU32( 9 ) | _BitU32( 10 ), // RRSet with members {9, 10} (Add two to empty set) 0, // RRSet with members {} (Remove two → empty set) }; static const uint32_t kRecordRegistrationTestConflictRRsetBitmaps_SingleRRSet[] = { _BitU32( 1 ), // RRSet with one member. }; static const RegisterRecordSubtest kRegisterRecordSubtests[] = { { .type = kRecordRegistrationSubtestType_ChangingRRSet, .registrationType = kRecordRegistrationTestRegistrationType_Unique, .interfaceSets = kRecordRegistrationTestAllInterfaceSets, .interfaceSetCount = countof( kRecordRegistrationTestAllInterfaceSets ), .rrSetBitmaps = kRecordRegistrationTestRRSetBitmaps_ChanginRRSet, .rrSetBitmapCount = countof( kRecordRegistrationTestRRSetBitmaps_ChanginRRSet ), }, { .type = kRecordRegistrationSubtestType_ChangingRRSet, .registrationType = kRecordRegistrationTestRegistrationType_KnownUnique, .interfaceSets = kRecordRegistrationTestAllInterfaceSets, .interfaceSetCount = countof( kRecordRegistrationTestAllInterfaceSets ), .rrSetBitmaps = kRecordRegistrationTestRRSetBitmaps_ChanginRRSet, .rrSetBitmapCount = countof( kRecordRegistrationTestRRSetBitmaps_ChanginRRSet ), }, { .type = kRecordRegistrationSubtestType_ChangingRRSet, .registrationType = kRecordRegistrationTestRegistrationType_Shared, .interfaceSets = kRecordRegistrationTestAllInterfaceSets, .interfaceSetCount = countof( kRecordRegistrationTestAllInterfaceSets ), .rrSetBitmaps = kRecordRegistrationTestRRSetBitmaps_ChanginRRSet, .rrSetBitmapCount = countof( kRecordRegistrationTestRRSetBitmaps_ChanginRRSet ), }, { .type = kRecordRegistrationSubtestType_Conflict, .registrationType = kRecordRegistrationTestRegistrationType_Unique, .interfaceSets = kRecordRegistrationTestAllInterfaceSetsExceptLocalOnly, .interfaceSetCount = countof( kRecordRegistrationTestAllInterfaceSetsExceptLocalOnly ), .rrSetBitmaps = kRecordRegistrationTestConflictRRsetBitmaps_SingleRRSet, .rrSetBitmapCount = countof( kRecordRegistrationTestConflictRRsetBitmaps_SingleRRSet ), }, { .type = kRecordRegistrationSubtestType_Conflict, .registrationType = kRecordRegistrationTestRegistrationType_KnownUnique, .interfaceSets = kRecordRegistrationTestAllInterfaceSetsExceptLocalOnly, .interfaceSetCount = countof( kRecordRegistrationTestAllInterfaceSetsExceptLocalOnly ), .rrSetBitmaps = kRecordRegistrationTestConflictRRsetBitmaps_SingleRRSet, .rrSetBitmapCount = countof( kRecordRegistrationTestConflictRRsetBitmaps_SingleRRSet ), }, }; //=========================================================================================================================== static void _RecordRegistrationTestRelease( const RecordRegistrationTestRef me ) { if( atomic_add_and_fetch_32( &me->refCount, -1 ) == 0 ) { dispatch_forget( &me->queue ); dispatch_forget( &me->doneSem ); check( !me->query ); check( !me->connection ); for( size_t i = 0; i < countof( me->recordRefs ); ++i ) { check( !me->recordRefs[ i ] ); } check( !me->timer ); ForgetMem( &me->rrSetName ); free( me ); } } //=========================================================================================================================== static RecordRegistrationTestRef _RecordRegistrationTestCreate( const unsigned int inRRSetChangeIntervalMs, OSStatus * const outError ) { OSStatus err; RecordRegistrationTestRef test = NULL; RecordRegistrationTestRef obj = (RecordRegistrationTestRef) calloc( 1, sizeof( *obj ) ); require_action( obj, exit, err = kNoMemoryErr ); obj->refCount = 1; obj->error = kInProgressErr; obj->rrSetChangeIntervalMs = inRRSetChangeIntervalMs; obj->queue = dispatch_queue_create( "com.apple.dnssdutil.record-registration-test", DISPATCH_QUEUE_SERIAL ); require_action( obj->queue, exit, err = kNoResourcesErr ); obj->doneSem = dispatch_semaphore_create( 0 ); require_action( obj->doneSem, exit, err = kNoResourcesErr ); test = obj; obj = NULL; err = kNoErr; exit: if( outError ) *outError = err; if( obj ) _RecordRegistrationTestRelease( obj ); return( test ); } //=========================================================================================================================== static void _RecordRegistrationTestTearDownDNSServiceRefs( const RecordRegistrationTestRef me ) { // Forget the DNSServiceQueryRecord() operation. DNSServiceForget( &me->query ); // Forget the connection for record registrations, then NULL out record registration references, which are now invalid. DNSServiceForget( &me->connection ); for( size_t i = 0; i < countof( me->recordRefs ); ++i ) { me->recordRefs[ i ] = NULL; } } //=========================================================================================================================== static void _RecordRegistrationTestStop( const RecordRegistrationTestRef me, const OSStatus inError ) { me->error = inError; if( !me->error ) { FPrintF( stdout, "%{du:time} Test PASSED\n", NULL ); } else { FPrintF( stdout, "%{du:time} Test FAILED: %#m\n", NULL, me->error ); } _RecordRegistrationTestTearDownDNSServiceRefs( me ); MDNSColliderForget( &me->collider ); dispatch_source_forget( &me->timer ); ForgetMem( &me->rrSetName ); dispatch_semaphore_signal( me->doneSem ); } //=========================================================================================================================== static const RegisterRecordSubtest * _RecordRegistrationTestGetCurrentSubtest( const RecordRegistrationTestRef me ) { require_fatal( me->subtestIndex < countof( kRegisterRecordSubtests ), "Invalid subtest index %zu >= %zu", me->subtestIndex, countof( kRegisterRecordSubtests ) ); return &kRegisterRecordSubtests[ me->subtestIndex ]; } //=========================================================================================================================== static RecordRegistrationSubtestType _RecordRegistrationTestGetCurrentSubtestType( const RecordRegistrationTestRef me ) { const RegisterRecordSubtest * const subtest = _RecordRegistrationTestGetCurrentSubtest( me ); return subtest->type; } //=========================================================================================================================== static void _RecordRegistrationTestRegisterRecordCallback( __unused const DNSServiceRef inSDRef, const DNSRecordRef inRecordRef, __unused const DNSServiceFlags inFlags, const DNSServiceErrorType inError, void * const inCtx ) { OSStatus err; size_t i; Boolean foundRecordRef = false; const RecordRegistrationTestRef me = (RecordRegistrationTestRef) inCtx; for( i = 0; i < countof( me->recordRefs ); ++i ) { if( me->recordRefs[ i ] == inRecordRef ) { foundRecordRef = true; break; } } const RecordRegistrationSubtestType subtestType = _RecordRegistrationTestGetCurrentSubtestType( me ); if( foundRecordRef ) { me->rdataBase[ 3 ] = (uint8_t) i; if( inError ) { err = inError; if( subtestType == kRecordRegistrationSubtestType_Conflict ) { if( me->collider && ( inError == kDNSServiceErr_NameConflict ) ) { FPrintF( stdout, "%{du:time} ✓ Got expected conflict for record registration: %s IN A %.4a\n", NULL, me->rrSetName, me->rdataBase ); me->conflictOccurred = true; err = kNoErr; } } if( err ) { FPrintF( stdout, "%{du:time} x Failed to register record: %s IN A %.4a\n", NULL, me->rrSetName, me->rdataBase ); } } else { FPrintF( stdout, "%{du:time} + Registered record: %s IN A %.4a\n", NULL, me->rrSetName, me->rdataBase ); err = kNoErr; } } else { FPrintF( stdout, "%{du:time} x Got register record callback for unrecognized record reference\n", NULL ); err = kUnexpectedErr; } if( err ) _RecordRegistrationTestStop( me, err ); } //=========================================================================================================================== static OSStatus _RecordRegistrationTestRegisterRecord( const RecordRegistrationTestRef me, const uint8_t inMemberID, DNSRecordRef * const outRecordRef ) { me->rdataBase[ 3 ] = inMemberID; FPrintF( stdout, "%{du:time} ± Registering record: %s IN A %.4a\n", NULL, me->rrSetName, me->rdataBase ); const OSStatus err = DNSServiceRegisterRecord( me->connection, outRecordRef, me->registrationFlags, me->registrationIfIndex, me->rrSetName, kDNSServiceType_A, kDNSServiceClass_IN, sizeof( me->rdataBase ), me->rdataBase, 1 * kSecondsPerHour, _RecordRegistrationTestRegisterRecordCallback, me ); return( err ); } //=========================================================================================================================== static void _RecordRegistrationTestRetain( const RecordRegistrationTestRef me ) { atomic_add_32( &me->refCount, 1 ); } //=========================================================================================================================== static RecordRegistrationTestInterfaceSet _RecordRegistrationTestGetCurrentInterfaceSet( const RecordRegistrationTestRef me ) { const RegisterRecordSubtest * const subtest = _RecordRegistrationTestGetCurrentSubtest( me ); require_fatal( me->interfaceSetIndex < subtest->interfaceSetCount, "Invalid interface set index %zu >= %zu", me->subtestIndex, subtest->interfaceSetCount ); return subtest->interfaceSets[ me->interfaceSetIndex ]; } //=========================================================================================================================== static RecordRegistrationTestRegistrationType _RecordRegistrationTestGetCurrentRegistrationType( const RecordRegistrationTestRef me ) { const RegisterRecordSubtest * const subtest = _RecordRegistrationTestGetCurrentSubtest( me ); return subtest->registrationType; } //=========================================================================================================================== static uint32_t _RecordRegistrationTestGetCurrentRRSetBitmap( const RecordRegistrationTestRef me ) { const RegisterRecordSubtest * const subtest = _RecordRegistrationTestGetCurrentSubtest( me ); require_fatal( me->rrSetBitmapIndex < subtest->rrSetBitmapCount, "Invalid RRSet bitmap index %zu >= %zu", me->rrSetBitmapIndex, subtest->rrSetBitmapCount ); return subtest->rrSetBitmaps[ me->rrSetBitmapIndex ]; } //=========================================================================================================================== static uint32_t _RecordRegistrationTestGetLoopbackInterfaceIndex( void ) { static uint32_t sIfIndex = 0; if( sIfIndex == 0 ) { const char * const loopbackIfName = "lo0"; sIfIndex = if_nametoindex( loopbackIfName ); require_fatal( sIfIndex != 0, "Failed to get loopback interface (%s) index", loopbackIfName ); } return( sIfIndex ); } //=========================================================================================================================== static OSStatus _RecordRegistrationTestTriggerConflict( const RecordRegistrationTestRef me ) { OSStatus err; MDNSColliderRef collider = NULL; require_action_quiet( !me->collider, exit, err = kNoErr ); // AAAA RDATA for IPv6 address 2001:db8::dead:beef. const uint8_t rdata[ 16 ] = { 0x20, 0x01, 0x0D, 0xB8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xDE, 0xAD, 0xBE, 0xEF }; FPrintF( stdout, "%{du:time} ! Scheduling send of conflicting record: %s IN AAAA %.16a\n", NULL, me->rrSetName, rdata ); err = MDNSColliderCreate( me->queue, &collider ); require_noerr( err, exit ); err = MDNSColliderSetProgram( collider, "probes 10r; send; wait 5000" ); require_noerr( err, exit ); uint8_t rrSetName[ kDomainNameLengthMax ]; err = DomainNameFromString( rrSetName, me->rrSetName, NULL ); require_noerr( err, exit ); // The subtest's RRSet consists of A records, but a AAAA record with the same name and class is sufficient to cause // a conflict. See rdar://3483349 (Any rrtype is a conflict for unique records). err = MDNSColliderSetRecord( collider, rrSetName, kDNSServiceType_AAAA, rdata, sizeof( rdata ) ); require_noerr( err, exit ); MDNSColliderSetProtocols( collider, kMDNSColliderProtocol_IPv4 ); MDNSColliderSetInterfaceIndex( collider, _RecordRegistrationTestGetLoopbackInterfaceIndex() ); err = MDNSColliderStart( collider ); require_noerr( err, exit ); me->collider = collider; collider = NULL; exit: CFForget( &collider ); return( err ); } //=========================================================================================================================== static void DNSSD_API _RecordRegistrationTestQueryRecordCallback( __unused const DNSServiceRef inSDRef, const DNSServiceFlags inFlags, __unused const uint32_t inIfIndex, const DNSServiceErrorType inError, const char * const inFullName, const uint16_t inType, const uint16_t inClass, const uint16_t inRDataLen, const void * const inRDataPtr, __unused const uint32_t inTTL, void * const inCtx ) { OSStatus err; const RecordRegistrationTestRef me = (RecordRegistrationTestRef) inCtx; // Print result. char ifNameBuf[ kInterfaceNameBufLen ]; const char * const ifName = InterfaceIndexToName( inIfIndex, ifNameBuf ); char typeBuf[ 32 ]; const char *typeStr = DNSRecordTypeValueToString( inType ); if( !typeStr ) { SNPrintF( typeBuf, sizeof( typeBuf ), "TYPE%u", inType ); typeStr = typeBuf; } char classBuf[ 32 ]; const char *classStr; if( inClass == kDNSServiceClass_IN ) { classStr = "IN"; } else { SNPrintF( classBuf, sizeof( classBuf ), "CLASS%u", inClass ); classStr = classBuf; } char *rdataStr = NULL; DNSRecordDataToString( inRDataPtr, inRDataLen, inType, &rdataStr ); if( !rdataStr ) { ASPrintF( &rdataStr, "%#H", inRDataPtr, (int) inRDataLen, (int) inRDataLen ); require_action( rdataStr, exit, err = kNoMemoryErr ); } FPrintF( stdout, "%{du:time} → QueryRecord result -- flags: 0x%0X (%s), interface: %s/%d, error: %#m, name: %s," " type: %s, class: %s, ttl: %u, rdata: %s\n", NULL, inFlags, (inFlags & kDNSServiceFlagsAdd) ? "Add" : "Rmv", ifName ? ifName : "", (int32_t) inIfIndex, inError, inFullName, typeStr, classStr, inTTL, rdataStr ); // Check callback arguments. uint8_t fullName[ kDomainNameLengthMax ]; err = DomainNameFromString( fullName, inFullName, NULL ); require_noerr_quiet( err, exit ); uint8_t rrSetName[ kDomainNameLengthMax ]; err = DomainNameFromString( rrSetName, me->rrSetName, NULL ); require_noerr_quiet( err, exit ); const Boolean nameMatch = DomainNameEqual( fullName, rrSetName ); require_action( nameMatch, exit, err = kNameErr ); require_action( inType == kDNSServiceType_A, exit, err = kTypeErr ); require_action( inClass == kDNSServiceClass_IN, exit, err = kTypeErr ); require_action( inRDataLen == 4, exit, err = kSizeErr ); const int cmp = memcmp( inRDataPtr, me->rdataBase, 3 ); require_action( cmp == 0, exit, err = kValueErr ); const uint8_t memberID = ( (const uint8_t *) inRDataPtr )[ 3 ]; require_action( memberID < kRecordRegistrationTestRRSetMemberCountMax, exit, err = kValueErr ); err = inError; require_noerr( err, exit ); // Keep track of adds and removes making sure we don't get any unexpected adds or removes in the process. const uint32_t rrSetBitmap = _RecordRegistrationTestGetCurrentRRSetBitmap( me ); const uint32_t bitmask = _BitU32( memberID ); const RecordRegistrationSubtestType subtestType = _RecordRegistrationTestGetCurrentSubtestType( me ); if( inFlags & kDNSServiceFlagsAdd ) { if( ( subtestType == kRecordRegistrationSubtestType_Conflict ) && me->conflictOccurred ) { FPrintF( stdout, "%{du:time} x Got an ADD query result after the expected conflict\n", NULL ); err = kUnexpectedErr; goto exit; } require_action( rrSetBitmap & bitmask, exit, err = kUnexpectedErr ); require_action( ( me->rrSetBitmapObserved & bitmask ) == 0, exit, err = kDuplicateErr ); me->rrSetBitmapObserved |= bitmask; if( subtestType == kRecordRegistrationSubtestType_Conflict ) { // If all members of the RRSet have been observed, proceed with the conflict. if( me->rrSetBitmapObserved == rrSetBitmap ) { _RecordRegistrationTestTriggerConflict( me ); } } } else { // During Changing RRSet subtests, getting a remove for members of the last registerered RRSet is considered // incorrect behavior because that's the very RRSet that we're expected to observe via the QueryRecord // operation. // // During Conflict RRSet subtests, this is also considered incorrect behavior, but only before the expected // conflict occurs. After the expected conflict, the removes are expected since the conflict is supposed to // flush the RRSet from the record cache. if( ( subtestType == kRecordRegistrationSubtestType_ChangingRRSet ) || ( ( subtestType == kRecordRegistrationSubtestType_Conflict ) && !me->conflictOccurred ) ) { require_action( ( rrSetBitmap & bitmask ) == 0, exit, err = kUnexpectedErr ); } require_action( ( me->rrSetBitmapObserved & bitmask ) != 0, exit, err = kUnexpectedErr ); me->rrSetBitmapObserved &= ~bitmask; } exit: ForgetMem( &rdataStr ); if( err ) _RecordRegistrationTestStop( me, err ); } //=========================================================================================================================== static const char * _RecordRegistrationTestInterfaceSetToString( const RecordRegistrationTestInterfaceSet inInterfaceSet ) { switch( inInterfaceSet ) { case kRecordRegistrationTestInterfaceSet_Any: return( "Any Interface" ); case kRecordRegistrationTestInterfaceSet_Loopback: return( "Loopback Interface" ); case kRecordRegistrationTestInterfaceSet_LocalOnly: return( "LocalOnly Pseudo-Interface" ); case kRecordRegistrationTestInterfaceSet_AnyPlusAWDL: return( "Any+AWDL Interfaces" ); case kRecordRegistrationTestInterfaceSet_AnyPlusP2P: return( "Any+P2P Interfaces" ); case kRecordRegistrationTestInterfaceSet_AnyPlusAWDLAndP2P: return( "Any+AWDL+P2P Interfaces" ); } return( "«INVALID INTERFACE SET»" ); } //=========================================================================================================================== static const char * _RecordRegistrationTestRegistrationTypeToString( const RecordRegistrationTestRegistrationType inRegType ) { switch( inRegType ) { case kRecordRegistrationTestRegistrationType_Shared: return( "Shared" ); case kRecordRegistrationTestRegistrationType_Unique: return( "Unique" ); case kRecordRegistrationTestRegistrationType_KnownUnique: return( "KnownUnique" ); } return( "«INVALID REGISTRATION TYPE»" ); } //=========================================================================================================================== static const char * _RecordRegistrationSubtestTypeToString( const RecordRegistrationSubtestType inSubtestType ) { switch( inSubtestType ) { case kRecordRegistrationSubtestType_ChangingRRSet: return("Changing RRSet"); case kRecordRegistrationSubtestType_Conflict: return("Conflict"); } return( "«INVALID SUBTEST TYPE»" ); } //=========================================================================================================================== static OSStatus _RecordRegistrationTestActualizeRRSet( RecordRegistrationTestRef inTest ); static OSStatus _RecordRegistrationTestStartRegistrationsAndQuery( const RecordRegistrationTestRef me ) { // Clean up previous DNSServiceRefs and collider. _RecordRegistrationTestTearDownDNSServiceRefs( me ); MDNSColliderForget( &me->collider ); me->conflictOccurred = false; const RecordRegistrationSubtestType subtestType = _RecordRegistrationTestGetCurrentSubtestType( me ); switch( subtestType ) { case kRecordRegistrationSubtestType_ChangingRRSet: case kRecordRegistrationSubtestType_Conflict: break; default: FatalErrorF( "Unhandled RecordRegistrationSubtestType value %d", subtestType ); } // Select interface indexes for DNSServiceRegisterRecord() and DNSServiceQueryRecord(). uint32_t queryRecordIfIndex; const RecordRegistrationTestInterfaceSet interfaceSet = _RecordRegistrationTestGetCurrentInterfaceSet( me ); switch( interfaceSet ) { case kRecordRegistrationTestInterfaceSet_Any: case kRecordRegistrationTestInterfaceSet_AnyPlusAWDL: case kRecordRegistrationTestInterfaceSet_AnyPlusP2P: case kRecordRegistrationTestInterfaceSet_AnyPlusAWDLAndP2P: me->registrationIfIndex = kDNSServiceInterfaceIndexAny; queryRecordIfIndex = _RecordRegistrationTestGetLoopbackInterfaceIndex(); break; case kRecordRegistrationTestInterfaceSet_Loopback: me->registrationIfIndex = _RecordRegistrationTestGetLoopbackInterfaceIndex(); queryRecordIfIndex = _RecordRegistrationTestGetLoopbackInterfaceIndex(); break; case kRecordRegistrationTestInterfaceSet_LocalOnly: me->registrationIfIndex = kDNSServiceInterfaceIndexLocalOnly; queryRecordIfIndex = kDNSServiceInterfaceIndexLocalOnly; break; default: FatalErrorF( "Unhandled RecordRegistrationTestInterfaceSet value %d", interfaceSet ); } // Set up flags for DNSServiceRegisterRecord(). me->registrationFlags = 0; switch( interfaceSet ) { case kRecordRegistrationTestInterfaceSet_AnyPlusAWDL: me->registrationFlags |= kDNSServiceFlagsIncludeAWDL; break; case kRecordRegistrationTestInterfaceSet_AnyPlusP2P: me->registrationFlags |= kDNSServiceFlagsIncludeP2P; break; case kRecordRegistrationTestInterfaceSet_AnyPlusAWDLAndP2P: me->registrationFlags |= ( kDNSServiceFlagsIncludeAWDL | kDNSServiceFlagsIncludeP2P ); break; case kRecordRegistrationTestInterfaceSet_Any: case kRecordRegistrationTestInterfaceSet_Loopback: case kRecordRegistrationTestInterfaceSet_LocalOnly: break; } const RecordRegistrationTestRegistrationType registrationType = _RecordRegistrationTestGetCurrentRegistrationType( me ); switch( registrationType ) { case kRecordRegistrationTestRegistrationType_Shared: me->registrationFlags |= kDNSServiceFlagsShared; break; case kRecordRegistrationTestRegistrationType_Unique: me->registrationFlags |= kDNSServiceFlagsUnique; break; case kRecordRegistrationTestRegistrationType_KnownUnique: me->registrationFlags |= kDNSServiceFlagsKnownUnique; break; default: FatalErrorF( "Unhandled RecordRegistrationTestRegistrationType value %d", registrationType ); } FPrintF( stdout, "%{du:time} Subtest %zu.%zu: Registering %s Records on %s (%s)\n", NULL, me->subtestIndex + 1, me->interfaceSetIndex + 1, _RecordRegistrationTestRegistrationTypeToString( registrationType ), _RecordRegistrationTestInterfaceSetToString( interfaceSet ), _RecordRegistrationSubtestTypeToString( subtestType ) ); char ifNameBuf[ kInterfaceNameBufLen ]; const char * const ifName = InterfaceIndexToName( me->registrationIfIndex, ifNameBuf ); FPrintF( stdout, "%{du:time} Record Registration interface and flags -- interface: %s/%d, flags: %#{flags}\n", NULL, ifName ? ifName : "", (int32_t) me->registrationIfIndex, me->registrationFlags, kDNSServiceFlagsDescriptors ); // Set up connection for record registrations. FPrintF( stdout, "%{du:time} * Creating connection\n", NULL ); OSStatus err = DNSServiceCreateConnection( &me->connection ); require_noerr( err, exit ); err = DNSServiceSetDispatchQueue( me->connection, me->queue ); require_noerr( err, exit ); // Create a randomly-generated record name. char tag[ 6 + 1 ]; _RandomStringExact( kLowerAlphaNumericCharSet, kLowerAlphaNumericCharSetSize, sizeof( tag ) - 1, tag ); ForgetMem( &me->rrSetName ); ASPrintF( &me->rrSetName, "register-record-test-%s.local", tag ); require_action( me->rrSetName, exit, err = kNoMemoryErr ); // Randomly-generate RDATA base for A record RDATA. RandomBytes( me->rdataBase, sizeof( me->rdataBase ) ); // Actualize the RRSet. me->rrSetBitmapIndex = 0; err = _RecordRegistrationTestActualizeRRSet( me ); require_noerr( err, exit ); // Start the query for the RRSet. me->rrSetBitmapObserved = 0; err = DNSServiceQueryRecord( &me->query, 0, queryRecordIfIndex, me->rrSetName, kDNSServiceType_A, kDNSServiceClass_IN, _RecordRegistrationTestQueryRecordCallback, me ); require_noerr( err, exit ); err = DNSServiceSetDispatchQueue( me->query, me->queue ); require_noerr( err, exit ); exit: return( err ); } //=========================================================================================================================== static OSStatus _RecordRegistrationTestSubtestStart( const RecordRegistrationTestRef me ) { OSStatus err; if( me->subtestIndex >= countof( kRegisterRecordSubtests ) ) { _RecordRegistrationTestRetain( me ); dispatch_async( me->queue, ^{ _RecordRegistrationTestStop( me, kNoErr ); _RecordRegistrationTestRelease( me ); } ); err = kNoErr; goto exit; } me->interfaceSetIndex = 0; err = _RecordRegistrationTestStartRegistrationsAndQuery( me ); require_noerr( err, exit ); exit: return( err ); } //=========================================================================================================================== static OSStatus _RecordRegistrationTestHandleTimeout( const RecordRegistrationTestRef me ) { uint32_t rrSetBitmapExpected; const RecordRegistrationSubtestType subtestType = _RecordRegistrationTestGetCurrentSubtestType( me ); if( subtestType == kRecordRegistrationSubtestType_Conflict ) { rrSetBitmapExpected = 0; // A conflict should cause all members of the RRSet to be flushed from the cache. } else { rrSetBitmapExpected = _RecordRegistrationTestGetCurrentRRSetBitmap( me ); } FPrintF( stdout, "%{du:time} ⧖ Expected RRSet = {", NULL ); const char *separator = ""; for( unsigned int i = 0; i < kRecordRegistrationTestRRSetMemberCountMax; ++i ) { if( rrSetBitmapExpected & _BitU32( i ) ) { me->rdataBase[ 3 ] = (uint8_t) i; FPrintF( stdout, "%s\n\t%s IN A %.4a", separator, me->rrSetName, me->rdataBase ); separator = ","; } } FPrintF( stdout, "\n}\n" ); OSStatus err; if( me->rrSetBitmapObserved != rrSetBitmapExpected ) { FPrintF( stdout, "%{du:time} x Actual RRSet = {", NULL ); separator = ""; for( unsigned int i = 0; i < kRecordRegistrationTestRRSetMemberCountMax; ++i ) { if( me->rrSetBitmapObserved & _BitU32( i ) ) { me->rdataBase[ 3 ] = (uint8_t) i; FPrintF( stdout, "%s\n\t%s IN A %.4a", separator, me->rrSetName, me->rdataBase ); separator = ","; } } FPrintF( stdout, "\n}\n" ); err = kMismatchErr; } else { FPrintF( stdout, "%{du:time} ✓ QueryRecord RRSet matches\n", NULL ); ++me->rrSetBitmapIndex; const RegisterRecordSubtest * const subtest = _RecordRegistrationTestGetCurrentSubtest( me ); if( me->rrSetBitmapIndex < subtest->rrSetBitmapCount ) { err = _RecordRegistrationTestActualizeRRSet( me ); } else { FPrintF( stdout, "\n" ); ++me->interfaceSetIndex; if( me->interfaceSetIndex < subtest->interfaceSetCount ) { err = _RecordRegistrationTestStartRegistrationsAndQuery( me ); } else { ++me->subtestIndex; err = _RecordRegistrationTestSubtestStart( me ); } } } return( err ); } //=========================================================================================================================== static OSStatus _RecordRegistrationTestActualizeRRSet( const RecordRegistrationTestRef me ) { FPrintF( stdout, "%{du:time} ⚒ Making changes to RRSet\n", NULL ); OSStatus err; const uint32_t rrSetBitmap = _RecordRegistrationTestGetCurrentRRSetBitmap( me ); for( unsigned int i = 0; i < kRecordRegistrationTestRRSetMemberCountMax; ++i ) { const uint32_t bitmask = _BitU32( i ); const Boolean needRRSetMember = ( rrSetBitmap & bitmask ) != 0; if( needRRSetMember ) { if( !me->recordRefs[ i ] ) { err = _RecordRegistrationTestRegisterRecord( me, (uint8_t) i, &me->recordRefs[ i ] ); require_noerr( err, exit ); } } else { if( me->recordRefs[ i ] ) { me->rdataBase[ 3 ] = (uint8_t) i; FPrintF( stdout, "%{du:time} - Deregistering record: %s IN A %.4a\n", NULL, me->rrSetName, me->rdataBase ); err = DNSServiceRemoveRecord( me->connection, me->recordRefs[ i ], 0 ); require_noerr( err, exit ); me->recordRefs[ i ] = NULL; } } } check( !me->timer ); me->timer = dispatch_source_create( DISPATCH_SOURCE_TYPE_TIMER, 0, 0, me->queue ); require_action_quiet( me->timer, exit, err = kNoResourcesErr ); unsigned int timeLimitMs; const RecordRegistrationSubtestType subtestType = _RecordRegistrationTestGetCurrentSubtestType( me ); switch( subtestType ) { case kRecordRegistrationSubtestType_ChangingRRSet: timeLimitMs = me->rrSetChangeIntervalMs; break; case kRecordRegistrationSubtestType_Conflict: // Allow for six seconds to pass in case mDNSResponder is currently rate limiting conflicts to one every // five seconds. See rdar://3809484 (Rate limiting imposed too soon) for background on this behavior. timeLimitMs = 6 * kMillisecondsPerSecond; break; } FPrintF( stdout, "%{du:time} ⧗ Will check QueryRecord results after %u ms\n", NULL, timeLimitMs ); dispatch_source_set_timer( me->timer, dispatch_time_milliseconds( timeLimitMs ), DISPATCH_TIME_FOREVER, 0 ); dispatch_source_set_event_handler( me->timer, ^{ dispatch_source_forget( &me->timer ); const OSStatus timeoutErr = _RecordRegistrationTestHandleTimeout( me ); if( timeoutErr ) _RecordRegistrationTestStop( me, timeoutErr ); } ); dispatch_activate( me->timer ); err = kNoErr; exit: return( err ); } //=========================================================================================================================== static void _RecordRegistrationTestStart( void * const inCtx ) { const RecordRegistrationTestRef me = (RecordRegistrationTestRef) inCtx; const OSStatus err = _RecordRegistrationTestSubtestStart( me ); require_noerr( err, exit ); exit: if( err ) _RecordRegistrationTestStop( me, err ); } //=========================================================================================================================== static OSStatus _RecordRegistrationTestRun( const RecordRegistrationTestRef me ) { dispatch_async_f( me->queue, me, _RecordRegistrationTestStart ); dispatch_semaphore_wait( me->doneSem, DISPATCH_TIME_FOREVER ); return( me->error ); } //=========================================================================================================================== static void RecordRegistrationTestCommand( void ) { RecordRegistrationTestRef test = NULL; OSStatus err = CheckIntegerArgument( gRecordRegistrationTest_RRSetChangeIntervalMs, "interval", 0, INT_MAX ); require_noerr_quiet( err, exit ); unsigned int rrSetChangeIntervalMs = (unsigned int) gRecordRegistrationTest_RRSetChangeIntervalMs; if( rrSetChangeIntervalMs == 0 ) rrSetChangeIntervalMs = 1500; test = _RecordRegistrationTestCreate( rrSetChangeIntervalMs, &err ); require_noerr( err, exit ); err = _RecordRegistrationTestRun( test ); require_noerr( err, exit ); exit: if( test ) _RecordRegistrationTestRelease( test ); gExitCode = err ? 1 : 0; } //=========================================================================================================================== // RecordCacheFlushTestCommand //=========================================================================================================================== typedef struct RecordCacheFlushTest * RecordCacheFlushTestRef; struct RecordCacheFlushTest { dispatch_queue_t queue; // Serial queue for test events. dispatch_semaphore_t doneSem; // Semaphore to signal when the test is done. DNSServiceRef queryA; // Reference for DNSServiceQueryRecord() operation for A record. DNSServiceRef queryAAAA; // Reference for DNSServiceQueryRecord() operation for AAAA record. dispatch_source_t timer; // Timer for enforcing time limits. char * recordName; // Name of records to put in record cache and then flush. MDNSColliderRef colliderA; // Collider for sending an A record to put in record cache. MDNSColliderRef colliderAAAA; // Collider for sending a AAAA record to put in record cache. mrc_record_cache_flush_t flush; // Record cache flush object to flush records. int32_t refCount; // Test's reference count. OSStatus error; // Test's error code. uint8_t rdataAAAA[ 16 ]; // The randomly-generated AAAA record RDATA base for the RRSet. uint8_t rdataA[ 4 ]; // The randomly-generated A record RDATA base for the RRSet. Boolean haveRecordA; // True if the A record has an outstanding Add result. Boolean haveRecordAAAA; // True if the AAAA record has an outstanding Add result. }; //=========================================================================================================================== static void _RecordCacheFlushTestRetain( const RecordCacheFlushTestRef me ) { atomic_add_32( &me->refCount, 1 ); } //=========================================================================================================================== static void _RecordCacheFlushTestRelease( const RecordCacheFlushTestRef me ) { if( atomic_add_and_fetch_32( &me->refCount, -1 ) == 0 ) { dispatch_forget( &me->queue ); dispatch_forget( &me->doneSem ); check( !me->queryA ); check( !me->queryAAAA ); check( !me->timer ); check( !me->recordName ); check( !me->colliderA ); check( !me->colliderAAAA ); check( !me->flush ); free( me ); } } //=========================================================================================================================== static RecordCacheFlushTestRef _RecordCacheFlushTestCreate( OSStatus * const outError ) { OSStatus err; RecordCacheFlushTestRef test = NULL; RecordCacheFlushTestRef obj = (RecordCacheFlushTestRef) calloc( 1, sizeof( *obj ) ); require_action( obj, exit, err = kNoMemoryErr ); obj->refCount = 1; obj->error = kInProgressErr; obj->queue = dispatch_queue_create( "com.apple.dnssdutil.record-cache-flush-test", DISPATCH_QUEUE_SERIAL ); require_action( obj->queue, exit, err = kNoResourcesErr ); obj->doneSem = dispatch_semaphore_create( 0 ); require_action( obj->doneSem, exit, err = kNoResourcesErr ); test = obj; obj = NULL; err = kNoErr; exit: if( outError ) *outError = err; if( obj ) _RecordCacheFlushTestRelease( obj ); return( test ); } //=========================================================================================================================== static void _RecordCacheFlushTestStop( const RecordCacheFlushTestRef me, const OSStatus inError ) { me->error = inError; if( !me->error ) { FPrintF( stdout, "%{du:time} Test PASSED\n", NULL ); } else { FPrintF( stdout, "%{du:time} Test FAILED: %#m\n", NULL, me->error ); } DNSServiceForget( &me->queryA ); DNSServiceForget( &me->queryAAAA ); dispatch_source_forget( &me->timer ); ForgetMem( &me->recordName ); MDNSColliderForget( &me->colliderA ); MDNSColliderForget( &me->colliderAAAA ); mrc_record_cache_flush_forget( &me->flush ); dispatch_semaphore_signal( me->doneSem ); } //=========================================================================================================================== static void DNSSD_API _RecordCacheFlushTestQueryRecordCallback( __unused const DNSServiceRef inSDRef, const DNSServiceFlags inFlags, __unused const uint32_t inIfIndex, const DNSServiceErrorType inError, const char * const inFullName, const uint16_t inType, const uint16_t inClass, const uint16_t inRDataLen, const void * const inRDataPtr, __unused const uint32_t inTTL, void * const inCtx ) { OSStatus err; const RecordCacheFlushTestRef me = (RecordCacheFlushTestRef) inCtx; // Print result. char ifNameBuf[ kInterfaceNameBufLen ]; const char * const ifName = InterfaceIndexToName( inIfIndex, ifNameBuf ); char typeBuf[ 32 ]; const char *typeStr = DNSRecordTypeValueToString( inType ); if( !typeStr ) { SNPrintF( typeBuf, sizeof( typeBuf ), "TYPE%u", inType ); typeStr = typeBuf; } char classBuf[ 32 ]; const char *classStr; if( inClass == kDNSServiceClass_IN ) { classStr = "IN"; } else { SNPrintF( classBuf, sizeof( classBuf ), "CLASS%u", inClass ); classStr = classBuf; } char *rdataStr = NULL; DNSRecordDataToString( inRDataPtr, inRDataLen, inType, &rdataStr ); if( !rdataStr ) { ASPrintF( &rdataStr, "%#H", inRDataPtr, (int) inRDataLen, (int) inRDataLen ); require_action( rdataStr, exit, err = kNoMemoryErr ); } FPrintF( stdout, "%{du:time} → QueryRecord result -- flags: 0x%0X (%s), interface: %s/%d, error: %#m, name: %s," " type: %s, class: %s, ttl: %u, rdata: %s\n", NULL, inFlags, (inFlags & kDNSServiceFlagsAdd) ? "Add" : "Rmv", ifName ? ifName : "", (int32_t) inIfIndex, inError, inFullName, typeStr, classStr, inTTL, rdataStr ); // Check callback arguments. uint8_t fullName[ kDomainNameLengthMax ]; err = DomainNameFromString( fullName, inFullName, NULL ); require_noerr_quiet( err, exit ); uint8_t recordName[ kDomainNameLengthMax ]; err = DomainNameFromString( recordName, me->recordName, NULL ); require_noerr_quiet( err, exit ); const Boolean nameMatch = DomainNameEqual( fullName, recordName ); require_action( nameMatch, exit, err = kNameErr ); require_action( inClass == kDNSServiceClass_IN, exit, err = kTypeErr ); switch( inType ) { case kDNSServiceType_A: { require_action( inRDataLen == 4, exit, err = kSizeErr ); const int cmp = memcmp( inRDataPtr, me->rdataA, sizeof( me->rdataA ) ); require_action( cmp == 0, exit, err = kValueErr ); break; } case kDNSServiceType_AAAA: { require_action( inRDataLen == 16, exit, err = kSizeErr ); const int cmp = memcmp( inRDataPtr, me->rdataAAAA, sizeof( me->rdataAAAA ) ); require_action( cmp == 0, exit, err = kValueErr ); break; } default: err = kTypeErr; goto exit; } err = inError; require_noerr( err, exit ); if( inFlags & kDNSServiceFlagsAdd ) { // Add events are only expected before flushing the records from the record cache. After the flush, there should // be no Add events. require_action( !me->flush, exit, err = kUnexpectedErr ); // Make sure we get exactly one Add for the A record and exactly one Add for the AAAA record. switch( inType ) { case kDNSServiceType_A: require_action( !me->haveRecordA, exit, err = kDuplicateErr ); me->haveRecordA = true; break; case kDNSServiceType_AAAA: require_action( !me->haveRecordAAAA, exit, err = kDuplicateErr ); me->haveRecordAAAA = true; break; } } else { // Rmv events are only expected after flushing the records from the record cache. Before the flush, there should // be no Rmv events. require_action( me->flush, exit, err = kUnexpectedErr ); // Make sure we get exactly one Rmv for the A record and exactly one Rmv for the AAAA record. Also, there should // be no Rmv event unless there was a corresponding Add event beforehand. switch( inType ) { case kDNSServiceType_A: require_action( me->haveRecordA, exit, err = kUnexpectedErr ); me->haveRecordA = false; break; case kDNSServiceType_AAAA: require_action( me->haveRecordAAAA, exit, err = kUnexpectedErr ); me->haveRecordAAAA = false; break; } } exit: ForgetMem( &rdataStr ); if( err ) _RecordCacheFlushTestStop( me, err ); } //=========================================================================================================================== static OSStatus _RecordCacheFlushTestHandlePostFlushTimeout( const RecordCacheFlushTestRef me ) { OSStatus err; if( !me->haveRecordA && !me->haveRecordAAAA ) { FPrintF( stdout, "%{du:time} ✓ Got expected QueryRecord Rmv results\n", NULL ); err = kNoErr; } else { if( me->haveRecordA ) { FPrintF( stdout, "%{du:time} x Didn't get Rmv for A record", NULL ); } if( me->haveRecordAAAA ) { FPrintF( stdout, "%{du:time} x Didn't get Rmv for AAAA record", NULL ); } err = kUnexpectedErr; } return( err ); } //=========================================================================================================================== static OSStatus _RecordCacheFlushTestHandlePreFlushTimeout( const RecordCacheFlushTestRef me ) { OSStatus err; mdns_domain_name_t recordName = NULL; if( me->haveRecordA && me->haveRecordAAAA ) { FPrintF( stdout, "%{du:time} ✓ Got expected QueryRecord Add results\n", NULL ); // Start record cache flush. FPrintF( stdout, "%{du:time} ⚒ Flushing %s records from record cache\n", NULL, me->recordName ); recordName = mdns_domain_name_create( me->recordName, 0, &err ); require_noerr( err, exit ); check( !me->flush ); me->flush = mrc_record_cache_flush_create(); require_action( me->flush, exit, err = kNoResourcesErr ); mrc_record_cache_flush_set_queue( me->flush, me->queue ); mrc_record_cache_flush_set_record_name( me->flush, recordName ); _RecordCacheFlushTestRetain( me ); mrc_record_cache_flush_set_result_handler( me->flush, ^( const mrc_record_cache_flush_result_t inResult, const OSStatus inError ) { if( me->flush ) { switch( inResult ) { case mrc_record_cache_flush_result_complete: FPrintF( stdout, "%{du:time} ✓ Record cache flush result: complete\n", NULL ); break; case mrc_record_cache_flush_result_incomplete: FPrintF( stdout, "%{du:time} x Record cache flush result: incomplete", NULL ); if( inError ) { FPrintF( stdout, " (error: %#m)", inError ); } FPrintF( stdout, "\n" ); break; } if( inError ) _RecordCacheFlushTestStop( me, inError ); } _RecordCacheFlushTestRelease( me ); } ); mrc_record_cache_flush_activate( me->flush ); // Start timer. check( !me->timer ); me->timer = dispatch_source_create( DISPATCH_SOURCE_TYPE_TIMER, 0, 0, me->queue ); require_action_quiet( me->timer, exit, err = kNoResourcesErr ); const unsigned int timeLimitMs = 3 * kMillisecondsPerSecond; FPrintF( stdout, "%{du:time} ⧗ Will check QueryRecord results after %u ms\n", NULL, timeLimitMs ); dispatch_source_set_timer( me->timer, dispatch_time_milliseconds( timeLimitMs ), DISPATCH_TIME_FOREVER, 0 ); dispatch_source_set_event_handler( me->timer, ^{ dispatch_source_forget( &me->timer ); const OSStatus timeoutErr = _RecordCacheFlushTestHandlePostFlushTimeout( me ); _RecordCacheFlushTestStop( me, timeoutErr ); } ); dispatch_activate( me->timer ); } else { if( !me->haveRecordA ) { FPrintF( stdout, "%{du:time} x Didn't get Add for A record", NULL ); } if( !me->haveRecordAAAA ) { FPrintF( stdout, "%{du:time} x Didn't get Add for AAAA record", NULL ); } err = kUnexpectedErr; } exit: mdns_forget( &recordName ); return( err ); } //=========================================================================================================================== static void _RecordCacheFlushTestStart( const RecordCacheFlushTestRef me ) { // Create a randomly-generated record name. OSStatus err; char tag[ 6 + 1 ]; _RandomStringExact( kLowerAlphaNumericCharSet, kLowerAlphaNumericCharSetSize, sizeof( tag ) - 1, tag ); check( !me->recordName ); ASPrintF( &me->recordName, "record-cache-flush-test-%s.local", tag ); require_action( me->recordName, exit, err = kNoMemoryErr ); // Query for A record. FPrintF( stdout, "%{du:time} ☀ Starting QueryRecord for %s A record\n", NULL, me->recordName ); check( !me->queryA ); const uint32_t loopbackIndex = _RecordRegistrationTestGetLoopbackInterfaceIndex(); err = DNSServiceQueryRecord( &me->queryA, 0, loopbackIndex, me->recordName, kDNSServiceType_A, kDNSServiceClass_IN, _RecordCacheFlushTestQueryRecordCallback, me ); require_noerr( err, exit ); err = DNSServiceSetDispatchQueue( me->queryA, me->queue ); require_noerr( err, exit ); // Query for AAAA record. FPrintF( stdout, "%{du:time} ☀ Starting QueryRecord for %s AAAA record\n", NULL, me->recordName ); check( !me->queryAAAA ); err = DNSServiceQueryRecord( &me->queryAAAA, 0, loopbackIndex, me->recordName, kDNSServiceType_AAAA, kDNSServiceClass_IN, _RecordCacheFlushTestQueryRecordCallback, me ); require_noerr( err, exit ); err = DNSServiceSetDispatchQueue( me->queryAAAA, me->queue ); require_noerr( err, exit ); // Generate random A and AAAA record RDATA. RandomBytes( me->rdataA, sizeof( me->rdataA ) ); RandomBytes( me->rdataAAAA, sizeof( me->rdataAAAA ) ); // Create collider to send A record. uint8_t recordName[ kDomainNameLengthMax ]; err = DomainNameFromString( recordName, me->recordName, NULL ); require_noerr( err, exit ); MDNSColliderRef collider = NULL; FPrintF( stdout, "%{du:time} ✈ Sending mDNS message containing record: %s IN A %.4a\n", NULL, me->recordName, me->rdataA ); err = MDNSColliderCreate( me->queue, &collider ); require_noerr( err, exit ); MDNSColliderSetProtocols( collider, kMDNSColliderProtocol_IPv4 ); MDNSColliderSetInterfaceIndex( collider, loopbackIndex ); err = MDNSColliderSetRecord( collider, recordName, kDNSServiceType_A, me->rdataA, sizeof( me->rdataA ) ); require_noerr( err, exit ); err = MDNSColliderSetProgram( collider, "send" ); require_noerr( err, exit ); err = MDNSColliderStart( collider ); require_noerr( err, exit ); check( !me->colliderA ); me->colliderA = collider; collider = NULL; // Create collider to send AAAA record. FPrintF( stdout, "%{du:time} ✈ Sending mDNS message containing record: %s IN AAAA %.16a\n", NULL, me->recordName, me->rdataAAAA ); err = MDNSColliderCreate( me->queue, &collider ); require_noerr( err, exit ); MDNSColliderSetProtocols( collider, kMDNSColliderProtocol_IPv4 ); MDNSColliderSetInterfaceIndex( collider, loopbackIndex ); err = MDNSColliderSetRecord( collider, recordName, kDNSServiceType_AAAA, me->rdataAAAA, sizeof( me->rdataAAAA ) ); require_noerr( err, exit ); err = MDNSColliderSetProgram( collider, "send" ); require_noerr( err, exit ); err = MDNSColliderStart( collider ); require_noerr( err, exit ); check( !me->colliderAAAA ); me->colliderAAAA = collider; collider = NULL; // Start timer. check( !me->timer ); me->timer = dispatch_source_create( DISPATCH_SOURCE_TYPE_TIMER, 0, 0, me->queue ); require_action_quiet( me->timer, exit, err = kNoResourcesErr ); const unsigned int timeLimitMs = 3 * kMillisecondsPerSecond; FPrintF( stdout, "%{du:time} ⧗ Will check QueryRecord results after %u ms\n", NULL, timeLimitMs ); dispatch_source_set_timer( me->timer, dispatch_time_milliseconds( timeLimitMs ), DISPATCH_TIME_FOREVER, 0 ); dispatch_source_set_event_handler( me->timer, ^{ dispatch_source_forget( &me->timer ); const OSStatus timeoutErr = _RecordCacheFlushTestHandlePreFlushTimeout( me ); if( timeoutErr ) _RecordCacheFlushTestStop( me, timeoutErr ); } ); dispatch_activate( me->timer ); exit: if( err ) _RecordCacheFlushTestStop( me, err ); } //=========================================================================================================================== static OSStatus _RecordCacheFlushTestRun( const RecordCacheFlushTestRef me ) { dispatch_async( me->queue, ^{ _RecordCacheFlushTestStart( me ); } ); dispatch_semaphore_wait( me->doneSem, DISPATCH_TIME_FOREVER ); return( me->error ); } //=========================================================================================================================== static void RecordCacheFlushTestCommand( void ) { OSStatus err; RecordCacheFlushTestRef test = _RecordCacheFlushTestCreate( &err ); require_noerr( err, exit ); err = _RecordCacheFlushTestRun( test ); require_noerr( err, exit ); exit: if( test ) _RecordCacheFlushTestRelease( test ); gExitCode = err ? 1 : 0; } //=========================================================================================================================== // ResolverOverrideTestCommand //=========================================================================================================================== // Each subtest uses DNSServiceQueryRecordWithAttribute() to query for an A record that can only be resolved via a DNS // server that is launched by the test itself. The DNS server is configured to only provide responses for domain names // in a randomized domain such as resolver-override-test-00zn9v.test, and is only capable of receiving DNS queries on // the loopback interface at a randomly-selected port. // // A libnetwork resolver configuration that describes the test's DNS service is published, but it uses a bogus // randomized domain such as resolver-override-test-cgm5xp.invalid, to preclude the possibility of mDNSResponder using // domain matching to assign the test's DNS service to the test's DNS queries. // // The test uses DNSServiceAttributeSetResolverOverride() to set the libnetwork resolver configuration's UUID in the // attribute passed to DNSServiceQueryRecordWithAttribute(). This should cause the associated DNS queries to be sent to // the test's DNS service, which is the only way to get the correct RDATA. Some subtests don't use the resolver override // attribute at all or use the resolver attribute with the incorrect resolver configuration UUID to verify that the // correct RDATA cannot be obtained unless the correct resolver UUID is used. // // Subtests are performed with and without the kDNSServiceFlagsPathEvaluationDone flag to make sure that the expected // outcome occurs regardless of whether or not path evaluation is performed. Path evaluation sometimes influences DNS // service selection, but it should have no effect when a resolver override is used. typedef enum { kResolverOverrideAction_UseCorrectUUID = 1, kResolverOverrideAction_UseIncorrectUUID = 2, kResolverOverrideAction_DoNotUse = 3, } ResolverOverrideAction; typedef struct { DNSServiceFlags flags; ResolverOverrideAction resolverOverrideAction; Boolean queryRecordsWithCNAMEs; } ResolverOverrideSubtest; static const ResolverOverrideSubtest kResolverOverrideSubtests[] = { { .resolverOverrideAction = kResolverOverrideAction_DoNotUse, .flags = 0, .queryRecordsWithCNAMEs = false, }, { .resolverOverrideAction = kResolverOverrideAction_DoNotUse, .flags = kDNSServiceFlagsPathEvaluationDone, .queryRecordsWithCNAMEs = false, }, { .resolverOverrideAction = kResolverOverrideAction_UseCorrectUUID, .flags = 0, .queryRecordsWithCNAMEs = false, }, { .resolverOverrideAction = kResolverOverrideAction_UseCorrectUUID, .flags = kDNSServiceFlagsPathEvaluationDone, .queryRecordsWithCNAMEs = false, }, { .resolverOverrideAction = kResolverOverrideAction_UseCorrectUUID, .flags = 0, .queryRecordsWithCNAMEs = true, }, { .resolverOverrideAction = kResolverOverrideAction_UseCorrectUUID, .flags = kDNSServiceFlagsPathEvaluationDone, .queryRecordsWithCNAMEs = true, }, { .resolverOverrideAction = kResolverOverrideAction_UseIncorrectUUID, .flags = 0, .queryRecordsWithCNAMEs = false, }, { .resolverOverrideAction = kResolverOverrideAction_UseIncorrectUUID, .flags = kDNSServiceFlagsPathEvaluationDone, .queryRecordsWithCNAMEs = false, }, }; typedef struct ResolverOverrideTest * ResolverOverrideTestRef; struct ResolverOverrideTest { dispatch_queue_t queue; // Serial queue for test events. dispatch_semaphore_t doneSem; // Semaphore to signal when the test is done. DNSServerRef server; // Reference to DNS server. DNSServiceAttributeRef resolverOverrideAttr; // Attribute for the resolver override. nw_resolver_config_t resolverConfig; // Resolver configuration for DNS service. DNSServiceRef query; // Reference for the QueryRecord operation to query for A records. dispatch_source_t timer; // Timer for enforcing time limits. size_t subtestIndex; // Index of current subtest. char * recordName; // Name of records to put in record cache and then flush. char * domain; // DNS service's domain. sockaddr_ip serverAddr; // DNS server address. int32_t refCount; // Test's reference count. OSStatus error; // Test's error code. uuid_t resolverUUID; // Resolver configuration's UUID. uint8_t expectedRData[ 4 ]; // Expected A record RDATA in the result of the current QueryRecord. Boolean gotRData; // True if we got a QueryRecord Add result for the record name. Boolean stopped; // True if the test has been stopped. }; //=========================================================================================================================== static void _ResolverOverrideTestRelease( const ResolverOverrideTestRef me ) { if( atomic_add_and_fetch_32( &me->refCount, -1 ) == 0 ) { dispatch_forget( &me->queue ); dispatch_forget( &me->doneSem ); check( !me->server ); check( !me->resolverConfig ); check( !me->resolverOverrideAttr ); check( !me->query ); check( !me->timer ); check( !me->recordName ); check( !me->domain ); free( me ); } } //=========================================================================================================================== static ResolverOverrideTestRef _ResolverOverrideTestCreate( OSStatus * const outError ) { OSStatus err; ResolverOverrideTestRef test = NULL; ResolverOverrideTestRef obj = (ResolverOverrideTestRef) calloc( 1, sizeof( *obj ) ); require_action( obj, exit, err = kNoMemoryErr ); obj->refCount = 1; obj->error = kInProgressErr; obj->queue = dispatch_queue_create( "com.apple.dnssdutil.resolver-override-test", DISPATCH_QUEUE_SERIAL ); require_action( obj->queue, exit, err = kNoResourcesErr ); obj->doneSem = dispatch_semaphore_create( 0 ); require_action( obj->doneSem, exit, err = kNoResourcesErr ); test = obj; obj = NULL; err = kNoErr; exit: if( outError ) *outError = err; if( obj ) _ResolverOverrideTestRelease( obj ); return( test ); } //=========================================================================================================================== static void _ResolverOverrideTestStop( const ResolverOverrideTestRef me, const OSStatus inError ) { require_return( !me->stopped ); me->stopped = true; me->error = inError; if( me->resolverConfig ) { uuid_t uuid; uuid_string_t uuidStr; nw_resolver_config_get_identifier( me->resolverConfig, uuid ); uuid_unparse_upper( uuid, uuidStr ); FPrintF( stdout, "\n%{du:time} ⚒ Unpublishing resolver configuration (%s): %@\n", NULL, uuidStr, me->resolverConfig ); nw_resolver_config_unpublish( me->resolverConfig ); nw_forget( &me->resolverConfig ); } FPrintF( stdout, "\n" ); if( !me->error ) { FPrintF( stdout, "%{du:time} Test PASSED\n", NULL ); } else { FPrintF( stdout, "%{du:time} Test FAILED: %#m\n", NULL, me->error ); } if( me->server ) { _DNSServerStop( me->server ); CFForget( &me->server ); } _DNSServiceAttrForget( &me->resolverOverrideAttr ); DNSServiceForget( &me->query ); dispatch_source_forget( &me->timer ); ForgetMem( &me->recordName ); ForgetMem( &me->domain ); dispatch_semaphore_signal( me->doneSem ); } //=========================================================================================================================== static const ResolverOverrideSubtest * _ResolverOverrideTestGetCurrentSubtest( const ResolverOverrideTestRef me ) { require_fatal( me->subtestIndex < countof( kResolverOverrideSubtests ), "Invalid subtest index %zu >= %zu", me->subtestIndex, countof( kResolverOverrideSubtests ) ); return( &kResolverOverrideSubtests[ me->subtestIndex ] ); } //=========================================================================================================================== static void DNSSD_API _ResolverOverrideTestQueryRecordCallback( __unused const DNSServiceRef inSDRef, const DNSServiceFlags inFlags, __unused const uint32_t inIfIndex, const DNSServiceErrorType inError, const char * const inFullName, const uint16_t inType, const uint16_t inClass, const uint16_t inRDataLen, const void * const inRDataPtr, __unused const uint32_t inTTL, void * const inCtx ) { OSStatus err; const ResolverOverrideTestRef me = (ResolverOverrideTestRef) inCtx; // Print result. char ifNameBuf[ kInterfaceNameBufLen ]; const char * const ifName = InterfaceIndexToName( inIfIndex, ifNameBuf ); char typeBuf[ 32 ]; const char *typeStr = DNSRecordTypeValueToString( inType ); if( !typeStr ) { SNPrintF( typeBuf, sizeof( typeBuf ), "TYPE%u", inType ); typeStr = typeBuf; } char classBuf[ 32 ]; const char *classStr; if( inClass == kDNSServiceClass_IN ) { classStr = "IN"; } else { SNPrintF( classBuf, sizeof( classBuf ), "CLASS%u", inClass ); classStr = classBuf; } char *rdataStr = NULL; DNSRecordDataToString( inRDataPtr, inRDataLen, inType, &rdataStr ); if( !rdataStr ) { ASPrintF( &rdataStr, "%#H", inRDataPtr, (int) inRDataLen, (int) inRDataLen ); require_action( rdataStr, exit, err = kNoMemoryErr ); } FPrintF( stdout, "%{du:time} → QueryRecord result -- flags: 0x%0X (%s), interface: %s/%d, error: %#m, name: %s," " type: %s, class: %s, ttl: %u, rdata: %s\n", NULL, inFlags, (inFlags & kDNSServiceFlagsAdd) ? "Add" : "Rmv", ifName ? ifName : "", (int32_t) inIfIndex, inError, inFullName, typeStr, classStr, inTTL, rdataStr ); // Check callback arguments. uint8_t fullName[ kDomainNameLengthMax ]; err = DomainNameFromString( fullName, inFullName, NULL ); require_noerr_quiet( err, exit ); uint8_t queriedRecordName[ kDomainNameLengthMax ]; err = DomainNameFromString( queriedRecordName, me->recordName, NULL ); require_noerr_quiet( err, exit ); const uint8_t *expectedRecordName; const ResolverOverrideSubtest * const subtest = _ResolverOverrideTestGetCurrentSubtest( me ); if( subtest->queryRecordsWithCNAMEs ) { expectedRecordName = DomainNameGetNextLabel( queriedRecordName ); // Skip alias label to get to the canonical name. } else { expectedRecordName = queriedRecordName; } const Boolean nameMatch = DomainNameEqual( fullName, expectedRecordName ); require_action( nameMatch, exit, err = kNameErr ); require_action( inClass == kDNSServiceClass_IN, exit, err = kTypeErr ); switch( inType ) { case kDNSServiceType_A: { require_action( inRDataLen == sizeof( me->expectedRData ), exit, err = kSizeErr ); const int cmp = memcmp( inRDataPtr, me->expectedRData, sizeof( me->expectedRData ) ); require_action( cmp == 0, exit, err = kValueErr ); break; } default: err = kTypeErr; goto exit; } err = inError; require_noerr( err, exit ); require_action( inFlags & kDNSServiceFlagsAdd, exit, err = kUnexpectedErr ); require_action( !me->gotRData, exit, err = kDuplicateErr ); me->gotRData = true; exit: ForgetMem( &rdataStr ); if( err ) _ResolverOverrideTestStop( me, err ); } //=========================================================================================================================== static void _ResolverOverrideTestRetain( const ResolverOverrideTestRef me ) { atomic_add_32( &me->refCount, 1 ); } //=========================================================================================================================== static OSStatus _ResolverOverrideTestHandleTimeout( ResolverOverrideTestRef inTest ); static OSStatus _ResolverOverrideTestStartSubtest( const ResolverOverrideTestRef me ) { OSStatus err; if( me->subtestIndex >= countof( kResolverOverrideSubtests ) ) { _ResolverOverrideTestRetain( me ); dispatch_async( me->queue, ^{ _ResolverOverrideTestStop( me, kNoErr ); _ResolverOverrideTestRelease( me ); } ); err = kNoErr; goto exit; } const char *resolverOverrideActionStr; const ResolverOverrideSubtest * const subtest = _ResolverOverrideTestGetCurrentSubtest( me ); switch( subtest->resolverOverrideAction ) { case kResolverOverrideAction_UseCorrectUUID: resolverOverrideActionStr = "correct UUID"; break; case kResolverOverrideAction_UseIncorrectUUID: resolverOverrideActionStr = "incorrect UUID"; break; case kResolverOverrideAction_DoNotUse: resolverOverrideActionStr = "not used"; break; default: FatalErrorF( "Unhandled resolver overraide action value: %d", subtest->resolverOverrideAction ); } FPrintF( stdout, "\n%{du:time} Subtest %zu: Querying for A record -- " "resolver override: %s, flags: %#{flags}, record has CNAMEs: %s\n", NULL, me->subtestIndex + 1, resolverOverrideActionStr, subtest->flags, kDNSServiceFlagsDescriptors, YesNoStr( subtest->queryRecordsWithCNAMEs ) ); // Create record name. char tag[ 6 + 1 ]; _RandomStringExact( kLowerAlphaNumericCharSet, kLowerAlphaNumericCharSetSize, sizeof( tag ) - 1, tag ); const uint8_t offset = (uint8_t)( ( me->subtestIndex + 1 ) * 10 ); ForgetMem( &me->recordName ); ASPrintF( &me->recordName, "%stag-%s.offset-%u.%s", subtest->queryRecordsWithCNAMEs ? "alias-3." : "", tag, offset, me->domain ); require_action( me->recordName, exit, err = kNoMemoryErr ); // Create an attribute for the resolver UUID. DNSServiceAttributeRef attr; switch( subtest->resolverOverrideAction ) { case kResolverOverrideAction_UseCorrectUUID: case kResolverOverrideAction_UseIncorrectUUID: if( !me->resolverOverrideAttr ) { me->resolverOverrideAttr = DNSServiceAttributeCreate(); require_action( me->resolverOverrideAttr, exit, err = kNoResourcesErr ); } uuid_t randomUUID; uuid_clear( randomUUID ); const uuid_t *resolverUUID; if( subtest->resolverOverrideAction == kResolverOverrideAction_UseCorrectUUID ) { resolverUUID = &me->resolverUUID; } else { uuid_generate_random( randomUUID ); resolverUUID = &randomUUID; } if( __builtin_available( macOS 15.0, iOS 18.0, tvOS 18.0, watchOS 11.0, * ) ) { uuid_string_t uuidStr; uuid_unparse_upper( *resolverUUID, uuidStr ); FPrintF( stdout, "%{du:time} ⚒ Setting resolver override: %s\n", NULL, uuidStr ); err = DNSServiceAttributeSetResolverOverride( me->resolverOverrideAttr, *resolverUUID ); require_noerr( err, exit ); } else { FPrintF( stderr, "DNSServiceAttributeSetResolverOverride() is not available on this OS build." ); err = kUnsupportedErr; goto exit; } attr = me->resolverOverrideAttr; break; case kResolverOverrideAction_DoNotUse: attr = NULL; break; default: FatalErrorF( "Unhandled resolver overraide action value: %d", subtest->resolverOverrideAction ); } // Query for A record. FPrintF( stdout, "%{du:time} ☀ Starting QueryRecord for %s A record\n", NULL, me->recordName ); const uint32_t expectedIPv4Addr = kDNSServerBaseAddrV4 + 1 + offset; WriteBig32Typed( me->expectedRData, expectedIPv4Addr ); check( !me->query ); err = DNSServiceQueryRecordWithAttribute( &me->query, subtest->flags, kDNSServiceInterfaceIndexAny, me->recordName, kDNSServiceType_A, kDNSServiceClass_IN, attr, _ResolverOverrideTestQueryRecordCallback, me ); require_noerr( err, exit ); err = DNSServiceSetDispatchQueue( me->query, me->queue ); require_noerr( err, exit ); // Start timer. check( !me->timer ); me->timer = dispatch_source_create( DISPATCH_SOURCE_TYPE_TIMER, 0, 0, me->queue ); require_action_quiet( me->timer, exit, err = kNoResourcesErr ); const unsigned int timeLimitSecs = 3; FPrintF( stdout, "%{du:time} ⧖ Will check QueryRecord results after %u seconds\n", NULL, timeLimitSecs ); dispatch_source_set_timer( me->timer, dispatch_time_seconds( timeLimitSecs ), DISPATCH_TIME_FOREVER, 0 ); dispatch_source_set_event_handler( me->timer, ^{ const OSStatus timeoutErr = _ResolverOverrideTestHandleTimeout( me ); if( timeoutErr ) _ResolverOverrideTestStop( me, timeoutErr ); } ); dispatch_activate( me->timer ); exit: return( err ); } //=========================================================================================================================== static OSStatus _ResolverOverrideTestHandleTimeout( const ResolverOverrideTestRef me ) { OSStatus err; DNSServiceForget( &me->query ); dispatch_source_forget( &me->timer ); const ResolverOverrideSubtest * const subtest = _ResolverOverrideTestGetCurrentSubtest( me ); switch( subtest->resolverOverrideAction ) { case kResolverOverrideAction_UseCorrectUUID: FPrintF( stdout, "%{du:time} %s correct RDATA: %.4a\n", NULL, me->gotRData ? "✓ Got" : "x Didn't get", me->expectedRData ); require_action( me->gotRData, exit, err = kValueErr ); break; case kResolverOverrideAction_UseIncorrectUUID: case kResolverOverrideAction_DoNotUse: if( !me->gotRData ) { FPrintF( stdout, "%{du:time} ✓ Didn't get RDATA, as expected, since correct resolver override wasn't used\n", NULL ); } else { FPrintF( stdout, "%{du:time} x Got correct RDATA despite not using resolver override: %.4a\n", NULL, me->expectedRData ); } require_action( !me->gotRData, exit, err = kUnexpectedErr ); break; default: FatalErrorF( "Unhandled resolver overraide action value: %d", subtest->resolverOverrideAction ); } ++me->subtestIndex; me->gotRData = false; err = _ResolverOverrideTestStartSubtest( me ); require_noerr( err, exit ); exit: return( err ); } //=========================================================================================================================== static void _ResolverOverrideTestDNSServerStartHandler( const uint16_t inDNSServerPort, void * const inCtx ) { OSStatus err; char *bogusDomain = NULL; const ResolverOverrideTestRef me = (ResolverOverrideTestRef) inCtx; SockAddrSetPort( &me->serverAddr, inDNSServerPort ); nw_resolver_config_t resolverConfig = nw_resolver_config_create(); require_action( resolverConfig, exit, err = kNoResourcesErr ); nw_resolver_config_set_protocol( resolverConfig, nw_resolver_protocol_dns53 ); nw_resolver_config_set_identifier( resolverConfig, me->resolverUUID ); char serverAddrStr[ kSockAddrStringMaxSize ]; err = SockAddrToString( &me->serverAddr.sa, kSockAddrStringFlagsNone, serverAddrStr ); require_noerr( err, exit ); nw_resolver_config_add_name_server( resolverConfig, serverAddrStr ); char tag[ 6 + 1 ]; _RandomStringExact( kLowerAlphaNumericCharSet, kLowerAlphaNumericCharSetSize, sizeof( tag ) - 1, tag ); ASPrintF( &bogusDomain, "resolver-override-test-%s.invalid", tag ); require_action( bogusDomain, exit, err = kNoMemoryErr ); nw_resolver_config_add_match_domain( resolverConfig, bogusDomain ); uuid_t uuid; uuid_string_t uuidStr; nw_resolver_config_get_identifier( resolverConfig, uuid ); uuid_unparse_upper( uuid, uuidStr ); FPrintF( stdout, "%{du:time} ⚒ Publishing resolver configuration (%s): %@\n", NULL, uuidStr, resolverConfig ); const bool ok = nw_resolver_config_publish( resolverConfig ); require_action( ok, exit, err = kUnknownErr ); check( !me->resolverConfig ); me->resolverConfig = resolverConfig; resolverConfig = NULL; err = _ResolverOverrideTestStartSubtest( me ); require_noerr( err, exit ); exit: ForgetMem( &bogusDomain ); nw_forget( &resolverConfig ); if( err ) _ResolverOverrideTestStop( me, err ); } //=========================================================================================================================== static void _ResolverOverrideTestDNSServerStopHandler( const OSStatus inError, void * const inCtx ) { if( inError ) { FPrintF( stdout, "%{du:time} ! DNS server stopped with unexpected error: %#m\n", NULL, inError ); } const ResolverOverrideTestRef me = (ResolverOverrideTestRef) inCtx; if( inError ) _ResolverOverrideTestStop( me, inError ); _ResolverOverrideTestRelease( me ); } //=========================================================================================================================== static void _ResolverOverrideTestStart( const ResolverOverrideTestRef me ) { // Generate a resolver UUID. uuid_generate_random( me->resolverUUID ); // Create a domain for DNS server. OSStatus err; char tag[ 6 + 1 ]; _RandomStringExact( kLowerAlphaNumericCharSet, kLowerAlphaNumericCharSetSize, sizeof( tag ) - 1, tag ); check( !me->domain ); ASPrintF( &me->domain, "resolver-override-test-%s.test", tag ); require_action( me->domain, exit, err = kNoMemoryErr ); // Set up a loopback server address for DNS server. _SockAddrInitIPv4( &me->serverAddr.v4, INADDR_LOOPBACK, 0 ); // Start up a DNS server. err = _DNSServerCreate( me->queue, _ResolverOverrideTestDNSServerStartHandler, _ResolverOverrideTestDNSServerStopHandler, me, 0, 60, &me->serverAddr, 1, me->domain, false, &me->server ); require_noerr( err, exit ); _ResolverOverrideTestRetain( me ); _DNSServerStart( me->server ); exit: if( err ) _ResolverOverrideTestStop( me, err ); } //=========================================================================================================================== static OSStatus _ResolverOverrideTestRun( const ResolverOverrideTestRef me ) { dispatch_async( me->queue, ^{ _ResolverOverrideTestStart( me ); } ); dispatch_semaphore_wait( me->doneSem, DISPATCH_TIME_FOREVER ); return( me->error ); } //=========================================================================================================================== static void ResolverOverrideTestCommand( void ) { OSStatus err; ResolverOverrideTestRef test = _ResolverOverrideTestCreate( &err ); require_noerr( err, exit ); err = _ResolverOverrideTestRun( test ); require_noerr( err, exit ); exit: if( test ) _ResolverOverrideTestRelease( test ); gExitCode = err ? 1 : 0; } //=========================================================================================================================== // SSDPDiscoverCmd //=========================================================================================================================== #define kSSDPPort 1900 typedef struct { HTTPHeader header; // HTTP header object for sending and receiving. dispatch_source_t readSourceV4; // Read dispatch source for IPv4 socket. dispatch_source_t readSourceV6; // Read dispatch source for IPv6 socket. int receiveSecs; // After send, the amount of time to spend receiving. uint32_t ifindex; // Index of the interface over which to send the query. Boolean useIPv4; // True if the query should be sent via IPv4 multicast. Boolean useIPv6; // True if the query should be sent via IPv6 multicast. } SSDPDiscoverContext; static void SSDPDiscoverPrintPrologue( const SSDPDiscoverContext *inContext ); static void SSDPDiscoverReadHandler( void *inContext ); static int SocketToPortNumber( SocketRef inSock ); static OSStatus WriteSSDPSearchRequest( HTTPHeader *inHeader, const void *inHostSA, int inMX, const char *inST ); static void SSDPDiscoverCmd( void ) { OSStatus err; struct timeval now; SSDPDiscoverContext * context; dispatch_source_t signalSource = NULL; SocketRef sockV4 = kInvalidSocketRef; SocketRef sockV6 = kInvalidSocketRef; ssize_t n; int sendCount; // Set up SIGINT handler. signal( SIGINT, SIG_IGN ); err = DispatchSignalSourceCreate( SIGINT, dispatch_get_main_queue(), Exit, kExitReason_SIGINT, &signalSource ); require_noerr( err, exit ); dispatch_resume( signalSource ); // Check command parameters. if( gSSDPDiscover_ReceiveSecs < -1 ) { FPrintF( stdout, "Invalid receive time: %d seconds.\n", gSSDPDiscover_ReceiveSecs ); err = kParamErr; goto exit; } // Create context. context = (SSDPDiscoverContext *) calloc( 1, sizeof( *context ) ); require_action( context, exit, err = kNoMemoryErr ); context->receiveSecs = gSSDPDiscover_ReceiveSecs; context->useIPv4 = ( gSSDPDiscover_UseIPv4 || !gSSDPDiscover_UseIPv6 ) ? true : false; context->useIPv6 = ( gSSDPDiscover_UseIPv6 || !gSSDPDiscover_UseIPv4 ) ? true : false; err = InterfaceIndexFromArgString( gInterface, &context->ifindex ); require_noerr_quiet( err, exit ); // Set up IPv4 socket. if( context->useIPv4 ) { int port; err = UDPClientSocketOpen( AF_INET, NULL, 0, -1, &port, &sockV4 ); require_noerr( err, exit ); err = SocketSetMulticastInterface( sockV4, NULL, context->ifindex ); require_noerr( err, exit ); err = setsockopt( sockV4, IPPROTO_IP, IP_MULTICAST_LOOP, (char *) &(uint8_t){ 1 }, (socklen_t) sizeof( uint8_t ) ); err = map_socket_noerr_errno( sockV4, err ); require_noerr( err, exit ); } // Set up IPv6 socket. if( context->useIPv6 ) { err = UDPClientSocketOpen( AF_INET6, NULL, 0, -1, NULL, &sockV6 ); require_noerr( err, exit ); err = SocketSetMulticastInterface( sockV6, NULL, context->ifindex ); require_noerr( err, exit ); err = setsockopt( sockV6, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, (char *) &(int){ 1 }, (socklen_t) sizeof( int ) ); err = map_socket_noerr_errno( sockV6, err ); require_noerr( err, exit ); } // Print prologue. SSDPDiscoverPrintPrologue( context ); // Send mDNS query message. sendCount = 0; if( IsValidSocket( sockV4 ) ) { struct sockaddr_in mcastAddr4; _SockAddrInitIPv4( &mcastAddr4, UINT32_C( 0xEFFFFFFA ), kSSDPPort ); // 239.255.255.250 err = WriteSSDPSearchRequest( &context->header, &mcastAddr4, gSSDPDiscover_MX, gSSDPDiscover_ST ); require_noerr( err, exit ); n = sendto( sockV4, context->header.buf, context->header.len, 0, (const struct sockaddr *) &mcastAddr4, (socklen_t) sizeof( mcastAddr4 ) ); err = map_socket_value_errno( sockV4, n == (ssize_t) context->header.len, n ); if( err ) { FPrintF( stderr, "*** Failed to send query on IPv4 socket with error %#m\n", err ); ForgetSocket( &sockV4 ); } else { if( gSSDPDiscover_Verbose ) { gettimeofday( &now, NULL ); FPrintF( stdout, "---\n" ); FPrintF( stdout, "Send time: %{du:time}\n", &now ); FPrintF( stdout, "Source Port: %d\n", SocketToPortNumber( sockV4 ) ); FPrintF( stdout, "Destination: %##a\n", &mcastAddr4 ); FPrintF( stdout, "Message size: %zu\n", context->header.len ); FPrintF( stdout, "HTTP header:\n%1{text}", context->header.buf, context->header.len ); } ++sendCount; } } if( IsValidSocket( sockV6 ) ) { struct sockaddr_in6 mcastAddr6; memset( &mcastAddr6, 0, sizeof( mcastAddr6 ) ); SIN6_LEN_SET( &mcastAddr6 ); mcastAddr6.sin6_family = AF_INET6; mcastAddr6.sin6_port = htons( kSSDPPort ); mcastAddr6.sin6_addr.s6_addr[ 0 ] = 0xFF; // SSDP IPv6 link-local multicast address FF02::C mcastAddr6.sin6_addr.s6_addr[ 1 ] = 0x02; mcastAddr6.sin6_addr.s6_addr[ 15 ] = 0x0C; err = WriteSSDPSearchRequest( &context->header, &mcastAddr6, gSSDPDiscover_MX, gSSDPDiscover_ST ); require_noerr( err, exit ); n = sendto( sockV6, context->header.buf, context->header.len, 0, (const struct sockaddr *) &mcastAddr6, (socklen_t) sizeof( mcastAddr6 ) ); err = map_socket_value_errno( sockV6, n == (ssize_t) context->header.len, n ); if( err ) { FPrintF( stderr, "*** Failed to send query on IPv6 socket with error %#m\n", err ); ForgetSocket( &sockV6 ); } else { if( gSSDPDiscover_Verbose ) { gettimeofday( &now, NULL ); FPrintF( stdout, "---\n" ); FPrintF( stdout, "Send time: %{du:time}\n", &now ); FPrintF( stdout, "Source Port: %d\n", SocketToPortNumber( sockV6 ) ); FPrintF( stdout, "Destination: %##a\n", &mcastAddr6 ); FPrintF( stdout, "Message size: %zu\n", context->header.len ); FPrintF( stdout, "HTTP header:\n%1{text}", context->header.buf, context->header.len ); } ++sendCount; } } require_action_quiet( sendCount > 0, exit, err = kUnexpectedErr ); // If there's no wait period after the send, then exit. if( context->receiveSecs == 0 ) goto exit; // Create dispatch read sources for socket(s). if( IsValidSocket( sockV4 ) ) { SocketContext * sockCtx; sockCtx = SocketContextCreate( sockV4, context, &err ); require_noerr( err, exit ); sockV4 = kInvalidSocketRef; err = DispatchReadSourceCreate( sockCtx->sock, NULL, SSDPDiscoverReadHandler, SocketContextCancelHandler, sockCtx, &context->readSourceV4 ); if( err ) ForgetSocketContext( &sockCtx ); require_noerr( err, exit ); dispatch_resume( context->readSourceV4 ); } if( IsValidSocket( sockV6 ) ) { SocketContext * sockCtx; sockCtx = SocketContextCreate( sockV6, context, &err ); require_noerr( err, exit ); sockV6 = kInvalidSocketRef; err = DispatchReadSourceCreate( sockCtx->sock, NULL, SSDPDiscoverReadHandler, SocketContextCancelHandler, sockCtx, &context->readSourceV6 ); if( err ) ForgetSocketContext( &sockCtx ); require_noerr( err, exit ); dispatch_resume( context->readSourceV6 ); } if( context->receiveSecs > 0 ) { dispatch_after_f( dispatch_time_seconds( context->receiveSecs ), dispatch_get_main_queue(), kExitReason_Timeout, Exit ); } dispatch_main(); exit: ForgetSocket( &sockV4 ); ForgetSocket( &sockV6 ); dispatch_source_forget( &signalSource ); exit( err ? 1 : 0 ); } static int SocketToPortNumber( SocketRef inSock ) { OSStatus err; sockaddr_ip sip; socklen_t len; len = (socklen_t) sizeof( sip ); err = getsockname( inSock, &sip.sa, &len ); err = map_socket_noerr_errno( inSock, err ); check_noerr( err ); return( err ? -1 : SockAddrGetPort( &sip ) ); } static OSStatus WriteSSDPSearchRequest( HTTPHeader *inHeader, const void *inHostSA, int inMX, const char *inST ) { OSStatus err; err = HTTPHeader_InitRequest( inHeader, "M-SEARCH", "*", "HTTP/1.1" ); require_noerr( err, exit ); err = HTTPHeader_SetField( inHeader, "Host", "%##a", inHostSA ); require_noerr( err, exit ); err = HTTPHeader_SetField( inHeader, "ST", "%s", inST ? inST : "ssdp:all" ); require_noerr( err, exit ); err = HTTPHeader_SetField( inHeader, "Man", "\"ssdp:discover\"" ); require_noerr( err, exit ); err = HTTPHeader_SetField( inHeader, "MX", "%d", inMX ); require_noerr( err, exit ); err = HTTPHeader_Commit( inHeader ); require_noerr( err, exit ); exit: return( err ); } //=========================================================================================================================== // SSDPDiscoverPrintPrologue //=========================================================================================================================== static void SSDPDiscoverPrintPrologue( const SSDPDiscoverContext *inContext ) { const int receiveSecs = inContext->receiveSecs; const char * ifName; char ifNameBuf[ IF_NAMESIZE + 1 ]; NetTransportType ifType; ifName = if_indextoname( inContext->ifindex, ifNameBuf ); ifType = kNetTransportType_Undefined; if( ifName ) SocketGetInterfaceInfo( kInvalidSocketRef, ifName, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &ifType ); FPrintF( stdout, "Interface: %s/%d/%s\n", ifName ? ifName : "?", inContext->ifindex, NetTransportTypeToString( ifType ) ); FPrintF( stdout, "IP protocols: %?s%?s%?s\n", inContext->useIPv4, "IPv4", ( inContext->useIPv4 && inContext->useIPv6 ), ", ", inContext->useIPv6, "IPv6" ); FPrintF( stdout, "Receive duration: " ); if( receiveSecs >= 0 ) FPrintF( stdout, "%d second%?c\n", receiveSecs, receiveSecs != 1, 's' ); else FPrintF( stdout, "∞\n" ); FPrintF( stdout, "Start time: %{du:time}\n", NULL ); } //=========================================================================================================================== // SSDPDiscoverReadHandler //=========================================================================================================================== static Boolean _HTTPHeader_Validate( HTTPHeader *inHeader ); static void SSDPDiscoverReadHandler( void *inContext ) { OSStatus err; struct timeval now; SocketContext * const sockCtx = (SocketContext *) inContext; SSDPDiscoverContext * const context = (SSDPDiscoverContext *) sockCtx->userContext; HTTPHeader * const header = &context->header; sockaddr_ip fromAddr; size_t msgLen; gettimeofday( &now, NULL ); err = SocketRecvFrom( sockCtx->sock, header->buf, sizeof( header->buf ), &msgLen, &fromAddr, sizeof( fromAddr ), NULL, NULL, NULL, NULL ); require_noerr( err, exit ); FPrintF( stdout, "---\n" ); FPrintF( stdout, "Receive time: %{du:time}\n", &now ); FPrintF( stdout, "Source: %##a\n", &fromAddr ); FPrintF( stdout, "Message size: %zu\n", msgLen ); header->len = msgLen; if( _HTTPHeader_Validate( header ) ) { FPrintF( stdout, "HTTP header:\n%1{text}", header->buf, header->len ); if( header->extraDataLen > 0 ) { FPrintF( stdout, "HTTP body: %1.1H", header->extraDataPtr, (int) header->extraDataLen, INT_MAX ); } } else { FPrintF( stdout, "Invalid HTTP message:\n%1.1H", header->buf, (int) msgLen, INT_MAX ); goto exit; } exit: if( err ) exit( 1 ); } //=========================================================================================================================== // _HTTPHeader_Validate // // Parses for the end of an HTTP header and updates the HTTPHeader structure so it's ready to parse. Returns true if valid. // This assumes the "buf" and "len" fields are set. The other fields are set by this function. // // Note: This was copied from CoreUtils because the HTTPHeader_Validate function is currently not exported in the framework. //=========================================================================================================================== static Boolean _HTTPHeader_Validate( HTTPHeader *inHeader ) { const char * src; const char * end; // Check for interleaved binary data (4 byte header that begins with $). See RFC 2326 section 10.12. require( inHeader->len < sizeof( inHeader->buf ), exit ); src = inHeader->buf; end = src + inHeader->len; if( ( ( end - src ) >= 4 ) && ( src[ 0 ] == '$' ) ) { src += 4; } else { // Search for an empty line (HTTP-style header/body separator). CRLFCRLF, LFCRLF, or LFLF accepted. // $$$ TO DO: Start from the last search location to avoid re-searching the same data over and over. for( ;; ) { while( ( src < end ) && ( src[ 0 ] != '\n' ) ) ++src; if( src >= end ) goto exit; ++src; if( ( ( end - src ) >= 2 ) && ( src[ 0 ] == '\r' ) && ( src[ 1 ] == '\n' ) ) // CFLFCRLF or LFCRLF { src += 2; break; } else if( ( ( end - src ) >= 1 ) && ( src[ 0 ] == '\n' ) ) // LFLF { src += 1; break; } } } inHeader->extraDataPtr = src; inHeader->extraDataLen = (size_t)( end - src ); inHeader->len = (size_t)( src - inHeader->buf ); return( true ); exit: return( false ); } #if( TARGET_OS_DARWIN ) //=========================================================================================================================== // ResQueryCmd //=========================================================================================================================== // res_query() from libresolv is actually called res_9_query (see /usr/include/resolv.h). SOFT_LINK_LIBRARY_EX( "/usr/lib", resolv ); SOFT_LINK_FUNCTION_EX( resolv, res_9_query, int, ( const char *dname, int class, int type, u_char *answer, int anslen ), ( dname, class, type, answer, anslen ) ); // res_query() from libinfo SOFT_LINK_LIBRARY_EX( "/usr/lib", info ); SOFT_LINK_FUNCTION_EX( info, res_query, int, ( const char *dname, int class, int type, u_char *answer, int anslen ), ( dname, class, type, answer, anslen ) ); typedef int ( *res_query_f )( const char *dname, int class, int type, u_char *answer, int anslen ); static void ResQueryCmd( void ) { OSStatus err; res_query_f res_query_ptr; int n; uint16_t type, class; uint8_t answer[ 1024 ]; // Get pointer to one of the res_query() functions. if( gResQuery_UseLibInfo ) { if( !SOFT_LINK_HAS_FUNCTION( info, res_query ) ) { FPrintF( stderr, "Failed to soft link res_query from libinfo.\n" ); err = kNotFoundErr; goto exit; } res_query_ptr = soft_res_query; } else { if( !SOFT_LINK_HAS_FUNCTION( resolv, res_9_query ) ) { FPrintF( stderr, "Failed to soft link res_query from libresolv.\n" ); err = kNotFoundErr; goto exit; } res_query_ptr = soft_res_9_query; } // Get record type. err = RecordTypeFromArgString( gResQuery_Type, &type ); require_noerr( err, exit ); // Get record class. if( gResQuery_Class ) { err = RecordClassFromArgString( gResQuery_Class, &class ); require_noerr( err, exit ); } else { class = kDNSServiceClass_IN; } // Print prologue. FPrintF( stdout, "Name: %s\n", gResQuery_Name ); FPrintF( stdout, "Type: %s (%u)\n", RecordTypeToString( type ), type ); FPrintF( stdout, "Class: %s (%u)\n", ( class == kDNSServiceClass_IN ) ? "IN" : "???", class ); FPrintF( stdout, "Start time: %{du:time}\n", NULL ); FPrintF( stdout, "---\n" ); // Call res_query(). n = res_query_ptr( gResQuery_Name, class, type, (u_char *) answer, (int) sizeof( answer ) ); if( n < 0 ) { FPrintF( stderr, "res_query() failed with error: %d (%s).\n", h_errno, hstrerror( h_errno ) ); err = kUnknownErr; goto exit; } // Print result. FPrintF( stdout, "Message size: %d\n\n%{du:dnsmsg}\n", n, answer, (size_t) n ); exit: if( err ) exit( 1 ); } //=========================================================================================================================== // ResolvDNSQueryCmd //=========================================================================================================================== // dns_handle_t is defined as a pointer to a privately-defined struct in /usr/include/dns.h. It's defined as a void * here to // avoid including the header file. typedef void * dns_handle_t; SOFT_LINK_FUNCTION_EX( resolv, dns_open, dns_handle_t, ( const char *path ), ( path ) ); SOFT_LINK_FUNCTION_VOID_RETURN_EX( resolv, dns_free, ( dns_handle_t *dns ), ( dns ) ); SOFT_LINK_FUNCTION_EX( resolv, dns_query, int32_t, ( dns_handle_t dns, const char * name, uint32_t dnsclass, uint32_t dnstype, char * buf, uint32_t len, struct sockaddr * from, uint32_t * fromlen ), ( dns, name, dnsclass, dnstype, buf, len, from, fromlen ) ); static void ResolvDNSQueryCmd( void ) { OSStatus err; int n; dns_handle_t dns = NULL; uint16_t type, class; sockaddr_ip from; uint32_t fromLen; uint8_t answer[ 1024 ]; // Make sure that the required symbols are available. if( !SOFT_LINK_HAS_FUNCTION( resolv, dns_open ) ) { FPrintF( stderr, "Failed to soft link dns_open from libresolv.\n" ); err = kNotFoundErr; goto exit; } if( !SOFT_LINK_HAS_FUNCTION( resolv, dns_free ) ) { FPrintF( stderr, "Failed to soft link dns_free from libresolv.\n" ); err = kNotFoundErr; goto exit; } if( !SOFT_LINK_HAS_FUNCTION( resolv, dns_query ) ) { FPrintF( stderr, "Failed to soft link dns_query from libresolv.\n" ); err = kNotFoundErr; goto exit; } // Get record type. err = RecordTypeFromArgString( gResolvDNSQuery_Type, &type ); require_noerr( err, exit ); // Get record class. if( gResolvDNSQuery_Class ) { err = RecordClassFromArgString( gResolvDNSQuery_Class, &class ); require_noerr( err, exit ); } else { class = kDNSServiceClass_IN; } // Get dns handle. dns = soft_dns_open( gResolvDNSQuery_Path ); if( !dns ) { FPrintF( stderr, "dns_open( %s ) failed.\n", gResolvDNSQuery_Path ); err = kUnknownErr; goto exit; } // Print prologue. FPrintF( stdout, "Name: %s\n", gResolvDNSQuery_Name ); FPrintF( stdout, "Type: %s (%u)\n", RecordTypeToString( type ), type ); FPrintF( stdout, "Class: %s (%u)\n", ( class == kDNSServiceClass_IN ) ? "IN" : "???", class ); FPrintF( stdout, "Path: %s\n", gResolvDNSQuery_Path ? gResolvDNSQuery_Name : "" ); FPrintF( stdout, "Start time: %{du:time}\n", NULL ); FPrintF( stdout, "---\n" ); // Call dns_query(). memset( &from, 0, sizeof( from ) ); fromLen = (uint32_t) sizeof( from ); n = soft_dns_query( dns, gResolvDNSQuery_Name, class, type, (char *) answer, (uint32_t) sizeof( answer ), &from.sa, &fromLen ); if( n < 0 ) { FPrintF( stderr, "dns_query() failed with error: %d (%s).\n", h_errno, hstrerror( h_errno ) ); err = kUnknownErr; goto exit; } // Print result. FPrintF( stdout, "From: %##a\n", &from ); FPrintF( stdout, "Message size: %d\n\n%{du:dnsmsg}\n", n, answer, (size_t) n ); exit: if( dns ) soft_dns_free( dns ); if( err ) exit( 1 ); } //=========================================================================================================================== // CFHostCmd //=========================================================================================================================== static void _CFHostResolveCallback( CFHostRef inHost, CFHostInfoType inInfoType, const CFStreamError * inError, void * inInfo ); static void CFHostCmd( void ) { OSStatus err; CFStringRef name; Boolean success; CFHostRef host = NULL; CFHostClientContext context; CFStreamError streamErr; name = CFStringCreateWithCString( kCFAllocatorDefault, gCFHost_Name, kCFStringEncodingUTF8 ); require_action( name, exit, err = kUnknownErr ); host = CFHostCreateWithName( kCFAllocatorDefault, name ); ForgetCF( &name ); require_action( host, exit, err = kUnknownErr ); memset( &context, 0, sizeof( context ) ); success = CFHostSetClient( host, _CFHostResolveCallback, &context ); require_action( success, exit, err = kUnknownErr ); CFHostScheduleWithRunLoop( host, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode ); // Print prologue. FPrintF( stdout, "Hostname: %s\n", gCFHost_Name ); FPrintF( stdout, "Start time: %{du:time}\n", NULL ); FPrintF( stdout, "---\n" ); success = CFHostStartInfoResolution( host, kCFHostAddresses, &streamErr ); require_action( success, exit, err = kUnknownErr ); err = kNoErr; CFRunLoopRun(); exit: CFReleaseNullSafe( host ); if( err ) exit( 1 ); } static void _CFHostResolveCallback( CFHostRef inHost, CFHostInfoType inInfoType, const CFStreamError *inError, void *inInfo ) { OSStatus err; struct timeval now; gettimeofday( &now, NULL ); Unused( inInfoType ); Unused( inInfo ); if( inError && ( inError->domain != 0 ) && ( inError->error ) ) { err = inError->error; if( inError->domain == kCFStreamErrorDomainNetDB ) { FPrintF( stderr, "Error %d: %s.\n", err, gai_strerror( err ) ); } else { FPrintF( stderr, "Error %#m\n", err ); } } else { CFArrayRef addresses; CFIndex count, i; CFDataRef addrData; const struct sockaddr * sockAddr; Boolean wasResolved = false; addresses = CFHostGetAddressing( inHost, &wasResolved ); check( wasResolved ); if( addresses ) { count = CFArrayGetCount( addresses ); for( i = 0; i < count; ++i ) { addrData = CFArrayGetCFDataAtIndex( addresses, i, &err ); require_noerr( err, exit ); sockAddr = (const struct sockaddr *) CFDataGetBytePtr( addrData ); FPrintF( stdout, "%##a\n", sockAddr ); } } err = kNoErr; } FPrintF( stdout, "---\n" ); FPrintF( stdout, "End time: %{du:time}\n", &now ); if( gCFHost_WaitSecs > 0 ) sleep( (unsigned int) gCFHost_WaitSecs ); exit: exit( err ? 1 : 0 ); } //=========================================================================================================================== // DNSConfigAddCmd // // Note: Based on ajn's supplemental test tool. //=========================================================================================================================== static void DNSConfigAddCmd( void ) { OSStatus err; mdns_dns_configurator_t configurator; uint32_t order; size_t i; configurator = mdns_dns_configurator_create_with_cfstring_id( gDNSConfigAdd_ID, &err ); require_noerr( err, exit ); for( i = 0; i < gDNSConfigAdd_IPAddrCount; ++i ) { err = mdns_dns_configurator_add_server_address_string( configurator, gDNSConfigAdd_IPAddrArray[ i ] ); require_noerr( err, exit ); } order = ( gDNSConfigAdd_SearchOrder > 0 ) ? ( (uint32_t) gDNSConfigAdd_SearchOrder ) : 0; if( gDNSConfigAdd_DomainCount > 0 ) { for( i = 0; i < gDNSConfigAdd_DomainCount; ++i ) { err = mdns_dns_configurator_add_domain( configurator, gDNSConfigAdd_DomainArray[ i ], order ); require_noerr( err, exit ); } } else { // There are no domains, but the domain array needs to be non-empty, so add a zero-length domain. err = mdns_dns_configurator_add_domain( configurator, "", order ); require_noerr( err, exit ); } if( gDNSConfigAdd_Interface ) { err = mdns_dns_configurator_set_interface( configurator, gDNSConfigAdd_Interface ); require_noerr( err, exit ); } err = mdns_dns_configurator_register( configurator, CFSTR( kDNSSDUtilIdentifier ) ); require_noerr( err, exit ); exit: mdns_forget( &configurator ); gExitCode = err ? 1 : 0; } //=========================================================================================================================== // DNSConfigRemoveCmd //=========================================================================================================================== static void DNSConfigRemoveCmd( void ) { OSStatus err = mdns_dns_configurator_deregister_configuration( gDNSConfigRemove_ID, CFSTR( kDNSSDUtilIdentifier ) ); gExitCode = err ? 1 : 0; } //=========================================================================================================================== // XPCSendCommand //=========================================================================================================================== static OSStatus _XPCDictionaryCreateFromString( const char *inString, xpc_object_t *outDict ); typedef struct { dispatch_queue_t queue; // Dispatch queue. dispatch_semaphore_t doneSem; // Semaphore to signal when done. char * serviceName; // Mach service name. char * msgStr; // Message to send as a string. xpc_connection_t connection; // XPC connection. dispatch_source_t cancelTimer; // Timer cancelling the XPC connection. dispatch_source_t sourceSigInt; // Dispatch source for SIGINT. dispatch_source_t sourceSigTerm; // Dispatch source for SIGTERM. int32_t refCount; // Reference count. int cancelDelaySecs; // Number of seconds to wait before cancelling the connection. OSStatus error; // Command's final error. Boolean noReply; // True if there was a message to send and a reply is expected. Boolean done; // True if the command is done. } XPCSendCmd; static OSStatus _XPCSendCmdCreate( XPCSendCmd **outCmd ); static void _XPCSendCmdRetain( XPCSendCmd *inCmd ); static void _XPCSendCmdRelease( XPCSendCmd *inCmd ); static OSStatus _XPCSendCmdRun( XPCSendCmd *inCmd ); static void _XPCSendCmdStart( void *inCtx ); static void _XPCSendCmdStop( XPCSendCmd *inCmd, OSStatus inError ); static OSStatus _XPCSendCmdScheduleConnectionCancellation( XPCSendCmd *inCmd ); static void XPCSendCommand( void ) { OSStatus err; XPCSendCmd * cmd = NULL; err = _XPCSendCmdCreate( &cmd ); require_noerr( err, exit ); cmd->serviceName = strdup( gXPCSend_ServiceName ); require_action( cmd->serviceName, exit, err = kNoMemoryErr ); if( gXPCSend_MessageStr ) { cmd->msgStr = strdup( gXPCSend_MessageStr ); require_action( cmd->msgStr, exit, err = kNoMemoryErr ); } cmd->cancelDelaySecs = gXPCSend_CancelDelaySecs; cmd->noReply = gXPCSend_NoReply ? true : false; err = _XPCSendCmdRun( cmd ); require_noerr_quiet( err, exit ); exit: if( err ) FPrintF( stderr, "error: %#m\n", err ); if( cmd ) _XPCSendCmdRelease( cmd ); gExitCode = err ? 1 : 0; } //=========================================================================================================================== static OSStatus _XPCSendCmdCreate( XPCSendCmd ** const outCmd ) { OSStatus err; XPCSendCmd * cmd; cmd = (XPCSendCmd *) calloc( 1, sizeof( *cmd ) ); require_action( cmd, exit, err = kNoResourcesErr ); cmd->refCount = 1; cmd->error = kInProgressErr; cmd->queue = dispatch_queue_create( "com.apple.dnssdutil.xpc-send-command", DISPATCH_QUEUE_SERIAL ); require_action( cmd->queue, exit, err = kNoResourcesErr ); cmd->doneSem = dispatch_semaphore_create( 0 ); require_action( cmd->doneSem, exit, err = kNoResourcesErr ); *outCmd = cmd; cmd = NULL; err = kNoErr; exit: if( cmd ) _XPCSendCmdRelease( cmd ); return( err ); } //=========================================================================================================================== static void _XPCSendCmdRetain( XPCSendCmd * const me ) { atomic_add_32( &me->refCount, 1 ); } //=========================================================================================================================== static void _XPCSendCmdRelease( XPCSendCmd * const me ) { if( atomic_add_and_fetch_32( &me->refCount, -1 ) == 0 ) { check( !me->connection ); check( !me->cancelTimer ); check( !me->sourceSigInt ); check( !me->sourceSigTerm ); dispatch_forget( &me->queue ); dispatch_forget( &me->doneSem ); ForgetMem( &me->serviceName ); ForgetMem( &me->msgStr ); free( me ); } } //=========================================================================================================================== static OSStatus _XPCSendCmdRun( XPCSendCmd * const me ) { dispatch_async_f( me->queue, me, _XPCSendCmdStart ); dispatch_semaphore_wait( me->doneSem, DISPATCH_TIME_FOREVER ); return( me->error ); } //=========================================================================================================================== static void _XPCSendCmdStart( void * const inCtx ) { OSStatus err; XPCSendCmd * const me = (XPCSendCmd *) inCtx; xpc_object_t msg = NULL; dispatch_source_t sourceSigInt = NULL; dispatch_source_t sourceSigTerm = NULL; sourceSigInt = dispatch_source_create( DISPATCH_SOURCE_TYPE_SIGNAL, SIGINT, 0, me->queue ); require_action( sourceSigInt, exit, err = kNoResourcesErr ); sourceSigTerm = dispatch_source_create( DISPATCH_SOURCE_TYPE_SIGNAL, SIGTERM, 0, me->queue ); require_action( sourceSigInt, exit, err = kNoResourcesErr ); me->sourceSigInt = sourceSigInt; sourceSigInt = NULL; me->sourceSigTerm = sourceSigTerm; sourceSigTerm = NULL; signal( SIGINT, SIG_IGN ); signal( SIGTERM, SIG_IGN ); dispatch_source_set_event_handler( me->sourceSigInt, ^{ FPrintF( stdout, "*** Got SIGINT signal ***\n" ); _XPCSendCmdStop( me, kNoErr ); } ); dispatch_activate( me->sourceSigInt ); dispatch_source_set_event_handler( me->sourceSigTerm, ^{ FPrintF( stdout, "*** Got SIGTERM signal ***\n" ); _XPCSendCmdStop( me, kNoErr ); } ); dispatch_activate( me->sourceSigTerm ); if( me->msgStr ) { err = _XPCDictionaryCreateFromString( me->msgStr, &msg ); require_noerr_quiet( err, exit ); } FPrintF( stdout, "Service: %s\n", me->serviceName ); FPrintF( stdout, "Message: '%s'\n", me->msgStr ); FPrintF( stdout, "Wait: %d second%s\n", me->cancelDelaySecs, ( me->cancelDelaySecs != 1 ) ? "s" : "" ); FPrintF( stdout, "Start time: %{du:time}\n", NULL ); FPrintF( stdout, "---\n" ); if( msg ) FPrintF( stdout, "XPC Message:\n%{xpc}\n", msg ); me->connection = xpc_connection_create_mach_service( me->serviceName, me->queue, 0 ); require_action( me->connection, exit, err = kNoResourcesErr ); _XPCSendCmdRetain( me ); xpc_connection_set_event_handler( me->connection, ^( const xpc_object_t inEvent ) { if( !me->done ) FPrintF( stdout, "[%{du:time}] Connection Event:\n%{xpc}\n", NULL, inEvent ); if( inEvent == XPC_ERROR_CONNECTION_INVALID ) { _XPCSendCmdRelease( me ); } } ); xpc_connection_activate( me->connection ); if( msg ) { if( me->noReply ) { xpc_connection_send_message( me->connection, msg ); _XPCSendCmdRetain( me ); xpc_connection_send_barrier( me->connection, ^{ if( !me->done ) { OSStatus localErr; localErr = _XPCSendCmdScheduleConnectionCancellation( me ); if( localErr ) _XPCSendCmdStop( me, localErr ); } _XPCSendCmdRelease( me ); } ); } else { _XPCSendCmdRetain( me ); xpc_connection_send_message_with_reply( me->connection, msg, me->queue, ^( const xpc_object_t inReply ) { if( !me->done ) { OSStatus localErr; FPrintF( stdout, "[%{du:time}] Reply:\n%{xpc}\n", NULL, inReply ); localErr = _XPCSendCmdScheduleConnectionCancellation( me ); if( localErr ) _XPCSendCmdStop( me, localErr ); } _XPCSendCmdRelease( me ); } ); } } else { err = _XPCSendCmdScheduleConnectionCancellation( me ); require_noerr( err, exit ); } err = kNoErr; exit: xpc_forget( &msg ); dispatch_forget( &sourceSigInt ); dispatch_forget( &sourceSigTerm ); if( err ) _XPCSendCmdStop( me, err ); } //=========================================================================================================================== static void _XPCSendCmdStop( XPCSendCmd * const me, const OSStatus inError ) { if( !me->done ) { me->done = true; me->error = inError; xpc_connection_forget( &me->connection ); dispatch_source_forget( &me->cancelTimer ); dispatch_source_forget( &me->sourceSigInt ); dispatch_source_forget( &me->sourceSigTerm ); FPrintF( stdout, "---\n" ); FPrintF( stdout, "End time: %{du:time}\n", NULL ); dispatch_semaphore_signal( me->doneSem ); } } //=========================================================================================================================== static OSStatus _XPCSendCmdScheduleConnectionCancellation( XPCSendCmd * const me ) { OSStatus err; if( me->cancelDelaySecs >= 0 ) { me->cancelTimer = dispatch_source_create( DISPATCH_SOURCE_TYPE_TIMER, 0, 0, me->queue ); require_action( me->cancelTimer, exit, err = kNoResourcesErr ); dispatch_source_set_timer( me->cancelTimer, dispatch_time_seconds( me->cancelDelaySecs ), DISPATCH_TIME_FOREVER, 0 ); dispatch_source_set_event_handler( me->cancelTimer, ^{ _XPCSendCmdStop( me, kNoErr ); } ); dispatch_activate( me->cancelTimer ); } err = kNoErr; exit: return( err ); } //=========================================================================================================================== // _XPCDictionaryCreateFromString //=========================================================================================================================== #define kXPCObjectPrefix_Bool "bool:" #define kXPCObjectPrefix_BoolAbbr "b:" #define kXPCObjectPrefix_Data "data:" #define kXPCObjectPrefix_DataAbbr "d:" #define kXPCObjectPrefix_Int64 "int:" #define kXPCObjectPrefix_Int64Abbr "i:" #define kXPCObjectPrefix_String "string:" #define kXPCObjectPrefix_StringAbbr "s:" #define kXPCObjectPrefix_UInt64 "uint:" #define kXPCObjectPrefix_UInt64Abbr "u:" #define kXPCObjectPrefix_UUID "uuid:" typedef struct XPCListItem XPCListItem; struct XPCListItem { XPCListItem * next; xpc_object_t obj; char * key; }; static OSStatus _XPCListItemCreate( xpc_object_t inObject, const char *inKey, XPCListItem **outItem ); static void _XPCListItemFree( XPCListItem *inItem ); static void _XPCListFree( XPCListItem *inList ); static OSStatus _XPCObjectFromString( const char *inString, xpc_object_t *outObject ); static OSStatus _XPCDictionaryCreateFromString( const char *inString, xpc_object_t *outDict ) { OSStatus err; xpc_object_t dict; xpc_object_t container = NULL; char * keyMem = NULL; const char * ptr = inString; const char * const end = inString + strlen( inString ); XPCListItem * stack = NULL; dict = xpc_dictionary_create( NULL, NULL, 0 ); require_action( dict, exit, err = kNoMemoryErr ); container = xpc_retain( dict ); while( *ptr ) { xpc_type_t containerType; int c; // At this point, zero or more of the current container's elements have been parsed. // Skip the white space leading up to the container's next element, if any, or the container's end. while( isspace_safe( *ptr ) ) ++ptr; c = *ptr; if( c == '\0' ) break; // Check if we're done with the current container. containerType = xpc_get_type( container ); if( ( ( containerType == XPC_TYPE_DICTIONARY ) && ( c == '}' ) ) || ( ( containerType == XPC_TYPE_ARRAY ) && ( c == ']' ) ) ) { XPCListItem * item; item = stack; require_action_quiet( item, exit, err = kMalformedErr ); // Add the current container to its parent container. if( item->key ) { xpc_dictionary_set_value( item->obj, item->key, container ); } else { xpc_array_append_value( item->obj, container ); } // Pop the parent container from the stack and continue with the parent container. xpc_forget( &container ); container = xpc_retain( item->obj ); stack = item->next; _XPCListItemFree( item ); ++ptr; // Increment past the end of the container. } else { const char * keyStr; char keyBuf[ 64 ]; // If the current container is a dictionary, parse the key string. if( containerType == XPC_TYPE_DICTIONARY ) { ForgetMem( &keyMem ); err = _ParseEscapedStringWithCopy( ptr, end, "={}[]" kWhiteSpaceCharSet, keyBuf, sizeof( keyBuf ), &keyStr, &keyMem, &ptr ); require_noerr_quiet( err, exit ); require_action_quiet( *ptr == '=', exit, err = kMalformedErr ); ++ptr; check( keyStr ); } else { keyStr = NULL; } // Parse the value string. c = *ptr; if( ( c == '{' ) || ( c == '[' ) ) // Check if the value is a container. { XPCListItem * item; // Push the current container onto the container stack. err = _XPCListItemCreate( container, keyStr, &item ); require_noerr( err, exit ); item->next = stack; stack = item; item = NULL; // Create and continue with the child container. xpc_forget( &container ); if( c == '{' ) { container = xpc_dictionary_create( NULL, NULL, 0 ); require_action( container, exit, err = kNoMemoryErr ); } else { container = xpc_array_create( NULL, 0 ); require_action( container, exit, err = kNoMemoryErr ); } ++ptr; // Increment past the start of the container. } else { const char * valStr; char * valMem; xpc_object_t value; char valBuf[ 64 ]; err = _ParseEscapedStringWithCopy( ptr, end, "{}[]" kWhiteSpaceCharSet, valBuf, sizeof( valBuf ), &valStr, &valMem, &ptr ); require_noerr_quiet( err, exit ); err = _XPCObjectFromString( valStr, &value ); valStr = NULL; ForgetMem( &valMem ); require_noerr_quiet( err, exit ); if( keyStr ) { xpc_dictionary_set_value( container, keyStr, value ); } else { xpc_array_append_value( container, value ); } xpc_forget( &value ); } } } // There should be no containers left on the stack. require_action_quiet( !stack, exit, err = kMalformedErr ); check( container == dict ); *outDict = dict; dict = NULL; err = kNoErr; exit: xpc_forget( &dict ); xpc_forget( &container ); ForgetMem( &keyMem ); if( stack ) _XPCListFree( stack ); return( err ); } typedef enum { kXPCObjectType_Invalid = 0, kXPCObjectType_Bool = 1, kXPCObjectType_Data = 2, kXPCObjectType_Int64 = 3, kXPCObjectType_String = 4, kXPCObjectType_UInt64 = 5, kXPCObjectType_UUID = 6 } XPCObjectType; static OSStatus _XPCObjectFromString( const char *inString, xpc_object_t *outObject ) { OSStatus err; xpc_object_t object; const char * valStr; size_t valOffset; XPCObjectType type; if( 0 ) {} // Bool else if( stricmp_prefix( inString, kXPCObjectPrefix_Bool ) == 0 ) { valOffset = sizeof_string( kXPCObjectPrefix_Bool ); type = kXPCObjectType_Bool; } else if( stricmp_prefix( inString, kXPCObjectPrefix_BoolAbbr ) == 0 ) { valOffset = sizeof_string( kXPCObjectPrefix_BoolAbbr ); type = kXPCObjectType_Bool; } // Data else if( stricmp_prefix( inString, kXPCObjectPrefix_Data ) == 0 ) { valOffset = sizeof_string( kXPCObjectPrefix_Data ); type = kXPCObjectType_Data; } else if( stricmp_prefix( inString, kXPCObjectPrefix_DataAbbr ) == 0 ) { valOffset = sizeof_string( kXPCObjectPrefix_DataAbbr ); type = kXPCObjectType_Data; } // Int64 else if( stricmp_prefix( inString, kXPCObjectPrefix_Int64 ) == 0 ) { valOffset = sizeof_string( kXPCObjectPrefix_Int64 ); type = kXPCObjectType_Int64; } else if( stricmp_prefix( inString, kXPCObjectPrefix_Int64Abbr ) == 0 ) { valOffset = sizeof_string( kXPCObjectPrefix_Int64Abbr ); type = kXPCObjectType_Int64; } // String else if( stricmp_prefix( inString, kXPCObjectPrefix_String ) == 0 ) { valOffset = sizeof_string( kXPCObjectPrefix_String ); type = kXPCObjectType_String; } else if( stricmp_prefix( inString, kXPCObjectPrefix_StringAbbr ) == 0 ) { valOffset = sizeof_string( kXPCObjectPrefix_StringAbbr ); type = kXPCObjectType_String; } // UInt64 else if( stricmp_prefix( inString, kXPCObjectPrefix_UInt64 ) == 0 ) { valOffset = sizeof_string( kXPCObjectPrefix_UInt64 ); type = kXPCObjectType_UInt64; } else if( stricmp_prefix( inString, kXPCObjectPrefix_UInt64Abbr ) == 0 ) { valOffset = sizeof_string( kXPCObjectPrefix_UInt64Abbr ); type = kXPCObjectType_UInt64; } // UUID else if( stricmp_prefix( inString, kXPCObjectPrefix_UUID ) == 0 ) { valOffset = sizeof_string( kXPCObjectPrefix_UUID ); type = kXPCObjectType_UUID; } // Unsupported prefix else { err = kValueErr; goto exit; } valStr = &inString[ valOffset ]; switch( type ) { case kXPCObjectType_Bool: { if( IsTrueString( valStr, kSizeCString ) ) { object = xpc_bool_create( true ); } else if( IsFalseString( valStr, kSizeCString ) ) { object = xpc_bool_create( false ); } else { err = kValueErr; goto exit; } break; } case kXPCObjectType_Data: { uint8_t * dataPtr; size_t dataLen; err = HexToDataCopy( valStr, kSizeCString, kHexToData_DefaultFlags, &dataPtr, &dataLen, NULL ); require_noerr( err, exit ); object = xpc_data_create( dataPtr, dataLen ); free( dataPtr ); require_action( object, exit, err = kNoMemoryErr ); break; } case kXPCObjectType_Int64: { int64_t i64; i64 = _StringToInt64( valStr, &err ); require_noerr_quiet( err, exit ); object = xpc_int64_create( i64 ); require_action( object, exit, err = kNoMemoryErr ); break; } case kXPCObjectType_String: { object = xpc_string_create( valStr ); require_action( object, exit, err = kNoMemoryErr ); break; } case kXPCObjectType_UInt64: { uint64_t u64; u64 = _StringToUInt64( valStr, &err ); require_noerr_quiet( err, exit ); object = xpc_uint64_create( u64 ); require_action( object, exit, err = kNoMemoryErr ); break; } case kXPCObjectType_UUID: { uuid_t uuid; err = uuid_parse( valStr, uuid ); require_noerr_action_quiet( err, exit, err = kValueErr ); object = xpc_uuid_create( uuid ); require_action( object, exit, err = kNoMemoryErr ); break; } default: { FatalErrorF( "Unhandled XPCObjectType %ld", (long) type ); } } check( object ); *outObject = object; err = kNoErr; exit: return( err ); } static OSStatus _XPCListItemCreate( xpc_object_t inObject, const char *inKey, XPCListItem **outItem ) { OSStatus err; XPCListItem * item; item = (XPCListItem *) calloc( 1, sizeof( *item ) ); require_action( item, exit, err = kNoMemoryErr ); item->obj = xpc_retain( inObject ); if( ( xpc_get_type( item->obj ) == XPC_TYPE_DICTIONARY ) && inKey ) { item->key = strdup( inKey ); require_action( item->key, exit, err = kNoMemoryErr ); } *outItem = item; item = NULL; err = kNoErr; exit: if( item ) _XPCListItemFree( item ); return( err ); } static void _XPCListItemFree( XPCListItem *inItem ) { xpc_forget( &inItem->obj ); ForgetMem( &inItem->key ); free( inItem ); } static void _XPCListFree( XPCListItem *inList ) { XPCListItem * item; while( ( item = inList ) != NULL ) { inList = item->next; _XPCListItemFree( item ); } } #endif // TARGET_OS_DARWIN #if( MDNSRESPONDER_PROJECT ) //=========================================================================================================================== // InterfaceMonitorCmd //=========================================================================================================================== static void _InterfaceMonitorPrint( mdns_interface_monitor_t inMonitor, mdns_interface_flags_t inUpdateFlags ); static void _InterfaceMonitorSignalHandler( void *inContext ); static void InterfaceMonitorCmd( void ) { OSStatus err; mdns_interface_monitor_t monitor; dispatch_source_t signalSource = NULL; uint32_t ifIndex; __block int exitCode; err = InterfaceIndexFromArgString( gInterface, &ifIndex ); require_noerr_quiet( err, exit ); monitor = mdns_interface_monitor_create( ifIndex ); require_action( monitor, exit, err = kNoResourcesErr ); exitCode = 0; mdns_interface_monitor_set_queue( monitor, dispatch_get_main_queue() ); mdns_interface_monitor_set_event_handler( monitor, ^( mdns_event_t inEvent, OSStatus inError ) { switch( inEvent ) { case mdns_event_error: FPrintF( stderr, "error: Interface monitor failed: %#m\n", inError ); mdns_interface_monitor_invalidate( monitor ); exitCode = 1; break; case mdns_event_invalidated: FPrintF( stdout, "Interface monitor invalidated.\n" ); mdns_release( monitor ); exit( exitCode ); default: FPrintF( stdout, "Unhandled event '%s' (%ld)\n", mdns_event_to_string( inEvent ), (long) inEvent ); break; } } ); mdns_interface_monitor_set_update_handler( monitor, ^( __unused mdns_interface_flags_t inUpdateFlags ) { _InterfaceMonitorPrint( monitor, inUpdateFlags ); } ); _InterfaceMonitorPrint( monitor, 0 ); mdns_interface_monitor_activate( monitor ); signal( SIGINT, SIG_IGN ); err = DispatchSignalSourceCreate( SIGINT, dispatch_get_main_queue(), _InterfaceMonitorSignalHandler, monitor, &signalSource ); require_noerr( err, exit ); dispatch_resume( signalSource ); dispatch_main(); exit: if( err ) ErrQuit( 1, "error: %#m\n", err ); } static void _InterfaceMonitorPrint( const mdns_interface_monitor_t inMonitor, const mdns_interface_flags_t inUpdateFlags ) { FPrintF( stdout, "%{du:time} %@%s\n", NULL, inMonitor, ( inUpdateFlags & mdns_interface_flag_network ) ? " (network changed)" : "" ); } static void _InterfaceMonitorSignalHandler( void *inContext ) { mdns_interface_monitor_invalidate( (mdns_interface_monitor_t) inContext ); } //=========================================================================================================================== // QuerierCommand //=========================================================================================================================== typedef struct { dispatch_queue_t queue; // Serial queue for command's events. dispatch_semaphore_t doneSem; // Semaphore to signal when command is done. uint8_t * qname; // Name of record to query for. mdns_querier_t querier; // Querier. dispatch_source_t sourceSigInt; // Dispatch source for SIGINT. dispatch_source_t sourceSigTerm; // Dispatch source for SIGTERM. int32_t refCount; // Reference count. OSStatus error; // Command's error. uint32_t ifIndex; // Interface index for scoping. int32_t startTimeLeewayMs; // Start time leeway in milliseconds. uint16_t qtype; // Type of record to query for. uint16_t qclass; // Class of record to query for. pid_t delegatorPID; // Delegator PID. uint8_t delegatorUUID[ 16 ]; // Delegator UUID. Boolean haveDelegatorPID; // True if delegatorPID is set. Boolean haveDelegatorUUID; // True if delegatorUUID is set. Boolean dnssecOK; // True if queries need an OPT record with the DO bit set. Boolean checkingDisabled; // True if queries need the CD bit set. Boolean sensitiveLogging; // True if querier's sensitive logging should be enabled. Boolean haveStartTimeLeeway; // True if the start time leeway was set. Boolean done; // True if the command is done. // Variables for resolver. mdns_resolver_type_t resolverType; // Type of resolver to use. mdns_resolver_t resolver; // Resolver. CFMutableArrayRef serverAddrs; // Server addresses to use for resolver. char * providerName; // Provider name for resolver. char * connectionHostname; // Overrides hostname used for transport layer connection. char * urlPath; // URL path for resolver. void * identityRefPtr; // Keychain identity reference. size_t identityRefLen; // Keychain identity reference length. void * odohCfgPtr; // Oblivious DoH configuration. size_t odohCfgLen; // Oblivious DoH configuration length. Boolean noConnectionReuse; // True if connection reuse is to be disabled. Boolean squashCNAMEs; // True if CNAMEs should be squashed. // Variables for DNS service manager. mdns_dns_service_manager_t manager; // DNS service manager. mdns_dns_service_t service; // DNS service for query. mdns_dns_service_definition_t definition; // DNS service definition. mdns_dns_service_id_t registered_service_id; // ID of DNS service registered with DNS service definition. } QuerierCmd; static OSStatus _QuerierCmdCreate( QuerierCmd **outCmd ); static void _QuerierCmdRetain( QuerierCmd *inCmd ); static void _QuerierCmdRelease( QuerierCmd *inCmd ); static OSStatus _QuerierCmdRun( QuerierCmd *inCmd ); static void QuerierCommand( void ) { OSStatus err; QuerierCmd * cmd = NULL; size_t i; uint8_t qname[ kDomainNameLengthMax ]; err = _QuerierCmdCreate( &cmd ); require_noerr( err, exit ); if( gInterface ) { err = InterfaceIndexFromArgString( gInterface, &cmd->ifIndex ); require_noerr_quiet( err, exit ); } else { cmd->ifIndex = 0; } err = DomainNameFromString( qname, gQuerier_Name, NULL ); if( err ) { FPrintF( stderr, "error: Invalid domain name: '%s'\n", gDNSQuery_Name ); goto exit; } err = DomainNameDup( qname, &cmd->qname, NULL ); require_noerr( err, exit ); err = RecordTypeFromArgString( gQuerier_Type, &cmd->qtype ); require_noerr_quiet( err, exit ); err = RecordClassFromArgString( gQuerier_Class, &cmd->qclass ); require_noerr( err, exit ); if( gQuerier_Delegator ) { err = StringToUUID( gQuerier_Delegator, kSizeCString, false, cmd->delegatorUUID ); if( !err ) { cmd->haveDelegatorUUID = true; } else { cmd->delegatorPID = _StringToPID( gQuerier_Delegator, &err ); if( err ) { FPrintF( stderr, "error: Invalid delegator PID or UUID: %s\n", gQuerier_Delegator ); err = kParamErr; goto exit; } cmd->haveDelegatorPID = true; } } if( gQuerier_ResolverType ) { cmd->resolverType = (mdns_resolver_type_t) CLIArgToValue( "resolverType", gQuerier_ResolverType, &err, kMDNSResolverTypeStr_Normal, (int) mdns_resolver_type_normal, kMDNSResolverTypeStr_TCPOnly, (int) mdns_resolver_type_tcp, kMDNSResolverTypeStr_TLS, (int) mdns_resolver_type_tls, kMDNSResolverTypeStr_HTTPS, (int) mdns_resolver_type_https, NULL ); require_noerr_quiet( err, exit ); for( i = 0; i < gQuerier_ServerAddrCount; ++i ) { const char * const addrStr = gQuerier_ServerAddrs[ i ]; mdns_address_t serverAddr; serverAddr = mdns_address_create_from_ip_address_string( addrStr ); if( !serverAddr ) { FPrintF( stderr, "error: Failed to create address for '%s'\n", addrStr ); err = kParamErr; goto exit; } CFArrayAppendValue( cmd->serverAddrs, serverAddr ); mdns_release( serverAddr ); } if( gQuerier_ProviderName ) { cmd->providerName = strdup( gQuerier_ProviderName ); require_action( cmd->providerName, exit, err = kNoMemoryErr ); } if( gQuerier_ConnectionHostname ) { cmd->connectionHostname = strdup( gQuerier_ConnectionHostname ); require_action( cmd->connectionHostname, exit, err = kNoMemoryErr ); } if( gQuerier_URLPath ) { cmd->urlPath = strdup( gQuerier_URLPath ); require_action( cmd->urlPath, exit, err = kNoMemoryErr ); } if( gQuerier_IdentityReference ) { err = HexToDataCopy( gQuerier_IdentityReference, kSizeCString, kHexToData_DefaultFlags, &cmd->identityRefPtr, &cmd->identityRefLen, NULL ); require_noerr_action( err, exit, FPrintF( stderr, "error: Failed to parse identity reference hex string: '%s'\n", gQuerier_IdentityReference ) ); } if( gQuerier_ODoHConfig ) { err = HexToDataCopy( gQuerier_ODoHConfig, kSizeCString, kHexToData_DefaultFlags, &cmd->odohCfgPtr, &cmd->odohCfgLen, NULL ); require_noerr_action( err, exit, FPrintF( stderr, "error: Failed to parse ODoH config hex string: '%s'\n", gQuerier_ODoHConfig ) ); } cmd->noConnectionReuse = gQuerier_NoConnectionReuse ? true : false; cmd->squashCNAMEs = gQuerier_SquashCNAMEs ? true : false; } else if ( ( gQuerier_ServerAddrCount > 0 ) || ( gQuerier_DomainCount > 0 ) ) { cmd->definition = mdns_dns_service_definition_create(); require_action( cmd->definition, exit, err = kNoResourcesErr ); if( cmd->ifIndex != 0 ) mdns_dns_service_definition_set_interface_index( cmd->definition, cmd->ifIndex, true ); for( i = 0; i < gQuerier_ServerAddrCount; ++i ) { const char * const addrStr = gQuerier_ServerAddrs[ i ]; mdns_address_t serverAddr; serverAddr = mdns_address_create_from_ip_address_string( addrStr ); if( !serverAddr ) { FPrintF( stderr, "error: Failed to create address for '%s'\n", addrStr ); err = kParamErr; goto exit; } err = mdns_dns_service_definition_append_server_address( cmd->definition, serverAddr ); mdns_forget( &serverAddr ); require_noerr( err, exit ); } for( i = 0; i < gQuerier_DomainCount; ++i ) { const char * const domainStr = gQuerier_Domains[ i ]; mdns_domain_name_t domain; domain = mdns_domain_name_create( domainStr, mdns_domain_name_create_opts_none, &err ); if( !domain ) { FPrintF( stderr, "error: Failed to create domain name for '%s': %#m\n", domainStr, err ); goto exit; } mdns_dns_service_definition_add_domain( cmd->definition, domain ); mdns_forget( &domain ); } } cmd->dnssecOK = gQuerier_DNSSECOK ? true : false; cmd->checkingDisabled = gQuerier_CheckingDisabled ? true : false; cmd->sensitiveLogging = gQuerier_SensitiveLogging ? true : false; if( gQuerier_StartLeewayMs ) { err = StringToInt32( gQuerier_StartLeewayMs, &cmd->startTimeLeewayMs ); require_noerr_action_quiet( err, exit, FPrintF( stderr, "Invalid startLeeway value: '%s'\n", gQuerier_StartLeewayMs ) ); cmd->haveStartTimeLeeway = true; } err = _QuerierCmdRun( cmd ); require_noerr( err, exit ); exit: if( cmd ) _QuerierCmdRelease( cmd ); gExitCode = err ? 1 : 0; } //=========================================================================================================================== static OSStatus _QuerierCmdCreate( QuerierCmd **outCmd ) { OSStatus err; QuerierCmd * cmd; cmd = (QuerierCmd *) calloc( 1, sizeof( *cmd ) ); require_action( cmd, exit, err = kNoResourcesErr ); cmd->refCount = 1; cmd->resolverType = mdns_resolver_type_null; cmd->queue = dispatch_queue_create( "com.apple.dnssdutil.querier-command", DISPATCH_QUEUE_SERIAL ); require_action( cmd->queue, exit, err = kNoResourcesErr ); cmd->doneSem = dispatch_semaphore_create( 0 ); require_action( cmd->doneSem, exit, err = kNoResourcesErr ); cmd->serverAddrs = CFArrayCreateMutable( kCFAllocatorDefault, 0, &mdns_cfarray_callbacks ); require_action( cmd->serverAddrs, exit, err = kNoResourcesErr ); *outCmd = cmd; cmd = NULL; err = kNoErr; exit: if( cmd ) _QuerierCmdRelease( cmd ); return( err ); } //=========================================================================================================================== static void _QuerierCmdRetain( QuerierCmd *inCmd ) { atomic_add_32( &inCmd->refCount, 1 ); } //=========================================================================================================================== static void _QuerierCmdRelease( QuerierCmd *inCmd ) { if( atomic_add_and_fetch_32( &inCmd->refCount, -1 ) == 0 ) { check( !inCmd->sourceSigInt ); check( !inCmd->sourceSigTerm ); check( !inCmd->resolver ); check( !inCmd->manager ); check( !inCmd->service ); check( !inCmd->querier ); dispatch_forget( &inCmd->queue ); dispatch_forget( &inCmd->doneSem ); ForgetMem( &inCmd->qname ); ForgetCF( &inCmd->serverAddrs ); ForgetMem( &inCmd->providerName ); ForgetMem( &inCmd->connectionHostname ); ForgetMem( &inCmd->urlPath ); ForgetPtrLen( &inCmd->identityRefPtr, &inCmd->identityRefLen ); ForgetPtrLen( &inCmd->odohCfgPtr, &inCmd->odohCfgLen ); mdns_forget( &inCmd->definition ); free( inCmd ); } } //=========================================================================================================================== static void _QuerierCmdStart( void *inCtx ); static void _QuerierCmdStop( QuerierCmd *inCmd, OSStatus inError ); static void _QuerierCmdSigIntHandler( void *inCtx ); static void _QuerierCmdSigTermHandler( void *inCtx ); static OSStatus _QuerierCmdRun( QuerierCmd *inCmd ) { dispatch_async_f( inCmd->queue, inCmd, _QuerierCmdStart ); dispatch_semaphore_wait( inCmd->doneSem, DISPATCH_TIME_FOREVER ); return( inCmd->error ); } static void _QuerierCmdStart( void *inCtx ) { OSStatus err; QuerierCmd * const cmd = (QuerierCmd *) inCtx; dns_config_t * config = NULL; const char * ifNamePtr; char ifNameBuf[ IF_NAMESIZE + 1 ]; signal( SIGINT, SIG_IGN ); err = DispatchSignalSourceCreate( SIGINT, cmd->queue, _QuerierCmdSigIntHandler, cmd, &cmd->sourceSigInt ); require_noerr( err, exit ); dispatch_resume( cmd->sourceSigInt ); signal( SIGTERM, SIG_IGN ); err = DispatchSignalSourceCreate( SIGTERM, cmd->queue, _QuerierCmdSigTermHandler, cmd, &cmd->sourceSigTerm ); require_noerr( err, exit ); dispatch_resume( cmd->sourceSigTerm ); ifNamePtr = if_indextoname( cmd->ifIndex, ifNameBuf ); FPrintF( stdout, "Interface: %u (%s)\n", cmd->ifIndex, ifNamePtr ? ifNamePtr : "?" ); FPrintF( stdout, "Name: %{du:dname}\n", cmd->qname ); FPrintF( stdout, "Type: %s (%u)\n", RecordTypeToString( cmd->qtype ), cmd->qtype ); FPrintF( stdout, "Class: %s (%u)\n", RecordClassToString( cmd->qclass ), cmd->qclass ); if( cmd->haveStartTimeLeeway ) { FPrintF( stdout, "Start leeway: " ); if( cmd->startTimeLeewayMs >= 0 ) FPrintF( stdout, "%d ms\n", cmd->startTimeLeewayMs ); else FPrintF( stdout, "∞ ms\n" ); } if( cmd->definition || ( cmd->resolverType == mdns_resolver_type_null ) ) { FPrintF( stdout, "Start time: %{du:time}\n", NULL ); FPrintF( stdout, "---\n" ); cmd->manager = mdns_dns_service_manager_create( cmd->queue, &err ); require_noerr( err, exit ); _QuerierCmdRetain( cmd ); mdns_dns_service_manager_set_event_handler( cmd->manager, ^( mdns_event_t inEvent, OSStatus inError ) { switch( inEvent ) { case mdns_event_error: if( !cmd->done ) { FPrintF( stderr, "error: DNS service manager failed: %#m\n", inError ); _QuerierCmdStop( cmd, inError ); } break; case mdns_event_invalidated: _QuerierCmdRelease( cmd ); break; default: break; } } ); if( cmd->definition ) { cmd->registered_service_id = mdns_dns_service_manager_register_native_service( cmd->manager, cmd->definition, &err ); require_noerr( err, exit ); } else { config = dns_configuration_copy(); require_action( config, exit, err = kUnknownErr ); mdns_dns_service_manager_apply_dns_config( cmd->manager, config ); } if( cmd->definition ) { if( cmd->ifIndex == 0 ) { cmd->service = mdns_dns_service_manager_get_unscoped_native_service( cmd->manager, cmd->qname ); } else { cmd->service = mdns_dns_service_manager_get_interface_scoped_native_service( cmd->manager, cmd->qname, cmd->ifIndex ); } } else { if( cmd->ifIndex == 0 ) { cmd->service = mdns_dns_service_manager_get_unscoped_system_service( cmd->manager, cmd->qname ); } else { cmd->service = mdns_dns_service_manager_get_interface_scoped_system_service( cmd->manager, cmd->qname, cmd->ifIndex ); } } if( !cmd->service ) { FPrintF( stderr, "error: Failed to get DNS service for %{du:dname}\n", cmd->qname ); err = kNotFoundErr; goto exit; } mdns_retain( cmd->service ); FPrintF( stdout, "Using DNS service -- %@\n\n", cmd->service ); cmd->querier = mdns_dns_service_create_querier( cmd->service, &err ); require_noerr( err, exit ); } else { CFIndex n, i; FPrintF( stdout, "Resolver Type: %s\n", mdns_resolver_type_to_string( cmd->resolverType ) ); if( cmd->providerName ) FPrintF( stdout, "Provider Name: %s\n", cmd->providerName ); if( cmd->connectionHostname ) FPrintF( stdout, "Connection Hostname: %s\n", cmd->connectionHostname ); if( cmd->urlPath ) FPrintF( stdout, "URL path: %s\n", cmd->urlPath ); if( cmd->identityRefPtr ) { FPrintF( stdout, "Identity Reference: %H\n", cmd->identityRefPtr, (int) cmd->identityRefLen, INT_MAX ); } if( cmd->odohCfgPtr ) { FPrintF( stdout, "ODoH Config: %H\n", cmd->odohCfgPtr, (int) cmd->odohCfgLen, INT_MAX ); } FPrintF( stdout, "Server(s): " ); n = CFArrayGetCount( cmd->serverAddrs ); for( i = 0; i < n; ++i ) { FPrintF( stdout, "%s%@", ( i == 0 ) ? "" : ", ", CFArrayGetValueAtIndex( cmd->serverAddrs, i ) ); } FPrintF( stdout, "\n" ); FPrintF( stdout, "Start time: %{du:time}\n", NULL ); FPrintF( stdout, "---\n" ); cmd->resolver = mdns_resolver_create( cmd->resolverType, cmd->ifIndex, &err ); require_noerr( err, exit ); if( cmd->providerName ) { err = mdns_resolver_set_provider_name( cmd->resolver, cmd->providerName ); require_noerr( err, exit ); } if( cmd->connectionHostname ) { err = mdns_resolver_set_connection_hostname( cmd->resolver, cmd->connectionHostname ); require_noerr( err, exit ); } if( cmd->urlPath ) { err = mdns_resolver_set_url_path( cmd->resolver, cmd->urlPath ); require_noerr( err, exit ); } if( cmd->identityRefPtr ) { err = mdns_resolver_set_identity_reference( cmd->resolver, cmd->identityRefPtr, cmd->identityRefLen ); require_noerr( err, exit ); } if( cmd->odohCfgPtr ) { err = mdns_resolver_update_odoh_config( cmd->resolver, cmd->providerName, cmd->urlPath, cmd->odohCfgPtr, cmd->odohCfgLen, NULL ); require_noerr( err, exit ); } if( cmd->noConnectionReuse ) mdns_resolver_disable_connection_reuse( cmd->resolver, true ); if( cmd->squashCNAMEs ) mdns_resolver_set_squash_cnames( cmd->resolver, true ); for( i = 0; i < n; ++i ) { const mdns_address_t addr = (mdns_address_t) CFArrayGetValueAtIndex( cmd->serverAddrs, i ); err = mdns_resolver_add_server_address( cmd->resolver, addr ); require_noerr( err, exit ); } mdns_resolver_activate( cmd->resolver ); cmd->querier = mdns_resolver_create_querier( cmd->resolver, &err ); require_noerr( err, exit ); } err = mdns_querier_set_query( cmd->querier, cmd->qname, cmd->qtype, cmd->qclass ); require_noerr( err, exit ); if( cmd->haveDelegatorPID ) mdns_querier_set_delegator_pid( cmd->querier, cmd->delegatorPID ); else if( cmd->haveDelegatorUUID ) mdns_querier_set_delegator_uuid( cmd->querier, cmd->delegatorUUID ); if( cmd->dnssecOK ) mdns_querier_set_dnssec_ok( cmd->querier, true ); if( cmd->checkingDisabled ) mdns_querier_set_checking_disabled( cmd->querier, true ); if( cmd->haveStartTimeLeeway ) mdns_querier_set_start_time_leeway( cmd->querier, cmd->startTimeLeewayMs ); if( cmd->sensitiveLogging ) mdns_querier_enable_sensitive_logging( cmd->querier, true ); _QuerierCmdRetain( cmd ); mdns_querier_set_queue( cmd->querier, cmd->queue ); mdns_querier_set_result_handler( cmd->querier, ^ { if( !cmd->done ) { const mdns_querier_result_type_t resultType = mdns_querier_get_result_type( cmd->querier ); if( resultType == mdns_querier_result_type_response ) { const uint8_t * const msgPtr = mdns_querier_get_response_ptr( cmd->querier ); const size_t msgLen = mdns_querier_get_response_length( cmd->querier ); FPrintF( stdout, "Message size: %zu bytes\n", msgLen ); FPrintF( stdout, "%{du:dnsmsg}\n", msgPtr, msgLen ); _QuerierCmdStop( cmd, kNoErr ); } else { OSStatus querierErr; if( resultType == mdns_querier_result_type_error ) { querierErr = mdns_querier_get_error( cmd->querier ); if( !querierErr ) querierErr = kUnknownErr; } else { querierErr = kUnexpectedErr; } FPrintF( stderr, "error: Unexpected querier result: %s, error: %#m\n", mdns_querier_result_type_to_string( resultType ), querierErr ); _QuerierCmdStop( cmd, querierErr ); } } _QuerierCmdRelease( cmd ); } ); mdns_querier_activate( cmd->querier ); exit: if( config ) dns_configuration_free( config ); if( err ) _QuerierCmdStop( cmd, err ); } //=========================================================================================================================== #define mdns_dns_service_manager_forget( X ) ForgetCustomEx( X, mdns_dns_service_manager_invalidate, mdns_release ) static void _QuerierCmdStop( QuerierCmd *inCmd, OSStatus inError ) { if( !inCmd->done ) { inCmd->done = true; inCmd->error = inError; dispatch_source_forget( &inCmd->sourceSigInt ); dispatch_source_forget( &inCmd->sourceSigTerm ); mdns_querier_forget( &inCmd->querier ); mdns_resolver_forget( &inCmd->resolver ); if( inCmd->manager && ( inCmd->registered_service_id != 0 ) ) { mdns_dns_service_manager_deregister_native_service( inCmd->manager, inCmd->registered_service_id ); } mdns_dns_service_manager_forget( &inCmd->manager ); mdns_forget( &inCmd->service ); mdns_forget( &inCmd->definition ); FPrintF( stdout, "---\n" ); FPrintF( stdout, "End time: %{du:time}\n", NULL ); dispatch_semaphore_signal( inCmd->doneSem ); } } //=========================================================================================================================== static void _QuerierCmdSigIntHandler( void *inCtx ) { FPrintF( stdout, "*** Got SIGINIT signal ***\n" ); _QuerierCmdStop( (QuerierCmd *) inCtx, kCanceledErr ); } //=========================================================================================================================== static void _QuerierCmdSigTermHandler( void *inCtx ) { FPrintF( stdout, "*** Got SIGTERM signal ***\n" ); _QuerierCmdStop( (QuerierCmd *) inCtx, kCanceledErr ); } //=========================================================================================================================== // DNSProxyCmd //=========================================================================================================================== static void _DNSProxyCmdSignalHandler( void *inContext ); static void DNSProxyCmd( void ) { OSStatus err; size_t i; mrc_dns_proxy_parameters_t params = NULL; mrc_dns_proxy_t dnsProxy = NULL; dispatch_source_t sigIntSource = NULL; dispatch_source_t sigTermSource = NULL; __block const char * separator; uint32_t outputIfIndex; char ifName[ kInterfaceNameBufLen ]; uint8_t dns64Prefix[ 16 ]; int dns64PrefixBitLen; params = mrc_dns_proxy_parameters_create( &err ); require_noerr( err, exit ); for( i = 0; i < gDNSProxy_InputInterfaceCount; ++i ) { uint32_t ifIndex; err = InterfaceIndexFromArgString( gDNSProxy_InputInterfaces[ i ], &ifIndex ); require_noerr_quiet( err, exit ); mrc_dns_proxy_parameters_add_input_interface( params, ifIndex ); } if( gDNSProxy_OutputInterface ) { err = InterfaceIndexFromArgString( gDNSProxy_OutputInterface, &outputIfIndex ); require_noerr_quiet( err, exit ); } else { outputIfIndex = 0; } mrc_dns_proxy_parameters_set_output_interface( params, outputIfIndex ); dns64PrefixBitLen = 0; if( gDNSProxy_DNS64IPv6Prefix ) { const char * end; err = _StringToIPv6Address( gDNSProxy_DNS64IPv6Prefix, kStringToIPAddressFlagsNoPort | kStringToIPAddressFlagsNoScope, dns64Prefix, NULL, NULL, &dns64PrefixBitLen, &end ); if( !err && ( *end != '\0' ) ) err = kMalformedErr; require_noerr_quiet( err, exit ); mrc_dns_proxy_parameters_set_nat64_prefix( params, dns64Prefix, (size_t) dns64PrefixBitLen ); } if( gDNSProxy_ForceAAAASynthesis ) mrc_dns_proxy_parameters_set_force_aaaa_synthesis( params, true ); FPrintF( stdout, "Input Interfaces: " ); separator = ""; mrc_dns_proxy_parameters_enumerate_input_interfaces( params, ^ bool ( const uint32_t inIfIndex ) { char inputIfName[ kInterfaceNameBufLen ]; FPrintF( stdout, "%s %u (%s)", separator, inIfIndex, InterfaceIndexToName( inIfIndex, inputIfName ) ); separator = ","; return( true ); } ); FPrintF( stdout, "\n" ); FPrintF( stdout, "Output Interface: %u (%s)\n", outputIfIndex, InterfaceIndexToName( outputIfIndex, ifName ) ); if( gDNSProxy_DNS64IPv6Prefix ) FPrintF( stdout, "DNS64 prefix: %.16a/%d\n", dns64Prefix, dns64PrefixBitLen ); FPrintF( stdout, "Force AAAA synthesis: %s\n", YesNoStr( mrc_dns_proxy_parameters_get_force_aaaa_synthesis( params ) ) ); FPrintF( stdout, "Start time: %{du:time}\n", NULL ); FPrintF( stdout, "---\n" ); dnsProxy = mrc_dns_proxy_create( params, &err ); mrc_forget( ¶ms ); require_noerr_quiet( err, exit ); mrc_dns_proxy_set_queue( dnsProxy, dispatch_get_main_queue() ); mrc_dns_proxy_set_event_handler( dnsProxy, ^( const mrc_dns_proxy_event_t inEvent, const OSStatus inError ) { switch( inEvent ) { case mrc_dns_proxy_event_started: { FPrintF( stdout, "%{du:time} DNS proxy was started\n", NULL ); break; } case mrc_dns_proxy_event_interruption: { FPrintF( stdout, "%{du:time} DNS proxy was interrupted\n", NULL ); break; } case mrc_dns_proxy_event_invalidation: { struct timeval now; gettimeofday( &now, NULL ); mrc_release( dnsProxy ); FPrintF( stdout, "---\n" ); FPrintF( stdout, "Error: %#m\n", inError ); FPrintF( stdout, "End time: %{du:time}\n", &now ); exit( 0 ); } case mrc_dns_proxy_event_none: break; } } ); signal( SIGINT, SIG_IGN ); err = DispatchSignalSourceCreate( SIGINT, dispatch_get_main_queue(), _DNSProxyCmdSignalHandler, dnsProxy, &sigIntSource ); require_noerr( err, exit ); dispatch_activate( sigIntSource ); signal( SIGTERM, SIG_IGN ); err = DispatchSignalSourceCreate( SIGTERM, dispatch_get_main_queue(), _DNSProxyCmdSignalHandler, dnsProxy, &sigTermSource ); require_noerr( err, exit ); dispatch_activate( sigTermSource ); mrc_dns_proxy_activate( dnsProxy ); dispatch_main(); exit: if( err ) ErrQuit( 1, "error: %#m\n", err ); } static void _DNSProxyCmdSignalHandler( void *inContext ) { mrc_dns_proxy_invalidate( (mrc_dns_proxy_t) inContext ); } //=========================================================================================================================== // DNSProxyStateCmd //=========================================================================================================================== static void DNSProxyStateCmd( void ) { OSStatus err; dispatch_queue_t queue; dispatch_semaphore_t doneSem = NULL; mrc_dns_proxy_state_inquiry_t inquiry = NULL; queue = dispatch_queue_create( "com.apple.dnssdutil.dns-proxy-state-command", DISPATCH_QUEUE_SERIAL ); require_action( queue, exit, err = kNoResourcesErr ); doneSem = dispatch_semaphore_create( 0 ); require_action( doneSem, exit, err = kNoResourcesErr ); inquiry = mrc_dns_proxy_state_inquiry_create(); require_action( inquiry, exit, err = kNoResourcesErr ); mrc_dns_proxy_state_inquiry_set_queue( inquiry, queue ); mrc_dns_proxy_state_inquiry_set_handler( inquiry, ^( const char * const inState, const OSStatus inError ) { if( inError ) FPrintF( stderr, "error: %#m\n", inError ); if( inState ) FPrintF( stdout, "%s\n", inState ); dispatch_semaphore_signal( doneSem ); } ); mrc_dns_proxy_state_inquiry_activate( inquiry ); // Wait for the DNS proxy state inquiry to complete. // Note: We intentionally use a semaphore that will be signalled by the asynchronous handler. The analyzer considers // this an antipattern, which is OK for the purposes of this internal test tool. Actual well-written production code // would usually not have to emulate this type of synchronous behavior. #if( !COMPILER_CLANG_ANALYZER ) dispatch_semaphore_wait( doneSem, DISPATCH_TIME_FOREVER ); #endif err = kNoErr; exit: dispatch_forget( &queue ); dispatch_forget( &doneSem ); mrc_forget( &inquiry ); gExitCode = err ? 1 : 0; } //=========================================================================================================================== // GetAddrInfoNewCommand //=========================================================================================================================== typedef enum { kDelegationType_None = 0, // No delegation. kDelegationType_PID = 1, // Delegation by PID. kDelegationType_UUID = 2, // Delegation by UUID. kDelegationType_AuditToken = 3 // Delegation by audit token. } DelegationType; typedef struct { DelegationType type; // Type of delegation. union { pid_t pid; // Delegator's PID if type is kDelegationType_PID. uuid_t uuid; // Delegator's UUID if type is kDelegationType_UUID. audit_token_t auditToken; // Delegator's audit token if type is kDelegationType_AuditToken. } ident; // Delegator's identifier. } Delegation; typedef struct { dispatch_queue_t queue; // Serial queue for command's events. dispatch_group_t group; // GCD group to know when command is done. dnssd_getaddrinfo_t gai; // dnssd_getaddrinfo object. dispatch_source_t timer; // Timer to impose time limit on dnssd_getaddrinfo activity. dispatch_source_t sigint; // Dispatch source for SIGINT. dispatch_source_t sigterm; // Dispatch source for SIGTERM. const char * hostname; // dnssd_getaddrinfo's hostname argument. const char * serviceScheme; // dnssd_getaddrinfo's service scheme argument. const char * accountID; // dnssd_getaddrinfo's account ID argument. char * stopReason; // Reason for stopping the command. uuid_t * resolverUUID; // UUID of libnetwork DNS resolver configuration to use. Delegation delegation; // Specifies the type of delegation to use for dnssd_getaddrinfo, if any. int32_t refCount; // Reference count. DNSServiceFlags flags; // dnssd_getaddrinfo's flags argument. DNSServiceProtocol protocols; // dnssd_getaddrinfo's protocols argument. uint32_t ifIndex; // dnssd_getaddrinfo's interface index argument. unsigned int timeLimitSecs; // Time limit in seconds for dnssd_getaddrinfo activity. OSStatus error; // Command's error. Boolean showTracker; // True if tracker hostname, if any, should be displayed. Boolean useFailover; // True if DNS service failover should be used if necessary. Boolean prohibitEncryptedDNS; // True if use of encrypted DNS protocols is prohibited. Boolean privateLogging; // True if private logging is to be enabled. Boolean stopped; // True if the command has been stopped. Boolean oneshot; // True if the command should stop after first set of results. Boolean printedHeader; // True if the results header has been printed. } GetAddrInfoNewCmd; static GetAddrInfoNewCmd * _GetAddrInfoNewCmdCreateEx( qos_class_t inQoS, Boolean inUseQoS, OSStatus *outError ); #define _GetAddrInfoNewCmdCreate( OUT_ERROR ) _GetAddrInfoNewCmdCreateEx( 0, false, OUT_ERROR ) #define _GetAddrInfoNewCmdCreateWithQoS( IN_QOS, OUT_ERROR ) _GetAddrInfoNewCmdCreateEx( IN_QOS, true, OUT_ERROR ) static OSStatus _GetAddrInfoNewCmdRun( GetAddrInfoNewCmd *inCmd ); static void _GetAddrInfoNewCmdStopF( GetAddrInfoNewCmd *inCmd, const char *inFmt, ... ); static GetAddrInfoNewCmd * _GetAddrInfoNewCmdRetain( GetAddrInfoNewCmd *inCmd ); static void _GetAddrInfoNewCmdRelease( GetAddrInfoNewCmd *inCmd ); #define _GetAddrInfoNewCmdForget( X ) ForgetCustom( X, _GetAddrInfoNewCmdRelease ) static void GetAddrInfoNewCommand( void ) { OSStatus err; GetAddrInfoNewCmd * cmd = NULL; if( gGAINew_QoS ) { qos_class_t qos; qos = (qos_class_t) CLIArgToValue( kQoSArgShortName, gGAINew_QoS, &err, kQoSTypeStr_Unspecified, QOS_CLASS_UNSPECIFIED, kQoSTypeStr_Background, QOS_CLASS_BACKGROUND, kQoSTypeStr_Utility, QOS_CLASS_UTILITY, kQoSTypeStr_Default, QOS_CLASS_DEFAULT, kQoSTypeStr_UserInitiated, QOS_CLASS_USER_INITIATED, kQoSTypeStr_UserInteractive, QOS_CLASS_USER_INTERACTIVE, NULL ); require_noerr_quiet( err, exit ); cmd = _GetAddrInfoNewCmdCreateWithQoS( qos, &err ); require_noerr( err, exit ); } else { cmd = _GetAddrInfoNewCmdCreate( &err ); require_noerr( err, exit ); } err = CheckIntegerArgument( gGAINew_TimeLimitSecs, "time limit", 0, INT_MAX ); require_noerr_quiet( err, exit ); cmd->timeLimitSecs = (unsigned int) gGAINew_TimeLimitSecs; cmd->hostname = gGAINew_Hostname; cmd->flags = GetDNSSDFlagsFromOpts(); cmd->serviceScheme = gGAINew_ServiceScheme; cmd->accountID = gGAINew_AccountID; cmd->showTracker = gGAINew_ShowTracker ? true : false; cmd->useFailover = gGAINew_UseFailover ? true : false; cmd->prohibitEncryptedDNS = gGAINew_ProhibitEncryptedDNS ? true : false; cmd->privateLogging = gGAINew_PrivateLogging ? true : false; cmd->oneshot = gGAINew_OneShot ? true : false; err = InterfaceIndexFromArgString( gInterface, &cmd->ifIndex ); require_noerr_quiet( err, exit ); cmd->protocols = 0; if( gGAINew_ProtocolIPv4 ) cmd->protocols |= kDNSServiceProtocol_IPv4; if( gGAINew_ProtocolIPv6 ) cmd->protocols |= kDNSServiceProtocol_IPv6; // Get delegate ID. if( gGAINew_DelegatorID ) { pid_t delegatorPID; delegatorPID = _StringToPID( gGAINew_DelegatorID, &err ); if( !err ) { if( delegatorPID >= 0 ) { cmd->delegation.ident.pid = delegatorPID; cmd->delegation.type = kDelegationType_PID; } else { delegatorPID = -delegatorPID; if( audit_token_for_pid( delegatorPID, &cmd->delegation.ident.auditToken ) ) { cmd->delegation.type = kDelegationType_AuditToken; } else { FPrintF( stderr, "Failed to get audit token for PID: %d\n", delegatorPID ); err = kParamErr; goto exit; } } } else { err = uuid_parse( gGAINew_DelegatorID, cmd->delegation.ident.uuid ); if( !err ) { cmd->delegation.type = kDelegationType_UUID; } else { FPrintF( stderr, "Invalid delegate ID (PID or UUID): %s\n", gGAINew_DelegatorID ); err = kParamErr; goto exit; } } } if( gGAINew_ResolverUUID ) { uuid_t uuid; err = uuid_parse( gGAINew_ResolverUUID, uuid ); if( err ) { FPrintF( stderr, "Invalid resolver UUID: %s\n", gGAINew_ResolverUUID ); err = kParamErr; goto exit; } cmd->resolverUUID = (uuid_t *) _memdup( uuid, sizeof( uuid ) ); require_action( cmd->resolverUUID, exit, err = kNoMemoryErr ); } err = _GetAddrInfoNewCmdRun( cmd ); require_noerr( err, exit ); exit: _GetAddrInfoNewCmdForget( &cmd ); gExitCode = err ? 1 : 0; } //=========================================================================================================================== static GetAddrInfoNewCmd * _GetAddrInfoNewCmdCreateEx( qos_class_t inQoS, Boolean inUseQoS, OSStatus *outError ) { OSStatus err; GetAddrInfoNewCmd * obj; GetAddrInfoNewCmd * cmd = NULL; obj = (GetAddrInfoNewCmd *) calloc( 1, sizeof( *obj ) ); require_action( obj, exit, err = kNoMemoryErr ); obj->refCount = 1; if( inUseQoS ) { dispatch_queue_attr_t attr; attr = dispatch_queue_attr_make_with_qos_class( DISPATCH_QUEUE_SERIAL, inQoS, 0 ); obj->queue = dispatch_queue_create( "com.apple.dnssdutil.getaddrinfo-new-command", attr ); require_action( obj->queue, exit, err = kNoResourcesErr ); } else { obj->queue = dispatch_queue_create( "com.apple.dnssdutil.getaddrinfo-new-command", DISPATCH_QUEUE_SERIAL ); require_action( obj->queue, exit, err = kNoResourcesErr ); } obj->group = dispatch_group_create(); require_action( obj->group, exit, err = kNoResourcesErr ); cmd = obj; obj = NULL; err = kNoErr; exit: if( outError ) *outError = err; _GetAddrInfoNewCmdForget( &obj ); return( cmd ); } //=========================================================================================================================== static void _GetAddrInfoNewCmdStart( void *inCtx ); static OSStatus _GetAddrInfoNewCmdRun( GetAddrInfoNewCmd *me ) { dispatch_group_enter( me->group ); dispatch_async_f( me->queue, me, _GetAddrInfoNewCmdStart ); dispatch_group_wait( me->group, DISPATCH_TIME_FOREVER ); FPrintF( stdout, "---\n" ); FPrintF( stdout, "End time: %{du:time}\n", NULL ); if( me->stopReason ) FPrintF( stdout, "End reason: %s\n", me->stopReason ); return( me->error ); } static void _GetAddrInfoNewCmdStart( void *inCtx ) { OSStatus err; GetAddrInfoNewCmd * const me = (GetAddrInfoNewCmd *) inCtx; char ifName[ kInterfaceNameBufLen ]; me->gai = dnssd_getaddrinfo_create(); require_action( me->gai, exit, err = kNoResourcesErr ); dnssd_getaddrinfo_set_hostname( me->gai, me->hostname ); dnssd_getaddrinfo_set_flags( me->gai, me->flags ); dnssd_getaddrinfo_set_interface_index( me->gai, me->ifIndex ); dnssd_getaddrinfo_set_protocols( me->gai, me->protocols ); dnssd_getaddrinfo_set_use_failover( me->gai, me->useFailover ); dnssd_getaddrinfo_prohibit_encrypted_dns( me->gai, me->prohibitEncryptedDNS ); if( me->privateLogging ) { dnssd_getaddrinfo_set_log_privacy_level( me->gai, dnssd_log_privacy_level_private ); } switch( me->delegation.type ) { case kDelegationType_PID: dnssd_getaddrinfo_set_delegate_pid( me->gai, me->delegation.ident.pid ); break; case kDelegationType_UUID: dnssd_getaddrinfo_set_delegate_uuid( me->gai, me->delegation.ident.uuid ); break; case kDelegationType_AuditToken: dnssd_getaddrinfo_set_delegate_audit_token( me->gai, me->delegation.ident.auditToken ); break; case kDelegationType_None: default: break; } if( me->serviceScheme ) { dnssd_getaddrinfo_set_service_scheme( me->gai, me->serviceScheme ); } if( me->accountID ) { dnssd_getaddrinfo_set_account_id( me->gai, me->accountID ); } if( me->resolverUUID ) { dnssd_getaddrinfo_add_resolver_uuid( me->gai, *me->resolverUUID ); } dnssd_getaddrinfo_set_queue( me->gai, me->queue ); dnssd_getaddrinfo_set_result_handler( me->gai, ^( dnssd_getaddrinfo_result_t *inResultArray, size_t inResultCount ) { size_t i; require_return( me->gai ); require_return( inResultCount > 0 ); for( i = 0; i < inResultCount; ++i ) { const dnssd_getaddrinfo_result_t result = inResultArray[ i ]; const dnssd_getaddrinfo_result_type_t type = dnssd_getaddrinfo_result_get_type( result ); const char * typeStr; const char * cacheStr; if( !me->printedHeader ) { FPrintF( stdout, "%-26s Type C? IF %-30s Address\n", "Timestamp", "Hostname" ); me->printedHeader = true; } switch( type ) { case dnssd_getaddrinfo_result_type_add: typeStr = "Add"; break; case dnssd_getaddrinfo_result_type_remove: typeStr = "Rmv"; break; case dnssd_getaddrinfo_result_type_no_address: typeStr = "NoA"; break; case dnssd_getaddrinfo_result_type_expired: typeStr = "Exp"; break; case dnssd_getaddrinfo_result_type_service_binding: typeStr = "SvB"; break; default: typeStr = "???"; break; } if( type == dnssd_getaddrinfo_result_type_remove ) { cacheStr = "-"; } else { cacheStr = dnssd_getaddrinfo_result_is_from_cache( result ) ? "Y" : "N"; } FPrintF( stdout, "%{du:time} %-4s %-2s %2d %-30s %##a", NULL, typeStr, cacheStr, dnssd_getaddrinfo_result_get_interface_index( result ), dnssd_getaddrinfo_result_get_hostname( result ), dnssd_getaddrinfo_result_get_address( result ) ); if( type == dnssd_getaddrinfo_result_type_no_address ) { FPrintF( stdout, " (%s)", dnssd_negative_reason_to_string( dnssd_getaddrinfo_result_get_negative_reason( result ) ) ); } FPrintF( stdout, "\n" ); if( me->flags & kDNSServiceFlagsReturnIntermediates ) { const dnssd_cname_array_t cnames = dnssd_getaddrinfo_result_get_cnames( result ); size_t j, n; FPrintF( stdout, " Canonical Names: [" ); n = dnssd_cname_array_get_count( cnames ); for( j = 0; j < n; ++j ) { FPrintF( stdout, "%s%s", ( j == 0 ) ? "" : ", ", dnssd_cname_array_get_cname( cnames, j ) ); } FPrintF( stdout, "]\n" ); } if( me->showTracker && ( type != dnssd_getaddrinfo_result_type_remove ) ) { const char * const tracker = dnssd_getaddrinfo_result_get_tracker_hostname( result ); if( tracker ) { const char * const owner = dnssd_getaddrinfo_result_get_tracker_owner( result ); FPrintF( stdout, " Tracker: %s", tracker ); if( owner ) FPrintF( stdout, ", Owner: %s", owner ); FPrintF( stdout, "\n" ); } else { FPrintF( stdout, " (Not a known tracker)\n" ); } } if( dnssd_getaddrinfo_result_has_extended_dns_error( result ) ) { FPrintF( stdout, " Extended DNS Error: {code: %u, extra-text: '%s'}\n", dnssd_getaddrinfo_result_get_extended_dns_error_code( result ), dnssd_getaddrinfo_result_get_extended_dns_error_text( result ) ); } } if( me->oneshot ) _GetAddrInfoNewCmdStopF( me, "one-shot done" ); } ); _GetAddrInfoNewCmdRetain( me ); dispatch_group_enter( me->group ); dnssd_getaddrinfo_set_event_handler( me->gai, ^( dnssd_event_t inEvent, DNSServiceErrorType inError ) { switch( inEvent ) { case dnssd_event_invalidated: dispatch_group_leave( me->group ); _GetAddrInfoNewCmdRelease( me ); break; case dnssd_event_error: if( !me->error ) me->error = inError; _GetAddrInfoNewCmdStopF( me, "error %#m", inError ); break; default: break; } } ); // Set up signal handlers. signal( SIGINT, SIG_IGN ); me->sigint = dispatch_source_create( DISPATCH_SOURCE_TYPE_SIGNAL, (uintptr_t) SIGINT, 0, me->queue ); require_action( me->sigint, exit, err = kNoResourcesErr ); dispatch_source_set_event_handler( me->sigint, ^{ _GetAddrInfoNewCmdStopF( me, "interrupt signal" ); } ); dispatch_activate( me->sigint ); signal( SIGTERM, SIG_IGN ); me->sigterm = dispatch_source_create( DISPATCH_SOURCE_TYPE_SIGNAL, (uintptr_t) SIGTERM, 0, me->queue ); require_action( me->sigterm, exit, err = kNoResourcesErr ); dispatch_source_set_event_handler( me->sigterm, ^{ _GetAddrInfoNewCmdStopF( me, "termination signal" ); } ); dispatch_activate( me->sigterm ); // Start getaddrinfo operation. InterfaceIndexToName( me->ifIndex, ifName ); FPrintF( stdout, "Service Scheme: %s\n", me->serviceScheme ); FPrintF( stdout, "Flags: %#{flags}\n", me->flags, kDNSServiceFlagsDescriptors ); FPrintF( stdout, "Interface: %d (%s)\n", (int32_t) me->ifIndex, ifName ); FPrintF( stdout, "Protocols: %#{flags}\n", me->protocols, kDNSServiceProtocolDescriptors ); FPrintF( stdout, "Name: %s\n", me->hostname ); FPrintF( stdout, "Mode: %s\n", me->oneshot ? "one-shot" : "continuous" ); if( me->serviceScheme ) FPrintF( stdout, "Service Scheme: %s\n", me->serviceScheme ); if( me->accountID ) FPrintF( stdout, "Account ID: %s\n", me->accountID ); FPrintF( stdout, "Time limit: " ); if( me->timeLimitSecs > 0 ) FPrintF( stdout, "%d second%?c\n", me->timeLimitSecs, me->timeLimitSecs != 1, 's' ); else FPrintF( stdout, "∞\n" ); FPrintF( stdout, "Start time: %{du:time}\n", NULL ); FPrintF( stdout, "---\n" ); if( me->timeLimitSecs > 0 ) { me->timer = dispatch_source_create( DISPATCH_SOURCE_TYPE_TIMER, 0, 0, me->queue ); require_action( me->timer, exit, err = kNoResourcesErr ); dispatch_source_set_timer( me->timer, dispatch_time_seconds( me->timeLimitSecs ), DISPATCH_TIME_FOREVER, me->timeLimitSecs * ( UINT64_C_safe( kNanosecondsPerSecond ) / 20 ) ); dispatch_source_set_event_handler( me->timer, ^{ _GetAddrInfoNewCmdStopF( me, "time limit" ); } ); dispatch_activate( me->timer ); } dnssd_getaddrinfo_activate( me->gai ); err = kNoErr; exit: if( err ) _GetAddrInfoNewCmdStopF( me, "error %#m", err ); } //=========================================================================================================================== static void _GetAddrInfoNewCmdStopF( GetAddrInfoNewCmd *me, const char *inFmt, ... ) { if( !me->stopped ) { me->stopped = true; dnssd_getaddrinfo_forget( &me->gai ); dispatch_source_forget( &me->timer ); dispatch_source_forget( &me->sigint ); dispatch_source_forget( &me->sigterm ); if( inFmt ) { va_list args; check( !me->stopReason ); va_start( args, inFmt ); VASPrintF( &me->stopReason, inFmt, args ); va_end( args ); check( me->stopReason ); } dispatch_group_leave( me->group ); } } //=========================================================================================================================== static GetAddrInfoNewCmd * _GetAddrInfoNewCmdRetain( GetAddrInfoNewCmd *me ) { atomic_add_32( &me->refCount, 1 ); return( me ); } //=========================================================================================================================== static void _GetAddrInfoNewCmdRelease( GetAddrInfoNewCmd *me ) { if( atomic_add_and_fetch_32( &me->refCount, -1 ) == 0 ) { check( !me->gai ); check( !me->timer ); check( !me->sigint ); check( !me->sigterm ); dispatch_forget( &me->queue ); dispatch_forget( &me->group ); ForgetMem( &me->stopReason ); ForgetMem( &me->resolverUUID ); free( me ); } } //=========================================================================================================================== // TCPInfoCommand //=========================================================================================================================== static void TCPInfoCommand( void ) { OSStatus err; sockaddr_ip local, remote; struct tcp_info ti; err = SockAddrFromArgString( gTCPInfo_LocalAddrStr, "local IP address", &local ); require_noerr_return( err ); err = SockAddrFromArgString( gTCPInfo_RemoteAddrStr, "remote IP address", &remote ); require_noerr_return( err ); cli_require_return( local.sa.sa_family == remote.sa.sa_family, "error: IP address version mismatch.\n" ); FPrintF( stdout, "Local Address: %##a\n", &local.sa ); FPrintF( stdout, "Remote Address: %##a\n", &remote.sa ); FPrintF( stdout, "Start time: %{du:time}\n", NULL ); FPrintF( stdout, "---\n" ); if( local.sa.sa_family == AF_INET ) { err = mdns_tcpinfo_get_ipv4( ntohl( local.v4.sin_addr.s_addr ), ntohs( local.v4.sin_port ), ntohl( remote.v4.sin_addr.s_addr ), ntohs( remote.v4.sin_port ), &ti ); cli_require_noerr_return( err, "error: mdns_tcpinfo_get_ipv4: %#m\n", err ); } else { err = mdns_tcpinfo_get_ipv6( local.v6.sin6_addr.s6_addr, ntohs( local.v6.sin6_port ), remote.v6.sin6_addr.s6_addr, ntohs( remote.v6.sin6_port ), &ti ); cli_require_noerr_return( err, "error: mdns_tcpinfo_get_ipv6: %#m\n", err ); } #define _TCPInfoPrint( INFO, FIELD ) FPrintF( stdout, "%-28s%llu\n", # FIELD, (unsigned long long)( (INFO)->FIELD ) ) _TCPInfoPrint( &ti, tcpi_state ); _TCPInfoPrint( &ti, tcpi_options ); _TCPInfoPrint( &ti, tcpi_snd_wscale ); _TCPInfoPrint( &ti, tcpi_rcv_wscale ); _TCPInfoPrint( &ti, tcpi_flags ); _TCPInfoPrint( &ti, tcpi_rto ); _TCPInfoPrint( &ti, tcpi_snd_mss ); _TCPInfoPrint( &ti, tcpi_rcv_mss ); _TCPInfoPrint( &ti, tcpi_rttcur ); _TCPInfoPrint( &ti, tcpi_srtt ); _TCPInfoPrint( &ti, tcpi_rttvar ); _TCPInfoPrint( &ti, tcpi_rttbest ); _TCPInfoPrint( &ti, tcpi_snd_ssthresh ); _TCPInfoPrint( &ti, tcpi_snd_cwnd ); _TCPInfoPrint( &ti, tcpi_rcv_space ); _TCPInfoPrint( &ti, tcpi_snd_wnd ); _TCPInfoPrint( &ti, tcpi_snd_nxt ); _TCPInfoPrint( &ti, tcpi_rcv_nxt ); _TCPInfoPrint( &ti, tcpi_last_outif ); _TCPInfoPrint( &ti, tcpi_snd_sbbytes ); _TCPInfoPrint( &ti, tcpi_txpackets ); _TCPInfoPrint( &ti, tcpi_txbytes ); _TCPInfoPrint( &ti, tcpi_txretransmitbytes ); _TCPInfoPrint( &ti, tcpi_txunacked ); _TCPInfoPrint( &ti, tcpi_rxpackets ); _TCPInfoPrint( &ti, tcpi_rxbytes ); _TCPInfoPrint( &ti, tcpi_rxduplicatebytes ); _TCPInfoPrint( &ti, tcpi_rxoutoforderbytes ); _TCPInfoPrint( &ti, tcpi_snd_bw ); _TCPInfoPrint( &ti, tcpi_synrexmits ); _TCPInfoPrint( &ti, tcpi_unused1 ); _TCPInfoPrint( &ti, tcpi_unused2 ); _TCPInfoPrint( &ti, tcpi_cell_rxpackets ); _TCPInfoPrint( &ti, tcpi_cell_rxbytes ); _TCPInfoPrint( &ti, tcpi_cell_txpackets ); _TCPInfoPrint( &ti, tcpi_cell_txbytes ); _TCPInfoPrint( &ti, tcpi_wifi_rxpackets ); _TCPInfoPrint( &ti, tcpi_wifi_rxbytes ); _TCPInfoPrint( &ti, tcpi_wifi_txpackets ); _TCPInfoPrint( &ti, tcpi_wifi_txbytes ); _TCPInfoPrint( &ti, tcpi_wired_rxpackets ); _TCPInfoPrint( &ti, tcpi_wired_rxbytes ); _TCPInfoPrint( &ti, tcpi_wired_txpackets ); _TCPInfoPrint( &ti, tcpi_wired_txbytes ); FPrintF( stdout, "tcpi_connstatus = {\n" ); FPrintF( stdout, " probe_activated %u\n", ti.tcpi_connstatus.probe_activated ); FPrintF( stdout, " write_probe_failed %u\n", ti.tcpi_connstatus.write_probe_failed ); FPrintF( stdout, " read_probe_failed %u\n", ti.tcpi_connstatus.read_probe_failed ); FPrintF( stdout, " conn_probe_failed %u\n", ti.tcpi_connstatus.conn_probe_failed ); FPrintF( stdout, "}\n" ); _TCPInfoPrint( &ti, tcpi_tfo_cookie_req ); _TCPInfoPrint( &ti, tcpi_tfo_cookie_rcv ); _TCPInfoPrint( &ti, tcpi_tfo_syn_loss ); _TCPInfoPrint( &ti, tcpi_tfo_syn_data_sent ); _TCPInfoPrint( &ti, tcpi_tfo_syn_data_acked ); _TCPInfoPrint( &ti, tcpi_tfo_syn_data_rcv ); _TCPInfoPrint( &ti, tcpi_tfo_cookie_req_rcv ); _TCPInfoPrint( &ti, tcpi_tfo_cookie_sent ); _TCPInfoPrint( &ti, tcpi_tfo_cookie_invalid ); _TCPInfoPrint( &ti, tcpi_tfo_cookie_wrong ); _TCPInfoPrint( &ti, tcpi_tfo_no_cookie_rcv ); _TCPInfoPrint( &ti, tcpi_tfo_heuristics_disable ); _TCPInfoPrint( &ti, tcpi_tfo_send_blackhole ); _TCPInfoPrint( &ti, tcpi_tfo_recv_blackhole ); _TCPInfoPrint( &ti, tcpi_tfo_onebyte_proxy ); _TCPInfoPrint( &ti, tcpi_ecn_client_setup ); _TCPInfoPrint( &ti, tcpi_ecn_server_setup ); _TCPInfoPrint( &ti, tcpi_ecn_success ); _TCPInfoPrint( &ti, tcpi_ecn_lost_syn ); _TCPInfoPrint( &ti, tcpi_ecn_lost_synack ); _TCPInfoPrint( &ti, tcpi_local_peer ); _TCPInfoPrint( &ti, tcpi_if_cell ); _TCPInfoPrint( &ti, tcpi_if_wifi ); _TCPInfoPrint( &ti, tcpi_if_wired ); _TCPInfoPrint( &ti, tcpi_if_wifi_infra ); _TCPInfoPrint( &ti, tcpi_if_wifi_awdl ); _TCPInfoPrint( &ti, tcpi_snd_background ); _TCPInfoPrint( &ti, tcpi_rcv_background ); _TCPInfoPrint( &ti, tcpi_ecn_recv_ce ); _TCPInfoPrint( &ti, tcpi_ecn_recv_cwr ); _TCPInfoPrint( &ti, tcpi_rcvoopack ); _TCPInfoPrint( &ti, tcpi_pawsdrop ); _TCPInfoPrint( &ti, tcpi_sack_recovery_episode ); _TCPInfoPrint( &ti, tcpi_reordered_pkts ); _TCPInfoPrint( &ti, tcpi_dsack_sent ); _TCPInfoPrint( &ti, tcpi_dsack_recvd ); _TCPInfoPrint( &ti, tcpi_flowhash ); _TCPInfoPrint( &ti, tcpi_txretransmitpackets ); #undef _TCPInfoPrint FPrintF( stdout, "---\n" ); FPrintF( stdout, "End time: %{du:time}\n", NULL ); } //=========================================================================================================================== // XCTestCmd //=========================================================================================================================== static void XCTestCmd( void ) { int result = 0; setenv(DNSSDUTIL_XCTEST, DNSSDUTIL_XCTEST, 0); if(!run_xctest_named(gXCTest_Classname)) { result = 1; } unsetenv(DNSSDUTIL_XCTEST); exit( result ); } //=========================================================================================================================== // MultiConnectTestCmd //=========================================================================================================================== static void MultiConnectTestCmd( void ) { int result = 0; dispatch_group_t group = dispatch_group_create(); int count = gMultiConnectTest_ConnectionCount; DNSServiceRef * refs = calloc( (uint32_t)count, sizeof(DNSServiceRef) ); // Create count connections on async queue // rdar://problem/59861422 __block int goodcount = 0; for ( int i = 0; i < count; i++ ) { char label[256]; sprintf( label, "multi-connect queue %d", i+1 ); dispatch_group_async( group, dispatch_queue_create( label, DISPATCH_QUEUE_SERIAL ), ^{ DNSServiceErrorType err = DNSServiceCreateConnection( &refs[i] ); if ( err ) { fprintf( stderr, "%s Failed to create connection # %d err(%d)\n", __FUNCTION__, i, err ); } else { goodcount++; } }); } dispatch_group_wait( group, DISPATCH_TIME_FOREVER ); dispatch_release( group ); fprintf( stderr, "%s Created %d connections of %d\n", __FUNCTION__, goodcount, count ); // Free them goodcount = 0; for ( int i = 0; i < count; i++ ) { if ( refs[i] ) { DNSServiceRefDeallocate( refs[i] ); goodcount++; } } fprintf( stderr, "%s Stopped %d connections of %d\n", __FUNCTION__, goodcount, count ); result = (goodcount == count) ? 0 : 1; exit( result ); } #endif // MDNSRESPONDER_PROJECT //=========================================================================================================================== // ThreadPFNAT64Command //=========================================================================================================================== static void ThreadPFNAT64Command( void ) { OSStatus err; const char * endPtr; StringToIPAddressFlags flags; uint32_t ipv4Addr; int prefixLen; uint8_t prefixAddr[ 16 ]; FPrintF( stdout, "Setting Thread border router NAT64 PF rules with IPv6 prefix '%s' and IPv4 address '%s'\n", gThreadPFNAT64_IPv6Prefix, gThreadPFNAT64_IPv4Address ); flags = kStringToIPAddressFlagsNoPort | kStringToIPAddressFlagsNoScope; err = _StringToIPv6Address( gThreadPFNAT64_IPv6Prefix, flags, prefixAddr, NULL, NULL, &prefixLen, NULL ); cli_require_noerr_return( err, "Invalid IPv6 prefix: \"%s\".\n", gThreadPFNAT64_IPv6Prefix ); flags = kStringToIPAddressFlagsNoPort | kStringToIPAddressFlagsNoPrefix; err = _StringToIPv4Address( gThreadPFNAT64_IPv4Address, flags, &ipv4Addr, NULL, NULL, NULL, &endPtr ); cli_require_return( !err && ( *endPtr == '\0' ), "Invalid IPv4 address: \"%s\".\n", gThreadPFNAT64_IPv4Address ); err = mdns_pf_set_thread_nat64_rules( prefixAddr, prefixLen, htonl( ipv4Addr ) ); cli_require_noerr_return( err, "error: %#m\n", err ); } //=========================================================================================================================== // ThreadPFDeleteCommand //=========================================================================================================================== static void ThreadPFDeleteCommand( void ) { OSStatus err; FPrintF( stdout, "Deleting Thread border router PF rules.\n" ); err = mdns_pf_delete_thread_rules(); cli_require_noerr_return( err, "error: %#m\n", err ); } //=========================================================================================================================== // IPv4FwdEnableCommand //=========================================================================================================================== static void IPv4FwdEnableCommand( void ) { OSStatus err; FPrintF( stdout, "Enabling IPv4 forwarding.\n" ); err = mdns_system_set_ipv4_forwarding( true ); cli_require_noerr_return( err, "error: %#m\n", err ); } //=========================================================================================================================== // IPv4FwdDisableCommand //=========================================================================================================================== static void IPv4FwdDisableCommand( void ) { OSStatus err; FPrintF( stdout, "Disabling IPv4 forwarding.\n" ); err = mdns_system_set_ipv4_forwarding( false ); cli_require_noerr_return( err, "error: %#m\n", err ); } //=========================================================================================================================== // IPv6FwdEnableCommand //=========================================================================================================================== static void IPv6FwdEnableCommand( void ) { OSStatus err; FPrintF( stdout, "Enabling IPv6 forwarding.\n" ); err = mdns_system_set_ipv6_forwarding( true ); cli_require_noerr_return( err, "error: %#m\n", err ); } //=========================================================================================================================== // IPv6FwdDisableCommand //=========================================================================================================================== static void IPv6FwdDisableCommand( void ) { OSStatus err; FPrintF( stdout, "Disabling IPv6 forwarding.\n" ); err = mdns_system_set_ipv6_forwarding( false ); cli_require_noerr_return( err, "error: %#m\n", err ); } //=========================================================================================================================== // PrintCommand //=========================================================================================================================== static void PrintCommand( void ) { OSStatus err; const char * inputPath; uint8_t * msgPtr = NULL; size_t msgLen; char * msgStr = NULL; inputPath = CLINextArgOrNULL(); if( !inputPath || ( strcmp( inputPath, "-" ) == 0 ) ) { err = CopyFileDataByFile( stdin, (char **) &msgPtr, &msgLen ); require_noerr_action_quiet( err, exit, FPrintF( stderr, "error: Failed to copy DNS message from stdin: %#m\n", err ) ); } else { err = CopyFileDataByPath( inputPath, (char **) &msgPtr, &msgLen ); require_noerr_action_quiet( err, exit, FPrintF( stderr, "error: Failed to copy DNS message from '%s': %#m\n", inputPath, err ) ); } require_action_quiet( msgLen <= UINT16_MAX, exit, err = kSizeErr; FPrintF( stderr, "error: DNS message size is greater than the supported maximum (%zu > %d bytes)\n", msgLen, UINT16_MAX ) ); err = DNSMessageToString( msgPtr, msgLen, kDNSMessageToStringFlag_Null, &msgStr ); if( !err ) { FPrintF( stdout, "%s\n", msgStr ); } else { FPrintF( stderr, "error: Failed to pretty print DNS message: %#m\n", err ); FPrintF( stdout, "%.1H\n", msgPtr, (int) msgLen, (int) msgLen ); err = kNoErr; } exit: ForgetMem( &msgPtr ); ForgetMem( &msgStr ); gExitCode = err ? 1 : 0; } //=========================================================================================================================== // WiFiOnCommand //=========================================================================================================================== static void WiFiOnCommand( void ) { OSStatus err; #if( TARGET_OS_OSX ) err = systemf( NULL, "/usr/sbin/networksetup -setairportpower Wi-Fi on" ); #else err = systemf( NULL, "/usr/local/bin/mobilewifitool -- manager power 1" ); #endif cli_require_noerr_return( err, "error: %#m\n", err ); } //=========================================================================================================================== // WiFiOffCommand //=========================================================================================================================== static void WiFiOffCommand( void ) { OSStatus err; #if( TARGET_OS_OSX ) err = systemf( NULL, "/usr/sbin/networksetup -setairportpower Wi-Fi off" ); #else err = systemf( NULL, "/usr/local/bin/mobilewifitool -- manager power 0" ); #endif cli_require_noerr_return( err, "error: %#m\n", err ); } //=========================================================================================================================== // DiscoveryProxyCommand //=========================================================================================================================== static void _DiscoveryProxyCmdSignalHandler( void *inContext ); static void DiscoveryProxyCommand( void ) { OSStatus err; mrc_discovery_proxy_parameters_t params = NULL; mrc_discovery_proxy_t proxy = NULL; dispatch_source_t sigIntSource = NULL; dispatch_source_t sigTermSource = NULL; params = mrc_discovery_proxy_parameters_create(); require_action( params, exit, err = kNoResourcesErr ); uint32_t ifIndex; err = InterfaceIndexFromArgString( gInterface, &ifIndex ); require_noerr_quiet( err, exit ); mrc_discovery_proxy_parameters_set_interface( params, ifIndex ); for( size_t i = 0; i < gDiscoveryProxy_ServerAddrCount; ++i ) { sockaddr_ip sip; err = SockAddrFromArgString( gDiscoveryProxy_ServerAddrs[ i ], "server address", &sip ); require_noerr_quiet( err, exit ); switch( sip.sa.sa_family ) { case AF_INET: { const struct sockaddr_in * const sin = &sip.v4; err = mrc_discovery_proxy_parameters_add_server_ipv4_address( params, ntohl( sin->sin_addr.s_addr ), ntohs( sin->sin_port ) ); require_noerr_quiet( err, exit ); break; } case AF_INET6: { const struct sockaddr_in6 * const sin6 = &sip.v6; err = mrc_discovery_proxy_parameters_add_server_ipv6_address( params, sin6->sin6_addr.s6_addr, ntohs( sin6->sin6_port ), sin6->sin6_scope_id ); require_noerr_quiet( err, exit ); break; } } } for( size_t i = 0; i < gDiscoveryProxy_MatchDomainCount; ++i ) { err = mrc_discovery_proxy_parameters_add_match_domain( params, gDiscoveryProxy_MatchDomains[ i ] ); require_noerr_quiet( err, exit ); } for( size_t i = 0; i < gDiscoveryProxy_CertificateCount; ++i ) { uint8_t * certPtr; size_t certLen; err = HexToDataCopy( gDiscoveryProxy_Certificates[ i ], kSizeCString, kHexToData_DefaultFlags, &certPtr, &certLen, NULL ); require_noerr_action( err, exit, FPrintF( stderr, "error: Failed to parse identity reference hex string: '%s'\n", gDiscoveryProxy_Certificates[ i ] ) ); mrc_discovery_proxy_parameters_add_server_certificate( params, certPtr, certLen ); ForgetMem( &certPtr ); } proxy = mrc_discovery_proxy_create( params ); mrc_forget( ¶ms ); require_action_quiet( proxy, exit, err = kNoResourcesErr ); mrc_discovery_proxy_set_queue( proxy, dispatch_get_main_queue() ); mrc_discovery_proxy_set_event_handler( proxy, ^( const mrc_discovery_proxy_event_t inEvent, const OSStatus inError ) { switch( inEvent ) { case mrc_discovery_proxy_event_started: { FPrintF( stdout, "%{du:time} Discovery Proxy was started\n", NULL ); break; } case mrc_discovery_proxy_event_interruption: { FPrintF( stdout, "%{du:time} Discovery Proxy was interrupted\n", NULL ); break; } case mrc_discovery_proxy_event_invalidation: { mrc_release( proxy ); FPrintF( stdout, "---\n" ); FPrintF( stdout, "Error: %#m\n", inError ); FPrintF( stdout, "End time: %{du:time}\n", NULL ); exit( 0 ); } case mrc_discovery_proxy_event_none: break; } } ); signal( SIGINT, SIG_IGN ); err = DispatchSignalSourceCreate( SIGINT, dispatch_get_main_queue(), _DiscoveryProxyCmdSignalHandler, proxy, &sigIntSource ); require_noerr( err, exit ); dispatch_activate( sigIntSource ); signal( SIGTERM, SIG_IGN ); err = DispatchSignalSourceCreate( SIGTERM, dispatch_get_main_queue(), _DiscoveryProxyCmdSignalHandler, proxy, &sigTermSource ); require_noerr( err, exit ); dispatch_activate( sigTermSource ); FPrintF( stdout, "Discovery Proxy: %@\n", proxy ); FPrintF( stdout, "Start time: %{du:time}\n", NULL ); FPrintF( stdout, "---\n" ); mrc_discovery_proxy_activate( proxy ); dispatch_main(); exit: if( err ) ErrQuit( 1, "error: %#m\n", err ); } static void _DiscoveryProxyCmdSignalHandler( void *inContext ) { mrc_discovery_proxy_invalidate( (mrc_discovery_proxy_t) inContext ); } //=========================================================================================================================== // CachedLocalRecordsCommand //=========================================================================================================================== static void CachedLocalRecordsCommand( void ) { OSStatus err; dispatch_semaphore_t doneSem = NULL; mrc_cached_local_records_inquiry_t inquiry = NULL; dispatch_queue_t queue = dispatch_queue_create( "com.apple.dnssdutil.cached-local-records-command", DISPATCH_QUEUE_SERIAL ); require_action( queue, exit, err = kNoResourcesErr ); doneSem = dispatch_semaphore_create( 0 ); require_action( doneSem, exit, err = kNoResourcesErr ); inquiry = mrc_cached_local_records_inquiry_create(); require_action( inquiry, exit, err = kNoResourcesErr ); mrc_cached_local_records_inquiry_set_queue( inquiry, queue ); mrc_cached_local_records_inquiry_set_result_handler( inquiry, ^( const xpc_object_t inRecordInfo, const OSStatus inError ) { if( inError ) { FPrintF( stderr, "error: %#m\n", inError ); } const size_t recordInfoCount = inRecordInfo ? xpc_array_get_count( inRecordInfo ) : 0; FPrintF( stdout, "Record count: %zu\n", recordInfoCount ); for( size_t i = 0; i < recordInfoCount; ++i ) { const xpc_object_t dict = xpc_array_get_dictionary( inRecordInfo, i ); if( !dict ) { continue; } FPrintF( stdout, "Record %zu of %zu:\n", i + 1, recordInfoCount ); const char * const name = xpc_dictionary_get_string( dict, mrc_cached_local_record_key_name ); if( name ) { FPrintF( stdout, "\tname: %s\n", name ); } const char * const first_label = xpc_dictionary_get_string( dict, mrc_cached_local_record_key_first_label ); if( first_label ) { FPrintF( stdout, "\tfirst label: %s\n", first_label ); } const char * const sourceAddr = xpc_dictionary_get_string( dict, mrc_cached_local_record_key_source_address ); if( sourceAddr ) { FPrintF( stdout, "\tsource address: %s\n", sourceAddr ); } const char * const rdata = xpc_dictionary_get_string( dict, mrc_cached_local_record_key_rdata ); if( rdata ) { FPrintF( stdout, "\trdata: %s\n", rdata ); } FPrintF( stdout, "\n" ); } dispatch_semaphore_signal( doneSem ); } ); mrc_cached_local_records_inquiry_activate( inquiry ); // Wait for the inquiry to complete. // Note: We intentionally use a semaphore that will be signaled by the asynchronous handler. The analyzer considers // this an antipattern, which is OK for the purposes of this internal test tool. Actual well-written production code // would usually not have to emulate this type of synchronous behavior. #if( !COMPILER_CLANG_ANALYZER ) dispatch_semaphore_wait( doneSem, DISPATCH_TIME_FOREVER ); #endif err = kNoErr; exit: dispatch_forget( &queue ); dispatch_forget( &doneSem ); mrc_forget( &inquiry ); gExitCode = err ? 1 : 0; } //=========================================================================================================================== // DaemonVersionCmd //=========================================================================================================================== static void DaemonVersionCmd( void ) { OSStatus err; uint32_t size, version; char strBuf[ 16 ]; size = (uint32_t) sizeof( version ); err = DNSServiceGetProperty( kDNSServiceProperty_DaemonVersion, &version, &size ); require_noerr( err, exit ); FPrintF( stdout, "Daemon version: %s\n", _DNSSDSourceVersionToCString( version, strBuf, sizeof( strBuf ) ) ); exit: if( err ) exit( 1 ); } //=========================================================================================================================== // Exit //=========================================================================================================================== static void Exit( void *inContext ) { const char * const reason = (const char *) inContext; FPrintF( stdout, "---\n" ); FPrintF( stdout, "End time: %{du:time}\n", NULL ); if( reason ) FPrintF( stdout, "End reason: %s\n", reason ); exit( gExitCode ); } //=========================================================================================================================== // _PrintFExtensionHandler_Timestamp //=========================================================================================================================== static int _PrintFExtensionHandler_Timestamp( PrintFContext * inContext, PrintFFormat * inFormat, PrintFVAList * inArgs, void * inUserContext ) { struct timeval now; const struct timeval * tv; struct tm * localTime; size_t len; int n; char dateTimeStr[ 32 ]; Unused( inUserContext ); tv = va_arg( inArgs->args, const struct timeval * ); require_action_quiet( !inFormat->suppress, exit, n = 0 ); if( !tv ) { gettimeofday( &now, NULL ); tv = &now; } localTime = localtime( &tv->tv_sec ); len = strftime( dateTimeStr, sizeof( dateTimeStr ), "%Y-%m-%d %H:%M:%S", localTime ); if( len == 0 ) dateTimeStr[ 0 ] = '\0'; n = PrintFCore( inContext, "%s.%06u", dateTimeStr, (unsigned int) tv->tv_usec ); exit: return( n ); } //=========================================================================================================================== // _PrintFExtensionHandler_DNSMessage //=========================================================================================================================== static int _PrintFExtensionHandler_DNSMessageCommon( PrintFContext * inContext, PrintFFormat * inFormat, PrintFVAList * inArgs, void * inUserContext, Boolean inRawRData ); static int _PrintFExtensionHandler_DNSMessage( PrintFContext * inContext, PrintFFormat * inFormat, PrintFVAList * inArgs, void * inUserContext ) { return( _PrintFExtensionHandler_DNSMessageCommon( inContext, inFormat, inArgs, inUserContext, false ) ); } static int _PrintFExtensionHandler_DNSMessageCommon( PrintFContext * inContext, PrintFFormat * inFormat, PrintFVAList * inArgs, void * inUserContext, Boolean inRawRData ) { OSStatus err; const void * msgPtr; size_t msgLen; char * msgStr; DNSMessageToStringFlags flags; int n; Unused( inUserContext ); msgPtr = va_arg( inArgs->args, const void * ); msgLen = va_arg( inArgs->args, size_t ); require_action_quiet( !inFormat->suppress, exit, n = 0 ); flags = kDNSMessageToStringFlags_None; if( inRawRData ) flags |= kDNSMessageToStringFlag_RawRData; if( inFormat->altForm == 1 ) flags |= kDNSMessageToStringFlag_MDNS; if( inFormat->precision == 1 ) flags |= kDNSMessageToStringFlag_OneLine; err = DNSMessageToString( msgPtr, msgLen, flags, &msgStr ); if( !err ) { n = PrintFCore( inContext, "%*{text}", inFormat->fieldWidth, msgStr, kSizeCString ); free( msgStr ); } else { n = PrintFCore( inContext, "%*.1H", inFormat->fieldWidth, msgPtr, (int) msgLen, (int) msgLen ); } exit: return( n ); } //=========================================================================================================================== // _PrintFExtensionHandler_RawDNSMessage //=========================================================================================================================== static int _PrintFExtensionHandler_RawDNSMessage( PrintFContext * inContext, PrintFFormat * inFormat, PrintFVAList * inArgs, void * inUserContext ) { return( _PrintFExtensionHandler_DNSMessageCommon( inContext, inFormat, inArgs, inUserContext, true ) ); } //=========================================================================================================================== // _PrintFExtensionHandler_CallbackFlags //=========================================================================================================================== static int _PrintFExtensionHandler_CallbackFlags( PrintFContext * inContext, PrintFFormat * inFormat, PrintFVAList * inArgs, void * inUserContext ) { DNSServiceFlags flags; int n; char dnssecResult; Unused( inUserContext ); flags = va_arg( inArgs->args, DNSServiceFlags ); require_action_quiet( !inFormat->suppress, exit, n = 0 ); if( ( flags & kDNSServiceFlagsValidate ) == 0 ) dnssecResult = ' '; else if( ( flags & kDNSServiceFlagsSecure ) == kDNSServiceFlagsSecure ) dnssecResult = 'S'; else if( ( flags & kDNSServiceFlagsInsecure ) == kDNSServiceFlagsInsecure ) dnssecResult = 'I'; else dnssecResult = 'E'; n = PrintFCore( inContext, "%08X %s%c %c%c%c", flags, DNSServiceFlagsToAddRmvStr( flags ), ( flags & kDNSServiceFlagsMoreComing ) ? '+' : ' ', ( flags & kDNSServiceFlagAnsweredFromCache ) ? 'C' : ' ', ( flags & kDNSServiceFlagsExpiredAnswer ) ? '*' : ' ', dnssecResult ); exit: return( n ); } //=========================================================================================================================== // _PrintFExtensionHandler_DNSRecordData //=========================================================================================================================== static int _PrintFExtensionHandler_DNSRecordData( PrintFContext * inContext, PrintFFormat * inFormat, PrintFVAList * inArgs, void * inUserContext ) { const void * rdataPtr; int recordType, n, fieldWidth; unsigned int rdataLen; Unused( inUserContext ); recordType = va_arg( inArgs->args, int ); rdataPtr = va_arg( inArgs->args, const void * ); rdataLen = va_arg( inArgs->args, unsigned int ); require_action_quiet( !inFormat->suppress, exit, n = 0 ); check( inFormat->fieldWidth < INT_MAX ); fieldWidth = inFormat->leftJustify ? -( (int) inFormat->fieldWidth ) : ( (int) inFormat->fieldWidth ); if( rdataLen > 0 ) { char * rdataStr = NULL; DNSRecordDataToString( rdataPtr, rdataLen, recordType, &rdataStr ); if( rdataStr ) { n = PrintFCore( inContext, "%*s", fieldWidth, rdataStr ); free( rdataStr ); } else { n = PrintFCore( inContext, "%*H", fieldWidth, rdataPtr, rdataLen, rdataLen ); } } else { n = PrintFCore( inContext, "%*s", fieldWidth, "<< ZERO-LENGTH RDATA >>" ); } exit: return( n ); } //=========================================================================================================================== // _PrintFExtensionHandler_DomainName //=========================================================================================================================== static int _PrintFExtensionHandler_DomainName( PrintFContext * inContext, PrintFFormat * inFormat, PrintFVAList * inArgs, void * inUserContext ) { OSStatus err; const uint8_t * namePtr; int n, fieldWidth; char nameStr[ kDNSServiceMaxDomainName ]; Unused( inUserContext ); namePtr = va_arg( inArgs->args, const uint8_t * ); require_action_quiet( !inFormat->suppress, exit, n = 0 ); check( inFormat->fieldWidth < INT_MAX ); fieldWidth = inFormat->leftJustify ? -( (int) inFormat->fieldWidth ) : ( (int) inFormat->fieldWidth ); err = DomainNameToString( namePtr, NULL, nameStr, NULL ); check_noerr( err ); if( !err ) { n = PrintFCore( inContext, "%*s", fieldWidth, nameStr ); } else { n = PrintFCore( inContext, "%*s", fieldWidth, "<< ERROR: domain name conversion failed >>" ); } exit: return( n ); } //=========================================================================================================================== // GetDNSSDFlagsFromOpts //=========================================================================================================================== static DNSServiceFlags GetDNSSDFlagsFromOpts( void ) { DNSServiceFlags flags; flags = (DNSServiceFlags) gDNSSDFlags; if( flags & kDNSServiceFlagsShareConnection ) { FPrintF( stderr, "*** Warning: kDNSServiceFlagsShareConnection (0x%X) is explicitly set in flag parameters.\n", kDNSServiceFlagsShareConnection ); } if( gDNSSDFlag_AllowExpiredAnswers ) flags |= kDNSServiceFlagsAllowExpiredAnswers; if( gDNSSDFlag_BrowseDomains ) flags |= kDNSServiceFlagsBrowseDomains; if( gDNSSDFlag_DenyCellular ) flags |= kDNSServiceFlagsDenyCellular; if( gDNSSDFlag_DenyConstrained ) flags |= kDNSServiceFlagsDenyConstrained; if( gDNSSDFlag_DenyExpensive ) flags |= kDNSServiceFlagsDenyExpensive; if( gDNSSDFlag_ForceMulticast ) flags |= kDNSServiceFlagsForceMulticast; if( gDNSSDFlag_IncludeAWDL ) flags |= kDNSServiceFlagsIncludeAWDL; if( gDNSSDFlag_KnownUnique ) flags |= kDNSServiceFlagsKnownUnique; if( gDNSSDFlag_NoAutoRename ) flags |= kDNSServiceFlagsNoAutoRename; if( gDNSSDFlag_PathEvaluationDone ) flags |= kDNSServiceFlagsPathEvaluationDone; if( gDNSSDFlag_RegistrationDomains ) flags |= kDNSServiceFlagsRegistrationDomains; if( gDNSSDFlag_ReturnIntermediates ) flags |= kDNSServiceFlagsReturnIntermediates; if( gDNSSDFlag_Shared ) flags |= kDNSServiceFlagsShared; if( gDNSSDFlag_SuppressUnusable ) flags |= kDNSServiceFlagsSuppressUnusable; if( gDNSSDFlag_Timeout ) flags |= kDNSServiceFlagsTimeout; if( gDNSSDFlag_UnicastResponse ) flags |= kDNSServiceFlagsUnicastResponse; if( gDNSSDFlag_Unique ) flags |= kDNSServiceFlagsUnique; if( gDNSSDFlag_WakeOnResolve ) flags |= kDNSServiceFlagsWakeOnResolve; if( gDNSSDFlag_EnableDNSSEC ) flags |= kDNSServiceFlagsEnableDNSSEC; return( flags ); } //=========================================================================================================================== // CreateConnectionFromArgString //=========================================================================================================================== static OSStatus CreateConnectionFromArgString( const char * inString, dispatch_queue_t inQueue, DNSServiceRef * outSDRef, ConnectionDesc * outDesc ) { OSStatus err; DNSServiceRef sdRef = NULL; ConnectionType type; int32_t pid = -1; // Initializing because the analyzer claims pid may be used uninitialized. uint8_t uuid[ 16 ]; if( strcasecmp( inString, kConnectionArg_Normal ) == 0 ) { err = DNSServiceCreateConnection( &sdRef ); require_noerr( err, exit ); type = kConnectionType_Normal; } else if( stricmp_prefix( inString, kConnectionArgPrefix_PID ) == 0 ) { const char * const pidStr = inString + sizeof_string( kConnectionArgPrefix_PID ); err = StringToInt32( pidStr, &pid ); if( err ) { FPrintF( stderr, "Invalid delegate connection PID value: %s\n", pidStr ); err = kParamErr; goto exit; } memset( uuid, 0, sizeof( uuid ) ); err = DNSServiceCreateDelegateConnection( &sdRef, pid, uuid ); if( err ) { FPrintF( stderr, "DNSServiceCreateDelegateConnection() returned %#m for PID %d\n", err, pid ); goto exit; } type = kConnectionType_DelegatePID; } else if( stricmp_prefix( inString, kConnectionArgPrefix_UUID ) == 0 ) { const char * const uuidStr = inString + sizeof_string( kConnectionArgPrefix_UUID ); check_compile_time_code( sizeof( uuid ) == sizeof( uuid_t ) ); err = StringToUUID( uuidStr, kSizeCString, false, uuid ); if( err ) { FPrintF( stderr, "Invalid delegate connection UUID value: %s\n", uuidStr ); err = kParamErr; goto exit; } err = DNSServiceCreateDelegateConnection( &sdRef, 0, uuid ); if( err ) { FPrintF( stderr, "DNSServiceCreateDelegateConnection() returned %#m for UUID %#U\n", err, uuid ); goto exit; } type = kConnectionType_DelegateUUID; } else { FPrintF( stderr, "Unrecognized connection string \"%s\".\n", inString ); err = kParamErr; goto exit; } err = DNSServiceSetDispatchQueue( sdRef, inQueue ); require_noerr( err, exit ); *outSDRef = sdRef; if( outDesc ) { outDesc->type = type; if( type == kConnectionType_DelegatePID ) outDesc->delegate.pid = pid; else if( type == kConnectionType_DelegateUUID ) memcpy( outDesc->delegate.uuid, uuid, 16 ); } sdRef = NULL; exit: if( sdRef ) DNSServiceRefDeallocate( sdRef ); return( err ); } //=========================================================================================================================== // InterfaceIndexFromArgString //=========================================================================================================================== static OSStatus InterfaceIndexFromArgString( const char *inString, uint32_t *outIndex ) { OSStatus err; uint32_t ifIndex; if( inString ) { ifIndex = if_nametoindex( inString ); if( ifIndex == 0 ) { err = StringToUInt32( inString, &ifIndex ); if( err ) { FPrintF( stderr, "error: Invalid interface value: %s\n", inString ); err = kParamErr; goto exit; } } } else { ifIndex = 0; } *outIndex = ifIndex; err = kNoErr; exit: return( err ); } //=========================================================================================================================== // RecordDataFromArgString //=========================================================================================================================== static OSStatus RecordDataFromArgString( const char *inString, uint8_t **outDataPtr, size_t *outDataLen ) { OSStatus err; uint8_t * dataPtr = NULL; size_t dataLen; if( 0 ) {} // Domain name else if( stricmp_prefix( inString, kRDataArgPrefix_Domain ) == 0 ) { const char * const str = inString + sizeof_string( kRDataArgPrefix_Domain ); err = StringToDomainName( str, &dataPtr, &dataLen ); require_noerr_quiet( err, exit ); } // File path else if( stricmp_prefix( inString, kRDataArgPrefix_File ) == 0 ) { const char * const path = inString + sizeof_string( kRDataArgPrefix_File ); err = CopyFileDataByPath( path, (char **) &dataPtr, &dataLen ); require_noerr( err, exit ); require_action( dataLen <= kDNSRecordDataLengthMax, exit, err = kSizeErr ); } // Hexadecimal string else if( stricmp_prefix( inString, kRDataArgPrefix_HexString ) == 0 ) { const char * const str = inString + sizeof_string( kRDataArgPrefix_HexString ); err = HexToDataCopy( str, kSizeCString, kHexToData_DefaultFlags, &dataPtr, &dataLen, NULL ); require_noerr( err, exit ); require_action( dataLen <= kDNSRecordDataLengthMax, exit, err = kSizeErr ); } // IPv4 address string else if( stricmp_prefix( inString, kRDataArgPrefix_IPv4 ) == 0 ) { const char * const str = inString + sizeof_string( kRDataArgPrefix_IPv4 ); err = StringToARecordData( str, &dataPtr, &dataLen ); require_noerr_quiet( err, exit ); } // IPv6 address string else if( stricmp_prefix( inString, kRDataArgPrefix_IPv6 ) == 0 ) { const char * const str = inString + sizeof_string( kRDataArgPrefix_IPv6 ); err = StringToAAAARecordData( str, &dataPtr, &dataLen ); require_noerr_quiet( err, exit ); } // SRV record else if( stricmp_prefix( inString, kRDataArgPrefix_SRV ) == 0 ) { const char * const str = inString + sizeof_string( kRDataArgPrefix_SRV ); err = CreateSRVRecordDataFromString( str, &dataPtr, &dataLen ); require_noerr( err, exit ); } // String with escaped hex and octal bytes else if( stricmp_prefix( inString, kRDataArgPrefix_String ) == 0 ) { const char * const str = inString + sizeof_string( kRDataArgPrefix_String ); const char * const end = str + strlen( str ); size_t copiedLen; size_t totalLen; Boolean success; if( str < end ) { success = _ParseQuotedEscapedString( str, end, "", NULL, 0, NULL, &totalLen, NULL ); require_action( success, exit, err = kParamErr ); require_action( totalLen <= kDNSRecordDataLengthMax, exit, err = kSizeErr ); dataLen = totalLen; dataPtr = (uint8_t *) malloc( dataLen ); require_action( dataPtr, exit, err = kNoMemoryErr ); success = _ParseQuotedEscapedString( str, end, "", (char *) dataPtr, dataLen, &copiedLen, NULL, NULL ); require_action( success, exit, err = kParamErr ); check( copiedLen == dataLen ); } else { dataPtr = NULL; dataLen = 0; } } // TXT record else if( stricmp_prefix( inString, kRDataArgPrefix_TXT ) == 0 ) { const char * const str = inString + sizeof_string( kRDataArgPrefix_TXT ); err = CreateTXTRecordDataFromString( str, ',', &dataPtr, &dataLen ); require_noerr( err, exit ); } // Unrecognized format else { FPrintF( stderr, "Unrecognized record data string \"%s\".\n", inString ); err = kParamErr; goto exit; } err = kNoErr; *outDataLen = dataLen; *outDataPtr = dataPtr; dataPtr = NULL; exit: FreeNullSafe( dataPtr ); return( err ); } //=========================================================================================================================== // RecordTypeFromArgString //=========================================================================================================================== static OSStatus RecordTypeFromArgString( const char *inString, uint16_t *outValue ) { OSStatus err; uint16_t value; value = DNSRecordTypeStringToValue( inString ); if( value == 0 ) { int32_t i32; err = StringToInt32( inString, &i32 ); require_noerr_quiet( err, exit ); require_action_quiet( ( i32 >= 0 ) && ( i32 <= UINT16_MAX ), exit, err = kParamErr ); value = (uint16_t) i32; } *outValue = value; err = kNoErr; exit: return( err ); } //=========================================================================================================================== // RecordClassFromArgString //=========================================================================================================================== static OSStatus RecordClassFromArgString( const char *inString, uint16_t *outValue ) { OSStatus err; int32_t i32; if( strcasecmp( inString, "IN" ) == 0 ) { *outValue = kDNSServiceClass_IN; err = kNoErr; goto exit; } err = StringToInt32( inString, &i32 ); require_noerr_quiet( err, exit ); require_action_quiet( ( i32 >= 0 ) && ( i32 <= UINT16_MAX ), exit, err = kParamErr ); *outValue = (uint16_t) i32; exit: return( err ); } //=========================================================================================================================== // SockAddrFromArgString //=========================================================================================================================== static OSStatus SockAddrFromArgString( const char *inArgStr, const char *inArgName, sockaddr_ip *outSA ) { OSStatus err; sockaddr_ip sip; err = StringToSockAddr( inArgStr, &sip, sizeof( sip ), NULL ); if( !err && ( ( sip.sa.sa_family == AF_INET ) || ( sip.sa.sa_family == AF_INET6 ) ) ) { if( outSA ) SockAddrCopy( &sip, outSA ); } else { FPrintF( stderr, "error: Invalid %s: '%s'\n", inArgName, inArgStr ); err = kParamErr; } return( err ); } //=========================================================================================================================== // InterfaceIndexToName //=========================================================================================================================== static char * InterfaceIndexToName( uint32_t inIfIndex, char inNameBuf[ kInterfaceNameBufLen ] ) { switch( inIfIndex ) { case kDNSServiceInterfaceIndexAny: strlcpy( inNameBuf, "Any", kInterfaceNameBufLen ); break; case kDNSServiceInterfaceIndexLocalOnly: strlcpy( inNameBuf, "LocalOnly", kInterfaceNameBufLen ); break; case kDNSServiceInterfaceIndexUnicast: strlcpy( inNameBuf, "Unicast", kInterfaceNameBufLen ); break; case kDNSServiceInterfaceIndexP2P: strlcpy( inNameBuf, "P2P", kInterfaceNameBufLen ); break; #if( defined( kDNSServiceInterfaceIndexBLE ) ) case kDNSServiceInterfaceIndexBLE: strlcpy( inNameBuf, "BLE", kInterfaceNameBufLen ); break; #endif default: { const char * name; name = if_indextoname( inIfIndex, inNameBuf ); if( !name ) strlcpy( inNameBuf, "NO NAME", kInterfaceNameBufLen ); break; } } return( inNameBuf ); } //=========================================================================================================================== // RecordTypeToString //=========================================================================================================================== static const char * RecordTypeToString( int inValue ) { const char * string; string = DNSRecordTypeValueToString( inValue ); if( !string ) string = "???"; return( string ); } #if( MDNSRESPONDER_PROJECT ) //=========================================================================================================================== // RecordClassToString //=========================================================================================================================== static const char * RecordClassToString( int inValue ) { return( ( inValue == kDNSServiceClass_IN ) ? "IN" : "???" ); } #endif //=========================================================================================================================== // WriteDNSQueryMessage //=========================================================================================================================== static OSStatus WriteDNSQueryMessage( uint8_t inMsg[ kDNSQueryMessageMaxLen ], uint16_t inMsgID, uint16_t inFlags, const char * inQName, uint16_t inQType, uint16_t inQClass, size_t * outMsgLen ) { OSStatus err; uint8_t qname[ kDomainNameLengthMax ]; err = DomainNameFromString( qname, inQName, NULL ); require_noerr_quiet( err, exit ); err = DNSMessageWriteQuery( inMsgID, inFlags, qname, inQType, inQClass, inMsg, outMsgLen ); require_noerr_quiet( err, exit ); exit: return( err ); } //=========================================================================================================================== // DispatchSignalSourceCreate //=========================================================================================================================== static OSStatus DispatchSignalSourceCreate( int inSignal, dispatch_queue_t inQueue, DispatchHandler inEventHandler, void * inContext, dispatch_source_t * outSource ) { OSStatus err; dispatch_source_t source; source = dispatch_source_create( DISPATCH_SOURCE_TYPE_SIGNAL, (uintptr_t) inSignal, 0, inQueue ); require_action( source, exit, err = kUnknownErr ); dispatch_set_context( source, inContext ); dispatch_source_set_event_handler_f( source, inEventHandler ); *outSource = source; err = kNoErr; exit: return( err ); } //=========================================================================================================================== // DispatchSocketSourceCreate //=========================================================================================================================== static OSStatus DispatchSocketSourceCreate( SocketRef inSock, dispatch_source_type_t inType, dispatch_queue_t inQueue, DispatchHandler inEventHandler, DispatchHandler inCancelHandler, void * inContext, dispatch_source_t * outSource ) { OSStatus err; dispatch_source_t source; source = dispatch_source_create( inType, (uintptr_t) inSock, 0, inQueue ? inQueue : dispatch_get_main_queue() ); require_action( source, exit, err = kNoResourcesErr ); dispatch_set_context( source, inContext ); dispatch_source_set_event_handler_f( source, inEventHandler ); dispatch_source_set_cancel_handler_f( source, inCancelHandler ); *outSource = source; err = kNoErr; exit: return( err ); } //=========================================================================================================================== // DispatchTimerCreate //=========================================================================================================================== static OSStatus DispatchTimerCreate( dispatch_time_t inStart, uint64_t inIntervalNs, uint64_t inLeewayNs, dispatch_queue_t inQueue, DispatchHandler inEventHandler, DispatchHandler inCancelHandler, void * inContext, dispatch_source_t * outTimer ) { OSStatus err; dispatch_source_t timer; timer = dispatch_source_create( DISPATCH_SOURCE_TYPE_TIMER, 0, 0, inQueue ? inQueue : dispatch_get_main_queue() ); require_action( timer, exit, err = kNoResourcesErr ); dispatch_source_set_timer( timer, inStart, inIntervalNs, inLeewayNs ); dispatch_set_context( timer, inContext ); dispatch_source_set_event_handler_f( timer, inEventHandler ); dispatch_source_set_cancel_handler_f( timer, inCancelHandler ); *outTimer = timer; err = kNoErr; exit: return( err ); } #if( TARGET_OS_DARWIN ) //=========================================================================================================================== // DispatchProcessMonitorCreate //=========================================================================================================================== static OSStatus DispatchProcessMonitorCreate( pid_t inPID, unsigned long inFlags, dispatch_queue_t inQueue, DispatchHandler inEventHandler, DispatchHandler inCancelHandler, void * inContext, dispatch_source_t * outMonitor ) { OSStatus err; dispatch_source_t monitor; monitor = dispatch_source_create( DISPATCH_SOURCE_TYPE_PROC, (uintptr_t) inPID, inFlags, inQueue ? inQueue : dispatch_get_main_queue() ); require_action( monitor, exit, err = kUnknownErr ); dispatch_set_context( monitor, inContext ); dispatch_source_set_event_handler_f( monitor, inEventHandler ); dispatch_source_set_cancel_handler_f( monitor, inCancelHandler ); *outMonitor = monitor; err = kNoErr; exit: return( err ); } #endif //=========================================================================================================================== // ServiceTypeDescription //=========================================================================================================================== typedef struct { const char * name; // Name of the service type in two-label "_service._proto" format. const char * description; // Description of the service type. } ServiceType; // A Non-comprehensive table of DNS-SD service types static const ServiceType kServiceTypes[] = { { "_acp-sync._tcp", "AirPort Base Station Sync" }, { "_adisk._tcp", "Automatic Disk Discovery" }, { "_afpovertcp._tcp", "Apple File Sharing" }, { "_airdrop._tcp", "AirDrop" }, { "_airplay._tcp", "AirPlay" }, { "_airport._tcp", "AirPort Base Station" }, { "_daap._tcp", "Digital Audio Access Protocol (iTunes)" }, { "_eppc._tcp", "Remote AppleEvents" }, { "_ftp._tcp", "File Transfer Protocol" }, { "_home-sharing._tcp", "Home Sharing" }, { "_homekit._tcp", "HomeKit" }, { "_http._tcp", "World Wide Web HTML-over-HTTP" }, { "_https._tcp", "HTTP over SSL/TLS" }, { "_ipp._tcp", "Internet Printing Protocol" }, { "_ldap._tcp", "Lightweight Directory Access Protocol" }, { "_mediaremotetv._tcp", "Media Remote" }, { "_net-assistant._tcp", "Apple Remote Desktop" }, { "_od-master._tcp", "OpenDirectory Master" }, { "_nfs._tcp", "Network File System" }, { "_presence._tcp", "Peer-to-peer messaging / Link-Local Messaging" }, { "_pdl-datastream._tcp", "Printer Page Description Language Data Stream" }, { "_raop._tcp", "Remote Audio Output Protocol" }, { "_rfb._tcp", "Remote Frame Buffer" }, { "_scanner._tcp", "Bonjour Scanning" }, { "_smb._tcp", "Server Message Block over TCP/IP" }, { "_sftp-ssh._tcp", "Secure File Transfer Protocol over SSH" }, { "_sleep-proxy._udp", "Sleep Proxy Server" }, { "_ssh._tcp", "SSH Remote Login Protocol" }, { "_teleport._tcp", "teleport" }, { "_tftp._tcp", "Trivial File Transfer Protocol" }, { "_workstation._tcp", "Workgroup Manager" }, { "_webdav._tcp", "World Wide Web Distributed Authoring and Versioning (WebDAV)" }, { "_webdavs._tcp", "WebDAV over SSL/TLS" } }; static const char * ServiceTypeDescription( const char *inName ) { const ServiceType * serviceType; const ServiceType * const end = kServiceTypes + countof( kServiceTypes ); for( serviceType = kServiceTypes; serviceType < end; ++serviceType ) { if( ( stricmp_prefix( inName, serviceType->name ) == 0 ) ) { const char * const ptr = &inName[ strlen( serviceType->name ) ]; if( ( ptr[ 0 ] == '\0' ) || ( ( ptr[ 0 ] == '.' ) && ( ptr[ 1 ] == '\0' ) ) ) { return( serviceType->description ); } } } return( NULL ); } //=========================================================================================================================== // SocketContextCreate //=========================================================================================================================== static SocketContext * SocketContextCreate( SocketRef inSock, void *inUserContext, OSStatus *outError ) { return( SocketContextCreateEx( inSock, inUserContext, NULL, outError ) ); } //=========================================================================================================================== // SocketContextCreateEx //=========================================================================================================================== static SocketContext * SocketContextCreateEx( SocketRef inSock, void * inUserContext, SocketContextFinalizer_f inUserFinalizer, OSStatus * outError ) { OSStatus err; SocketContext * context; context = (SocketContext *) calloc( 1, sizeof( *context ) ); require_action( context, exit, err = kNoMemoryErr ); context->refCount = 1; context->sock = inSock; context->userContext = inUserContext; context->userFinalizer = inUserFinalizer; err = kNoErr; exit: if( outError ) *outError = err; return( context ); } //=========================================================================================================================== // SocketContextRetain //=========================================================================================================================== static SocketContext * SocketContextRetain( SocketContext *inContext ) { atomic_add_32( &inContext->refCount, 1 ); return( inContext ); } //=========================================================================================================================== // SocketContextRelease //=========================================================================================================================== static void SocketContextRelease( SocketContext *me ) { if( atomic_add_and_fetch_32( &me->refCount, -1 ) == 0 ) { ForgetSocket( &me->sock ); if( me->userFinalizer ) { me->userFinalizer( me->userContext ); me->userFinalizer = NULL; } me->userContext = NULL; free( me ); } } //=========================================================================================================================== // SocketContextCancelHandler //=========================================================================================================================== static void SocketContextCancelHandler( void *inContext ) { SocketContextRelease( (SocketContext *) inContext ); } //=========================================================================================================================== // SocketContextFinalizerCF //=========================================================================================================================== static void SocketContextFinalizerCF( void *inUserCtx ) { CFRelease( (CFTypeRef) inUserCtx ); } //=========================================================================================================================== // StringToInt32 //=========================================================================================================================== static OSStatus StringToInt32( const char *inString, int32_t *outValue ) { OSStatus err; long value; char * endPtr; value = strtol( inString, &endPtr, 0 ); require_action_quiet( ( *endPtr == '\0' ) && ( endPtr != inString ), exit, err = kParamErr ); require_action_quiet( ( value >= INT32_MIN ) && ( value <= INT32_MAX ), exit, err = kRangeErr ); *outValue = (int32_t) value; err = kNoErr; exit: return( err ); } //=========================================================================================================================== // StringToUInt32 //=========================================================================================================================== static OSStatus StringToUInt32( const char *inString, uint32_t *outValue ) { OSStatus err; uint32_t value; char * endPtr; value = (uint32_t) strtol( inString, &endPtr, 0 ); require_action_quiet( ( *endPtr == '\0' ) && ( endPtr != inString ), exit, err = kParamErr ); *outValue = value; err = kNoErr; exit: return( err ); } #if( TARGET_OS_DARWIN ) //=========================================================================================================================== // _StringToInt64 //=========================================================================================================================== static int64_t _StringToInt64( const char *inString, OSStatus *outError ) { OSStatus err; long long ll; char * end; int64_t i64 = 0; int errnoVal; set_errno_compat( 0 ); ll = strtoll( inString, &end, 0 ); errnoVal = errno_compat(); require_action_quiet( ( *end == '\0' ) && ( end != inString ), exit, err = kMalformedErr ); require_action_quiet( ( ( ll != LLONG_MIN ) && ( ll != LLONG_MAX ) ) || ( errnoVal != ERANGE ), exit, err = kRangeErr ); require_action_quiet( ( ll >= INT64_MIN ) && ( ll <= INT64_MAX ), exit, err = kRangeErr ); i64 = (int64_t) ll; err = kNoErr; exit: if( outError ) *outError = err; return( i64 ); } //=========================================================================================================================== // _StringToUInt64 //=========================================================================================================================== static uint64_t _StringToUInt64( const char *inString, OSStatus *outError ) { OSStatus err; unsigned long long val; char * end; int errnoVal; set_errno_compat( 0 ); val = strtoull( inString, &end, 0 ); errnoVal = errno_compat(); require_action_quiet( ( *end == '\0' ) && ( end != inString ), exit, err = kMalformedErr ); require_action_quiet( ( val != ULLONG_MAX ) || ( errnoVal != ERANGE ), exit, err = kRangeErr ); require_action_quiet( val <= UINT64_MAX, exit, err = kRangeErr ); err = kNoErr; exit: if( outError ) *outError = err; return( (uint64_t)val ); } //=========================================================================================================================== // _StringToPID //=========================================================================================================================== static pid_t _StringToPID( const char *inString, OSStatus *outError ) { OSStatus err; int64_t i64; pid_t pid = 0; i64 = _StringToInt64( inString, &err ); require_noerr_quiet( err, exit ); require_action_quiet( i64 == (pid_t) i64, exit, err = kRangeErr ); pid = (pid_t) i64; err = kNoErr; exit: if( outError ) *outError = err; return( pid ); } //=========================================================================================================================== // _ParseEscapedString // // Note: Similar to ParseEscapedString() from CoreUtils except that _ParseEscapedString() takes an optional C string // containing delimiter characters instead of being limited to one delimiter character. Also, when the function returns // due to a delimiter, the output pointer is set to the delimiter character instead of the character after the delimiter. //=========================================================================================================================== static OSStatus _ParseEscapedString( const char * inSrc, const char * inEnd, const char * inDelimiters, char * inBufPtr, size_t inBufLen, size_t * outCopiedLen, size_t * outActualLen, const char ** outPtr ) { OSStatus err; const char * ptr; char * dst = inBufPtr; const char * const lim = ( inBufLen > 0 ) ? &inBufPtr[ inBufLen - 1 ] : inBufPtr; size_t len; len = 0; ptr = inSrc; if( !inDelimiters ) inDelimiters = ""; while( ptr < inEnd ) { int c; const char * del; c = *ptr; for( del = inDelimiters; ( *del != '\0' ) && ( c != *del ); ++del ) {} if( *del != '\0' ) break; ++ptr; if( c == '\\' ) { require_action_quiet( ptr < inEnd, exit, err = kUnderrunErr ); c = *ptr++; } ++len; if( dst < lim ) *dst++ = (char) c; } if( inBufLen > 0 ) *dst = '\0'; if( outCopiedLen ) *outCopiedLen = (size_t)( dst - inBufPtr ); if( outActualLen ) *outActualLen = len; if( outPtr ) *outPtr = ptr; err = kNoErr; exit: return( err ); } //=========================================================================================================================== // _ParseEscapedStringWithCopy //=========================================================================================================================== static OSStatus _ParseEscapedStringWithCopy( const char * inSrc, const char * inEnd, const char * inDelimiters, char * inBufPtr, size_t inBufLen, const char ** outString, char ** outMemory, const char ** outPtr ) { OSStatus err; size_t actualLen, bufLen; char * bufPtr; char * mem = NULL; err = _ParseEscapedString( inSrc, inEnd, inDelimiters, NULL, 0, NULL, &actualLen, NULL ); require_noerr_quiet( err, exit ); if( actualLen < inBufLen ) { bufPtr = inBufPtr; bufLen = inBufLen; } else { bufLen = actualLen + 1; mem = (char *) malloc( bufLen ); require_action( mem, exit, err = kNoMemoryErr ); bufPtr = mem; } err = _ParseEscapedString( inSrc, inEnd, inDelimiters, bufPtr, bufLen, NULL, NULL, outPtr ); require_noerr_quiet( err, exit ); *outString = bufPtr; *outMemory = mem; mem = NULL; exit: ForgetMem( &mem ); return( err ); } #endif //=========================================================================================================================== // StringToARecordData //=========================================================================================================================== static OSStatus StringToARecordData( const char *inString, uint8_t **outPtr, size_t *outLen ) { OSStatus err; uint32_t * addrPtr; const size_t addrLen = sizeof( *addrPtr ); const char * end; addrPtr = (uint32_t *) malloc( addrLen ); require_action( addrPtr, exit, err = kNoMemoryErr ); err = _StringToIPv4Address( inString, kStringToIPAddressFlagsNoPort | kStringToIPAddressFlagsNoPrefix, addrPtr, NULL, NULL, NULL, &end ); if( !err && ( *end != '\0' ) ) err = kMalformedErr; require_noerr_quiet( err, exit ); *addrPtr = HostToBig32( *addrPtr ); *outPtr = (uint8_t *) addrPtr; addrPtr = NULL; *outLen = addrLen; exit: FreeNullSafe( addrPtr ); return( err ); } //=========================================================================================================================== // StringToAAAARecordData //=========================================================================================================================== static OSStatus StringToAAAARecordData( const char *inString, uint8_t **outPtr, size_t *outLen ) { OSStatus err; uint8_t * addrPtr; const size_t addrLen = 16; const char * end; addrPtr = (uint8_t *) malloc( addrLen ); require_action( addrPtr, exit, err = kNoMemoryErr ); err = _StringToIPv6Address( inString, kStringToIPAddressFlagsNoPort | kStringToIPAddressFlagsNoPrefix | kStringToIPAddressFlagsNoScope, addrPtr, NULL, NULL, NULL, &end ); if( !err && ( *end != '\0' ) ) err = kMalformedErr; require_noerr_quiet( err, exit ); *outPtr = addrPtr; addrPtr = NULL; *outLen = addrLen; exit: FreeNullSafe( addrPtr ); return( err ); } //=========================================================================================================================== // StringToDomainName //=========================================================================================================================== static OSStatus StringToDomainName( const char *inString, uint8_t **outPtr, size_t *outLen ) { OSStatus err; uint8_t * namePtr; size_t nameLen; uint8_t * end; uint8_t nameBuf[ kDomainNameLengthMax ]; err = DomainNameFromString( nameBuf, inString, &end ); require_noerr_quiet( err, exit ); nameLen = (size_t)( end - nameBuf ); namePtr = _memdup( nameBuf, nameLen ); require_action( namePtr, exit, err = kNoMemoryErr ); *outPtr = namePtr; namePtr = NULL; if( outLen ) *outLen = nameLen; exit: return( err ); } #if( TARGET_OS_DARWIN ) //=========================================================================================================================== // GetDefaultDNSServer //=========================================================================================================================== static OSStatus GetDefaultDNSServer( sockaddr_ip *outAddr ) { OSStatus err; dns_config_t * config; struct sockaddr * addr; int32_t i; config = dns_configuration_copy(); require_action( config, exit, err = kUnknownErr ); addr = NULL; for( i = 0; i < config->n_resolver; ++i ) { const dns_resolver_t * const resolver = config->resolver[ i ]; if( !resolver->domain && ( resolver->n_nameserver > 0 ) ) { addr = resolver->nameserver[ 0 ]; break; } } require_action_quiet( addr, exit, err = kNotFoundErr ); SockAddrCopy( addr, outAddr ); err = kNoErr; exit: if( config ) dns_configuration_free( config ); return( err ); } #endif //=========================================================================================================================== // GetMDNSMulticastAddrV4 //=========================================================================================================================== static void _MDNSMulticastAddrV4Init( void *inContext ); static const struct sockaddr * GetMDNSMulticastAddrV4( void ) { static struct sockaddr_in sMDNSMulticastAddrV4; static dispatch_once_t sMDNSMulticastAddrV4InitOnce = 0; dispatch_once_f( &sMDNSMulticastAddrV4InitOnce, &sMDNSMulticastAddrV4, _MDNSMulticastAddrV4Init ); return( (const struct sockaddr *) &sMDNSMulticastAddrV4 ); } static void _MDNSMulticastAddrV4Init( void *inContext ) { struct sockaddr_in * const addr = (struct sockaddr_in *) inContext; _SockAddrInitIPv4( addr, UINT32_C( 0xE00000FB ), kMDNSPort ); // 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; } //=========================================================================================================================== // 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 //=========================================================================================================================== extern char ** environ; static OSStatus _SpawnCommand( pid_t * outPID, const char * inStdOutRedirect, const char * inStdErrRedirect, const char * inFormat, ... ) { OSStatus err; va_list args; char * cmdStr = NULL; size_t cmdLen; const char * cmdEnd; char * bufPtr = NULL; size_t bufLen; const char * bufLim; pid_t pid; const char * src; char * dst; char ** argArray = NULL; size_t argCapacity, argCount; posix_spawn_file_actions_t actions; posix_spawn_file_actions_t * actionsPtr = NULL; // Create command string from format string and its arguments. va_start( args, inFormat ); VASPrintF( &cmdStr, inFormat, args ); va_end( args ); require_action( cmdStr, exit, err = kUnknownErr ); cmdLen = strlen( cmdStr ); cmdEnd = &cmdStr[ cmdLen ]; // Allocate buffer for argument strings. // In the worst-case scenario in terms of memory requirements, the only non-escaped white space is one non-escaped // white space character between each pair of adjacent arguments. The amount of required memory is equal to the size // of cmdStr. bufLen = cmdLen + 1; // +1 for NUL terminator. bufPtr = (char *) malloc( bufLen ); require_action( bufPtr, exit, err = kNoMemoryErr ); bufLim = &bufPtr[ bufLen ]; // Allocate initial argument array. argCount = 0; argCapacity = 8; argArray = (char **) malloc( ( argCapacity + 1 ) * sizeof( *argArray ) ); // +1 for NULL arg. require_action( argArray, exit, err = kNoMemoryErr ); // Extract argument strings from command string. src = cmdStr; dst = bufPtr; for( ;; ) { size_t maxLen, copiedLen, totalLen; Boolean more; maxLen = (size_t)( bufLim - dst ); if( maxLen > 0 ) --maxLen; // -1 for NUL terminator. more = _ParseQuotedEscapedString( src, cmdEnd, kWhiteSpaceCharSet, dst, maxLen, &copiedLen, &totalLen, &src ); if( !more ) break; require_fatal( copiedLen == totalLen, "Incorrect assumption about maximum required buffer space." ); if( argCount >= argCapacity ) { size_t newCapactiy; char ** newArray; newCapactiy = 2 * argCapacity; newArray = (char **) realloc( argArray, ( newCapactiy + 1 ) * sizeof( *newArray ) ); // +1 for NULL arg. require_action( newArray, exit, err = kNoMemoryErr ); argArray = newArray; argCapacity = newCapactiy; } argArray[ argCount++ ] = dst; dst += copiedLen; *dst++ = '\0'; check( dst <= bufLim ); } require_action_quiet( argCount > 0, exit, err = kCommandErr ); argArray[ argCount ] = NULL; // Set up stdout and stderr redirections, if any. if( inStdOutRedirect || inStdErrRedirect ) { err = posix_spawn_file_actions_init( &actions ); require_noerr( err, exit ); actionsPtr = &actions; if( inStdOutRedirect ) { err = posix_spawn_file_actions_addopen( actionsPtr, STDOUT_FILENO, inStdOutRedirect, O_WRONLY, 0 ); require_noerr( err, exit ); } if( inStdErrRedirect ) { err = posix_spawn_file_actions_addopen( actionsPtr, STDERR_FILENO, inStdErrRedirect, O_WRONLY, 0 ); require_noerr( err, exit ); } } // Spawn command. err = posix_spawnp( &pid, argArray[ 0 ], actionsPtr, NULL, argArray, environ ); require_noerr_quiet( err, exit ); if( outPID ) *outPID = pid; exit: FreeNullSafe( cmdStr ); FreeNullSafe( bufPtr ); FreeNullSafe( argArray ); if( actionsPtr ) posix_spawn_file_actions_destroy( actionsPtr ); return( err ); } //=========================================================================================================================== // OutputFormatFromArgString //=========================================================================================================================== static OSStatus OutputFormatFromArgString( const char *inArgString, OutputFormatType *outFormat ) { OSStatus err; OutputFormatType format; format = (OutputFormatType) CLIArgToValue( "format", inArgString, &err, kOutputFormatStr_JSON, kOutputFormatType_JSON, kOutputFormatStr_XML, kOutputFormatType_XML, kOutputFormatStr_Binary, kOutputFormatType_Binary, NULL ); if( outFormat ) *outFormat = format; return( err ); } //=========================================================================================================================== // OutputPropertyList //=========================================================================================================================== static OSStatus OutputPropertyList( CFPropertyListRef inPList, OutputFormatType inType, 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( inType == kOutputFormatType_JSON ) { err = WriteANSIFile( file, "\n", 1 ); require_noerr_quiet( err, exit ); } exit: if( file && ( file != stdout ) ) fclose( file ); CFReleaseNullSafe( results ); return( err ); } //=========================================================================================================================== // 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; WriteBig16Typed( buf, (uint16_t) 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 . 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; WriteBig16Typed( rdlengthPtr, (uint16_t) rdataLen ); } if( outLen ) *outLen = inNameLen + sizeof( dns_fixed_fields_record ) + 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; dns_fixed_fields_soa 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 ); dns_fixed_fields_soa_init( &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; dns_fixed_fields_question fields; err = DataBuffer_Append( inDB, inNamePtr, inNameLen ); require_noerr( err, exit ); dns_fixed_fields_question_init( &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; dns_fixed_fields_record fields; require_action_quiet( inRDataLen < kDNSRecordDataLengthMax, exit, err = kSizeErr ); err = DataBuffer_Append( inDB, inNamePtr, inNameLen ); require_noerr( err, exit ); dns_fixed_fields_record_init( &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 ); } //=========================================================================================================================== // _NanoTime64ToTimestamp //=========================================================================================================================== static char * _NanoTime64ToTimestamp( NanoTime64 inTime, char *inBuf, size_t inMaxLen ) { struct timeval tv; NanoTimeToTimeVal( inTime, &tv ); return( MakeFractionalDateString( &tv, inBuf, inMaxLen ) ); } //=========================================================================================================================== // _MDNSInterfaceListCreate //=========================================================================================================================== static Boolean _MDNSInterfaceIsBlacklisted( SocketRef inInfoSock, const char *inIfName ); static OSStatus _MDNSInterfaceListCreate( MDNSInterfaceSubset inSubset, size_t inItemSize, MDNSInterfaceItem **outList ) { OSStatus err; struct ifaddrs * ifaList; const struct ifaddrs * ifa; MDNSInterfaceItem * interfaceList; MDNSInterfaceItem ** ptr; SocketRef infoSock; ifaList = NULL; interfaceList = NULL; infoSock = kInvalidSocketRef; if( inItemSize == 0 ) inItemSize = sizeof( MDNSInterfaceItem ); require_action_quiet( inItemSize >= sizeof( MDNSInterfaceItem ), exit, err = kSizeErr ); infoSock = socket( AF_INET, SOCK_DGRAM, 0 ); err = map_socket_creation_errno( infoSock ); require_noerr( err, exit ); err = getifaddrs( &ifaList ); err = map_global_noerr_errno( err ); require_noerr( err, exit ); ptr = &interfaceList; for( ifa = ifaList; ifa; ifa = ifa->ifa_next ) { MDNSInterfaceItem * item; int family; const unsigned int flagsMask = IFF_UP | IFF_MULTICAST | IFF_POINTOPOINT; const unsigned int flagsNeeded = IFF_UP | IFF_MULTICAST; if( ( ifa->ifa_flags & flagsMask ) != flagsNeeded ) continue; if( !ifa->ifa_addr || !ifa->ifa_name ) continue; family = ifa->ifa_addr->sa_family; if( ( family != AF_INET ) && ( family != AF_INET6 ) ) continue; for( item = interfaceList; item && ( strcmp( item->ifName, ifa->ifa_name ) != 0 ); item = item->next ) {} if( !item ) { NetTransportType type; uint32_t ifIndex; const char * const ifName = ifa->ifa_name; if( _MDNSInterfaceIsBlacklisted( infoSock, ifName ) ) continue; err = SocketGetInterfaceInfo( infoSock, ifName, NULL, &ifIndex, NULL, NULL, NULL, NULL, NULL, &type ); require_noerr( err, exit ); if( ifIndex == 0 ) continue; if( type == kNetTransportType_AWDL ) { if( inSubset == kMDNSInterfaceSubset_NonAWDL ) continue; } else { if( inSubset == kMDNSInterfaceSubset_AWDL ) continue; } item = (MDNSInterfaceItem *) calloc( 1, inItemSize ); require_action( item, exit, err = kNoMemoryErr ); *ptr = item; ptr = &item->next; item->ifName = strdup( ifName ); require_action( item->ifName, exit, err = kNoMemoryErr ); item->ifIndex = ifIndex; if( type == kNetTransportType_AWDL ) item->isAWDL = true; else if( type == kNetTransportType_WiFi ) item->isWiFi = true; } if( family == AF_INET ) item->hasIPv4 = true; else item->hasIPv6 = true; } require_action_quiet( interfaceList, exit, err = kNotFoundErr ); if( outList ) { *outList = interfaceList; interfaceList = NULL; } exit: if( ifaList ) freeifaddrs( ifaList ); _MDNSInterfaceListFree( interfaceList ); ForgetSocket( &infoSock ); return( err ); } static Boolean _MDNSInterfaceIsBlacklisted( SocketRef inInfoSock, const char *inIfName ) { OSStatus err; int i; static const char * const kMDNSInterfacePrefixBlacklist[] = { "llw", "nan" }; struct ifreq ifr; // Check if the interface name's prefix matches the prefix blacklist. for( i = 0; i < (int) countof( kMDNSInterfacePrefixBlacklist ); ++i ) { const char * const prefix = kMDNSInterfacePrefixBlacklist[ i ]; if( strcmp_prefix( inIfName, prefix ) == 0 ) { const char * ptr = &inIfName[ strlen( prefix ) ]; while( isdigit_safe( *ptr ) ) ++ptr; if( *ptr == '\0' ) return( true ); } } // Check if the interface is used for inter-(co)processor networking. memset( &ifr, 0, sizeof( ifr ) ); strlcpy( ifr.ifr_name, inIfName, sizeof( ifr.ifr_name ) ); err = ioctl( inInfoSock, SIOCGIFFUNCTIONALTYPE, &ifr ); err = map_global_value_errno( err != -1, err ); if( !err && ( ifr.ifr_functional_type == IFRTYPE_FUNCTIONAL_INTCOPROC ) ) return( true ); return( false ); } //=========================================================================================================================== // _MDNSInterfaceListFree //=========================================================================================================================== static void _MDNSInterfaceListFree( MDNSInterfaceItem *inList ) { MDNSInterfaceItem * item; while( ( item = inList ) != NULL ) { inList = item->next; FreeNullSafe( item->ifName ); free( item ); } } //=========================================================================================================================== // _MDNSInterfaceGetAny //=========================================================================================================================== static OSStatus _MDNSInterfaceGetAny( MDNSInterfaceSubset inSubset, char inNameBuf[ IF_NAMESIZE + 1 ], uint32_t *outIndex ) { OSStatus err; MDNSInterfaceItem * list; const MDNSInterfaceItem * item; list = NULL; err = _MDNSInterfaceListCreate( inSubset, 0, &list ); require_noerr_quiet( err, exit ); require_action_quiet( list, exit, err = kNotFoundErr ); for( item = list; item; item = item->next ) { if( item->hasIPv4 && item->hasIPv6 ) break; } if( !item ) item = list; if( inNameBuf ) strlcpy( inNameBuf, item->ifName, IF_NAMESIZE + 1 ); if( outIndex ) *outIndex = item->ifIndex; exit: _MDNSInterfaceListFree( list ); return( err ); } //=========================================================================================================================== // _SetComputerName //=========================================================================================================================== static OSStatus _SetComputerName( CFStringRef inComputerName, CFStringEncoding inEncoding ) { OSStatus err; SCPreferencesRef prefs; Boolean ok; prefs = SCPreferencesCreateWithAuthorization( NULL, CFSTR( kDNSSDUtilIdentifier ), NULL, NULL ); err = map_scerror( prefs ); require_noerr_quiet( err, exit ); ok = SCPreferencesSetComputerName( prefs, inComputerName, inEncoding ); err = map_scerror( ok ); require_noerr_quiet( err, exit ); ok = SCPreferencesCommitChanges( prefs ); err = map_scerror( ok ); require_noerr_quiet( err, exit ); ok = SCPreferencesApplyChanges( prefs ); err = map_scerror( ok ); require_noerr_quiet( err, exit ); exit: CFReleaseNullSafe( prefs ); return( err ); } //=========================================================================================================================== // _SetComputerNameWithUTF8CString //=========================================================================================================================== static OSStatus _SetComputerNameWithUTF8CString( const char *inComputerName ) { OSStatus err; CFStringRef computerName; computerName = CFStringCreateWithCString( NULL, inComputerName, kCFStringEncodingUTF8 ); require_action( computerName, exit, err = kNoMemoryErr ); err = _SetComputerName( computerName, kCFStringEncodingUTF8 ); require_noerr_quiet( err, exit ); exit: CFReleaseNullSafe( computerName ); return( err ); } //=========================================================================================================================== // _SetLocalHostName //=========================================================================================================================== static OSStatus _SetLocalHostName( CFStringRef inLocalHostName ) { OSStatus err; SCPreferencesRef prefs; Boolean ok; prefs = SCPreferencesCreateWithAuthorization( NULL, CFSTR( kDNSSDUtilIdentifier ), NULL, NULL ); err = map_scerror( prefs ); require_noerr_quiet( err, exit ); ok = SCPreferencesSetLocalHostName( prefs, inLocalHostName ); err = map_scerror( ok ); require_noerr_quiet( err, exit ); ok = SCPreferencesCommitChanges( prefs ); err = map_scerror( ok ); require_noerr_quiet( err, exit ); ok = SCPreferencesApplyChanges( prefs ); err = map_scerror( ok ); require_noerr_quiet( err, exit ); exit: CFReleaseNullSafe( prefs ); return( err ); } //=========================================================================================================================== // _SetLocalHostNameWithUTF8CString //=========================================================================================================================== static OSStatus _SetLocalHostNameWithUTF8CString( const char *inLocalHostName ) { OSStatus err; CFStringRef localHostName; localHostName = CFStringCreateWithCString( NULL, inLocalHostName, kCFStringEncodingUTF8 ); require_action( localHostName, exit, err = kNoMemoryErr ); err = _SetLocalHostName( localHostName ); require_noerr_quiet( err, exit ); exit: CFReleaseNullSafe( localHostName ); return( err ); } #if( TARGET_OS_DARWIN ) //=========================================================================================================================== // _InterfaceIPv6AddressAdd //=========================================================================================================================== static OSStatus _InterfaceIPv6AddressAdd( const char *inIfName, uint8_t inAddr[ STATIC_PARAM 16 ], int inMaskBitLen ) { OSStatus err; SocketRef infoSock = kInvalidSocketRef; struct in6_aliasreq ifra; size_t len; struct sockaddr_in6 * sin6; int wholeBytes, remainingBits; require_action_quiet( ( inMaskBitLen >= 0 ) && ( inMaskBitLen <= 128 ), exit, err = kSizeErr ); infoSock = socket( AF_INET6, SOCK_DGRAM, 0 ); err = map_socket_creation_errno( infoSock ); require_noerr( err, exit ); // Set interface name. memset( &ifra, 0, sizeof( ifra ) ); len = strlcpy( ifra.ifra_name, inIfName, sizeof( ifra.ifra_name ) ); require_action_quiet( len < sizeof( ifra.ifra_name ), exit, err = kSizeErr ); // Set IPv6 address. sin6 = &ifra.ifra_addr; SIN6_LEN_SET( sin6 ); sin6->sin6_family = AF_INET6; memcpy( sin6->sin6_addr.s6_addr, inAddr, 16 ); // Set prefix mask. sin6 = &ifra.ifra_prefixmask; SIN6_LEN_SET( sin6 ); sin6->sin6_family = AF_INET6; wholeBytes = inMaskBitLen / 8; if( wholeBytes > 0 ) memset( sin6->sin6_addr.s6_addr, 0xFF, (size_t) wholeBytes ); remainingBits = inMaskBitLen % 8; if( remainingBits > 0 ) sin6->sin6_addr.s6_addr[ wholeBytes ] = ( 0xFFU << ( 8 - remainingBits ) ) & 0xFFU; ifra.ifra_lifetime.ia6t_vltime = ND6_INFINITE_LIFETIME; ifra.ifra_lifetime.ia6t_pltime = ND6_INFINITE_LIFETIME; err = ioctl( infoSock, SIOCAIFADDR_IN6, &ifra ); err = map_global_value_errno( err != -1, err ); require_noerr_quiet( err, exit ); exit: ForgetSocket( &infoSock ); return( err ); } //=========================================================================================================================== // _InterfaceIPv6AddressRemove //=========================================================================================================================== static OSStatus _InterfaceIPv6AddressRemove( const char *inIfName, const uint8_t inAddr[ STATIC_PARAM 16 ] ) { OSStatus err; SocketRef infoSock = kInvalidSocketRef; struct in6_ifreq ifr; size_t len; struct sockaddr_in6 * sin6; infoSock = socket( AF_INET6, SOCK_DGRAM, 0 ); err = map_socket_creation_errno( infoSock ); require_noerr( err, exit ); memset( &ifr, 0, sizeof( ifr ) ); len = strlcpy( ifr.ifr_name, inIfName, sizeof( ifr.ifr_name ) ); require_action_quiet( len < sizeof( ifr.ifr_name ), exit, err = kSizeErr ); sin6 = &ifr.ifr_ifru.ifru_addr; SIN6_LEN_SET( sin6 ); sin6->sin6_family = AF_INET6; memcpy( sin6->sin6_addr.s6_addr, inAddr, 16 ); err = ioctl( infoSock, SIOCDIFADDR_IN6, &ifr ); err = map_global_value_errno( err != -1, err ); require_noerr_quiet( err, exit ); exit: ForgetSocket( &infoSock ); return( err ); } #endif // TARGET_OS_DARWIN //=========================================================================================================================== // _TicksDiff //=========================================================================================================================== static int64_t _TicksDiff( uint64_t inT1, uint64_t inT2 ) { return( (int64_t)( inT1 - inT2 ) ); } //=========================================================================================================================== // _SockAddrInitIPv4 //=========================================================================================================================== static void _SockAddrInitIPv4( struct sockaddr_in *inSA, uint32_t inIPv4, uint16_t inPort ) { memset( inSA, 0, sizeof( *inSA ) ); SIN_LEN_SET( inSA ); inSA->sin_family = AF_INET; inSA->sin_port = htons( inPort ); inSA->sin_addr.s_addr = htonl( inIPv4 ); } //=========================================================================================================================== // _SockAddrInitIPv6 //=========================================================================================================================== static void _SockAddrInitIPv6( struct sockaddr_in6 * inSA, const uint8_t inIPv6[ STATIC_PARAM 16 ], uint32_t inScope, uint16_t inPort ) { check_compile_time_code( sizeof( inSA->sin6_addr.s6_addr ) == 16 ); memset( inSA, 0, sizeof( *inSA ) ); SIN6_LEN_SET( inSA ); inSA->sin6_family = AF_INET6; inSA->sin6_port = htons( inPort ); memcpy( inSA->sin6_addr.s6_addr, inIPv6, 16 ); inSA->sin6_scope_id = inScope; } //=========================================================================================================================== // _WriteReverseIPv6DomainNameString //=========================================================================================================================== static void _WriteReverseIPv6DomainNameString( const uint8_t inIPv6Addr[ STATIC_PARAM 16 ], char outBuffer[ STATIC_PARAM kReverseIPv6DomainNameBufLen ] ) { char * dst; int i; dst = outBuffer; for( i = 0; i < 16; ++i ) { const unsigned int octet = inIPv6Addr[ 15 - i ]; *dst++ = kHexDigitsLowercase[ octet & 0x0F ]; *dst++ = '.'; *dst++ = kHexDigitsLowercase[ octet >> 4 ]; *dst++ = '.'; } memcpy( dst, kIP6ArpaDomainStr, sizeof( kIP6ArpaDomainStr ) ); dst += sizeof( kIP6ArpaDomainStr ); check( ( dst - outBuffer ) == kReverseIPv6DomainNameBufLen ); } //=========================================================================================================================== // _WriteReverseIPv4DomainNameString //=========================================================================================================================== static void _WriteReverseIPv4DomainNameString( uint32_t inIPv4Addr, char outBuffer[ STATIC_PARAM kReverseIPv4DomainNameBufLen ] ) { SNPrintF( outBuffer, kReverseIPv4DomainNameBufLen, "%u.%u.%u.%u.%s", inIPv4Addr & 0xFF, ( inIPv4Addr >> 8 ) & 0xFF, ( inIPv4Addr >> 16 ) & 0xFF, ( inIPv4Addr >> 24 ) & 0xFF, kInAddrArpaDomainStr ); } #if( MDNSRESPONDER_PROJECT ) //=========================================================================================================================== // _SetDefaultFallbackDNSService //=========================================================================================================================== static OSStatus _SetDefaultFallbackDNSService( const char *inFallbackDNSServiceStr ) { OSStatus err; nw_resolver_config_t resolverConfig; CFDataRef plistData; if( stricmp_prefix( inFallbackDNSServiceStr, kFallbackDNSServiceArgPrefix_DoH ) == 0 ) { nw_endpoint_t endpoint; const char * const url = inFallbackDNSServiceStr + sizeof_string( kFallbackDNSServiceArgPrefix_DoH ); endpoint = nw_endpoint_create_url( url ); require_action( endpoint, exit, err = kUnknownErr ); resolverConfig = nw_resolver_config_create_https( endpoint ); nw_forget( &endpoint ); require_action( resolverConfig, exit, err = kUnknownErr ); } else if( stricmp_prefix( inFallbackDNSServiceStr, kFallbackDNSServiceArgPrefix_DoT ) == 0 ) { nw_endpoint_t endpoint; const char * const hostname = inFallbackDNSServiceStr + sizeof_string( kFallbackDNSServiceArgPrefix_DoT ); endpoint = nw_endpoint_create_host( hostname, "0" ); require_action( endpoint, exit, err = kUnknownErr ); resolverConfig = nw_resolver_config_create_tls( endpoint ); nw_forget( &endpoint ); require_action( resolverConfig, exit, err = kUnknownErr ); } else { FPrintF( stderr, "error: Unrecognized fallback DNS service string: \"%s\"\n", inFallbackDNSServiceStr ); err = kParamErr; goto exit; } plistData = nw_resolver_config_copy_plist_data_ref( resolverConfig ); require_action( plistData, exit, err = kUnknownErr ); err = DNSServiceSetResolverDefaults( CFDataGetBytePtr( plistData ), (size_t) CFDataGetLength( plistData ), true ); ForgetCF( &plistData ); require_noerr( err, exit ); exit: return( err ); } #endif //=========================================================================================================================== // 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( kDNSSDUtilIdentifier, 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 ); sockCtx = SocketContextCreate( sock, me, &err ); 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 ); sockCtx = SocketContextCreate( sock, me, &err ); 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" "\x05" "local" #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 | kMDNSClassCacheFlushBit, 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; gettimeofday( &now, NULL ); err = SocketRecvFrom( sockCtx->sock, me->msgBuf, sizeof( me->msgBuf ), &msgLen, &sender, sizeof( sender ), NULL, NULL, NULL, NULL ); require_noerr( err, exit ); require_quiet( msgLen >= kDNSHeaderLength, exit ); hdr = (const DNSHeader *) me->msgBuf; probeFound = false; probeIsQU = false; qCount = DNSHeaderGetQuestionCount( hdr ); ptr = (const uint8_t *) &hdr[ 1 ]; for( i = 0; i < qCount; ++i ) { 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 & ~kMDNSClassUnicastResponseBit ) != kDNSServiceClass_IN ) continue; if( !DomainNameEqual( qname, me->target ) ) continue; if( !probeFound ) { probeFound = true; probeIsQU = ( qclass & kMDNSClassUnicastResponseBit ) ? 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) -- %#.1{du:dnsmsg}\n", &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. #if( MDNSRESPONDER_PROJECT ) Boolean useNewGAI; // Use dnssd_getaddrinfo_* instead of DNSServiceGetAddrInfo(). #endif Boolean includeAWDL; // True if the IncludeAWDL flag should be used for DNS-SD ops that // use the "any" interface. Boolean validateResults; // Validate results. }; 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. 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. char * fqdn; // Fully qualified domain name of service instance (for logging/debugging). 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 gai; // Reference to DNSServiceGetAddrInfo op for service instance's hostname. #if( MDNSRESPONDER_PROJECT ) dnssd_getaddrinfo_t newGAI; // Reference to dnssd_getaddrinfo object for service instance's hostname. #endif uint64_t gaiStartTicks; // Value of UpTicks() when the DNSServiceGetAddrInfo op began. SBIPAddress * ipaddrList; // List of IP addresses that the hostname resolved to. int32_t refCount; // This object's reference count. }; 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. Boolean validated; // True if IP address is validated. }; 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 ); #if( MDNSRESPONDER_PROJECT ) static void _ServiceBrowserGAIResultHandler( ServiceBrowserRef inBrowser, SBServiceInstance * inInstance, dnssd_getaddrinfo_result_t * inResultArray, size_t inResultCount ); #endif 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, const DNSServiceAttribute * inAttr ); 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, Boolean inValidated ); 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, const char * inType, const char * inDomain, uint32_t inIfIndex, uint64_t inDiscoverTimeUs, ServiceBrowserRef inBrowser, SBServiceInstance ** outInstance ); #if( MDNSRESPONDER_PROJECT ) static void _SBServiceInstanceRetain( SBServiceInstance *inInstance ); #endif static void _SBServiceInstanceStop( SBServiceInstance *inInstance ); static void _SBServiceInstanceRelease( SBServiceInstance *inInstance ); #define _SBServiceInstanceForget( X ) ForgetCustomEx( X, _SBServiceInstanceStop, _SBServiceInstanceRelease ) static OSStatus _SBIPAddressCreate( const struct sockaddr * inSockAddr, uint64_t inResolveTimeUs, Boolean inValidated, 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, Boolean inValidated, SBRIPAddress ** outIPAddress ); static void _SBRIPAddressFree( SBRIPAddress *inIPAddress ); #define ForgetSBIPAddressList( X ) ForgetCustom( X, _SBIPAddressFreeList ) CF_CLASS_DEFINE( ServiceBrowser ); ulog_define_ex( kDNSSDUtilIdentifier, ServiceBrowser, kLogLevelTrace, kLogFlags_None, "ServiceBrowser", NULL ); #define sb_ulog( LEVEL, ... ) ulog( &log_category_from_name( ServiceBrowser ), (LEVEL), __VA_ARGS__ ) 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 ); } //=========================================================================================================================== // ServiceBrowserSetUseNewGAI //=========================================================================================================================== static void ServiceBrowserSetUseNewGAI( ServiceBrowserRef me, Boolean inUseNewGAI ) { me->useNewGAI = inUseNewGAI; } //=========================================================================================================================== // ServiceBrowserSetValidateResults //=========================================================================================================================== static void ServiceBrowserSetValidateResults( const ServiceBrowserRef me, const Boolean inValidateResults ) { me->validateResults = inValidateResults; } //=========================================================================================================================== // 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; const char * const recordName = "b._dns-sd._udp.local."; const uint32_t ifIndex = kDNSServiceInterfaceIndexLocalOnly; // Perform PTR meta-query for "b._dns-sd._udp.local." to enumerate recommended browsing domains. // See . sb_ulog( kLogLevelTrace, "Starting PTR QueryRecord on interface %d for %s", (int32_t) ifIndex, recordName ); sdRef = me->connection; err = DNSServiceQueryRecord( &sdRef, kDNSServiceFlagsShareConnection, ifIndex, recordName, 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; 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 ); } dispatch_source_forget( &me->stopTimer ); DNSServiceForget( &me->domainsQuery ); for( d = me->domainList; d; d = d->next ) { SBServiceType * t; DNSServiceForget( &d->servicesQuery ); for( t = d->typeList; t; t = t->next ) { SBServiceBrowse * b; for( b = t->browseList; b; b = b->next ) { SBServiceInstance * i; DNSServiceForget( &b->browse ); for( i = b->instanceList; i; i = i->next ) { _SBServiceInstanceStop( i ); } } } } DNSServiceForget( &me->connection ); 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; DNSServiceRef sdRef; DNSServiceFlags flags; // Perform PTR meta-query for _services._dns-sd._udp. to enumerate service types in domain. // See . 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; sb_ulog( kLogLevelTrace, "Starting PTR QueryRecord on interface %d for %s", (int32_t) me->ifIndex, recordName ); 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( inClass ); Unused( inTTL ); sb_ulog( kLogLevelTrace, "QueryRecord result: %s on interface %d for %s -> %{du:rdata}%?{end} (error: %#m)", DNSServiceFlagsToAddRmvStr( inFlags ), (int32_t) inInterfaceIndex, inFullName, inType, inRDataPtr, inRDataLen, !inError, inError ); 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( inTTL ); Unused( inClass ); sb_ulog( kLogLevelTrace, "QueryRecord result: %s on interface %d for %s -> %{du:rdata}%?{end} (error: %#m)", DNSServiceFlagsToAddRmvStr( inFlags ), (int32_t) inInterfaceIndex, inFullName, inType, inRDataPtr, inRDataLen, !inError, inError ); 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 . 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; DNSServiceAttributeRef attr = NULL; Unused( inSDRef ); sb_ulog( kLogLevelTrace, "Browse result: %s on interface %d for %s.%s%s%?{end} (error: %#m)", DNSServiceFlagsToAddRmvStr( inFlags ), (int32_t) inInterfaceIndex, inName, inRegType, inDomain, !inError, inError ); require_noerr( inError, exit ); if( inFlags & kDNSServiceFlagsAdd ) { if( me->validateResults ) { const uint8_t * dataPtr; size_t dataLen; dataPtr = DNSServiceGetValidationData( inSDRef, &dataLen ); sb_ulog( kLogLevelTrace, "Got %zu bytes of validation data for browse result", dataLen ); if( dataPtr ) { attr = DNSServiceAttributeCreate(); require( attr, exit ); err = DNSServiceAttrSetValidationData( attr, dataPtr, dataLen ); require_noerr( err, exit ); } } err = _ServiceBrowserAddServiceInstance( me, browse, inInterfaceIndex, inName, inRegType, inDomain, UpTicksToMicroseconds( nowTicks - browse->startTicks ), attr ); 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: _DNSServiceAttrForget( &attr ); } //=========================================================================================================================== // _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; DNSServiceAttributeRef attr = NULL; const uint8_t * validationDataPtr = NULL; size_t validationDataLen = 0; Unused( inSDRef ); Unused( inFlags ); sb_ulog( kLogLevelTrace, "Resolve result on interface %d for %s -> %s:%u %#{txt}%?{end} (error %#m)", (int32_t) inInterfaceIndex, inFullName, inHostname, inPort, inTXTPtr, (size_t) inTXTLen, !inError, inError ); 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; } if( me->validateResults ) { const uint8_t * dataPtr; size_t dataLen; dataPtr = DNSServiceGetValidationData( inSDRef, &dataLen ); sb_ulog( kLogLevelTrace, "Got %zu bytes of validation data for resolve result\n", dataLen ); if( dataPtr ) { mdns_signed_resolve_result_t signedResult; signedResult = mdns_signed_resolve_result_create_from_data( dataPtr, dataLen, &err ); bc_ulog( kLogLevelTrace, "Signed resolve result -- %@", signedResult ); if( signedResult ) { if( mdns_signed_resolve_result_covers_txt_rdata( signedResult, instance->txtPtr, instance->txtLen ) ) { sb_ulog( kLogLevelTrace, "Signed resolve result covers TXT record data\n" ); validationDataPtr = dataPtr; validationDataLen = dataLen; } else { sb_ulog( kLogLevelError, "Signed resolve result doesn't cover TXT record data\n" ); } mdns_forget( &signedResult ); } else { sb_ulog( kLogLevelError, "mdns_signed_resolve_result_create_from_data() failed: %#m\n", err ); } } } 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 ); sb_ulog( kLogLevelTrace, "Starting GetAddrInfo on interface %d for %s", (int32_t) instance->ifIndex, instance->hostname ); ForgetSBIPAddressList( &instance->ipaddrList ); if( me->useNewGAI ) { dnssd_getaddrinfo_t gai; dnssd_getaddrinfo_forget( &instance->newGAI ); gai = dnssd_getaddrinfo_create(); require_action( gai, exit, err = kNoResourcesErr ); dnssd_getaddrinfo_set_hostname( gai, instance->hostname ); dnssd_getaddrinfo_set_flags( gai, 0 ); dnssd_getaddrinfo_set_interface_index( gai, instance->ifIndex ); dnssd_getaddrinfo_set_protocols( gai, kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6 ); if( validationDataPtr ) { dnssd_getaddrinfo_set_validation_data( gai, validationDataPtr, validationDataLen ); } dnssd_getaddrinfo_set_queue( gai, me->queue ); _SBServiceInstanceRetain( instance ); CFRetain( me ); dnssd_getaddrinfo_set_result_handler( gai, ^( dnssd_getaddrinfo_result_t * const inResultArray, const size_t inResultCount ) { if( instance->newGAI == gai ) { _ServiceBrowserGAIResultHandler( me, instance, inResultArray, inResultCount ); } } ); dnssd_getaddrinfo_set_event_handler( gai, ^( dnssd_event_t inEvent, DNSServiceErrorType inGAIError ) { switch( inEvent ) { case dnssd_event_invalidated: dnssd_release( gai ); _SBServiceInstanceRelease( instance ); CFRelease( me ); break; case dnssd_event_error: if( instance->newGAI == gai ) { sb_ulog( kLogLevelError, "dnssd_getaddrinfo error %#m\n", inGAIError ); dnssd_getaddrinfo_forget( &instance->newGAI ); } break; default: break; } } ); instance->newGAI = gai; dnssd_retain( instance->newGAI ); dnssd_getaddrinfo_activate( instance->newGAI ); } else { DNSServiceForget( &instance->gai ); sdRef = me->connection; instance->gaiStartTicks = UpTicks(); if( validationDataPtr ) { attr = DNSServiceAttributeCreate(); require( attr, exit ); err = DNSServiceAttrSetValidationData( attr, validationDataPtr, validationDataLen ); require_noerr( err, exit ); err = DNSServiceGetAddrInfoEx( &sdRef, kDNSServiceFlagsShareConnection, instance->ifIndex, kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6, instance->hostname, attr, _ServiceBrowserGAICallback, instance ); require_noerr( err, exit ); } else { err = DNSServiceGetAddrInfo( &sdRef, kDNSServiceFlagsShareConnection, instance->ifIndex, kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6, instance->hostname, _ServiceBrowserGAICallback, instance ); require_noerr( err, exit ); } instance->gai = sdRef; } } exit: _DNSServiceAttrForget( &attr ); } #if( MDNSRESPONDER_PROJECT ) //=========================================================================================================================== // _ServiceBrowserGAIResultHandler //=========================================================================================================================== static void _ServiceBrowserGAIResultHandler( ServiceBrowserRef me, SBServiceInstance * inInstance, dnssd_getaddrinfo_result_t * inResultArray, size_t inResultCount ) { OSStatus err; size_t i; const uint64_t nowTicks = UpTicks(); for( i = 0; i < inResultCount; ++i ) { const dnssd_getaddrinfo_result_t result = inResultArray[ i ]; const dnssd_getaddrinfo_result_type_t type = dnssd_getaddrinfo_result_get_type( result ); sb_ulog( kLogLevelTrace, "dnssd_getaddrinfo result: %@\n", result ); if( type == dnssd_getaddrinfo_result_type_add ) { Boolean validated = false; if( me->validateResults ) { const uint8_t * dataPtr; size_t dataLen; dataPtr = dnssd_getaddrinfo_result_get_validation_data( result, &dataLen ); sb_ulog( kLogLevelTrace, "Got %zu bytes of validation data for dnssd_getaddrinfo result", dataLen ); if( dataPtr ) { OSStatus createErr; mdns_signed_hostname_result_t signedResult; signedResult = mdns_signed_hostname_result_create_from_data( dataPtr, dataLen, &createErr ); sb_ulog( kLogLevelTrace, "Signed hostname result -- %@", signedResult ); if( signedResult ) { validated = true; mdns_forget( &signedResult ); } } } err = _ServiceBrowserAddIPAddress( me, inInstance, dnssd_getaddrinfo_result_get_address( result ), UpTicksToMicroseconds( nowTicks - inInstance->gaiStartTicks ), validated ); if( err == kDuplicateErr ) err = kNoErr; require_noerr( err, exit ); } else if( type == dnssd_getaddrinfo_result_type_remove ) { err = _ServiceBrowserRemoveIPAddress( me, inInstance, dnssd_getaddrinfo_result_get_address( result ) ); if( err == kNotFoundErr ) err = kNoErr; require_noerr( err, exit ); } } exit: return; } #endif //=========================================================================================================================== // _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( inTTL ); sb_ulog( kLogLevelTrace, "GetAddrInfo result: %s on interface %d for (%s ->) %s -> %##a%?{end} (error: %#m)", DNSServiceFlagsToAddRmvStr( inFlags ), (int32_t) inInterfaceIndex, instance->fqdn, inHostname, inSockAddr, !inError, inError ); 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 ) { Boolean validated = false; if( me->validateResults ) { const uint8_t * dataPtr; size_t dataLen; dataPtr = DNSServiceGetValidationData( inSDRef, &dataLen ); sb_ulog( kLogLevelTrace, "Got %zu bytes of validation data for GetAddrInfo result", dataLen ); if( dataPtr ) { OSStatus createErr; mdns_signed_hostname_result_t signedResult; signedResult = mdns_signed_hostname_result_create_from_data( dataPtr, dataLen, &createErr ); sb_ulog( kLogLevelTrace, "Signed hostname result -- %@", signedResult ); if( signedResult ) { validated = true; mdns_forget( &signedResult ); } } } err = _ServiceBrowserAddIPAddress( me, instance, inSockAddr, UpTicksToMicroseconds( nowTicks - instance->gaiStartTicks ), validated ); 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; sb_ulog( kLogLevelTrace, "Starting Browse on interface %d for %s%s", (int32_t) newBrowse->ifIndex, type->name, inDomain->name ); sdRef = me->connection; newBrowse->startTicks = UpTicks(); if( me->validateResults ) { err = DNSServiceBrowseEx( &sdRef, flags, newBrowse->ifIndex, type->name, inDomain->name, &kDNSServiceAttrValidationRequired, _ServiceBrowserBrowseCallback, newBrowse ); require_noerr( err, exit ); } else { 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, const DNSServiceAttribute * inAttr ) { 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, inRegType, inDomain, inIfIndex, inDiscoverTimeUs, me, &newInstance ); require_noerr_quiet( err, exit ); sb_ulog( kLogLevelTrace, "Starting Resolve on interface %d for %s.%s%s", (int32_t) newInstance->ifIndex, inName, inRegType, inDomain ); sdRef = me->connection; newInstance->resolveStartTicks = UpTicks(); if( inAttr ) { err = DNSServiceResolveEx( &sdRef, kDNSServiceFlagsShareConnection, newInstance->ifIndex, inName, inRegType, inDomain, inAttr, _ServiceBrowserResolveCallback, newInstance ); require_noerr( err, exit ); } else { 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 ) _SBServiceInstanceRelease( 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; _SBServiceInstanceForget( &instance ); err = kNoErr; exit: return( err ); } //=========================================================================================================================== // _ServiceBrowserAddIPAddress //=========================================================================================================================== static OSStatus _ServiceBrowserAddIPAddress( ServiceBrowserRef me, SBServiceInstance * inInstance, const struct sockaddr * inSockAddr, uint64_t inResolveTimeUs, Boolean inValidated ) { 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, inValidated, &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, a->validated, &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; _SBServiceInstanceForget( &instance ); } free( inBrowse ); } //=========================================================================================================================== // _SBServiceInstanceCreate //=========================================================================================================================== static OSStatus _SBServiceInstanceCreate( const char * inName, const char * inType, const char * inDomain, 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->refCount = 1; obj->name = strdup( inName ); require_action( obj->name, exit, err = kNoMemoryErr ); ASPrintF( &obj->fqdn, "%s.%s%s", obj->name, inType, inDomain ); require_action( obj->fqdn, exit, err = kNoMemoryErr ); obj->ifIndex = inIfIndex; obj->discoverTimeUs = inDiscoverTimeUs; obj->browser = inBrowser; *outInstance = obj; obj = NULL; err = kNoErr; exit: if( obj ) _SBServiceInstanceRelease( obj ); return( err ); } #if( MDNSRESPONDER_PROJECT ) //=========================================================================================================================== // _SBServiceInstanceRetain //=========================================================================================================================== static void _SBServiceInstanceRetain( SBServiceInstance *inInstance ) { atomic_add_32( &inInstance->refCount, 1 ); } #endif //=========================================================================================================================== // _SBServiceInstanceStop //=========================================================================================================================== static void _SBServiceInstanceStop( SBServiceInstance *inInstance ) { DNSServiceForget( &inInstance->resolve ); DNSServiceForget( &inInstance->gai ); #if( MDNSRESPONDER_PROJECT ) dnssd_getaddrinfo_forget( &inInstance->newGAI ); #endif } //=========================================================================================================================== // _SBServiceInstanceRelease //=========================================================================================================================== static void _SBServiceInstanceRelease( SBServiceInstance *inInstance ) { if( atomic_add_and_fetch_32( &inInstance->refCount, -1 ) == 0 ) { check( !inInstance->resolve ); check( !inInstance->gai ); #if( MDNSRESPONDER_PROJECT ) check( !inInstance->newGAI ); #endif ForgetMem( &inInstance->name ); ForgetMem( &inInstance->fqdn ); ForgetMem( &inInstance->hostname ); ForgetMem( &inInstance->txtPtr ); ForgetSBIPAddressList( &inInstance->ipaddrList ); free( inInstance ); } } //=========================================================================================================================== // _SBIPAddressCreate //=========================================================================================================================== static OSStatus _SBIPAddressCreate( const struct sockaddr *inSockAddr, uint64_t inResolveTimeUs, Boolean inValidated, 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; obj->validated = inValidated; *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, Boolean inValidated, 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; obj->validated = inValidated; *outIPAddress = obj; err = kNoErr; exit: return( err ); } //=========================================================================================================================== // _SBRIPAddressFree //=========================================================================================================================== static void _SBRIPAddressFree( SBRIPAddress *inIPAddress ) { free( inIPAddress ); } //=========================================================================================================================== // _ParseIPv4Address // // Warning: "inBuffer" may be modified even in error cases. // // Note: This was copied from CoreUtils because the StringToIPv4Address function is currently not exported in the framework. //=========================================================================================================================== static OSStatus _ParseIPv4Address( const char *inStr, uint8_t inBuffer[ 4 ], const char **outStr ) { OSStatus err; uint8_t * dst; int segments; int sawDigit; int c; int v; check( inBuffer ); check( outStr ); dst = inBuffer; *dst = 0; sawDigit = 0; segments = 0; for( ; ( c = *inStr ) != '\0'; ++inStr ) { if( isdigit_safe( c ) ) { v = ( *dst * 10 ) + ( c - '0' ); require_action_quiet( v <= 255, exit, err = kRangeErr ); *dst = (uint8_t) v; if( !sawDigit ) { ++segments; require_action_quiet( segments <= 4, exit, err = kOverrunErr ); sawDigit = 1; } } else if( ( c == '.' ) && sawDigit ) { require_action_quiet( segments < 4, exit, err = kMalformedErr ); *++dst = 0; sawDigit = 0; } else { break; } } require_action_quiet( segments == 4, exit, err = kUnderrunErr ); *outStr = inStr; err = kNoErr; exit: return( err ); } //=========================================================================================================================== // _StringToIPv4Address // // Note: This was copied from CoreUtils because the StringToIPv4Address function is currently not exported in the framework. //=========================================================================================================================== static OSStatus _StringToIPv4Address( const char * inStr, StringToIPAddressFlags inFlags, uint32_t * outIP, int * outPort, uint32_t * outSubnet, uint32_t * outRouter, const char ** outStr ) { OSStatus err; uint8_t buf[ 4 ]; int c; uint32_t ip; int hasPort; int port; int hasPrefix; int prefix; uint32_t subnetMask; uint32_t router; require_action( inStr, exit, err = kParamErr ); // Parse the address-only part of the address (e.g. "1.2.3.4"). err = _ParseIPv4Address( inStr, buf, &inStr ); require_noerr_quiet( err, exit ); ip = (uint32_t)( ( buf[ 0 ] << 24 ) | ( buf[ 1 ] << 16 ) | ( buf[ 2 ] << 8 ) | buf[ 3 ] ); c = *inStr; // Parse the port (if any). hasPort = 0; port = 0; if( c == ':' ) { require_action_quiet( !( inFlags & kStringToIPAddressFlagsNoPort ), exit, err = kUnexpectedErr ); while( ( ( c = *( ++inStr ) ) != '\0' ) && ( ( c >= '0' ) && ( c <= '9' ) ) ) port = ( port * 10 ) + ( c - '0' ); require_action_quiet( port <= 65535, exit, err = kRangeErr ); hasPort = 1; } // Parse the prefix length (if any). hasPrefix = 0; prefix = 0; subnetMask = 0; router = 0; if( c == '/' ) { require_action_quiet( !( inFlags & kStringToIPAddressFlagsNoPrefix ), exit, err = kUnexpectedErr ); while( ( ( c = *( ++inStr ) ) != '\0' ) && ( ( c >= '0' ) && ( c <= '9' ) ) ) prefix = ( prefix * 10 ) + ( c - '0' ); require_action_quiet( ( prefix >= 0 ) && ( prefix <= 32 ), exit, err = kRangeErr ); hasPrefix = 1; subnetMask = ( prefix > 0 ) ? ( UINT32_C( 0xFFFFFFFF ) << ( 32 - prefix ) ) : 0; router = ( ip & subnetMask ) | 1; } // Return the results. Only fill in port/prefix/router results if the info was found to allow for defaults. if( outIP ) *outIP = ip; if( outPort && hasPort ) *outPort = port; if( outSubnet && hasPrefix ) *outSubnet = subnetMask; if( outRouter && hasPrefix ) *outRouter = router; if( outStr ) *outStr = inStr; err = kNoErr; exit: return( err ); } //=========================================================================================================================== // _ParseIPv6Address // // Note: Parsed according to the rules specified in RFC 3513. // Warning: "inBuffer" may be modified even in error cases. // // Note: This was copied from CoreUtils because the StringToIPv6Address function is currently not exported in the framework. //=========================================================================================================================== static OSStatus _ParseIPv6Address( const char *inStr, int inAllowV4Mapped, uint8_t inBuffer[ 16 ], const char **outStr ) { // Table to map uppercase hex characters - '0' to their numeric values. // 0 1 2 3 4 5 6 7 8 9 : ; < = > ? @ A B C D E F static const uint8_t kASCIItoHexTable[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, 0, 10, 11, 12, 13, 14, 15 }; OSStatus err; const char * ptr; uint8_t * dst; uint8_t * lim; uint8_t * colonPtr; int c; int sawDigit; unsigned int v; int i; int n; // Pre-zero the address to simplify handling of compressed addresses (e.g. "::1"). for( i = 0; i < 16; ++i ) inBuffer[ i ] = 0; // Special case leading :: (e.g. "::1") to simplify processing later. if( *inStr == ':' ) { ++inStr; require_action_quiet( *inStr == ':', exit, err = kMalformedErr ); } // Parse the address. ptr = inStr; dst = inBuffer; lim = dst + 16; colonPtr = NULL; sawDigit = 0; v = 0; while( ( ( c = *inStr++ ) != '\0' ) && ( c != '%' ) && ( c != '/' ) && ( c != ']' ) ) { if( ( c >= 'a' ) && ( c <= 'f' ) ) c -= ( 'a' - 'A' ); if( ( ( c >= '0' ) && ( c <= '9' ) ) || ( ( c >= 'A' ) && ( c <= 'F' ) ) ) { c -= '0'; check( c < (int) countof( kASCIItoHexTable ) ); v = ( v << 4 ) | kASCIItoHexTable[ c ]; require_action_quiet( v <= 0xFFFF, exit, err = kRangeErr ); sawDigit = 1; continue; } if( c == ':' ) { ptr = inStr; if( !sawDigit ) { require_action_quiet( !colonPtr, exit, err = kMalformedErr ); colonPtr = dst; continue; } require_action_quiet( *inStr != '\0', exit, err = kUnderrunErr ); require_action_quiet( ( dst + 2 ) <= lim, exit, err = kOverrunErr ); *dst++ = (uint8_t)( ( v >> 8 ) & 0xFF ); *dst++ = (uint8_t)( v & 0xFF ); sawDigit = 0; v = 0; continue; } // Handle IPv4-mapped/compatible addresses (e.g. ::FFFF:1.2.3.4). if( inAllowV4Mapped && ( c == '.' ) && ( ( dst + 4 ) <= lim ) ) { err = _ParseIPv4Address( ptr, dst, &inStr ); require_noerr_quiet( err, exit ); dst += 4; sawDigit = 0; ++inStr; // Increment because the code below expects the end to be at "inStr - 1". } break; } if( sawDigit ) { require_action_quiet( ( dst + 2 ) <= lim, exit, err = kOverrunErr ); *dst++ = (uint8_t)( ( v >> 8 ) & 0xFF ); *dst++ = (uint8_t)( v & 0xFF ); } check( dst <= lim ); if( colonPtr ) { require_action_quiet( dst < lim, exit, err = kOverrunErr ); n = (int)( dst - colonPtr ); for( i = 1; i <= n; ++i ) { lim[ -i ] = colonPtr[ n - i ]; colonPtr[ n - i ] = 0; } dst = lim; } require_action_quiet( dst == lim, exit, err = kUnderrunErr ); *outStr = inStr - 1; err = kNoErr; exit: return( err ); } //=========================================================================================================================== // _ParseIPv6Scope // // Note: This was copied from CoreUtils because the StringToIPv6Address function is currently not exported in the framework. //=========================================================================================================================== static OSStatus _ParseIPv6Scope( const char *inStr, uint32_t *outScope, const char **outStr ) { #if( TARGET_OS_POSIX ) OSStatus err; char scopeStr[ 64 ]; char * dst; char * lim; int c; uint32_t scope; const char * ptr; // Copy into a local NULL-terminated string since that is what if_nametoindex expects. dst = scopeStr; lim = dst + ( countof( scopeStr ) - 1 ); while( ( ( c = *inStr ) != '\0' ) && ( c != ':' ) && ( c != '/' ) && ( c != ']' ) && ( dst < lim ) ) { *dst++ = *inStr++; } *dst = '\0'; check( dst <= lim ); // First try to map as a name and if that fails, treat it as a numeric scope. scope = if_nametoindex( scopeStr ); if( scope == 0 ) { for( ptr = scopeStr; ( ( c = *ptr ) >= '0' ) && ( c <= '9' ); ++ptr ) { scope = ( scope * 10 ) + ( ( (uint8_t) c ) - '0' ); } require_action_quiet( c == '\0', exit, err = kMalformedErr ); require_action_quiet( ( ptr != scopeStr ) && ( ( (int)( ptr - scopeStr ) ) <= 10 ), exit, err = kMalformedErr ); } *outScope = scope; *outStr = inStr; err = kNoErr; exit: return( err ); #else OSStatus err; uint32_t scope; const char * start; int c; scope = 0; for( start = inStr; ( ( c = *inStr ) >= '0' ) && ( c <= '9' ); ++inStr ) { scope = ( scope * 10 ) + ( c - '0' ); } require_action_quiet( ( inStr != start ) && ( ( (int)( inStr - start ) ) <= 10 ), exit, err = kMalformedErr ); *outScope = scope; *outStr = inStr; err = kNoErr; exit: return( err ); #endif } //=========================================================================================================================== // _StringToIPv6Address // // Note: This was copied from CoreUtils because the StringToIPv6Address function is currently not exported in the framework. //=========================================================================================================================== static OSStatus _StringToIPv6Address( const char * inStr, StringToIPAddressFlags inFlags, uint8_t outIPv6[ 16 ], uint32_t * outScope, int * outPort, int * outPrefix, const char ** outStr ) { OSStatus err; uint8_t ipv6[ 16 ]; int c; int hasScope; uint32_t scope; int hasPort; int port; int hasPrefix; int prefix; int hasBracket; int i; require_action( inStr, exit, err = kParamErr ); if( *inStr == '[' ) ++inStr; // Skip a leading bracket for []-wrapped addresses (e.g. "[::1]:80"). // Parse the address-only part of the address (e.g. "1::1"). err = _ParseIPv6Address( inStr, !( inFlags & kStringToIPAddressFlagsNoIPv4Mapped ), ipv6, &inStr ); require_noerr_quiet( err, exit ); c = *inStr; // Parse the scope, port, or prefix length. hasScope = 0; scope = 0; hasPort = 0; port = 0; hasPrefix = 0; prefix = 0; hasBracket = 0; for( ;; ) { if( c == '%' ) // Scope (e.g. "%en0" or "%5") { require_action_quiet( !hasScope, exit, err = kMalformedErr ); require_action_quiet( !( inFlags & kStringToIPAddressFlagsNoScope ), exit, err = kUnexpectedErr ); ++inStr; err = _ParseIPv6Scope( inStr, &scope, &inStr ); require_noerr_quiet( err, exit ); hasScope = 1; c = *inStr; } else if( c == ':' ) // Port (e.g. ":80") { require_action_quiet( !hasPort, exit, err = kMalformedErr ); require_action_quiet( !( inFlags & kStringToIPAddressFlagsNoPort ), exit, err = kUnexpectedErr ); while( ( ( c = *( ++inStr ) ) != '\0' ) && ( ( c >= '0' ) && ( c <= '9' ) ) ) port = ( port * 10 ) + ( c - '0' ); require_action_quiet( port <= 65535, exit, err = kRangeErr ); hasPort = 1; } else if( c == '/' ) // Prefix Length (e.g. "/64") { require_action_quiet( !hasPrefix, exit, err = kMalformedErr ); require_action_quiet( !( inFlags & kStringToIPAddressFlagsNoPrefix ), exit, err = kUnexpectedErr ); while( ( ( c = *( ++inStr ) ) != '\0' ) && ( ( c >= '0' ) && ( c <= '9' ) ) ) prefix = ( prefix * 10 ) + ( c - '0' ); require_action_quiet( ( prefix >= 0 ) && ( prefix <= 128 ), exit, err = kRangeErr ); hasPrefix = 1; } else if( c == ']' ) { require_action_quiet( !hasBracket, exit, err = kMalformedErr ); hasBracket = 1; c = *( ++inStr ); } else { break; } } // Return the results. Only fill in scope/port/prefix results if the info was found to allow for defaults. if( outIPv6 ) for( i = 0; i < 16; ++i ) outIPv6[ i ] = ipv6[ i ]; if( outScope && hasScope ) *outScope = scope; if( outPort && hasPort ) *outPort = port; if( outPrefix && hasPrefix ) *outPrefix = prefix; if( outStr ) *outStr = inStr; err = kNoErr; exit: return( err ); } //=========================================================================================================================== // _ParseQuotedEscapedString // // Note: This was copied from CoreUtils because it's currently not exported in the framework. //=========================================================================================================================== static Boolean _ParseQuotedEscapedString( const char * inSrc, const char * inEnd, const char * inDelimiters, char * inBuf, size_t inMaxLen, size_t * outCopiedLen, size_t * outTotalLen, const char ** outSrc ) { const unsigned char * src; const unsigned char * end; unsigned char * dst; unsigned char * lim; unsigned char c; unsigned char c2; size_t totalLen; Boolean singleQuote; Boolean doubleQuote; if( inEnd == NULL ) inEnd = inSrc + strlen( inSrc ); src = (const unsigned char *) inSrc; end = (const unsigned char *) inEnd; dst = (unsigned char *) inBuf; lim = dst + inMaxLen; while( ( src < end ) && isspace_safe( *src ) ) ++src; // Skip leading spaces. if( src >= end ) return( false ); // Parse each argument from the string. // // See for details. totalLen = 0; singleQuote = false; doubleQuote = false; while( src < end ) { c = *src++; if( singleQuote ) { // Single quotes protect everything (even backslashes, newlines, etc.) except single quotes. if( c == '\'' ) { singleQuote = false; continue; } } else if( doubleQuote ) { // Double quotes protect everything except double quotes and backslashes. A backslash can be // used to protect " or \ within double quotes. A backslash-newline pair disappears completely. // A backslash followed by x or X and 2 hex digits (e.g. "\x1f") is stored as that hex byte. // A backslash followed by 3 octal digits (e.g. "\377") is stored as that octal byte. // A backslash that does not precede ", \, x, X, or a newline is taken literally. if( c == '"' ) { doubleQuote = false; continue; } else if( c == '\\' ) { if( src < end ) { c2 = *src; if( ( c2 == '"' ) || ( c2 == '\\' ) ) { ++src; c = c2; } else if( c2 == '\n' ) { ++src; continue; } else if( ( c2 == 'x' ) || ( c2 == 'X' ) ) { ++src; c = c2; if( ( ( end - src ) >= 2 ) && IsHexPair( src ) ) { c = HexPairToByte( src ); src += 2; } } else if( isoctal_safe( c2 ) ) { if( ( ( end - src ) >= 3 ) && IsOctalTriple( src ) ) { c = OctalTripleToByte( src ); src += 3; } } } } } else if( strchr( inDelimiters, c ) ) { break; } else if( c == '\\' ) { // A backslash protects the next character, except a newline, x, X and 2 hex bytes or 3 octal bytes. // A backslash followed by a newline disappears completely. // A backslash followed by x or X and 2 hex digits (e.g. "\x1f") is stored as that hex byte. // A backslash followed by 3 octal digits (e.g. "\377") is stored as that octal byte. if( src < end ) { c = *src; if( c == '\n' ) { ++src; continue; } else if( ( c == 'x' ) || ( c == 'X' ) ) { ++src; if( ( ( end - src ) >= 2 ) && IsHexPair( src ) ) { c = HexPairToByte( src ); src += 2; } } else if( isoctal_safe( c ) ) { if( ( ( end - src ) >= 3 ) && IsOctalTriple( src ) ) { c = OctalTripleToByte( src ); src += 3; } else { ++src; } } else { ++src; } } } else if( c == '\'' ) { singleQuote = true; continue; } else if( c == '"' ) { doubleQuote = true; continue; } if( dst < lim ) { if( inBuf ) *dst = c; ++dst; } ++totalLen; } if( outCopiedLen ) *outCopiedLen = (size_t)( dst - ( (unsigned char *) inBuf ) ); if( outTotalLen ) *outTotalLen = totalLen; if( outSrc ) *outSrc = (const char *) src; return( true ); } //=========================================================================================================================== // _ServerSocketOpenEx2 // // Note: Based on ServerSocketOpenEx() from CoreUtils. Added parameter to not use SO_REUSEPORT. //=========================================================================================================================== static OSStatus _ServerSocketOpenEx2( int inFamily, int inType, int inProtocol, const void * inAddr, int inPort, int * outPort, int inRcvBufSize, Boolean inNoPortReuse, SocketRef * outSock ) { OSStatus err; int port; SocketRef sock; int name; int option; sockaddr_ip sip; socklen_t len; port = ( inPort < 0 ) ? -inPort : inPort; // Negated port number means "try this port, but allow dynamic". sock = socket( inFamily, inType, inProtocol ); err = map_socket_creation_errno( sock ); require_noerr_quiet( err, exit ); #if( defined( SO_NOSIGPIPE ) ) setsockopt( sock, SOL_SOCKET, SO_NOSIGPIPE, &(int){ 1 }, (socklen_t) sizeof( int ) ); #endif err = SocketMakeNonBlocking( sock ); require_noerr( err, exit ); // Set receive buffer size. This has to be done on the listening socket *before* listen is called because // accept does not return until after the window scale option is exchanged during the 3-way handshake. // Since accept returns a new socket, the only way to use a larger window scale option is to set the buffer // size on the listening socket since SO_RCVBUF is inherited by the accepted socket. See UNPv1e3 Section 7.5. err = SocketSetBufferSize( sock, SO_RCVBUF, inRcvBufSize ); check_noerr( err ); // Allow port or address reuse because we may bind separate IPv4 and IPv6 sockets to the same port. if( ( inType != SOCK_DGRAM ) || !inNoPortReuse ) { option = 1; name = ( inType == SOCK_DGRAM ) ? SO_REUSEPORT : SO_REUSEADDR; err = setsockopt( sock, SOL_SOCKET, name, (char *) &option, (socklen_t) sizeof( option ) ); err = map_socket_noerr_errno( sock, err ); require_noerr( err, exit ); } if( inFamily == AF_INET ) { // Bind to the port. If it fails, retry with a dynamic port. memset( &sip.v4, 0, sizeof( sip.v4 ) ); SIN_LEN_SET( &sip.v4 ); sip.v4.sin_family = AF_INET; sip.v4.sin_port = htons( (uint16_t) port ); sip.v4.sin_addr.s_addr = inAddr ? *( (const uint32_t *) inAddr ) : htonl( INADDR_ANY ); err = bind( sock, &sip.sa, (socklen_t) sizeof( sip.v4 ) ); err = map_socket_noerr_errno( sock, err ); if( err && ( inPort < 0 ) ) { sip.v4.sin_port = 0; err = bind( sock, &sip.sa, (socklen_t) sizeof( sip.v4 ) ); err = map_socket_noerr_errno( sock, err ); } require_noerr( err, exit ); } #if( defined( AF_INET6 ) ) else if( inFamily == AF_INET6 ) { // Restrict this socket to IPv6 only because we're going to use a separate socket for IPv4. option = 1; err = setsockopt( sock, IPPROTO_IPV6, IPV6_V6ONLY, (char *) &option, (socklen_t) sizeof( option ) ); err = map_socket_noerr_errno( sock, err ); require_noerr( err, exit ); // Bind to the port. If it fails, retry with a dynamic port. memset( &sip.v6, 0, sizeof( sip.v6 ) ); SIN6_LEN_SET( &sip.v6 ); sip.v6.sin6_family = AF_INET6; sip.v6.sin6_port = htons( (uint16_t) port ); sip.v6.sin6_addr = inAddr ? *( (const struct in6_addr *) inAddr ) : in6addr_any; err = bind( sock, &sip.sa, (socklen_t) sizeof( sip.v6 ) ); err = map_socket_noerr_errno( sock, err ); if( err && ( inPort < 0 ) ) { sip.v6.sin6_port = 0; err = bind( sock, &sip.sa, (socklen_t) sizeof( sip.v6 ) ); err = map_socket_noerr_errno( sock, err ); } require_noerr( err, exit ); } #endif else { dlogassert( "Unsupported family: %d", inFamily ); err = kUnsupportedErr; goto exit; } if( inType == SOCK_STREAM ) { err = listen( sock, SOMAXCONN ); err = map_socket_noerr_errno( sock, err ); if( err ) { err = listen( sock, 5 ); err = map_socket_noerr_errno( sock, err ); require_noerr( err, exit ); } } if( outPort ) { len = (socklen_t) sizeof( sip ); err = getsockname( sock, &sip.sa, &len ); err = map_socket_noerr_errno( sock, err ); require_noerr( err, exit ); *outPort = SockAddrGetPort( &sip ); } *outSock = sock; sock = kInvalidSocketRef; exit: ForgetSocket( &sock ); return( err ); } //=========================================================================================================================== // _memdup // // Note: This was copied from CoreUtils because it's currently not exported in the framework. //=========================================================================================================================== static void * _memdup( const void *inPtr, size_t inLen ) { void * mem; mem = malloc( ( inLen > 0 ) ? inLen : 1 ); // If inLen is 0, use 1 since malloc( 0 ) is not well defined. require( mem, exit ); if( inLen > 0 ) memcpy( mem, inPtr, inLen ); exit: return( mem ); } //=========================================================================================================================== // _memicmp // // Note: This was copied from CoreUtils because it's currently not exported in the framework. //=========================================================================================================================== static int _memicmp( const void *inP1, const void *inP2, size_t inLen ) { const unsigned char * p1; const unsigned char * e1; const unsigned char * p2; int c1; int c2; p1 = (const unsigned char *) inP1; e1 = p1 + inLen; p2 = (const unsigned char *) inP2; while( p1 < e1 ) { c1 = *p1++; c2 = *p2++; c1 = tolower( c1 ); c2 = tolower( c2 ); if( c1 < c2 ) return( -1 ); if( c1 > c2 ) return( 1 ); } return( 0 ); } //=========================================================================================================================== // _FNV1 // // Note: This was copied from CoreUtils because it's currently not exported in the framework. //=========================================================================================================================== static 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 ); } //=========================================================================================================================== // _UIint32FromArgString //=========================================================================================================================== static OSStatus _UInt32FromArgString( const char * const inArgStr, const char * const inArgName, uint32_t * const outValue ) { OSStatus err; int64_t i64; i64 = _StringToInt64( inArgStr, &err ); require_noerr_quiet( err, exit ); require_action_quiet( ( i64 >= 0 ) && ( i64 <= UINT32_MAX ), exit, err = kRangeErr ); *outValue = (uint32_t) i64; exit: if( err ) FPrintF( stderr, "error: Invalid %s '%s'. Valid range is [0, %u].\n", inArgName, inArgStr, UINT32_MAX ); return( err ); } //=========================================================================================================================== // _UnixTimeToDateAndTimeString //=========================================================================================================================== static char * _UnixTimeToDateAndTimeString( const int64_t inTimeSecs, char * const inBufPtr, const size_t inBufLen ) { OSStatus err; // If the number of seconds is representable as a time_t value, then use the standard library functions to create a // local date and time string. If not (e.g., time_t may be a 32-bit signed integer type), then try to create a UTC // date and time string with CoreUtils as a fallback. if( ( (time_t) inTimeSecs ) == inTimeSecs ) { struct tm tm; struct tm * tmPtr; size_t len; const time_t timeSecs = (time_t) inTimeSecs; tmPtr = localtime_r( &timeSecs, &tm ); require_action( tmPtr, exit, err = kUnknownErr ); // Local date and time in ISO 8601 date, hours, minutes, seconds, and time zone format. len = strftime( inBufPtr, inBufLen, "%Y-%m-%dT%H:%M:%S%z", tmPtr ); require_action_quiet( len > 0, exit, err = kUnknownErr ); } else { const int64_t epochSecs = INT64_C_safe( kDaysToUnixEpoch ) * kSecondsPerDay; const int64_t year10KSecs = ( YearToDays( 10000 ) - INT64_C_safe( kDaysToUnixEpoch ) ) * kSecondsPerDay; int year, month, day, hour, minute, second; // Ensure that the time's year is representable by four digits, i.e., before 10,000 A.D. require_action_quiet( inTimeSecs >= -epochSecs, exit, err = kNotHandledErr ); require_action_quiet( inTimeSecs < year10KSecs, exit, err = kNotHandledErr ); // UTC date and time in ISO 8601 date, hours, minutes, and seconds format. SecondsToYMD_HMS( epochSecs + inTimeSecs, &year, &month, &day, &hour, &minute, &second ); SNPrintF( inBufPtr, inBufLen, "%04d-%02d-%02dT%02d:%02d:%02dZ", year, month, day, hour, minute, second ); } err = kNoErr; exit: if( err && ( inBufLen > 0 ) ) inBufPtr[ 0 ] = '\0'; return( err ? NULL : inBufPtr ); } //=========================================================================================================================== // _DNSSDSourceVersionToCString //=========================================================================================================================== static char * _DNSSDSourceVersionToCString( const uint32_t inVersion, char * const inBufPtr, const size_t inBufLen ) { uint32_t x, y, z; // Version strings are of the form x[.y[.z]]. // Newer version strings are encoded as (x * 1000000) + (y * 1000) + z, where 0 ≤ y,z ≤ 999. // Older version strings were encoded as (x * 10000) + (y * 100) + z, where 0 ≤ y,z ≤ 99. if( inVersion > DNS_SD_ORIGINAL_ENCODING_VERSION_NUMBER_MAX ) { x = inVersion / 1000000; y = ( inVersion / 1000 ) % 1000; z = inVersion % 1000; } else { x = inVersion / 10000; y = ( inVersion / 100 ) % 100; z = inVersion % 100; } if( z > 0 ) SNPrintF( inBufPtr, inBufLen, "%u.%u.%u", x, y, z ); else if( y > 0 ) SNPrintF( inBufPtr, inBufLen, "%u.%u", x, y ); else SNPrintF( inBufPtr, inBufLen, "%u", x ); return( inBufPtr ); } //=========================================================================================================================== // _StdOutIsTTY //=========================================================================================================================== static Boolean _StdOutIsTTY( void ) { static dispatch_once_t sOnce = 0; static Boolean sIsTTY = false; dispatch_once( &sOnce, ^{ sIsTTY = isatty( STDOUT_FILENO ) ? true : false; }); return( sIsTTY ); } #if( TARGET_OS_IOS ) //=========================================================================================================================== // _StdErrIsTTY //=========================================================================================================================== static Boolean _StdErrIsTTY( void ) { static dispatch_once_t sOnce = 0; static Boolean sIsTTY = false; dispatch_once( &sOnce, ^{ sIsTTY = isatty( STDERR_FILENO ) ? true : false; }); return( sIsTTY ); } #endif //=========================================================================================================================== // _PrintValidatedToStdOut //=========================================================================================================================== static void _PrintValidatedToStdOut( const char * const inPrefix, const Boolean inValidated, const char * const inSuffix ) { FPrintF_safe( stdout, "%s%s%svalidated%s%s", inPrefix, _StdOutIsTTY() ? ( inValidated ? kANSIGreen : kANSIRed ) : "", inValidated ? "" : "not ", _StdOutIsTTY() ? kANSINormal : "", inSuffix ); } //=========================================================================================================================== // _DNSProtocolIsSecure //=========================================================================================================================== static Boolean _DNSProtocolIsSecure( const DNSProtocol inProtocol ) { switch( inProtocol ) { case kDNSProtocol_Do53: return( false ); case kDNSProtocol_DoT: return( true ); case kDNSProtocol_DoH: return( true ); } return( false ); } //=========================================================================================================================== // _DNSProtocolToString //=========================================================================================================================== static const char * _DNSProtocolToString( const DNSProtocol inProtocol ) { switch( inProtocol ) { case kDNSProtocol_Do53: return( kDNSProtocolStr_Do53 ); case kDNSProtocol_DoT: return( kDNSProtocolStr_DoT ); case kDNSProtocol_DoH: return( kDNSProtocolStr_DoH ); } return( NULL ); }