/* Copyright (c) 2016-2018 Apple Inc. All rights reserved. dnssdutil is a command-line utility for testing the DNS-SD API. */ #include // Include early. #include #include #include #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 #endif #if( DNSSDUTIL_INCLUDE_DNSCRYPT ) #include "tweetnacl.h" // TweetNaCl from . #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 //=========================================================================================================================== // 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" "NonBrowsable\0" \ "\x0E" "ShareConnection\0" \ "\x0F" "SuppressUnusable\0" \ "\x10" "Timeout\0" \ "\x11" "IncludeP2P\0" \ "\x12" "WakeOnResolve\0" \ "\x13" "BackgroundTrafficClass\0" \ "\x14" "IncludeAWDL\0" \ "\x15" "Validate\0" \ "\x16" "UnicastResponse\0" \ "\x17" "ValidateOptional\0" \ "\x18" "WakeOnlyService\0" \ "\x19" "ThresholdOne\0" \ "\x1A" "ThresholdFinder\0" \ "\x1B" "DenyCellular\0" \ "\x1C" "ServiceIndex\0" \ "\x1D" "DenyExpensive\0" \ "\x1E" "PathEvaluationDone\0" \ "\x00" #define kDNSServiceProtocolDescriptors \ "\x00" "IPv4\0" \ "\x01" "IPv6\0" \ "\x04" "UDP\0" \ "\x05" "TCP\0" \ "\x00" #define kBadDNSServiceRef ( (DNSServiceRef)(intptr_t) -1 ) //=========================================================================================================================== // DNS //=========================================================================================================================== #define kDNSPort 53 #define kDNSCompressionOffsetMax 0x3FFF #define kDNSMaxUDPMessageSize 512 #define kDNSMaxTCPMessageSize UINT16_MAX #define kDomainLabelLengthMax 63 #define kDomainNameLengthMax 256 typedef struct { uint8_t id[ 2 ]; uint8_t flags[ 2 ]; uint8_t questionCount[ 2 ]; uint8_t answerCount[ 2 ]; uint8_t authorityCount[ 2 ]; uint8_t additionalCount[ 2 ]; } DNSHeader; #define kDNSHeaderLength 12 check_compile_time( sizeof( DNSHeader ) == kDNSHeaderLength ); #define DNSHeaderGetID( HDR ) ReadBig16( ( HDR )->id ) #define DNSHeaderGetFlags( HDR ) ReadBig16( ( HDR )->flags ) #define DNSHeaderGetQuestionCount( HDR ) ReadBig16( ( HDR )->questionCount ) #define DNSHeaderGetAnswerCount( HDR ) ReadBig16( ( HDR )->answerCount ) #define DNSHeaderGetAuthorityCount( HDR ) ReadBig16( ( HDR )->authorityCount ) #define DNSHeaderGetAdditionalCount( HDR ) ReadBig16( ( HDR )->additionalCount ) #define DNSHeaderSetID( HDR, X ) WriteBig16( ( HDR )->id, (X) ) #define DNSHeaderSetFlags( HDR, X ) WriteBig16( ( HDR )->flags, (X) ) #define DNSHeaderSetQuestionCount( HDR, X ) WriteBig16( ( HDR )->questionCount, (X) ) #define DNSHeaderSetAnswerCount( HDR, X ) WriteBig16( ( HDR )->answerCount, (X) ) #define DNSHeaderSetAuthorityCount( HDR, X ) WriteBig16( ( HDR )->authorityCount, (X) ) #define DNSHeaderSetAdditionalCount( HDR, X ) WriteBig16( ( HDR )->additionalCount, (X) ) // Single-bit DNS header fields #define kDNSHeaderFlag_Response ( 1 << 15 ) // QR (bit 15), Query (0)/Response (1) #define kDNSHeaderFlag_AuthAnswer ( 1 << 10 ) // AA (bit 10), Authoritative Answer #define kDNSHeaderFlag_Truncation ( 1 << 9 ) // TC (bit 9), TrunCation #define kDNSHeaderFlag_RecursionDesired ( 1 << 8 ) // RD (bit 8), Recursion Desired #define kDNSHeaderFlag_RecursionAvailable ( 1 << 7 ) // RA (bit 7), Recursion Available #define kDNSHeaderFlag_Z ( 1 << 6 ) // Z (bit 6), Reserved (must be zero) #define kDNSHeaderFlag_AuthenticData ( 1 << 5 ) // AD (bit 5), Authentic Data (RFC 2535, Section 6) #define kDNSHeaderFlag_CheckingDisabled ( 1 << 4 ) // CD (bit 4), Checking Disabled (RFC 2535, Section 6) // OPCODE (bits 14-11), Operation Code #define DNSFlagsGetOpCode( FLAGS ) ( ( (FLAGS) >> 11 ) & 0x0FU ) #define DNSFlagsSetOpCode( FLAGS, OPCODE ) \ do{ (FLAGS) = ( (FLAGS) & ~0x7800U ) | ( ( (OPCODE) & 0x0FU ) << 11 ); } while( 0 ) #define kDNSOpCode_Query 0 // QUERY (standard query) #define kDNSOpCode_InverseQuery 1 // IQUERY (inverse query) #define kDNSOpCode_Status 2 // STATUS #define kDNSOpCode_Notify 4 // NOTIFY #define kDNSOpCode_Update 5 // UPDATE // RCODE (bits 3-0), Response Code #define DNSFlagsGetRCode( FLAGS ) ( (FLAGS) & 0x0FU ) #define DNSFlagsSetRCode( FLAGS, RCODE ) \ do{ (FLAGS) = ( (FLAGS) & ~0x000FU ) | ( (RCODE) & 0x0FU ); } while( 0 ) #define kDNSRCode_NoError 0 #define kDNSRCode_FormatError 1 #define kDNSRCode_ServerFailure 2 #define kDNSRCode_NXDomain 3 #define kDNSRCode_NotImplemented 4 #define kDNSRCode_Refused 5 typedef struct { uint8_t type[ 2 ]; uint8_t class[ 2 ]; } DNSQuestionFixedFields; check_compile_time( sizeof( DNSQuestionFixedFields ) == 4 ); #define DNSQuestionFixedFieldsInit( FIELDS, QTYPE, QCLASS ) \ do { WriteBig16( (FIELDS)->type, QTYPE ); WriteBig16( (FIELDS)->class, QCLASS ); } while( 0 ) #define DNSQuestionFixedFieldsGetType( FIELDS ) ReadBig16( (FIELDS)->type ) #define DNSQuestionFixedFieldsGetClass( FIELDS ) ReadBig16( (FIELDS)->class ) typedef struct { uint8_t type[ 2 ]; uint8_t class[ 2 ]; uint8_t ttl[ 4 ]; uint8_t rdlength[ 2 ]; } DNSRecordFixedFields; check_compile_time( sizeof( DNSRecordFixedFields ) == 10 ); #define DNSRecordFixedFieldsInit( FIELDS, TYPE, CLASS, TTL, RDLENGTH ) \ do \ { \ WriteBig16( (FIELDS)->type, TYPE ); \ WriteBig16( (FIELDS)->class, CLASS ); \ WriteBig32( (FIELDS)->ttl, TTL ); \ WriteBig16( (FIELDS)->rdlength, RDLENGTH ); \ \ } while( 0 ) //=========================================================================================================================== // mDNS //=========================================================================================================================== #define kMDNSPort 5353 #define kDefaultMDNSMessageID 0 #define kDefaultMDNSQueryFlags 0 #define kQClassUnicastResponseBit ( 1U << 15 ) #define kRRClassCacheFlushBit ( 1U << 15 ) //=========================================================================================================================== // Test DNS Server //=========================================================================================================================== // IPv4 address block 203.0.113.0/24 (TEST-NET-3) is reserved for documentation. See . #define kTestDNSServerBaseAddrV4 UINT32_C( 0xCB007100 ) // 203.0.113.0 // IPv6 address block 2001:db8::/32 is reserved for documentation. See . #define kTestDNSServerBaseAddrV6 \ ( (const uint8_t *) "\x20\x01\x0D\xB8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" ) //=========================================================================================================================== // Gerneral Command Options //=========================================================================================================================== // Command option macros #define Command( NAME, CALLBACK, SUB_OPTIONS, SHORT_HELP, IS_NOTCOMMON ) \ CLI_COMMAND_EX( NAME, CALLBACK, SUB_OPTIONS, (IS_NOTCOMMON) ? kCLIOptionFlags_NotCommon : kCLIOptionFlags_None, \ (SHORT_HELP), NULL ) #define kRequiredOptionSuffix " [REQUIRED]" #define MultiStringOptionEx( SHORT_CHAR, LONG_NAME, VAL_PTR, VAL_COUNT_PTR, ARG_HELP, SHORT_HELP, IS_REQUIRED, LONG_HELP ) \ CLI_OPTION_MULTI_STRING_EX( SHORT_CHAR, LONG_NAME, VAL_PTR, VAL_COUNT_PTR, ARG_HELP, \ (IS_REQUIRED) ? SHORT_HELP kRequiredOptionSuffix : SHORT_HELP, \ (IS_REQUIRED) ? kCLIOptionFlags_Required : kCLIOptionFlags_None, LONG_HELP ) #define MultiStringOption( SHORT_CHAR, LONG_NAME, VAL_PTR, VAL_COUNT_PTR, ARG_HELP, SHORT_HELP, IS_REQUIRED ) \ MultiStringOptionEx( SHORT_CHAR, LONG_NAME, VAL_PTR, VAL_COUNT_PTR, ARG_HELP, SHORT_HELP, IS_REQUIRED, NULL ) #define IntegerOption( SHORT_CHAR, LONG_NAME, VAL_PTR, ARG_HELP, SHORT_HELP, IS_REQUIRED ) \ CLI_OPTION_INTEGER_EX( SHORT_CHAR, LONG_NAME, VAL_PTR, ARG_HELP, \ (IS_REQUIRED) ? SHORT_HELP kRequiredOptionSuffix : SHORT_HELP, \ (IS_REQUIRED) ? kCLIOptionFlags_Required : kCLIOptionFlags_None, NULL ) #define BooleanOption( SHORT_CHAR, LONG_NAME, VAL_PTR, SHORT_HELP ) \ CLI_OPTION_BOOLEAN( (SHORT_CHAR), (LONG_NAME), (VAL_PTR), (SHORT_HELP), NULL ) #define StringOptionEx( SHORT_CHAR, LONG_NAME, VAL_PTR, ARG_HELP, SHORT_HELP, IS_REQUIRED, LONG_HELP ) \ CLI_OPTION_STRING_EX( SHORT_CHAR, LONG_NAME, VAL_PTR, ARG_HELP, \ (IS_REQUIRED) ? SHORT_HELP kRequiredOptionSuffix : SHORT_HELP, \ (IS_REQUIRED) ? kCLIOptionFlags_Required : kCLIOptionFlags_None, LONG_HELP ) #define StringOption( SHORT_CHAR, LONG_NAME, VAL_PTR, ARG_HELP, SHORT_HELP, IS_REQUIRED ) \ StringOptionEx( SHORT_CHAR, LONG_NAME, VAL_PTR, ARG_HELP, SHORT_HELP, IS_REQUIRED, NULL ) #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_BrowseDomains = false; static int gDNSSDFlag_DenyCellular = false; static int gDNSSDFlag_DenyExpensive = false; static int gDNSSDFlag_ForceMulticast = false; static int gDNSSDFlag_IncludeAWDL = false; static int gDNSSDFlag_NoAutoRename = false; static int gDNSSDFlag_PathEvaluationDone = false; static int gDNSSDFlag_RegistrationDomains = false; static int gDNSSDFlag_ReturnIntermediates = false; static int gDNSSDFlag_Shared = false; static int gDNSSDFlag_SuppressUnusable = false; static int gDNSSDFlag_Timeout = false; static int gDNSSDFlag_UnicastResponse = false; static int gDNSSDFlag_Unique = false; static int gDNSSDFlag_WakeOnResolve = 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_DenyCellular() DNSSDFlagOption( 'C', DenyCellular ) #define DNSSDFlagsOption_DenyExpensive() DNSSDFlagOption( 'E', DenyExpensive ) #define DNSSDFlagsOption_ForceMulticast() DNSSDFlagOption( 'M', ForceMulticast ) #define DNSSDFlagsOption_IncludeAWDL() DNSSDFlagOption( 'A', IncludeAWDL ) #define DNSSDFlagsOption_NoAutoRename() DNSSDFlagOption( 'N', NoAutoRename ) #define DNSSDFlagsOption_PathEvalDone() DNSSDFlagOption( 'P', PathEvaluationDone ) #define DNSSDFlagsOption_ReturnIntermediates() DNSSDFlagOption( 'I', ReturnIntermediates ) #define DNSSDFlagsOption_Shared() DNSSDFlagOption( 'S', Shared ) #define DNSSDFlagsOption_SuppressUnusable() DNSSDFlagOption( 'S', SuppressUnusable ) #define DNSSDFlagsOption_Timeout() DNSSDFlagOption( 'T', Timeout ) #define DNSSDFlagsOption_UnicastResponse() DNSSDFlagOption( 'U', UnicastResponse ) #define DNSSDFlagsOption_Unique() DNSSDFlagOption( 'U', Unique ) #define DNSSDFlagsOption_WakeOnResolve() DNSSDFlagOption( 'W', WakeOnResolve ) // Interface option 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/rdata.bin\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 (w/escaped hex bytes) string: string:'\\x09color=red'\n" \ "TXT record keys and values txt: txt:'vers=1.0,lang=en\\,es\\,fr,passreq'\n" #define RecordDataSection() CLI_SECTION( kRecordDataSection_Name, kRecordDataSection_Text ) //=========================================================================================================================== // Browse Command Options //=========================================================================================================================== static char ** gBrowse_ServiceTypes = NULL; static size_t gBrowse_ServiceTypesCount = 0; static const char * gBrowse_Domain = NULL; static int gBrowse_DoResolve = false; static int gBrowse_QueryTXT = false; static int gBrowse_TimeLimitSecs = 0; static CLIOption kBrowseOpts[] = { InterfaceOption(), MultiStringOption( 't', "type", &gBrowse_ServiceTypes, &gBrowse_ServiceTypesCount, "service type", "Service type(s), e.g., \"_ssh._tcp\".", true ), StringOption( 'd', "domain", &gBrowse_Domain, "domain", "Domain in which to browse for the service type(s).", false ), CLI_OPTION_GROUP( "Flags" ), DNSSDFlagsOption(), DNSSDFlagsOption_IncludeAWDL(), CLI_OPTION_GROUP( "Operation" ), ConnectionOptions(), BooleanOption( 0 , "resolve", &gBrowse_DoResolve, "Resolve service instances." ), BooleanOption( 0 , "queryTXT", &gBrowse_QueryTXT, "Query TXT records of service instances." ), IntegerOption( 'l', "timeLimit", &gBrowse_TimeLimitSecs, "seconds", "Specifies the max duration of the browse operation. Use '0' for no time limit.", false ), ConnectionSection(), CLI_OPTION_END() }; //=========================================================================================================================== // GetAddrInfo Command Options //=========================================================================================================================== static const char * gGetAddrInfo_Name = NULL; static int gGetAddrInfo_ProtocolIPv4 = false; static int gGetAddrInfo_ProtocolIPv6 = false; static int gGetAddrInfo_OneShot = false; static int gGetAddrInfo_TimeLimitSecs = 0; static CLIOption kGetAddrInfoOpts[] = { InterfaceOption(), StringOption( 'n', "name", &gGetAddrInfo_Name, "domain name", "Domain name to resolve.", true ), BooleanOption( 0 , "ipv4", &gGetAddrInfo_ProtocolIPv4, "Use kDNSServiceProtocol_IPv4." ), BooleanOption( 0 , "ipv6", &gGetAddrInfo_ProtocolIPv6, "Use kDNSServiceProtocol_IPv6." ), CLI_OPTION_GROUP( "Flags" ), DNSSDFlagsOption(), DNSSDFlagsOption_DenyCellular(), DNSSDFlagsOption_DenyExpensive(), DNSSDFlagsOption_PathEvalDone(), DNSSDFlagsOption_ReturnIntermediates(), DNSSDFlagsOption_SuppressUnusable(), DNSSDFlagsOption_Timeout(), CLI_OPTION_GROUP( "Operation" ), ConnectionOptions(), BooleanOption( 'o', "oneshot", &gGetAddrInfo_OneShot, "Finish after first set of results." ), IntegerOption( 'l', "timeLimit", &gGetAddrInfo_TimeLimitSecs, "seconds", "Maximum duration of the GetAddrInfo operation. Use '0' for no time limit.", false ), ConnectionSection(), CLI_OPTION_END() }; //=========================================================================================================================== // QueryRecord Command Options //=========================================================================================================================== static const char * gQueryRecord_Name = NULL; static const char * gQueryRecord_Type = NULL; static int gQueryRecord_OneShot = false; static int gQueryRecord_TimeLimitSecs = 0; static int gQueryRecord_RawRData = false; static CLIOption kQueryRecordOpts[] = { InterfaceOption(), StringOption( 'n', "name", &gQueryRecord_Name, "domain name", "Full domain name of record to query.", true ), StringOption( 't', "type", &gQueryRecord_Type, "record type", "Record type by name (e.g., TXT, SRV, etc.) or number.", true ), CLI_OPTION_GROUP( "Flags" ), DNSSDFlagsOption(), DNSSDFlagsOption_IncludeAWDL(), DNSSDFlagsOption_ForceMulticast(), DNSSDFlagsOption_Timeout(), DNSSDFlagsOption_ReturnIntermediates(), DNSSDFlagsOption_SuppressUnusable(), DNSSDFlagsOption_UnicastResponse(), DNSSDFlagsOption_DenyCellular(), DNSSDFlagsOption_DenyExpensive(), DNSSDFlagsOption_PathEvalDone(), CLI_OPTION_GROUP( "Operation" ), ConnectionOptions(), BooleanOption( 'o', "oneshot", &gQueryRecord_OneShot, "Finish after first set of results." ), IntegerOption( 'l', "timeLimit", &gQueryRecord_TimeLimitSecs, "seconds", "Maximum duration of the query record operation. Use '0' for no time limit.", false ), BooleanOption( 0 , "raw", &gQueryRecord_RawRData, "Show record data as a hexdump." ), ConnectionSection(), CLI_OPTION_END() }; //=========================================================================================================================== // Register Command Options //=========================================================================================================================== static const char * gRegister_Name = NULL; static const char * gRegister_Type = NULL; static const char * gRegister_Domain = NULL; static int gRegister_Port = 0; static const char * gRegister_TXT = NULL; static int gRegister_LifetimeMs = -1; static const char ** gAddRecord_Types = NULL; static size_t gAddRecord_TypesCount = 0; static const char ** gAddRecord_Data = NULL; static size_t gAddRecord_DataCount = 0; static const char ** gAddRecord_TTLs = NULL; static size_t gAddRecord_TTLsCount = 0; static const char * gUpdateRecord_Data = NULL; static int gUpdateRecord_DelayMs = 0; static int gUpdateRecord_TTL = 0; static CLIOption kRegisterOpts[] = { InterfaceOption(), StringOption( 'n', "name", &gRegister_Name, "service name", "Name of service.", false ), StringOption( 't', "type", &gRegister_Type, "service type", "Service type, e.g., \"_ssh._tcp\".", true ), StringOption( 'd', "domain", &gRegister_Domain, "domain", "Domain in which to advertise the service.", false ), IntegerOption( 'p', "port", &gRegister_Port, "port number", "Service's port number.", true ), StringOption( 0 , "txt", &gRegister_TXT, "record data", "The TXT record data. See " kRecordDataSection_Name " below.", false ), CLI_OPTION_GROUP( "Flags" ), DNSSDFlagsOption(), DNSSDFlagsOption_IncludeAWDL(), DNSSDFlagsOption_NoAutoRename(), CLI_OPTION_GROUP( "Operation" ), IntegerOption( 'l', "lifetime", &gRegister_LifetimeMs, "ms", "Lifetime of the service registration in milliseconds.", false ), CLI_OPTION_GROUP( "Options for updating the registered service's primary TXT record with DNSServiceUpdateRecord()\n" ), StringOption( 0 , "updateData", &gUpdateRecord_Data, "record data", "Record data for the record update. See " kRecordDataSection_Name " below.", false ), IntegerOption( 0 , "updateDelay", &gUpdateRecord_DelayMs, "ms", "Number of milliseconds after registration to wait before record update.", false ), IntegerOption( 0 , "updateTTL", &gUpdateRecord_TTL, "seconds", "Time-to-live of the updated record.", false ), CLI_OPTION_GROUP( "Options for adding extra record(s) to the registered service with DNSServiceAddRecord()\n" ), MultiStringOption( 0 , "addType", &gAddRecord_Types, &gAddRecord_TypesCount, "record type", "Type of additional record by name (e.g., TXT, SRV, etc.) or number.", false ), MultiStringOptionEx( 0 , "addData", &gAddRecord_Data, &gAddRecord_DataCount, "record data", "Additional record's data. See " kRecordDataSection_Name " below.", false, NULL ), MultiStringOption( 0 , "addTTL", &gAddRecord_TTLs, &gAddRecord_TTLsCount, "seconds", "Time-to-live of additional record in seconds. Use '0' for default.", false ), RecordDataSection(), CLI_OPTION_END() }; //=========================================================================================================================== // RegisterRecord Command Options //=========================================================================================================================== static const char * gRegisterRecord_Name = NULL; static const char * gRegisterRecord_Type = NULL; static const char * gRegisterRecord_Data = NULL; static int gRegisterRecord_TTL = 0; static int gRegisterRecord_LifetimeMs = -1; static const char * gRegisterRecord_UpdateData = NULL; static int gRegisterRecord_UpdateDelayMs = 0; static int gRegisterRecord_UpdateTTL = 0; static CLIOption kRegisterRecordOpts[] = { InterfaceOption(), StringOption( 'n', "name", &gRegisterRecord_Name, "record name", "Fully qualified domain name of record.", true ), StringOption( 't', "type", &gRegisterRecord_Type, "record type", "Record type by name (e.g., TXT, PTR, A) or number.", true ), StringOption( 'd', "data", &gRegisterRecord_Data, "record data", "The record data. See " kRecordDataSection_Name " below.", false ), IntegerOption( 0 , "ttl", &gRegisterRecord_TTL, "seconds", "Time-to-live in seconds. Use '0' for default.", false ), CLI_OPTION_GROUP( "Flags" ), DNSSDFlagsOption(), DNSSDFlagsOption_IncludeAWDL(), DNSSDFlagsOption_Shared(), DNSSDFlagsOption_Unique(), CLI_OPTION_GROUP( "Operation" ), IntegerOption( 'l', "lifetime", &gRegisterRecord_LifetimeMs, "ms", "Lifetime of the service registration in milliseconds.", false ), CLI_OPTION_GROUP( "Options for updating the registered record with DNSServiceUpdateRecord()\n" ), StringOption( 0 , "updateData", &gRegisterRecord_UpdateData, "record data", "Record data for the record update.", false ), IntegerOption( 0 , "updateDelay", &gRegisterRecord_UpdateDelayMs, "ms", "Number of milliseconds after registration to wait before record update.", false ), IntegerOption( 0 , "updateTTL", &gRegisterRecord_UpdateTTL, "seconds", "Time-to-live of the updated record.", false ), RecordDataSection(), CLI_OPTION_END() }; //=========================================================================================================================== // Resolve Command Options //=========================================================================================================================== static char * gResolve_Name = NULL; static char * gResolve_Type = NULL; static char * gResolve_Domain = NULL; static int gResolve_TimeLimitSecs = 0; static CLIOption kResolveOpts[] = { InterfaceOption(), StringOption( 'n', "name", &gResolve_Name, "service name", "Name of the service instance to resolve.", true ), StringOption( 't', "type", &gResolve_Type, "service type", "Type of the service instance to resolve.", true ), StringOption( 'd', "domain", &gResolve_Domain, "domain", "Domain of the service instance to resolve.", true ), CLI_OPTION_GROUP( "Flags" ), DNSSDFlagsOption(), DNSSDFlagsOption_ForceMulticast(), DNSSDFlagsOption_IncludeAWDL(), DNSSDFlagsOption_ReturnIntermediates(), 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 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() }; //=========================================================================================================================== // BrowseAll Command Options //=========================================================================================================================== static const char * gBrowseAll_Domain = NULL; static char ** gBrowseAll_ServiceTypes = NULL; static size_t gBrowseAll_ServiceTypesCount = 0; static int gBrowseAll_BrowseTimeSecs = 5; static int gBrowseAll_MaxConnectTimeSecs = 0; static CLIOption kBrowseAllOpts[] = { InterfaceOption(), StringOption( 'd', "domain", &gBrowseAll_Domain, "domain", "Domain in which to browse for the service.", false ), MultiStringOption( 't', "type", &gBrowseAll_ServiceTypes, &gBrowseAll_ServiceTypesCount, "service type", "Service type(s), e.g., \"_ssh._tcp\". All services are browsed for if none is specified.", false ), CLI_OPTION_GROUP( "Flags" ), DNSSDFlagsOption_IncludeAWDL(), CLI_OPTION_GROUP( "Operation" ), IntegerOption( 'b', "browseTime", &gBrowseAll_BrowseTimeSecs, "seconds", "Amount of time to spend browsing. (Default: 5 seconds)", false ), IntegerOption( 'c', "maxConnectTime", &gBrowseAll_MaxConnectTimeSecs, "seconds", "Max duration of connection attempts. If <= 0, then no connections are attempted. (Default: 0 seconds)", false ), 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_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 value is 'A'.", false ), StringOption( 's', "server", &gDNSQuery_Server, "IP address", "DNS server's IPv4 or IPv6 address.", kDNSQueryServerOptionIsRequired ), IntegerOption( 'l', "timeLimit", &gDNSQuery_TimeLimitSecs, "seconds", "Specifies query time limit. Use '-1' for no limit and '0' to exit immediately after sending.", false ), BooleanOption( 0 , "tcp", &gDNSQuery_UseTCP, "Send the DNS query via TCP instead of UDP." ), IntegerOption( 'f', "flags", &gDNSQuery_Flags, "flags", "16-bit value for DNS header flags/codes field. Default value is 0x0100 (Recursion Desired).", false ), BooleanOption( 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() }; //=========================================================================================================================== // PIDToUUID Command Options //=========================================================================================================================== static int gPIDToUUID_PID = 0; static CLIOption kPIDToUUIDOpts[] = { IntegerOption( 'p', "pid", &gPIDToUUID_PID, "PID", "Process ID.", true ), CLI_OPTION_END() }; //=========================================================================================================================== // DNSServer Command Options //=========================================================================================================================== #define kDNSServerInfoText_Intro \ "The DNS server answers certain queries in the d.test. domain. Responses are dynamically generated based on the\n" \ "presence of special labels in the query's QNAME. There are currently seven types of special labels that can be\n" \ "used to generate specific responses: Alias labels, Alias-TTL labels, Count labels, Tag labels, TTL labels, the\n" \ "IPv4 label, and the IPv6 label.\n" #define kDNSServerInfoText_NameExistence \ "A name is considered to exist if and only if it ends in d.test., and the other labels, if\n" \ "any, consist of\n" \ "\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" #define kDNSServerInfoText_ResourceRecords \ "Currently, the server only provides CNAME, A, and AAAA records.\n" \ "\n" \ "Names that exist and begin with an Alias or Alias-TTL label are aliases of canonical names, i.e., they're the\n" \ "names of CNAME records. See \"Alias Labels\" and \"Alias-TTL Labels\" for details.\n" \ "\n" \ "Names that exist and have an IPv4 label have at least one A record, but no AAAA records. Names that exist and\n" \ "have an IPv6 label, have at least one AAAA record, but no A records. All other names that exist have at least\n" \ "one A record and at least one AAAA record. See \"Count Labels\" for how the number of address records for a\n" \ "given name is determined.\n" \ "\n" \ "A records contain IPv4 addresses in the 203.0.113.0/24 block, while AAAA records contain IPv6 addresses in the\n" \ "2001:db8::/32 block. Both of these address blocks are reserved for documentation.\n" \ "See and .\n" \ "\n" \ "Unless otherwise specified, all resource records will use a default TTL. The default TTL can be set with the\n" \ "--defaultTTL option. See \"Alias-TTL Labels\" and \"TTL Labels\" for details on how to query for records with\n" \ "specific TTL values.\n" #define 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 exist and its first label is Alias label \"alias-N\", then the response will contain exactly N CNAME\n" \ "records:\n" \ "\n" \ " 1. For each i in [3 .. N], the response will contain a CNAME record whose name is identical to QNAME,\n" \ " except that the first label is \"alias-i\" instead, and whose RDATA is the name of the other CNAME\n" \ " record whose name has \"alias-(i - 1)\" as its first label.\n" \ "\n" \ " 2. The response will contain a CNAME record whose name is identical to QNAME, except that the first label\n" \ " is \"alias-2\" instead, and whose RDATA is the name identical to QNAME, except that the first label is\n" \ " \"alias\" instead.\n" \ "\n" \ " 3. The response will contain a CNAME record whose name is identical to QNAME, except that the first label\n" \ " is \"alias\" instead, and whose RDATA is the name identical to QNAME stripped of its first label.\n" \ "\n" \ "If QNAME exist and its first label is Alias label \"alias\", then the response will contain a single CNAME\n" \ "record. The CNAME record's name will be equal to QNAME and its RDATA will be the name identical to QNAME\n" \ "stripped of its first label.\n" \ "\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" #define kDNSServerInfoText_AliasTTLLabel \ "Alias-TTL labels are of the form \"alias-ttl-T_1[-T_2[...-T_N]]\", where each T_i is an integer in\n" \ "[0 .. 2^31 - 1] and N is a positive integer bounded by the size of the maximum legal label length (63 octets).\n" \ "\n" \ "If QNAME exists and its first label is Alias-TTL label \"alias-ttl-T_1...-T_N\", then the response will contain\n" \ "exactly N CNAME records:\n" \ "\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" #define kDNSServerInfoText_CountLabel \ "Count labels are of the form \"count-N_1\" or \"count-N_1-N_2\", where N_1 is an integer in [1 .. 255] and N_2\n" \ "is an integer in [N_1 .. 255].\n" \ "\n" \ "If QNAME exists, contains Count label \"count-N\", and has the type of address records specified by QTYPE, then\n" \ "the response will contain exactly N address records:\n" \ "\n" \ " 1. For i in [1 .. N], the response will contain an address record of type QTYPE whose name is equal to\n" \ " QNAME and whose RDATA is an address equal to a constant base address + i.\n" \ "\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 exists, contains Count label \"count-N_1-N_2\", and has the type of address records specified by\n" \ "QTYPE, then the response will contain exactly N_1 address records:\n" \ "\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::c\n" \ " count-3-100.ttl-20.d.test. 20 IN AAAA 2001:db8::3a\n" \ " count-3-100.ttl-20.d.test. 20 IN AAAA 2001:db8::4f\n" \ "\n" \ "If QNAME exists, but doesn't have the type of address records specified by QTYPE, then the response will\n" \ "contain no address records, regardless of whether it contains a Count label.\n" \ "\n" \ "QNAMEs that exist, but don't have a Count label are treated as though they contain a count label equal to\n" \ "\"count-1\".\n" #define 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" #define kDNSServerInfoText_TTLLabel \ "TTL labels are of the form \"ttl-T\", where T is an integer in [0 .. 2^31 - 1].\n" \ "\n" \ "If the name specified by QNAME exists, and contains TTL label \"ttl-T\", then all non-CNAME records contained\n" \ "in the response will have a TTL value equal to T.\n" #define kDNSServerInfoText_IPv4Label \ "The IPv4 label is \"ipv4\". See \"Resource Records\" for the affect of this label.\n" #define kDNSServerInfoText_IPv6Label \ "The IPv6 label is \"ipv6\". See \"Resource Records\" for the affect of this label.\n" #define kDNSServerDefaultTTL 60 static int gDNSServer_LoopbackOnly = false; static int gDNSServer_Foreground = false; static int gDNSServer_ResponseDelayMs = 0; static int gDNSServer_DefaultTTL = kDNSServerDefaultTTL; #if( TARGET_OS_DARWIN ) static const char * gDNSServer_FollowPID = NULL; #endif static CLIOption kDNSServerOpts[] = { BooleanOption( 'l', "loopback", &gDNSServer_LoopbackOnly, "Bind to to the loopback interface." ), BooleanOption( 'f', "foreground", &gDNSServer_Foreground, "Directlog output to stdout instead of system logging." ), 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 ), #if( TARGET_OS_DARWIN ) StringOption( 0 , "followPID", &gDNSServer_FollowPID, "pid", "Exit when the process (usually the parent proccess) specified by PID exits.", false ), #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_OPTION_END() }; static void DNSServerCmd( void ); //=========================================================================================================================== // Test Command Options //=========================================================================================================================== static const char * gGAIPerf_TestSuite = NULL; static int gGAIPerf_CallDelayMs = 10; static int gGAIPerf_ServerDelayMs = 10; static int gGAIPerf_DefaultIterCount = 100; static const char * gGAIPerf_OutputFilePath = NULL; static const char * gGAIPerf_OutputFormat = "json"; static int gGAIPerf_OutputAppendNewLine = false; static void GAIPerfCmd( void ); #define kGAIPerfSectionTitle_TestSuiteBasic "Test Suite \"Basic\"" #define kGAIPerfSectionText_TestSuiteBasic \ "This test suite consists of the following three test cases:\n" \ "\n" \ "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 kGAIPerfSectionTitle_TestSuiteAdvanced "Test Suite \"Advanced\"" #define kGAIPerfSectionText_TestSuiteAdvanced \ "This test suite consists of 33 test cases. Test cases 1 through 32 can be described in the following way\n" \ "\n" \ "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, 'basic' and 'advanced', which are described below.\n" "\n" ), StringOption( 'o', "output", &gGAIPerf_OutputFilePath, "path", "Path of the file to write test results to instead of standard output (stdout).", false ), StringOptionEx( 'f', "format", &gGAIPerf_OutputFormat, "format", "Specifies the test results output format. (default: json)", false, "\n" "Use 'json' for JavaScript Object Notation (JSON).\n" "Use 'xml' for property list XML version 1.0.\n" "Use 'binary' for property list binary version 1.0.\n" "\n" ), BooleanOption( 'n', "appendNewline", &gGAIPerf_OutputAppendNewLine, "If the output format is JSON, output a trailing newline character." ), IntegerOption( 0 , "callDelay", &gGAIPerf_CallDelayMs, "ms", "Time to wait before calling DNSServiceGetAddrInfo() in milliseconds. (default: 10)", false ), IntegerOption( 0 , "responseDelay", &gGAIPerf_ServerDelayMs, "ms", "Additional delay in milliseconds to have the test DNS server apply to responses. (default: 0)", false ), IntegerOption( 'i', "iterations", &gGAIPerf_DefaultIterCount, "count", "The default number of test case iterations. (default: 100)", false ), CLI_SECTION( kGAIPerfSectionTitle_TestSuiteBasic, kGAIPerfSectionText_TestSuiteBasic ), CLI_SECTION( kGAIPerfSectionTitle_TestSuiteAdvanced, kGAIPerfSectionText_TestSuiteAdvanced ), CLI_OPTION_END() }; static CLIOption kTestOpts[] = { Command( "gaiperf", GAIPerfCmd, kGAIPerfOpts, "Run DNSServiceGetAddrInfo() performance tests.", 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 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 ), 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() }; #endif // TARGET_OS_DARWIN //=========================================================================================================================== // 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 ); static void PIDToUUIDCmd( void ); static void DaemonVersionCmd( void ); static CLIOption kGlobalOpts[] = { CLI_OPTION_CALLBACK_EX( 'V', "version", VersionOptionCallback, NULL, NULL, kCLIOptionFlags_NoArgument | kCLIOptionFlags_GlobalOnly, "Displays the version of this tool.", NULL ), CLI_OPTION_HELP(), // Common commands. Command( "browse", BrowseCmd, kBrowseOpts, "Uses DNSServiceBrowse() to browse for one or more service types.", false ), Command( "getAddrInfo", GetAddrInfoCmd, kGetAddrInfoOpts, "Uses DNSServiceGetAddrInfo() to resolve a hostname to IP addresses.", false ), Command( "queryRecord", QueryRecordCmd, kQueryRecordOpts, "Uses DNSServiceQueryRecord() to query for an arbitrary DNS record.", false ), Command( "register", RegisterCmd, kRegisterOpts, "Uses DNSServiceRegister() to register a service.", false ), Command( "registerRecord", RegisterRecordCmd, kRegisterRecordOpts, "Uses DNSServiceRegisterRecord() to register a record.", false ), Command( "resolve", ResolveCmd, kResolveOpts, "Uses DNSServiceResolve() to resolve a service.", false ), Command( "reconfirm", ReconfirmCmd, kReconfirmOpts, "Uses DNSServiceReconfirmRecord() to reconfirm a record.", false ), Command( "getaddrinfo-posix", GetAddrInfoPOSIXCmd, kGetAddrInfoPOSIXOpts, "Uses getaddrinfo() to resolve a hostname to IP addresses.", false ), Command( "reverseLookup", ReverseLookupCmd, kReverseLookupOpts, "Uses DNSServiceQueryRecord() to perform a reverse IP address lookup.", false ), Command( "portMapping", PortMappingCmd, kPortMappingOpts, "Uses DNSServiceNATPortMappingCreate() to create a port mapping.", false ), 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( "pid2uuid", PIDToUUIDCmd, kPIDToUUIDOpts, "Prints the UUID of a process.", true ), Command( "server", DNSServerCmd, kDNSServerOpts, "DNS server for testing.", true ), Command( "test", NULL, kTestOpts, "Commands for testing DNS-SD.", true ), Command( "ssdp", NULL, kSSDPOpts, "Commands for testing Simple Service Discovery Protocol (SSDP).", true ), #if( TARGET_OS_DARWIN ) Command( "legacy", NULL, kLegacyOpts, "Commands for legacy non-DNS-SD API.", true ), Command( "dnsconfig", NULL, kDNSConfigOpts, "Add/remove a supplemental resolver entry to/from the system's DNS configuration.", true ), #endif 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 int PrintFTimestampHandler( PrintFContext * inContext, PrintFFormat * inFormat, PrintFVAList * inArgs, void * inUserContext ); static int PrintFDNSMessageHandler( PrintFContext * inContext, PrintFFormat * inFormat, PrintFVAList * inArgs, void * inUserContext ); static DNSServiceFlags GetDNSSDFlagsFromOpts( void ); typedef enum { kConnectionType_None = 0, kConnectionType_Normal = 1, kConnectionType_DelegatePID = 2, kConnectionType_DelegateUUID = 3 } ConnectionType; typedef struct { ConnectionType type; union { int32_t pid; uint8_t uuid[ 16 ]; } delegate; } ConnectionDesc; static OSStatus CreateConnectionFromArgString( const char * inString, dispatch_queue_t inQueue, DNSServiceRef * outSDRef, ConnectionDesc * outDesc ); static OSStatus InterfaceIndexFromArgString( const char *inString, uint32_t *outIndex ); static OSStatus RecordDataFromArgString( const char *inString, uint8_t **outDataPtr, size_t *outDataLen ); static OSStatus RecordTypeFromArgString( const char *inString, uint16_t *outValue ); static OSStatus RecordClassFromArgString( const char *inString, uint16_t *outValue ); #define kInterfaceNameBufLen ( Max( IF_NAMESIZE, 16 ) + 1 ) static char * InterfaceIndexToName( uint32_t inIfIndex, char inNameBuf[ kInterfaceNameBufLen ] ); static const char * RecordTypeToString( unsigned int inValue ); static OSStatus DNSMessageExtractDomainName( const uint8_t * inMsgPtr, size_t inMsgLen, const uint8_t * inNamePtr, uint8_t inBuf[ kDomainNameLengthMax ], const uint8_t ** outNextPtr ); static OSStatus DNSMessageExtractDomainNameString( const void * inMsgPtr, size_t inMsgLen, const void * inNamePtr, char inBuf[ kDNSServiceMaxDomainName ], const uint8_t ** outNextPtr ); static OSStatus DNSMessageExtractRecord( const uint8_t * inMsgPtr, size_t inMsgLen, const uint8_t * inPtr, uint8_t inNameBuf[ kDomainNameLengthMax ], uint16_t * outType, uint16_t * outClass, uint32_t * outTTL, const uint8_t ** outRDataPtr, size_t * outRDataLen, const uint8_t ** outPtr ); static OSStatus DNSMessageGetAnswerSection( const uint8_t *inMsgPtr, size_t inMsgLen, const uint8_t **outPtr ); static OSStatus DNSRecordDataToString( const void * inRDataPtr, size_t inRDataLen, unsigned int inRDataType, const void * inMsgPtr, size_t inMsgLen, char ** outString ); static OSStatus DomainNameAppendString( uint8_t inDomainName[ kDomainNameLengthMax ], const char * inString, uint8_t ** outEndPtr ); static Boolean DomainNameEqual( const uint8_t *inName1, const uint8_t *inName2 ); static size_t DomainNameLength( const uint8_t *inName ); static OSStatus DomainNameFromString( uint8_t inDomainName[ kDomainNameLengthMax ], const char * inString, uint8_t ** outEndPtr ); static OSStatus DomainNameToString( const uint8_t * inDomainName, const uint8_t * inEnd, char inBuf[ kDNSServiceMaxDomainName ], const uint8_t ** outNextPtr ); static OSStatus DNSMessageToText( const uint8_t * inMsgPtr, size_t inMsgLen, Boolean inIsMDNS, Boolean inPrintRaw, char ** outText ); #define kDNSQueryMessageMaxLen ( kDNSHeaderLength + kDomainNameLengthMax + 4 ) static OSStatus WriteDNSQueryMessage( uint8_t inMsg[ kDNSQueryMessageMaxLen ], uint16_t inMsgID, uint16_t inFlags, const char * inQName, uint16_t inQType, uint16_t inQClass, size_t * outMsgLen ); // Dispatch helpers typedef void ( *DispatchHandler )( void *inContext ); static OSStatus DispatchSignalSourceCreate( int inSignal, DispatchHandler inEventHandler, void * inContext, dispatch_source_t * outSource ); static OSStatus 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 ); static OSStatus DispatchProcessMonitorCreate( pid_t inPID, unsigned long inFlags, dispatch_queue_t inQueue, DispatchHandler inEventHandler, DispatchHandler inCancelHandler, void * inContext, dispatch_source_t * outMonitor ); static const char * ServiceTypeDescription( const char *inName ); typedef struct { SocketRef sock; // Socket. void * userContext; // User context. int32_t refCount; // Reference count. } SocketContext; static OSStatus SocketContextCreate( SocketRef inSock, void * inUserContext, SocketContext **outContext ); static SocketContext * SocketContextRetain( SocketContext *inContext ); static void SocketContextRelease( SocketContext *inContext ); static void SocketContextCancelHandler( void *inContext ); #define ForgetSocketContext( X ) ForgetCustom( X, SocketContextRelease ) static OSStatus StringToInt32( const char *inString, int32_t *outValue ); static OSStatus StringToUInt32( const char *inString, uint32_t *outValue ); static OSStatus StringToLongLong( const char *inString, long long *outValue ); 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 ); typedef uint64_t MicroTime64; static MicroTime64 GetCurrentMicroTime( void ); // Gets the number of milliseconds since 1970-01-01T00:00:00Z #define AddRmvString( X ) ( ( (X) & kDNSServiceFlagsAdd ) ? "Add" : "Rmv" ) #define Unused( X ) (void)(X) //=========================================================================================================================== // main //=========================================================================================================================== int main( int argc, const char **argv ) { OSStatus err; // Route DebugServices logging output to stderr. dlog_control( "DebugServices:output=file;stderr" ); PrintFRegisterExtension( "du:time", PrintFTimestampHandler, NULL ); PrintFRegisterExtension( "du:dnsmsg", PrintFDNSMessageHandler, NULL ); CLIInit( argc, argv ); err = CLIParse( kGlobalOpts, kCLIFlags_None ); if( err ) exit( 1 ); return( gExitCode ); } //=========================================================================================================================== // VersionOptionCallback //=========================================================================================================================== static OSStatus VersionOptionCallback( CLIOption *inOption, const char *inArg, int inUnset ) { const char * srcVers; #if( MDNSRESPONDER_PROJECT ) char srcStr[ 16 ]; #endif Unused( inOption ); Unused( inArg ); Unused( inUnset ); #if( MDNSRESPONDER_PROJECT ) srcVers = SourceVersionToCString( _DNS_SD_H, srcStr ); #else srcVers = DNSSDUTIL_SOURCE_VERSION; #endif FPrintF( stdout, "%s version %v (%s)\n", gProgramName, kDNSSDUtilNumVersion, srcVers ); return( kEndingErr ); } //=========================================================================================================================== // BrowseCmd //=========================================================================================================================== typedef struct BrowseResolveOp BrowseResolveOp; struct BrowseResolveOp { BrowseResolveOp * next; // Next resolve operation in list. DNSServiceRef sdRef; // sdRef of the DNSServiceResolve or DNSServiceQueryRecord operation. char * fullName; // Full name of the service to resolve. uint32_t interfaceIndex; // Interface index of the DNSServiceResolve or DNSServiceQueryRecord operation. }; typedef struct { DNSServiceRef mainRef; // Main sdRef for shared connection. DNSServiceRef * opRefs; // Array of sdRefs for individual Browse operarions. size_t opRefsCount; // Count of array of sdRefs for non-shared connections. const char * domain; // Domain for DNSServiceBrowse operation(s). DNSServiceFlags flags; // Flags for DNSServiceBrowse operation(s). char ** serviceTypes; // Array of service types to browse for. size_t serviceTypesCount; // Count of array of service types to browse for. int timeLimitSecs; // Time limit of DNSServiceBrowse operation in seconds. BrowseResolveOp * resolveList; // List of resolve and/or TXT record query operations. uint32_t ifIndex; // Interface index of DNSServiceBrowse operation(s). Boolean printedHeader; // True if results header has been printed. Boolean doResolve; // True if service instances are to be resolved. Boolean doResolveTXTOnly; // True if TXT records of service instances are to be queried. } BrowseContext; static void BrowsePrintPrologue( const BrowseContext *inContext ); static void BrowseContextFree( BrowseContext *inContext ); static OSStatus BrowseResolveOpCreate( const char *inFullName, uint32_t inInterfaceIndex, BrowseResolveOp **outOp ); static void BrowseResolveOpFree( BrowseResolveOp *inOp ); static void DNSSD_API BrowseCallback( DNSServiceRef inSDRef, DNSServiceFlags inFlags, uint32_t inInterfaceIndex, DNSServiceErrorType inError, const char * inName, const char * inRegType, const char * inDomain, void * inContext ); static void DNSSD_API BrowseResolveCallback( DNSServiceRef inSDRef, DNSServiceFlags inFlags, uint32_t inInterfaceIndex, DNSServiceErrorType inError, const char * inFullName, const char * inHostname, uint16_t inPort, uint16_t inTXTLen, const unsigned char * inTXTPtr, void * inContext ); static void DNSSD_API BrowseQueryRecordCallback( DNSServiceRef inSDRef, DNSServiceFlags inFlags, uint32_t inInterfaceIndex, DNSServiceErrorType inError, const char * inFullName, uint16_t inType, uint16_t inClass, uint16_t inRDataLen, const void * inRDataPtr, uint32_t inTTL, void * inContext ); static void BrowseCmd( void ) { OSStatus err; size_t i; BrowseContext * context = NULL; dispatch_source_t signalSource = NULL; int useMainConnection; // Set up SIGINT handler. signal( SIGINT, SIG_IGN ); err = DispatchSignalSourceCreate( SIGINT, Exit, kExitReason_SIGINT, &signalSource ); require_noerr( err, exit ); dispatch_resume( signalSource ); // Create context. context = (BrowseContext *) calloc( 1, sizeof( *context ) ); require_action( context, exit, err = kNoMemoryErr ); context->opRefs = (DNSServiceRef *) calloc( gBrowse_ServiceTypesCount, sizeof( DNSServiceRef ) ); require_action( context->opRefs, exit, err = kNoMemoryErr ); context->opRefsCount = gBrowse_ServiceTypesCount; // Check command parameters. if( gBrowse_TimeLimitSecs < 0 ) { FPrintF( stderr, "Invalid time limit: %d seconds.\n", gBrowse_TimeLimitSecs ); err = kParamErr; goto exit; } // Create main connection. if( gConnectionOpt ) { err = CreateConnectionFromArgString( gConnectionOpt, dispatch_get_main_queue(), &context->mainRef, NULL ); require_noerr_quiet( err, exit ); useMainConnection = true; } else { useMainConnection = false; } // Get flags. context->flags = GetDNSSDFlagsFromOpts(); if( useMainConnection ) context->flags |= kDNSServiceFlagsShareConnection; // Get interface. err = InterfaceIndexFromArgString( gInterface, &context->ifIndex ); require_noerr_quiet( err, exit ); // Set remaining parameters. context->serviceTypes = gBrowse_ServiceTypes; context->serviceTypesCount = gBrowse_ServiceTypesCount; context->domain = gBrowse_Domain; context->doResolve = gBrowse_DoResolve ? true : false; context->timeLimitSecs = gBrowse_TimeLimitSecs; context->doResolveTXTOnly = gBrowse_QueryTXT ? true : false; // Print prologue. BrowsePrintPrologue( context ); // Start operation(s). for( i = 0; i < context->serviceTypesCount; ++i ) { DNSServiceRef sdRef; sdRef = useMainConnection ? context->mainRef : kBadDNSServiceRef; 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; char fullName[ kDNSServiceMaxDomainName ]; struct timeval now; Unused( inSDRef ); gettimeofday( &now, NULL ); err = inError; require_noerr( err, exit ); if( !context->printedHeader ) { FPrintF( stdout, "%-26s A/R Flags IF %-20s %-20s Instance Name\n", "Timestamp", "Domain", "Service Type" ); context->printedHeader = true; } FPrintF( stdout, "%{du:time} %-3s %5X %2d %-20s %-20s %s\n", &now, AddRmvString( inFlags ), inFlags, (int32_t) inInterfaceIndex, inDomain, inRegType, inName ); if( !context->doResolve && !context->doResolveTXTOnly ) goto exit; err = DNSServiceConstructFullName( fullName, inName, inRegType, inDomain ); require_noerr( err, exit ); if( inFlags & kDNSServiceFlagsAdd ) { DNSServiceRef sdRef; DNSServiceFlags flags; err = BrowseResolveOpCreate( fullName, inInterfaceIndex, &newOp ); require_noerr( err, exit ); if( context->mainRef ) { sdRef = context->mainRef; flags = kDNSServiceFlagsShareConnection; } else { flags = 0; } if( context->doResolve ) { err = DNSServiceResolve( &sdRef, flags, inInterfaceIndex, inName, inRegType, inDomain, BrowseResolveCallback, NULL ); require_noerr( err, exit ); } else { err = DNSServiceQueryRecord( &sdRef, flags, inInterfaceIndex, fullName, kDNSServiceType_TXT, kDNSServiceClass_IN, BrowseQueryRecordCallback, NULL ); require_noerr( err, exit ); } newOp->sdRef = sdRef; if( !context->mainRef ) { err = DNSServiceSetDispatchQueue( newOp->sdRef, dispatch_get_main_queue() ); require_noerr( err, exit ); } for( p = &context->resolveList; *p; p = &( *p )->next ) {} *p = newOp; newOp = NULL; } else { BrowseResolveOp * resolveOp; for( p = &context->resolveList; ( resolveOp = *p ) != NULL; p = &resolveOp->next ) { if( ( resolveOp->interfaceIndex == inInterfaceIndex ) && ( strcasecmp( resolveOp->fullName, fullName ) == 0 ) ) { break; } } if( resolveOp ) { *p = resolveOp->next; BrowseResolveOpFree( resolveOp ); } } exit: if( newOp ) BrowseResolveOpFree( newOp ); if( err ) exit( 1 ); } //=========================================================================================================================== // BrowseQueryRecordCallback //=========================================================================================================================== static void DNSSD_API BrowseQueryRecordCallback( DNSServiceRef inSDRef, DNSServiceFlags inFlags, uint32_t inInterfaceIndex, DNSServiceErrorType inError, const char * inFullName, uint16_t inType, uint16_t inClass, uint16_t inRDataLen, const void * inRDataPtr, uint32_t inTTL, void * inContext ) { OSStatus err; struct timeval now; Unused( inSDRef ); Unused( inClass ); Unused( inTTL ); Unused( inContext ); gettimeofday( &now, NULL ); err = inError; require_noerr( err, exit ); require_action( inType == kDNSServiceType_TXT, exit, err = kTypeErr ); FPrintF( stdout, "%{du:time} %s %s TXT on interface %d\n TXT: %#{txt}\n", &now, AddRmvString( inFlags ), inFullName, (int32_t) inInterfaceIndex, inRDataPtr, (size_t) inRDataLen ); 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 ) { 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 ); } } //=========================================================================================================================== // GetAddrInfoCmd //=========================================================================================================================== typedef struct { DNSServiceRef mainRef; // Main sdRef for shared connection. DNSServiceRef opRef; // sdRef for the DNSServiceGetAddrInfo operation. const char * name; // Hostname to resolve. DNSServiceFlags flags; // Flags argument for DNSServiceGetAddrInfo(). DNSServiceProtocol protocols; // Protocols argument for DNSServiceGetAddrInfo(). uint32_t ifIndex; // Interface index argument for DNSServiceGetAddrInfo(). int timeLimitSecs; // Time limit for the DNSServiceGetAddrInfo() operation in seconds. Boolean printedHeader; // True if the results header has been printed. Boolean oneShotMode; // True if command is done after the first set of results (one-shot mode). Boolean needIPv4; // True if in one-shot mode and an IPv4 result is needed. Boolean needIPv6; // True if in one-shot mode and an IPv6 result is needed. } GetAddrInfoContext; static void GetAddrInfoPrintPrologue( const GetAddrInfoContext *inContext ); static void GetAddrInfoContextFree( GetAddrInfoContext *inContext ); static void DNSSD_API GetAddrInfoCallback( DNSServiceRef inSDRef, DNSServiceFlags inFlags, uint32_t inInterfaceIndex, DNSServiceErrorType inError, const char * inHostname, const struct sockaddr * inSockAddr, uint32_t inTTL, void * inContext ); static void GetAddrInfoCmd( void ) { OSStatus err; DNSServiceRef sdRef; GetAddrInfoContext * context = NULL; dispatch_source_t signalSource = NULL; int useMainConnection; // Set up SIGINT handler. signal( SIGINT, SIG_IGN ); err = DispatchSignalSourceCreate( SIGINT, Exit, kExitReason_SIGINT, &signalSource ); require_noerr( err, exit ); dispatch_resume( signalSource ); // Check command parameters. if( gGetAddrInfo_TimeLimitSecs < 0 ) { FPrintF( stderr, "Invalid time limit: %d s.\n", gGetAddrInfo_TimeLimitSecs ); err = kParamErr; goto exit; } // Create context. context = (GetAddrInfoContext *) calloc( 1, sizeof( *context ) ); require_action( context, exit, err = kNoMemoryErr ); // Create main connection. if( gConnectionOpt ) { err = CreateConnectionFromArgString( gConnectionOpt, dispatch_get_main_queue(), &context->mainRef, NULL ); require_noerr_quiet( err, exit ); useMainConnection = true; } else { useMainConnection = false; } // Get flags. context->flags = GetDNSSDFlagsFromOpts(); if( useMainConnection ) context->flags |= kDNSServiceFlagsShareConnection; // Get interface. err = InterfaceIndexFromArgString( gInterface, &context->ifIndex ); require_noerr_quiet( err, exit ); // Set remaining parameters. context->name = gGetAddrInfo_Name; context->timeLimitSecs = gGetAddrInfo_TimeLimitSecs; if( gGetAddrInfo_ProtocolIPv4 ) context->protocols |= kDNSServiceProtocol_IPv4; if( gGetAddrInfo_ProtocolIPv6 ) context->protocols |= kDNSServiceProtocol_IPv6; if( gGetAddrInfo_OneShot ) { context->oneShotMode = true; context->needIPv4 = ( gGetAddrInfo_ProtocolIPv4 || !gGetAddrInfo_ProtocolIPv6 ) ? true : false; context->needIPv6 = ( gGetAddrInfo_ProtocolIPv6 || !gGetAddrInfo_ProtocolIPv4 ) ? true : false; } // Print prologue. GetAddrInfoPrintPrologue( context ); // Start operation. 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 ) ? "No Such Record (A)" : "No Such Record (AAAA)"; } if( !context->printedHeader ) { FPrintF( stdout, "%-26s A/R Flags IF %-32s %-38s %6s\n", "Timestamp", "Hostname", "Address", "TTL" ); context->printedHeader = true; } FPrintF( stdout, "%{du:time} %s %5X %2d %-32s %-38s %6u\n", &now, AddRmvString( inFlags ), inFlags, (int32_t) inInterfaceIndex, inHostname, addrStr, inTTL ); if( context->oneShotMode ) { if( inFlags & kDNSServiceFlagsAdd ) { if( inSockAddr->sa_family == AF_INET ) context->needIPv4 = false; else context->needIPv6 = false; } if( !( inFlags & kDNSServiceFlagsMoreComing ) && !context->needIPv4 && !context->needIPv6 ) { Exit( kExitReason_OneShotDone ); } } exit: if( err ) exit( 1 ); } //=========================================================================================================================== // QueryRecordCmd //=========================================================================================================================== typedef struct { DNSServiceRef mainRef; // Main sdRef for shared connection. DNSServiceRef opRef; // sdRef for the DNSServiceQueryRecord operation. const char * recordName; // Resource record name argument for DNSServiceQueryRecord(). DNSServiceFlags flags; // Flags argument for DNSServiceQueryRecord(). uint32_t ifIndex; // Interface index argument for DNSServiceQueryRecord(). int timeLimitSecs; // Time limit for the DNSServiceQueryRecord() operation in seconds. uint16_t recordType; // Resource record type argument for DNSServiceQueryRecord(). Boolean printedHeader; // True if the results header was printed. Boolean oneShotMode; // True if command is done after the first set of results (one-shot mode). Boolean gotRecord; // True if in one-shot mode and received at least one record of the desired type. Boolean printRawRData; // True if RDATA results are not to be formatted 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; QueryRecordContext * context = NULL; dispatch_source_t signalSource = NULL; int useMainConnection; // Set up SIGINT handler. signal( SIGINT, SIG_IGN ); err = DispatchSignalSourceCreate( SIGINT, Exit, kExitReason_SIGINT, &signalSource ); require_noerr( err, exit ); dispatch_resume( signalSource ); // Create context. context = (QueryRecordContext *) calloc( 1, sizeof( *context ) ); require_action( context, exit, err = kNoMemoryErr ); // Check command parameters. if( gQueryRecord_TimeLimitSecs < 0 ) { FPrintF( stderr, "Invalid time limit: %d seconds.\n", gQueryRecord_TimeLimitSecs ); err = kParamErr; goto exit; } // Create main connection. if( gConnectionOpt ) { err = CreateConnectionFromArgString( gConnectionOpt, dispatch_get_main_queue(), &context->mainRef, NULL ); require_noerr_quiet( err, exit ); useMainConnection = true; } else { useMainConnection = false; } // Get flags. context->flags = GetDNSSDFlagsFromOpts(); if( useMainConnection ) context->flags |= kDNSServiceFlagsShareConnection; // Get interface. err = InterfaceIndexFromArgString( gInterface, &context->ifIndex ); require_noerr_quiet( err, exit ); // Get record type. err = RecordTypeFromArgString( gQueryRecord_Type, &context->recordType ); require_noerr( err, exit ); // Set remaining parameters. context->recordName = gQueryRecord_Name; context->timeLimitSecs = gQueryRecord_TimeLimitSecs; context->oneShotMode = gQueryRecord_OneShot ? true : false; context->printRawRData = gQueryRecord_RawRData ? true : false; // Print prologue. QueryRecordPrintPrologue( context ); // Start operation. 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 ); } //=========================================================================================================================== // QueryRecordContextFree //=========================================================================================================================== static void QueryRecordContextFree( QueryRecordContext *inContext ) { DNSServiceForget( &inContext->opRef ); DNSServiceForget( &inContext->mainRef ); free( inContext ); } //=========================================================================================================================== // QueryRecordPrintPrologue //=========================================================================================================================== static void QueryRecordPrintPrologue( const QueryRecordContext *inContext ) { const int timeLimitSecs = inContext->timeLimitSecs; char ifName[ kInterfaceNameBufLen ]; InterfaceIndexToName( inContext->ifIndex, ifName ); FPrintF( stdout, "Flags: %#{flags}\n", inContext->flags, kDNSServiceFlagsDescriptors ); FPrintF( stdout, "Interface: %d (%s)\n", (int32_t) inContext->ifIndex, ifName ); FPrintF( stdout, "Name: %s\n", inContext->recordName ); FPrintF( stdout, "Type: %s (%u)\n", RecordTypeToString( inContext->recordType ), inContext->recordType ); FPrintF( stdout, "Mode: %s\n", inContext->oneShotMode ? "one-shot" : "continuous" ); FPrintF( stdout, "Time limit: " ); if( timeLimitSecs > 0 ) FPrintF( stdout, "%d second%?c\n", timeLimitSecs, timeLimitSecs != 1, 's' ); else FPrintF( stdout, "∞\n" ); FPrintF( stdout, "Start time: %{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 * rdataStr = NULL; 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( inError == kDNSServiceErr_NoSuchRecord ) { ASPrintF( &rdataStr, "No Such Record" ); } else { if( !context->printRawRData ) DNSRecordDataToString( inRDataPtr, inRDataLen, inType, NULL, 0, &rdataStr ); if( !rdataStr ) { ASPrintF( &rdataStr, "%#H", inRDataPtr, inRDataLen, INT_MAX ); require_action( rdataStr, exit, err = kNoMemoryErr ); } } if( !context->printedHeader ) { FPrintF( stdout, "%-26s A/R Flags IF %-32s %-5s %-5s %6s RData\n", "Timestamp", "Name", "Type", "Class", "TTL" ); context->printedHeader = true; } FPrintF( stdout, "%{du:time} %-3s %5X %2d %-32s %-5s %?-5s%?5u %6u %s\n", &now, AddRmvString( inFlags ), inFlags, (int32_t) inInterfaceIndex, inFullName, RecordTypeToString( inType ), ( inClass == kDNSServiceClass_IN ), "IN", ( inClass != kDNSServiceClass_IN ), inClass, inTTL, rdataStr ); if( context->oneShotMode ) { if( ( inFlags & kDNSServiceFlagsAdd ) && ( ( context->recordType == kDNSServiceType_ANY ) || ( context->recordType == inType ) ) ) { context->gotRecord = true; } if( !( inFlags & kDNSServiceFlagsMoreComing ) && context->gotRecord ) Exit( kExitReason_OneShotDone ); } exit: FreeNullSafe( rdataStr ); if( err ) exit( 1 ); } //=========================================================================================================================== // RegisterCmd //=========================================================================================================================== typedef struct { DNSRecordRef recordRef; // Reference returned by DNSServiceAddRecord(). uint8_t * dataPtr; // Record data. size_t dataLen; // Record data length. uint32_t ttl; // Record TTL value. uint16_t type; // Record type. } ExtraRecord; typedef struct { DNSServiceRef opRef; // sdRef for DNSServiceRegister operation. const char * name; // Service name argument for DNSServiceRegister(). const char * type; // Service type argument for DNSServiceRegister(). const char * domain; // Domain in which advertise the service. uint8_t * txtPtr; // Service TXT record data. (malloc'd) size_t txtLen; // Service TXT record data len. ExtraRecord * extraRecords; // Array of extra records to add to registered service. size_t extraRecordsCount; // Number of extra records. uint8_t * updateTXTPtr; // Pointer to record data for TXT record update. (malloc'd) size_t updateTXTLen; // Length of record data for TXT record update. uint32_t updateTTL; // TTL of updated TXT record. int updateDelayMs; // Post-registration TXT record update delay in milliseconds. DNSServiceFlags flags; // Flags argument for DNSServiceRegister(). uint32_t ifIndex; // Interface index argument for DNSServiceRegister(). int lifetimeMs; // Lifetime of the record registration in milliseconds. uint16_t port; // Service instance's port number. Boolean printedHeader; // True if results header was printed. Boolean didRegister; // True if service was registered. } RegisterContext; static void RegisterPrintPrologue( const RegisterContext *inContext ); static void RegisterContextFree( RegisterContext *inContext ); static void DNSSD_API RegisterCallback( DNSServiceRef inSDRef, DNSServiceFlags inFlags, DNSServiceErrorType inError, const char * inName, const char * inType, const char * inDomain, void * inContext ); static void RegisterUpdate( void *inContext ); static void RegisterCmd( void ) { OSStatus err; RegisterContext * context = NULL; dispatch_source_t signalSource = NULL; // Set up SIGINT handler. signal( SIGINT, SIG_IGN ); err = DispatchSignalSourceCreate( SIGINT, Exit, kExitReason_SIGINT, &signalSource ); require_noerr( err, exit ); dispatch_resume( signalSource ); // Create context. context = (RegisterContext *) calloc( 1, sizeof( *context ) ); require_action( context, exit, err = kNoMemoryErr ); // Check command parameters. if( ( gRegister_Port < 0 ) || ( gRegister_Port > UINT16_MAX ) ) { FPrintF( stderr, "Port number %d is out-of-range.\n", gRegister_Port ); err = kParamErr; goto exit; } if( ( gAddRecord_DataCount != gAddRecord_TypesCount ) || ( gAddRecord_TTLsCount != gAddRecord_TypesCount ) ) { FPrintF( stderr, "There are missing additional record parameters.\n" ); err = kParamErr; goto exit; } // Get flags. context->flags = GetDNSSDFlagsFromOpts(); // Get interface index. err = InterfaceIndexFromArgString( gInterface, &context->ifIndex ); require_noerr_quiet( err, exit ); // Get TXT record data. if( gRegister_TXT ) { err = RecordDataFromArgString( gRegister_TXT, &context->txtPtr, &context->txtLen ); require_noerr_quiet( err, exit ); } // Set remaining parameters. context->name = gRegister_Name; context->type = gRegister_Type; context->domain = gRegister_Domain; context->port = (uint16_t) gRegister_Port; context->lifetimeMs = gRegister_LifetimeMs; if( gAddRecord_TypesCount > 0 ) { size_t i; context->extraRecords = (ExtraRecord *) calloc( gAddRecord_TypesCount, sizeof( ExtraRecord ) ); require_action( context, exit, err = kNoMemoryErr ); context->extraRecordsCount = gAddRecord_TypesCount; for( i = 0; i < gAddRecord_TypesCount; ++i ) { ExtraRecord * const extraRecord = &context->extraRecords[ i ]; err = RecordTypeFromArgString( gAddRecord_Types[ i ], &extraRecord->type ); require_noerr( err, exit ); err = StringToUInt32( gAddRecord_TTLs[ i ], &extraRecord->ttl ); if( err ) { FPrintF( stderr, "Invalid TTL value: %s\n", gAddRecord_TTLs[ i ] ); err = kParamErr; goto exit; } err = RecordDataFromArgString( gAddRecord_Data[ i ], &extraRecord->dataPtr, &extraRecord->dataLen ); require_noerr_quiet( err, exit ); } } if( gUpdateRecord_Data ) { err = RecordDataFromArgString( gUpdateRecord_Data, &context->updateTXTPtr, &context->updateTXTLen ); require_noerr_quiet( err, exit ); context->updateTTL = (uint32_t) gUpdateRecord_TTL; context->updateDelayMs = gUpdateRecord_DelayMs; } // Print prologue. RegisterPrintPrologue( context ); // Start operation. err = DNSServiceRegister( &context->opRef, context->flags, context->ifIndex, context->name, context->type, context->domain, NULL, 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: dispatch_source_forget( &signalSource ); if( context ) RegisterContextFree( context ); if( err ) exit( 1 ); } //=========================================================================================================================== // RegisterPrintPrologue //=========================================================================================================================== static void RegisterPrintPrologue( const RegisterContext *inContext ) { size_t i; int infinite; char ifName[ kInterfaceNameBufLen ]; 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 ); infinite = ( inContext->lifetimeMs < 0 ) ? true : false; FPrintF( stdout, "Lifetime: %?s%?d ms\n", infinite, "∞", !infinite, inContext->lifetimeMs ); if( inContext->updateTXTPtr ) { FPrintF( stdout, "\nUpdate record:\n" ); FPrintF( stdout, " Delay: %d ms\n", ( inContext->updateDelayMs > 0 ) ? inContext->updateDelayMs : 0 ); FPrintF( stdout, " TTL: %u%?s\n", inContext->updateTTL, inContext->updateTTL == 0, " (system will use a default value.)" ); FPrintF( stdout, " TXT data: %#{txt}\n", inContext->updateTXTPtr, inContext->updateTXTLen ); } if( inContext->extraRecordsCount > 0 ) FPrintF( stdout, "\n" ); for( i = 0; i < inContext->extraRecordsCount; ++i ) { const ExtraRecord * record = &inContext->extraRecords[ i ]; FPrintF( stdout, "Extra record %zu:\n", i + 1 ); FPrintF( stdout, " Type: %s (%u)\n", RecordTypeToString( record->type ), record->type ); FPrintF( stdout, " TTL: %u%?s\n", record->ttl, record->ttl == 0, " (system will use a default value.)" ); FPrintF( stdout, " RData: %#H\n\n", record->dataPtr, (int) record->dataLen, INT_MAX ); } FPrintF( stdout, "Start time: %{du:time}\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 ); ForgetMem( &inContext->updateTXTPtr ); free( inContext ); } //=========================================================================================================================== // RegisterCallback //=========================================================================================================================== static void DNSSD_API RegisterCallback( DNSServiceRef inSDRef, DNSServiceFlags inFlags, DNSServiceErrorType inError, const char * inName, const char * inType, const char * inDomain, void * inContext ) { RegisterContext * const context = (RegisterContext *) inContext; OSStatus err; struct timeval now; Unused( inSDRef ); gettimeofday( &now, NULL ); if( !context->printedHeader ) { FPrintF( stdout, "%-26s A/R Flags Service\n", "Timestamp" ); context->printedHeader = true; } FPrintF( stdout, "%{du:time} %-3s %5X %s.%s%s %?#m\n", &now, AddRmvString( inFlags ), inFlags, inName, inType, inDomain, inError, inError ); require_noerr_action_quiet( inError, exit, err = inError ); if( !context->didRegister && ( inFlags & kDNSServiceFlagsAdd ) ) { context->didRegister = true; if( context->updateTXTPtr ) { if( context->updateDelayMs > 0 ) { dispatch_after_f( dispatch_time_milliseconds( context->updateDelayMs ), dispatch_get_main_queue(), context, RegisterUpdate ); } else { RegisterUpdate( context ); } } if( context->extraRecordsCount > 0 ) { ExtraRecord * record; const ExtraRecord * const end = context->extraRecords + context->extraRecordsCount; for( record = context->extraRecords; record < end; ++record ) { err = DNSServiceAddRecord( context->opRef, &record->recordRef, 0, record->type, (uint16_t) record->dataLen, record->dataPtr, record->ttl ); require_noerr( err, exit ); } } if( context->lifetimeMs == 0 ) { Exit( kExitReason_TimeLimit ); } else if( context->lifetimeMs > 0 ) { dispatch_after_f( dispatch_time_milliseconds( context->lifetimeMs ), dispatch_get_main_queue(), kExitReason_TimeLimit, Exit ); } } err = kNoErr; exit: if( err ) exit( 1 ); } //=========================================================================================================================== // RegisterUpdate //=========================================================================================================================== static void RegisterUpdate( void *inContext ) { OSStatus err; RegisterContext * const context = (RegisterContext *) inContext; err = DNSServiceUpdateRecord( context->opRef, NULL, 0, (uint16_t) context->updateTXTLen, context->updateTXTPtr, context->updateTTL ); require_noerr( err, exit ); exit: if( err ) exit( 1 ); } //=========================================================================================================================== // RegisterRecordCmd //=========================================================================================================================== typedef struct { DNSServiceRef conRef; // sdRef to be initialized by DNSServiceCreateConnection(). DNSRecordRef recordRef; // Registered record reference. const char * recordName; // Name of resource record. uint8_t * dataPtr; // Pointer to resource record data. size_t dataLen; // Length of resource record data. uint32_t ttl; // TTL value of resource record in seconds. uint32_t ifIndex; // Interface index argument for DNSServiceRegisterRecord(). DNSServiceFlags flags; // Flags argument for DNSServiceRegisterRecord(). int lifetimeMs; // Lifetime of the record registration in milliseconds. uint16_t recordType; // Resource record type. uint8_t * updateDataPtr; // Pointer to data for record update. (malloc'd) size_t updateDataLen; // Length of data for record update. uint32_t updateTTL; // TTL for updated record. int updateDelayMs; // Post-registration record update delay in milliseconds. Boolean didRegister; // True if the record was registered. } RegisterRecordContext; static void RegisterRecordPrintPrologue( const RegisterRecordContext *inContext ); static void RegisterRecordContextFree( RegisterRecordContext *inContext ); static void DNSSD_API RegisterRecordCallback( DNSServiceRef inSDRef, DNSRecordRef inRecordRef, DNSServiceFlags inFlags, DNSServiceErrorType inError, void * inContext ); static void RegisterRecordUpdate( void *inContext ); static void RegisterRecordCmd( void ) { OSStatus err; RegisterRecordContext * context = NULL; dispatch_source_t signalSource = NULL; // Set up SIGINT handler. signal( SIGINT, SIG_IGN ); err = DispatchSignalSourceCreate( SIGINT, Exit, kExitReason_SIGINT, &signalSource ); require_noerr( err, exit ); dispatch_resume( signalSource ); // Create context. context = (RegisterRecordContext *) calloc( 1, sizeof( *context ) ); require_action( context, exit, err = kNoMemoryErr ); // Create connection. err = CreateConnectionFromArgString( gConnectionOpt, dispatch_get_main_queue(), &context->conRef, NULL ); require_noerr_quiet( err, exit ); // Get flags. context->flags = GetDNSSDFlagsFromOpts(); // Get interface. err = InterfaceIndexFromArgString( gInterface, &context->ifIndex ); require_noerr_quiet( err, exit ); // Get record type. err = RecordTypeFromArgString( gRegisterRecord_Type, &context->recordType ); require_noerr( err, exit ); // Get record data. if( gRegisterRecord_Data ) { err = RecordDataFromArgString( gRegisterRecord_Data, &context->dataPtr, &context->dataLen ); require_noerr_quiet( err, exit ); } // Set remaining parameters. context->recordName = gRegisterRecord_Name; context->ttl = (uint32_t) gRegisterRecord_TTL; context->lifetimeMs = gRegisterRecord_LifetimeMs; // Get update data. if( gRegisterRecord_UpdateData ) { err = RecordDataFromArgString( gRegisterRecord_UpdateData, &context->updateDataPtr, &context->updateDataLen ); require_noerr_quiet( err, exit ); context->updateTTL = (uint32_t) gRegisterRecord_UpdateTTL; context->updateDelayMs = gRegisterRecord_UpdateDelayMs; } // Print prologue. RegisterRecordPrintPrologue( context ); // Start operation. err = DNSServiceRegisterRecord( context->conRef, &context->recordRef, context->flags, context->ifIndex, context->recordName, context->recordType, kDNSServiceClass_IN, (uint16_t) context->dataLen, context->dataPtr, context->ttl, RegisterRecordCallback, context ); if( err ) { FPrintF( stderr, "DNSServiceRegisterRecord() returned %#m\n", err ); goto exit; } dispatch_main(); exit: dispatch_source_forget( &signalSource ); if( context ) RegisterRecordContextFree( context ); if( err ) exit( 1 ); } //=========================================================================================================================== // RegisterRecordPrintPrologue //=========================================================================================================================== static void RegisterRecordPrintPrologue( const RegisterRecordContext *inContext ) { int infinite; char ifName[ kInterfaceNameBufLen ]; InterfaceIndexToName( inContext->ifIndex, ifName ); FPrintF( stdout, "Flags: %#{flags}\n", inContext->flags, kDNSServiceFlagsDescriptors ); FPrintF( stdout, "Interface: %d (%s)\n", (int32_t) inContext->ifIndex, ifName ); FPrintF( stdout, "Name: %s\n", inContext->recordName ); FPrintF( stdout, "Type: %s (%u)\n", RecordTypeToString( inContext->recordType ), inContext->recordType ); FPrintF( stdout, "TTL: %u\n", inContext->ttl ); FPrintF( stdout, "Data: %#H\n", inContext->dataPtr, (int) inContext->dataLen, INT_MAX ); infinite = ( inContext->lifetimeMs < 0 ) ? true : false; FPrintF( stdout, "Lifetime: %?s%?d ms\n", infinite, "∞", !infinite, inContext->lifetimeMs ); if( inContext->updateDataPtr ) { FPrintF( stdout, "\nUpdate record:\n" ); FPrintF( stdout, " Delay: %d ms\n", ( inContext->updateDelayMs >= 0 ) ? inContext->updateDelayMs : 0 ); FPrintF( stdout, " TTL: %u%?s\n", inContext->updateTTL, inContext->updateTTL == 0, " (system will use a default value.)" ); FPrintF( stdout, " RData: %#H\n", inContext->updateDataPtr, (int) inContext->updateDataLen, INT_MAX ); } FPrintF( stdout, "Start time: %{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, 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 // 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[ ( 16 * 4 ) + 9 + 1 ]; int useMainConnection; const char * endPtr; // Set up SIGINT handler. signal( SIGINT, SIG_IGN ); err = DispatchSignalSourceCreate( SIGINT, Exit, kExitReason_SIGINT, &signalSource ); require_noerr( err, exit ); dispatch_resume( signalSource ); // Create context. context = (QueryRecordContext *) calloc( 1, sizeof( *context ) ); require_action( context, exit, err = kNoMemoryErr ); // Check command parameters. if( gReverseLookup_TimeLimitSecs < 0 ) { FPrintF( stderr, "Invalid time limit: %d s.\n", gReverseLookup_TimeLimitSecs ); err = kParamErr; goto exit; } // Create main connection. if( gConnectionOpt ) { err = CreateConnectionFromArgString( gConnectionOpt, dispatch_get_main_queue(), &context->mainRef, NULL ); require_noerr_quiet( err, exit ); useMainConnection = true; } else { useMainConnection = false; } // Get flags. context->flags = GetDNSSDFlagsFromOpts(); if( useMainConnection ) context->flags |= kDNSServiceFlagsShareConnection; // Get interface index. err = InterfaceIndexFromArgString( gInterface, &context->ifIndex ); require_noerr_quiet( err, exit ); // Create reverse lookup record name. err = StringToIPv4Address( gReverseLookup_IPAddr, kStringToIPAddressFlagsNoPort | kStringToIPAddressFlagsNoPrefix, &ipv4Addr, NULL, NULL, NULL, &endPtr ); if( err || ( *endPtr != '\0' ) ) { char * dst; int i; 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; } dst = recordName; for( i = 15; i >= 0; --i ) { *dst++ = kHexDigitsLowercase[ ipv6Addr[ i ] & 0x0F ]; *dst++ = '.'; *dst++ = kHexDigitsLowercase[ ipv6Addr[ i ] >> 4 ]; *dst++ = '.'; } strcpy( dst, "ip6.arpa." ); check( ( strlen( recordName ) + 1 ) <= sizeof( recordName ) ); } else { SNPrintF( recordName, sizeof( recordName ), "%u.%u.%u.%u.in-addr.arpa.", ipv4Addr & 0xFF, ( ipv4Addr >> 8 ) & 0xFF, ( ipv4Addr >> 16 ) & 0xFF, ( ipv4Addr >> 24 ) & 0xFF ); } // Set remaining parameters. context->recordName = recordName; context->recordType = kDNSServiceType_PTR; context->timeLimitSecs = gReverseLookup_TimeLimitSecs; context->oneShotMode = gReverseLookup_OneShot ? true : false; // Print prologue. QueryRecordPrintPrologue( context ); // Start operation. 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, 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 ); } //=========================================================================================================================== // BrowseAllCmd //=========================================================================================================================== typedef struct BrowseDomain BrowseDomain; typedef struct BrowseType BrowseType; typedef struct BrowseOp BrowseOp; typedef struct BrowseInstance BrowseInstance; typedef struct BrowseIPAddr BrowseIPAddr; typedef struct { int refCount; DNSServiceRef mainRef; DNSServiceRef domainsQuery; const char * domain; BrowseDomain * domainList; char ** serviceTypes; size_t serviceTypesCount; dispatch_source_t exitTimer; uint32_t ifIndex; int pendingConnectCount; int browseTimeSecs; int maxConnectTimeSecs; Boolean includeAWDL; Boolean useColoredText; } BrowseAllContext; struct BrowseDomain { BrowseDomain * next; char * name; DNSServiceRef servicesQuery; BrowseAllContext * context; BrowseType * typeList; }; struct BrowseType { BrowseType * next; char * name; BrowseOp * browseList; }; struct BrowseOp { BrowseOp * next; BrowseAllContext * context; DNSServiceRef browse; uint64_t startTicks; BrowseInstance * instanceList; uint32_t ifIndex; Boolean isTCP; }; struct BrowseInstance { BrowseInstance * next; BrowseAllContext * context; char * name; uint64_t foundTicks; DNSServiceRef resolve; uint64_t resolveStartTicks; uint64_t resolveDoneTicks; DNSServiceRef getAddr; uint64_t getAddrStartTicks; BrowseIPAddr * addrList; uint8_t * txtPtr; size_t txtLen; char * hostname; uint32_t ifIndex; uint16_t port; Boolean isTCP; }; typedef enum { kConnectStatus_None = 0, kConnectStatus_Pending = 1, kConnectStatus_Succeeded = 2, kConnectStatus_Failed = 3 } ConnectStatus; struct BrowseIPAddr { BrowseIPAddr * next; sockaddr_ip sip; int refCount; BrowseAllContext * context; uint64_t foundTicks; AsyncConnectionRef connection; ConnectStatus connectStatus; CFTimeInterval connectTimeSecs; OSStatus connectError; }; static void BrowseAllPrintPrologue( const BrowseAllContext *inContext ); static void DNSSD_API BrowseAllQueryDomainsCallback( DNSServiceRef inSDRef, DNSServiceFlags inFlags, uint32_t inInterfaceIndex, DNSServiceErrorType inError, const char * inFullName, uint16_t inType, uint16_t inClass, uint16_t inRDataLen, const void * inRDataPtr, uint32_t inTTL, void * inContext ); static void DNSSD_API BrowseAllQueryCallback( DNSServiceRef inSDRef, DNSServiceFlags inFlags, uint32_t inInterfaceIndex, DNSServiceErrorType inError, const char * inFullName, uint16_t inType, uint16_t inClass, uint16_t inRDataLen, const void * inRDataPtr, uint32_t inTTL, void * inContext ); static void DNSSD_API BrowseAllBrowseCallback( DNSServiceRef inSDRef, DNSServiceFlags inFlags, uint32_t inInterfaceIndex, DNSServiceErrorType inError, const char * inName, const char * inRegType, const char * inDomain, void * inContext ); static void DNSSD_API BrowseAllResolveCallback( DNSServiceRef inSDRef, DNSServiceFlags inFlags, uint32_t inInterfaceIndex, DNSServiceErrorType inError, const char * inFullName, const char * inHostname, uint16_t inPort, uint16_t inTXTLen, const unsigned char * inTXTPtr, void * inContext ); static void DNSSD_API BrowseAllGAICallback( DNSServiceRef inSDRef, DNSServiceFlags inFlags, uint32_t inInterfaceIndex, DNSServiceErrorType inError, const char * inHostname, const struct sockaddr * inSockAddr, uint32_t inTTL, void * inContext ); static void BrowseAllConnectionProgress( int inPhase, const void *inDetails, void *inArg ); static void BrowseAllConnectionHandler( SocketRef inSock, OSStatus inError, void *inArg ); static void BrowseAllStop( void *inContext ); static void BrowseAllExit( void *inContext ); static OSStatus BrowseAllAddDomain( BrowseAllContext *inContext, const char *inName ); static OSStatus BrowseAllRemoveDomain( BrowseAllContext *inContext, const char *inName ); static void BrowseAllContextRelease( BrowseAllContext *inContext ); static OSStatus BrowseAllAddServiceType( BrowseAllContext * inContext, BrowseDomain * inDomain, const char * inName, uint32_t inIfIndex, Boolean inIncludeAWDL ); static OSStatus BrowseAllRemoveServiceType( BrowseAllContext * inContext, BrowseDomain * inDomain, const char * inName, uint32_t inIfIndex ); static OSStatus BrowseAllAddServiceInstance( BrowseAllContext * inContext, BrowseOp * inBrowse, const char * inName, const char * inRegType, const char * inDomain, uint32_t inIfIndex ); static OSStatus BrowseAllRemoveServiceInstance( BrowseAllContext * inContext, BrowseOp * inBrowse, const char * inName, uint32_t inIfIndex ); static OSStatus BrowseAllAddIPAddress( BrowseAllContext * inContext, BrowseInstance * inInstance, const struct sockaddr * inSockAddr ); static OSStatus BrowseAllRemoveIPAddress( BrowseAllContext * inContext, BrowseInstance * inInstance, const struct sockaddr * inSockAddr ); static void BrowseDomainFree( BrowseDomain *inDomain ); static void BrowseTypeFree( BrowseType *inType ); static void BrowseOpFree( BrowseOp *inBrowse ); static void BrowseInstanceFree( BrowseInstance *inInstance ); static void BrowseIPAddrRelease( BrowseIPAddr *inAddr ); static void BrowseIPAddrReleaseList( BrowseIPAddr *inList ); #define ForgetIPAddressList( X ) ForgetCustom( X, BrowseIPAddrReleaseList ) #define ForgetBrowseAllContext( X ) ForgetCustom( X, BrowseAllContextRelease ) #define kBrowseAllOpenFileMin 4096 static void BrowseAllCmd( void ) { OSStatus err; BrowseAllContext * context = NULL; // Check command parameters. if( gBrowseAll_BrowseTimeSecs <= 0 ) { FPrintF( stdout, "Invalid browse time: %d seconds.\n", gBrowseAll_BrowseTimeSecs ); err = kParamErr; goto exit; } #if( TARGET_OS_POSIX ) // Set open file minimum. { struct rlimit fdLimits; err = getrlimit( RLIMIT_NOFILE, &fdLimits ); err = map_global_noerr_errno( err ); require_noerr( err, exit ); if( fdLimits.rlim_cur < kBrowseAllOpenFileMin ) { fdLimits.rlim_cur = kBrowseAllOpenFileMin; err = setrlimit( RLIMIT_NOFILE, &fdLimits ); err = map_global_noerr_errno( err ); require_noerr( err, exit ); } } #endif context = (BrowseAllContext *) calloc( 1, sizeof( *context ) ); require_action( context, exit, err = kNoMemoryErr ); context->refCount = 1; context->domain = gBrowseAll_Domain; context->serviceTypes = gBrowseAll_ServiceTypes; context->serviceTypesCount = gBrowseAll_ServiceTypesCount; gBrowseAll_ServiceTypes = NULL; gBrowseAll_ServiceTypesCount = 0; context->browseTimeSecs = gBrowseAll_BrowseTimeSecs; context->maxConnectTimeSecs = gBrowseAll_MaxConnectTimeSecs; context->includeAWDL = gDNSSDFlag_IncludeAWDL ? true : false; #if( TARGET_OS_POSIX ) context->useColoredText = isatty( STDOUT_FILENO ) ? true : false; #endif err = DNSServiceCreateConnection( &context->mainRef ); require_noerr( err, exit ); err = DNSServiceSetDispatchQueue( context->mainRef, dispatch_get_main_queue() ); require_noerr( err, exit ); // Set interface index. err = InterfaceIndexFromArgString( gInterface, &context->ifIndex ); require_noerr_quiet( err, exit ); BrowseAllPrintPrologue( context ); if( context->domain ) { err = BrowseAllAddDomain( context, context->domain ); require_noerr( err, exit ); } else { DNSServiceRef sdRef; sdRef = context->mainRef; err = DNSServiceQueryRecord( &sdRef, kDNSServiceFlagsShareConnection, kDNSServiceInterfaceIndexLocalOnly, "b._dns-sd._udp.local.", kDNSServiceType_PTR, kDNSServiceClass_IN, BrowseAllQueryDomainsCallback, context ); require_noerr( err, exit ); context->domainsQuery = sdRef; } dispatch_after_f( dispatch_time_seconds( context->browseTimeSecs ), dispatch_get_main_queue(), context, BrowseAllStop ); dispatch_main(); exit: if( context ) BrowseAllContextRelease( context ); if( err ) exit( 1 ); } //=========================================================================================================================== // BrowseAllPrintPrologue //=========================================================================================================================== static void BrowseAllPrintPrologue( const BrowseAllContext *inContext ) { size_t i; char ifName[ kInterfaceNameBufLen ]; InterfaceIndexToName( inContext->ifIndex, ifName ); FPrintF( stdout, "Interface: %d (%s)\n", (int32_t) inContext->ifIndex, ifName ); FPrintF( stdout, "Service types: "); if( inContext->serviceTypesCount > 0 ) { FPrintF( stdout, "%s", inContext->serviceTypes[ 0 ] ); for( i = 1; i < inContext->serviceTypesCount; ++i ) FPrintF( stdout, ", %s", inContext->serviceTypes[ i ] ); FPrintF( stdout, "\n" ); } else { FPrintF( stdout, "all services\n" ); } FPrintF( stdout, "Domain: %s\n", inContext->domain ? inContext->domain : "default domains" ); FPrintF( stdout, "Browse time: %d second%?c\n", inContext->browseTimeSecs, inContext->browseTimeSecs != 1, 's' ); FPrintF( stdout, "Max connect time: %d second%?c\n", inContext->maxConnectTimeSecs, inContext->maxConnectTimeSecs != 1, 's' ); FPrintF( stdout, "IncludeAWDL: %s\n", inContext->includeAWDL ? "YES" : "NO" ); FPrintF( stdout, "Start time: %{du:time}\n", NULL ); FPrintF( stdout, "---\n" ); } //=========================================================================================================================== // BrowseAllQueryDomainsCallback //=========================================================================================================================== static void DNSSD_API BrowseAllQueryDomainsCallback( DNSServiceRef inSDRef, DNSServiceFlags inFlags, uint32_t inInterfaceIndex, DNSServiceErrorType inError, const char * inFullName, uint16_t inType, uint16_t inClass, uint16_t inRDataLen, const void * inRDataPtr, uint32_t inTTL, void * inContext ) { OSStatus err; BrowseAllContext * const context = (BrowseAllContext *) inContext; char domainStr[ kDNSServiceMaxDomainName ]; Unused( inSDRef ); Unused( inInterfaceIndex ); Unused( inFullName ); Unused( inType ); Unused( inClass ); Unused( inTTL ); err = inError; require_noerr( err, exit ); err = DomainNameToString( inRDataPtr, ( (const uint8_t *) inRDataPtr ) + inRDataLen, domainStr, NULL ); require_noerr( err, exit ); if( inFlags & kDNSServiceFlagsAdd ) { err = BrowseAllAddDomain( context, domainStr ); if( err == kDuplicateErr ) err = kNoErr; require_noerr( err, exit ); } else { err = BrowseAllRemoveDomain( context, domainStr ); if( err == kNotFoundErr ) err = kNoErr; require_noerr( err, exit ); } exit: if( err ) exit( 1 ); } //=========================================================================================================================== // BrowseAllQueryCallback //=========================================================================================================================== static void DNSSD_API BrowseAllQueryCallback( DNSServiceRef inSDRef, DNSServiceFlags inFlags, uint32_t inInterfaceIndex, DNSServiceErrorType inError, const char * inFullName, uint16_t inType, uint16_t inClass, uint16_t inRDataLen, const void * inRDataPtr, uint32_t inTTL, void * inContext ) { OSStatus err; BrowseDomain * const domain = (BrowseDomain *) inContext; const uint8_t * firstLabel; const uint8_t * secondLabel; char * serviceTypeStr = NULL; const uint8_t * const end = ( (uint8_t * ) inRDataPtr ) + inRDataLen; Unused( inSDRef ); Unused( inFullName ); Unused( inTTL ); Unused( inType ); Unused( inClass ); err = inError; require_noerr( err, exit ); check( inType == kDNSServiceType_PTR ); check( inClass == kDNSServiceClass_IN ); require_action( inRDataLen > 0, exit, err = kSizeErr ); firstLabel = inRDataPtr; require_action_quiet( ( firstLabel + 1 + firstLabel[ 0 ] ) < end , exit, err = kUnderrunErr ); secondLabel = firstLabel + 1 + firstLabel[ 0 ]; require_action_quiet( ( secondLabel + 1 + secondLabel[ 0 ] ) < end , exit, err = kUnderrunErr ); ASPrintF( &serviceTypeStr, "%#s.%#s", firstLabel, secondLabel ); require_action( serviceTypeStr, exit, err = kNoMemoryErr ); if( inFlags & kDNSServiceFlagsAdd ) { err = BrowseAllAddServiceType( domain->context, domain, serviceTypeStr, inInterfaceIndex, false ); if( err == kDuplicateErr ) err = kNoErr; require_noerr( err, exit ); } else { err = BrowseAllRemoveServiceType( domain->context, domain, serviceTypeStr, inInterfaceIndex ); if( err == kNotFoundErr ) err = kNoErr; require_noerr( err, exit ); } exit: FreeNullSafe( serviceTypeStr ); } //=========================================================================================================================== // BrowseAllBrowseCallback //=========================================================================================================================== static void DNSSD_API BrowseAllBrowseCallback( DNSServiceRef inSDRef, DNSServiceFlags inFlags, uint32_t inInterfaceIndex, DNSServiceErrorType inError, const char * inName, const char * inRegType, const char * inDomain, void * inContext ) { OSStatus err; BrowseOp * const browse = (BrowseOp *) inContext; Unused( inSDRef ); err = inError; require_noerr( err, exit ); if( inFlags & kDNSServiceFlagsAdd ) { err = BrowseAllAddServiceInstance( browse->context, browse, inName, inRegType, inDomain, inInterfaceIndex ); if( err == kDuplicateErr ) err = kNoErr; require_noerr( err, exit ); } else { err = BrowseAllRemoveServiceInstance( browse->context, browse, inName, inInterfaceIndex ); if( err == kNotFoundErr ) err = kNoErr; require_noerr( err, exit ); } exit: return; } //=========================================================================================================================== // BrowseAllResolveCallback //=========================================================================================================================== static void DNSSD_API BrowseAllResolveCallback( DNSServiceRef inSDRef, DNSServiceFlags inFlags, uint32_t inInterfaceIndex, DNSServiceErrorType inError, const char * inFullName, const char * inHostname, uint16_t inPort, uint16_t inTXTLen, const unsigned char * inTXTPtr, void * inContext ) { OSStatus err; const uint64_t nowTicks = UpTicks(); BrowseInstance * const instance = (BrowseInstance *) inContext; Unused( inSDRef ); Unused( inFlags ); Unused( inInterfaceIndex ); Unused( inFullName ); err = inError; require_noerr( err, exit ); if( !MemEqual( instance->txtPtr, instance->txtLen, inTXTPtr, inTXTLen ) ) { FreeNullSafe( instance->txtPtr ); instance->txtPtr = malloc( inTXTLen ); require_action( instance->txtPtr, exit, err = kNoMemoryErr ); memcpy( instance->txtPtr, inTXTPtr, inTXTLen ); instance->txtLen = inTXTLen; } instance->port = ntohs( inPort ); if( !instance->hostname || ( strcasecmp( instance->hostname, inHostname ) != 0 ) ) { DNSServiceRef sdRef; if( !instance->hostname ) instance->resolveDoneTicks = nowTicks; FreeNullSafe( instance->hostname ); instance->hostname = strdup( inHostname ); require_action( instance->hostname, exit, err = kNoMemoryErr ); DNSServiceForget( &instance->getAddr ); ForgetIPAddressList( &instance->addrList ); sdRef = instance->context->mainRef; instance->getAddrStartTicks = UpTicks(); err = DNSServiceGetAddrInfo( &sdRef, kDNSServiceFlagsShareConnection, instance->ifIndex, kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6, instance->hostname, BrowseAllGAICallback, instance ); require_noerr( err, exit ); instance->getAddr = sdRef; } exit: if( err ) exit( 1 ); } //=========================================================================================================================== // BrowseAllGAICallback //=========================================================================================================================== static void DNSSD_API BrowseAllGAICallback( DNSServiceRef inSDRef, DNSServiceFlags inFlags, uint32_t inInterfaceIndex, DNSServiceErrorType inError, const char * inHostname, const struct sockaddr * inSockAddr, uint32_t inTTL, void * inContext ) { OSStatus err; BrowseInstance * const instance = (BrowseInstance *) inContext; Unused( inSDRef ); Unused( inInterfaceIndex ); Unused( inHostname ); Unused( inTTL ); err = inError; require_noerr( err, exit ); if( ( inSockAddr->sa_family != AF_INET ) && ( inSockAddr->sa_family != AF_INET6 ) ) { dlogassert( "Unexpected address family: %d", inSockAddr->sa_family ); goto exit; } if( inFlags & kDNSServiceFlagsAdd ) { err = BrowseAllAddIPAddress( instance->context, instance, inSockAddr ); if( err == kDuplicateErr ) err = kNoErr; require_noerr( err, exit ); } else { err = BrowseAllRemoveIPAddress( instance->context, instance, inSockAddr ); if( err == kNotFoundErr ) err = kNoErr; require_noerr( err, exit ); } exit: return; } //=========================================================================================================================== // BrowseAllConnectionProgress //=========================================================================================================================== static void BrowseAllConnectionProgress( int inPhase, const void *inDetails, void *inArg ) { BrowseIPAddr * const addr = (BrowseIPAddr *) inArg; if( inPhase == kAsyncConnectionPhase_Connected ) { const AsyncConnectedInfo * const info = (AsyncConnectedInfo *) inDetails; addr->connectTimeSecs = info->connectSecs; } } //=========================================================================================================================== // BrowseAllConnectionHandler //=========================================================================================================================== static void BrowseAllConnectionHandler( SocketRef inSock, OSStatus inError, void *inArg ) { BrowseIPAddr * const addr = (BrowseIPAddr *) inArg; BrowseAllContext * const context = addr->context; if( inError ) { addr->connectStatus = kConnectStatus_Failed; addr->connectError = inError; } else { addr->connectStatus = kConnectStatus_Succeeded; } check( context->pendingConnectCount > 0 ); if( --context->pendingConnectCount == 0 ) { if( context->exitTimer ) { dispatch_source_forget( &context->exitTimer ); dispatch_async_f( dispatch_get_main_queue(), context, BrowseAllExit ); } } ForgetSocket( &inSock ); BrowseIPAddrRelease( addr ); } //=========================================================================================================================== // BrowseAllStop //=========================================================================================================================== static void BrowseAllStop( void *inContext ) { OSStatus err; BrowseAllContext * const context = (BrowseAllContext *) inContext; BrowseDomain * domain; BrowseType * type; BrowseOp * browse; BrowseInstance * instance; DNSServiceForget( &context->domainsQuery ); for( domain = context->domainList; domain; domain = domain->next ) { DNSServiceForget( &domain->servicesQuery ); for( type = domain->typeList; type; type = type->next ) { for( browse = type->browseList; browse; browse = browse->next ) { DNSServiceForget( &browse->browse ); for( instance = browse->instanceList; instance; instance = instance->next ) { DNSServiceForget( &instance->resolve ); DNSServiceForget( &instance->getAddr ); } } } } DNSServiceForget( &context->mainRef ); if( ( context->pendingConnectCount > 0 ) && ( context->maxConnectTimeSecs > 0 ) ) { check( !context->exitTimer ); err = DispatchTimerCreate( dispatch_time_seconds( context->maxConnectTimeSecs ), DISPATCH_TIME_FOREVER, 100 * kNanosecondsPerMillisecond, NULL, BrowseAllExit, NULL, context, &context->exitTimer ); require_noerr( err, exit ); dispatch_resume( context->exitTimer ); } else { dispatch_async_f( dispatch_get_main_queue(), context, BrowseAllExit ); } err = kNoErr; exit: if( err ) exit( 1 ); } //=========================================================================================================================== // BrowseAllExit //=========================================================================================================================== #define kStatusStr_CouldConnect "connected" #define kStatusStr_CouldConnectColored kANSIGreen kStatusStr_CouldConnect kANSINormal #define kStatusStr_CouldNotConnect "could not connect" #define kStatusStr_CouldNotConnectColored kANSIRed kStatusStr_CouldNotConnect kANSINormal #define kStatusStr_NoConnectionAttempted "no connection attempted" #define kStatusStr_Unknown "unknown" #define Indent( X ) ( (X) * 4 ), "" static void BrowseAllExit( void *inContext ) { BrowseAllContext * const context = (BrowseAllContext *) inContext; BrowseDomain * domain; BrowseType * type; BrowseOp * browse; BrowseInstance * instance; BrowseIPAddr * addr; dispatch_source_forget( &context->exitTimer ); for( domain = context->domainList; domain; domain = domain->next ) { FPrintF( stdout, "%s\n\n", domain->name ); for( type = domain->typeList; type; type = type->next ) { const char * desc; desc = ServiceTypeDescription( type->name ); if( desc ) FPrintF( stdout, "%*s" "%s (%s)\n\n", Indent( 1 ), desc, type->name ); else FPrintF( stdout, "%*s" "%s\n\n", Indent( 1 ), type->name ); for( browse = type->browseList; browse; browse = browse->next ) { for( instance = browse->instanceList; instance; instance = instance->next ) { char ifname[ IF_NAMESIZE + 1 ]; FPrintF( stdout, "%*s" "%s via ", Indent( 2 ), instance->name ); if( instance->ifIndex == 0 ) { FPrintF( stdout, "the Internet" ); } else if( if_indextoname( instance->ifIndex, ifname ) ) { NetTransportType netType; SocketGetInterfaceInfo( kInvalidSocketRef, ifname, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &netType ); FPrintF( stdout, "%s (%s)", ( netType == kNetTransportType_Ethernet ) ? "ethernet" : NetTransportTypeToString( netType ), ifname ); } else { FPrintF( stdout, "interface index %u", instance->ifIndex ); } FPrintF( stdout, "\n\n" ); if( instance->hostname ) { char buffer[ 256 ]; SNPrintF( buffer, sizeof( buffer ), "%s:%u", instance->hostname, instance->port ); FPrintF( stdout, "%*s" "%-51s %4llu ms\n", Indent( 3 ), buffer, UpTicksToMilliseconds( instance->resolveDoneTicks - instance->resolveStartTicks ) ); } else { FPrintF( stdout, "%*s" "%s:%u\n", Indent( 3 ), instance->hostname, instance->port ); } for( addr = instance->addrList; addr; addr = addr->next ) { AsyncConnection_Forget( &addr->connection ); if( addr->connectStatus == kConnectStatus_Pending ) { addr->connectStatus = kConnectStatus_Failed; addr->connectError = kTimeoutErr; } FPrintF( stdout, "%*s" "%-##47a %4llu ms", Indent( 4 ), &addr->sip.sa, UpTicksToMilliseconds( addr->foundTicks - instance->getAddrStartTicks ) ); if( context->maxConnectTimeSecs <= 0 ) { FPrintF( stdout, "\n" ); continue; } switch( addr->connectStatus ) { case kConnectStatus_None: FPrintF( stdout, " (%s)\n", kStatusStr_NoConnectionAttempted ); break; case kConnectStatus_Succeeded: FPrintF( stdout, " (%s in %.2f ms)\n", context->useColoredText ? kStatusStr_CouldConnectColored : kStatusStr_CouldConnect, addr->connectTimeSecs * 1000 ); break; case kConnectStatus_Failed: FPrintF( stdout, " (%s: %m)\n", context->useColoredText ? kStatusStr_CouldNotConnectColored : kStatusStr_CouldNotConnect, addr->connectError ); break; default: FPrintF( stdout, " (%s)\n", kStatusStr_Unknown ); break; } } FPrintF( stdout, "\n" ); if( instance->txtLen == 0 ) continue; FPrintF( stdout, "%*s" "TXT record:\n", Indent( 3 ) ); if( instance->txtLen > 1 ) { FPrintF( stdout, "%3{txt}", instance->txtPtr, instance->txtLen ); } else { FPrintF( stdout, "%*s" "%#H\n", Indent( 3 ), instance->txtPtr, (int) instance->txtLen, INT_MAX ); } FPrintF( stdout, "\n" ); } } FPrintF( stdout, "\n" ); } } while( ( domain = context->domainList ) != NULL ) { context->domainList = domain->next; BrowseDomainFree( domain ); } BrowseAllContextRelease( context ); Exit( NULL ); } //=========================================================================================================================== // BrowseAllAddDomain //=========================================================================================================================== static OSStatus BrowseAllAddDomain( BrowseAllContext *inContext, const char *inName ) { OSStatus err; BrowseDomain * domain; BrowseDomain ** p; BrowseDomain * newDomain = NULL; for( p = &inContext->domainList; ( domain = *p ) != NULL; p = &domain->next ) { if( strcasecmp( domain->name, inName ) == 0 ) break; } require_action_quiet( !domain, exit, err = kDuplicateErr ); newDomain = (BrowseDomain *) calloc( 1, sizeof( *newDomain ) ); require_action( newDomain, exit, err = kNoMemoryErr ); ++inContext->refCount; newDomain->context = inContext; newDomain->name = strdup( inName ); require_action( newDomain->name, exit, err = kNoMemoryErr ); if( inContext->serviceTypesCount > 0 ) { size_t i; for( i = 0; i < inContext->serviceTypesCount; ++i ) { err = BrowseAllAddServiceType( inContext, newDomain, inContext->serviceTypes[ i ], inContext->ifIndex, inContext->includeAWDL ); if( err == kDuplicateErr ) err = kNoErr; require_noerr( err, exit ); } } else { char * recordName; DNSServiceFlags flags; DNSServiceRef sdRef; ASPrintF( &recordName, "_services._dns-sd._udp.%s", newDomain->name ); require_action( recordName, exit, err = kNoMemoryErr ); flags = kDNSServiceFlagsShareConnection; if( inContext->includeAWDL ) flags |= kDNSServiceFlagsIncludeAWDL; sdRef = newDomain->context->mainRef; err = DNSServiceQueryRecord( &sdRef, flags, inContext->ifIndex, recordName, kDNSServiceType_PTR, kDNSServiceClass_IN, BrowseAllQueryCallback, newDomain ); free( recordName ); require_noerr( err, exit ); newDomain->servicesQuery = sdRef; } *p = newDomain; newDomain = NULL; err = kNoErr; exit: if( newDomain ) BrowseDomainFree( newDomain ); return( err ); } //=========================================================================================================================== // BrowseAllRemoveDomain //=========================================================================================================================== static OSStatus BrowseAllRemoveDomain( BrowseAllContext *inContext, const char *inName ) { OSStatus err; BrowseDomain * domain; BrowseDomain ** p; for( p = &inContext->domainList; ( domain = *p ) != NULL; p = &domain->next ) { if( strcasecmp( domain->name, inName ) == 0 ) break; } if( domain ) { *p = domain->next; BrowseDomainFree( domain ); err = kNoErr; } else { err = kNotFoundErr; } return( err ); } //=========================================================================================================================== // BrowseAllContextRelease //=========================================================================================================================== static void BrowseAllContextRelease( BrowseAllContext *inContext ) { if( --inContext->refCount == 0 ) { check( !inContext->domainsQuery ); check( !inContext->domainList ); check( !inContext->exitTimer ); check( !inContext->pendingConnectCount ); DNSServiceForget( &inContext->mainRef ); if( inContext->serviceTypes ) { StringArray_Free( inContext->serviceTypes, inContext->serviceTypesCount ); inContext->serviceTypes = NULL; inContext->serviceTypesCount = 0; } free( inContext ); } } //=========================================================================================================================== // BrowseAllAddServiceType //=========================================================================================================================== static OSStatus BrowseAllAddServiceType( BrowseAllContext * inContext, BrowseDomain * inDomain, const char * inName, uint32_t inIfIndex, Boolean inIncludeAWDL ) { OSStatus err; DNSServiceRef sdRef; DNSServiceFlags flags; BrowseType * type; BrowseType ** typePtr; BrowseType * newType = NULL; BrowseOp * browse; BrowseOp ** browsePtr; BrowseOp * newBrowse = NULL; for( typePtr = &inDomain->typeList; ( type = *typePtr ) != NULL; typePtr = &type->next ) { if( strcasecmp( type->name, inName ) == 0 ) break; } if( !type ) { newType = (BrowseType *) calloc( 1, sizeof( *newType ) ); require_action( newType, exit, err = kNoMemoryErr ); newType->name = strdup( inName ); require_action( newType->name, exit, err = kNoMemoryErr ); type = newType; } for( browsePtr = &type->browseList; ( browse = *browsePtr ) != NULL; browsePtr = &browse->next ) { if( browse->ifIndex == inIfIndex ) break; } require_action_quiet( !browse, exit, err = kDuplicateErr ); newBrowse = (BrowseOp *) calloc( 1, sizeof( *newBrowse ) ); require_action( newBrowse, exit, err = kNoMemoryErr ); ++inContext->refCount; newBrowse->context = inContext; newBrowse->ifIndex = inIfIndex; if( stricmp_suffix( inName, "._tcp" ) == 0 ) newBrowse->isTCP = true; flags = kDNSServiceFlagsShareConnection; if( inIncludeAWDL ) flags |= kDNSServiceFlagsIncludeAWDL; newBrowse->startTicks = UpTicks(); sdRef = inContext->mainRef; err = DNSServiceBrowse( &sdRef, flags, newBrowse->ifIndex, type->name, inDomain->name, BrowseAllBrowseCallback, newBrowse ); require_noerr( err, exit ); newBrowse->browse = sdRef; *browsePtr = newBrowse; newBrowse = NULL; if( newType ) { *typePtr = newType; newType = NULL; } exit: if( newBrowse ) BrowseOpFree( newBrowse ); if( newType ) BrowseTypeFree( newType ); return( err ); } //=========================================================================================================================== // BrowseAllRemoveServiceType //=========================================================================================================================== static OSStatus BrowseAllRemoveServiceType( BrowseAllContext * inContext, BrowseDomain * inDomain, const char * inName, uint32_t inIfIndex ) { OSStatus err; BrowseType * type; BrowseType ** typePtr; BrowseOp * browse; BrowseOp ** browsePtr; Unused( inContext ); for( typePtr = &inDomain->typeList; ( type = *typePtr ) != NULL; typePtr = &type->next ) { if( strcasecmp( type->name, inName ) == 0 ) break; } require_action_quiet( type, exit, err = kNotFoundErr ); for( browsePtr = &type->browseList; ( browse = *browsePtr ) != NULL; browsePtr = &browse->next ) { if( browse->ifIndex == inIfIndex ) break; } require_action_quiet( browse, exit, err = kNotFoundErr ); *browsePtr = browse->next; BrowseOpFree( browse ); if( !type->browseList ) { *typePtr = type->next; BrowseTypeFree( type ); } err = kNoErr; exit: return( err ); } //=========================================================================================================================== // BrowseAllAddServiceInstance //=========================================================================================================================== static OSStatus BrowseAllAddServiceInstance( BrowseAllContext * inContext, BrowseOp * inBrowse, const char * inName, const char * inRegType, const char * inDomain, uint32_t inIfIndex ) { OSStatus err; DNSServiceRef sdRef; BrowseInstance * instance; BrowseInstance ** p; const uint64_t nowTicks = UpTicks(); BrowseInstance * newInstance = NULL; for( p = &inBrowse->instanceList; ( instance = *p ) != NULL; p = &instance->next ) { if( ( instance->ifIndex == inIfIndex ) && ( strcasecmp( instance->name, inName ) == 0 ) ) break; } require_action_quiet( !instance, exit, err = kDuplicateErr ); newInstance = (BrowseInstance *) calloc( 1, sizeof( *newInstance ) ); require_action( newInstance, exit, err = kNoMemoryErr ); ++inContext->refCount; newInstance->context = inContext; newInstance->foundTicks = nowTicks; newInstance->ifIndex = inIfIndex; newInstance->isTCP = inBrowse->isTCP; newInstance->name = strdup( inName ); require_action( newInstance->name, exit, err = kNoMemoryErr ); sdRef = inContext->mainRef; newInstance->resolveStartTicks = UpTicks(); err = DNSServiceResolve( &sdRef, kDNSServiceFlagsShareConnection, newInstance->ifIndex, inName, inRegType, inDomain, BrowseAllResolveCallback, newInstance ); require_noerr( err, exit ); newInstance->resolve = sdRef; *p = newInstance; newInstance = NULL; exit: if( newInstance ) BrowseInstanceFree( newInstance ); return( err ); } //=========================================================================================================================== // BrowseAllRemoveServiceInstance //=========================================================================================================================== static OSStatus BrowseAllRemoveServiceInstance( BrowseAllContext * inContext, BrowseOp * inBrowse, const char * inName, uint32_t inIfIndex ) { OSStatus err; BrowseInstance * instance; BrowseInstance ** p; Unused( inContext ); for( p = &inBrowse->instanceList; ( instance = *p ) != NULL; p = &instance->next ) { if( ( instance->ifIndex == inIfIndex ) && ( strcasecmp( instance->name, inName ) == 0 ) ) break; } require_action_quiet( instance, exit, err = kNotFoundErr ); *p = instance->next; BrowseInstanceFree( instance ); err = kNoErr; exit: return( err ); } //=========================================================================================================================== // BrowseAllAddIPAddress //=========================================================================================================================== #define kDiscardProtocolPort 9 static OSStatus BrowseAllAddIPAddress( BrowseAllContext * inContext, BrowseInstance * inInstance, const struct sockaddr * inSockAddr ) { OSStatus err; BrowseIPAddr * addr; BrowseIPAddr ** p; const uint64_t nowTicks = UpTicks(); BrowseIPAddr * newAddr = NULL; if( ( inSockAddr->sa_family != AF_INET ) && ( inSockAddr->sa_family != AF_INET6 ) ) { dlogassert( "Unexpected address family: %d", inSockAddr->sa_family ); err = kTypeErr; goto exit; } for( p = &inInstance->addrList; ( addr = *p ) != NULL; p = &addr->next ) { if( SockAddrCompareAddr( &addr->sip, inSockAddr ) == 0 ) break; } require_action_quiet( !addr, exit, err = kDuplicateErr ); newAddr = (BrowseIPAddr *) calloc( 1, sizeof( *newAddr ) ); require_action( newAddr, exit, err = kNoMemoryErr ); ++inContext->refCount; newAddr->refCount = 1; newAddr->context = inContext; newAddr->foundTicks = nowTicks; SockAddrCopy( inSockAddr, &newAddr->sip.sa ); if( ( inContext->maxConnectTimeSecs > 0 ) && inInstance->isTCP && ( inInstance->port != kDiscardProtocolPort ) ) { char destination[ kSockAddrStringMaxSize ]; err = SockAddrToString( &newAddr->sip, kSockAddrStringFlagsNoPort, destination ); require_noerr( err, exit ); err = AsyncConnection_Connect( &newAddr->connection, destination, -inInstance->port, kAsyncConnectionFlag_P2P, kAsyncConnectionNoTimeout, kSocketBufferSize_DontSet, kSocketBufferSize_DontSet, BrowseAllConnectionProgress, newAddr, BrowseAllConnectionHandler, newAddr, dispatch_get_main_queue() ); require_noerr( err, exit ); ++newAddr->refCount; newAddr->connectStatus = kConnectStatus_Pending; ++inContext->pendingConnectCount; } *p = newAddr; newAddr = NULL; err = kNoErr; exit: if( newAddr ) BrowseIPAddrRelease( newAddr ); return( err ); } //=========================================================================================================================== // BrowseAllRemoveIPAddress //=========================================================================================================================== static OSStatus BrowseAllRemoveIPAddress( BrowseAllContext * inContext, BrowseInstance * inInstance, const struct sockaddr * inSockAddr ) { OSStatus err; BrowseIPAddr * addr; BrowseIPAddr ** p; Unused( inContext ); for( p = &inInstance->addrList; ( addr = *p ) != NULL; p = &addr->next ) { if( SockAddrCompareAddr( &addr->sip.sa, inSockAddr ) == 0 ) break; } require_action_quiet( addr, exit, err = kNotFoundErr ); *p = addr->next; BrowseIPAddrRelease( addr ); err = kNoErr; exit: return( err ); } //=========================================================================================================================== // BrowseDomainFree //=========================================================================================================================== static void BrowseDomainFree( BrowseDomain *inDomain ) { BrowseType * type; ForgetBrowseAllContext( &inDomain->context ); ForgetMem( &inDomain->name ); DNSServiceForget( &inDomain->servicesQuery ); while( ( type = inDomain->typeList ) != NULL ) { inDomain->typeList = type->next; BrowseTypeFree( type ); } free( inDomain ); } //=========================================================================================================================== // BrowseTypeFree //=========================================================================================================================== static void BrowseTypeFree( BrowseType *inType ) { BrowseOp * browse; ForgetMem( &inType->name ); while( ( browse = inType->browseList ) != NULL ) { inType->browseList = browse->next; BrowseOpFree( browse ); } free( inType ); } //=========================================================================================================================== // BrowseOpFree //=========================================================================================================================== static void BrowseOpFree( BrowseOp *inBrowse ) { BrowseInstance * instance; ForgetBrowseAllContext( &inBrowse->context ); DNSServiceForget( &inBrowse->browse ); while( ( instance = inBrowse->instanceList ) != NULL ) { inBrowse->instanceList = instance->next; BrowseInstanceFree( instance ); } free( inBrowse ); } //=========================================================================================================================== // BrowseInstanceFree //=========================================================================================================================== static void BrowseInstanceFree( BrowseInstance *inInstance ) { ForgetBrowseAllContext( &inInstance->context ); ForgetMem( &inInstance->name ); DNSServiceForget( &inInstance->resolve ); DNSServiceForget( &inInstance->getAddr ); ForgetMem( &inInstance->txtPtr ); ForgetMem( &inInstance->hostname ); ForgetIPAddressList( &inInstance->addrList ); free( inInstance ); } //=========================================================================================================================== // BrowseIPAddrRelease //=========================================================================================================================== static void BrowseIPAddrRelease( BrowseIPAddr *inAddr ) { AsyncConnection_Forget( &inAddr->connection ); if( --inAddr->refCount == 0 ) { ForgetBrowseAllContext( &inAddr->context ); free( inAddr ); } } //=========================================================================================================================== // BrowseIPAddrReleaseList //=========================================================================================================================== static void BrowseIPAddrReleaseList( BrowseIPAddr *inList ) { BrowseIPAddr * addr; while( ( addr = inList ) != NULL ) { inList = addr->next; BrowseIPAddrRelease( addr ); } } //=========================================================================================================================== // 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 ); err = RecordTypeFromArgString( gDNSQuery_Type, &context->type ); require_noerr( err, exit ); // Write query message. check_compile_time_code( sizeof( context->msgBuf ) >= ( kDNSQueryMessageMaxLen + 2 ) ); msgPtr = context->useTCP ? &context->msgBuf[ 2 ] : context->msgBuf; err = WriteDNSQueryMessage( msgPtr, context->queryID, (uint16_t) gDNSQuery_Flags, context->name, context->type, kDNSServiceClass_IN, &msgLen ); require_noerr( err, exit ); check( msgLen <= UINT16_MAX ); if( context->useTCP ) { WriteBig16( context->msgBuf, msgLen ); sendLen = 2 + msgLen; } else { sendLen = msgLen; } DNSQueryPrintPrologue( context ); if( gDNSQuery_Verbose ) { FPrintF( stdout, "DNS message to send:\n\n%{du:dnsmsg}", 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 ) ); FPrintF( stdout, "%.*{du:dnsmsg}", context->printRawRData ? 1 : 0, context->msgPtr, context->msgLen ); if( ( context->msgLen >= kDNSHeaderLength ) && ( DNSHeaderGetID( (DNSHeader *) context->msgPtr ) == context->queryID ) ) { Exit( kExitReason_ReceivedResponse ); } exit: if( err ) dispatch_source_forget( &context->readSource ); } //=========================================================================================================================== // 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 ); err = SocketContextCreate( sock, context, &sockCtx ); require_noerr( err, exit ); sock = kInvalidSocketRef; err = DispatchReadSourceCreate( sockCtx->sock, NULL, DNSCryptReceiveCertHandler, SocketContextCancelHandler, sockCtx, &context->readSource ); if( err ) ForgetSocketContext( &sockCtx ); require_noerr( err, exit ); dispatch_resume( context->readSource ); if( context->timeLimitSecs > 0 ) { dispatch_after_f( dispatch_time_seconds( context->timeLimitSecs ), dispatch_get_main_queue(), kExitReason_Timeout, Exit ); } dispatch_main(); exit: 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 ) ); FPrintF( stdout, "%.*{du:dnsmsg}", context->printRawRData ? 1 : 0, context->msgBuf, context->msgLen ); require_action_quiet( context->msgLen >= kDNSHeaderLength, exit, err = kSizeErr ); hdr = (DNSHeader *) context->msgBuf; require_action_quiet( DNSHeaderGetID( hdr ) == context->queryID, exit, err = kMismatchErr ); err = DNSMessageGetAnswerSection( context->msgBuf, context->msgLen, &ptr ); require_noerr( err, exit ); err = DomainNameFromString( targetName, context->providerName, NULL ); require_noerr( err, exit ); answerCount = DNSHeaderGetAnswerCount( hdr ); for( i = 0; i < answerCount; ++i ) { 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; FPrintF( stdout, "%.*{du:dnsmsg}", context->printRawRData ? 1 : 0, 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 ); err = SocketContextCreate( sock, inContext, &sockCtx ); require_noerr( err, exit ); sock = kInvalidSocketRef; err = DispatchReadSourceCreate( sockCtx->sock, NULL, DNSCryptReceiveResponseHandler, SocketContextCancelHandler, sockCtx, &inContext->readSource ); if( err ) ForgetSocketContext( &sockCtx ); require_noerr( err, exit ); dispatch_resume( inContext->readSource ); exit: ForgetSocket( &sock ); return( err ); } //=========================================================================================================================== // 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[ 8940 ]; // Message buffer. 8940 is max size used by mDNSResponder. } MDNSQueryContext; static void MDNSQueryPrintPrologue( const MDNSQueryContext *inContext ); static void MDNSQueryReadHandler( void *inContext ); static void MDNSQueryCmd( void ) { OSStatus err; MDNSQueryContext * context; struct sockaddr_in mcastAddr4; struct sockaddr_in6 mcastAddr6; SocketRef sockV4 = kInvalidSocketRef; SocketRef sockV6 = kInvalidSocketRef; ssize_t n; const char * ifNamePtr; size_t msgLen; unsigned int sendCount; // Check command parameters. if( gMDNSQuery_ReceiveSecs < -1 ) { FPrintF( stdout, "Invalid receive time value: %d seconds.\n", gMDNSQuery_ReceiveSecs ); err = kParamErr; goto exit; } context = (MDNSQueryContext *) calloc( 1, sizeof( *context ) ); require_action( context, exit, err = kNoMemoryErr ); context->qnameStr = gMDNSQuery_Name; context->receiveSecs = gMDNSQuery_ReceiveSecs; context->isQU = gMDNSQuery_IsQU ? true : false; context->allResponses = gMDNSQuery_AllResponses ? true : false; context->printRawRData = gMDNSQuery_RawRData ? true : false; context->useIPv4 = ( gMDNSQuery_UseIPv4 || !gMDNSQuery_UseIPv6 ) ? true : false; context->useIPv6 = ( gMDNSQuery_UseIPv6 || !gMDNSQuery_UseIPv4 ) ? true : false; err = InterfaceIndexFromArgString( gInterface, &context->ifIndex ); require_noerr_quiet( err, exit ); ifNamePtr = if_indextoname( context->ifIndex, context->ifName ); require_action( ifNamePtr, exit, err = kNameErr ); err = RecordTypeFromArgString( gMDNSQuery_Type, &context->qtype ); require_noerr( err, exit ); // Set up IPv4 socket. if( context->useIPv4 ) { err = ServerSocketOpen( AF_INET, SOCK_DGRAM, IPPROTO_UDP, gMDNSQuery_SourcePort ? gMDNSQuery_SourcePort : ( context->isQU ? context->localPort : kMDNSPort ), &context->localPort, kSocketBufferSize_DontSet, &sockV4 ); require_noerr( err, exit ); err = SocketSetMulticastInterface( sockV4, ifNamePtr, context->ifIndex ); require_noerr( err, exit ); err = setsockopt( sockV4, IPPROTO_IP, IP_MULTICAST_LOOP, (char *) &(uint8_t){ 1 }, (socklen_t) sizeof( uint8_t ) ); err = map_socket_noerr_errno( sockV4, err ); require_noerr( err, exit ); memset( &mcastAddr4, 0, sizeof( mcastAddr4 ) ); SIN_LEN_SET( &mcastAddr4 ); mcastAddr4.sin_family = AF_INET; mcastAddr4.sin_port = htons( kMDNSPort ); mcastAddr4.sin_addr.s_addr = htonl( 0xE00000FB ); // The mDNS IPv4 multicast address is 224.0.0.251 if( !context->isQU && ( context->localPort == kMDNSPort ) ) { err = SocketJoinMulticast( sockV4, &mcastAddr4, ifNamePtr, context->ifIndex ); require_noerr( err, exit ); } } // Set up IPv6 socket. if( context->useIPv6 ) { err = ServerSocketOpen( AF_INET6, SOCK_DGRAM, IPPROTO_UDP, gMDNSQuery_SourcePort ? gMDNSQuery_SourcePort : ( context->isQU ? context->localPort : kMDNSPort ), &context->localPort, kSocketBufferSize_DontSet, &sockV6 ); require_noerr( err, exit ); err = SocketSetMulticastInterface( sockV6, ifNamePtr, context->ifIndex ); require_noerr( err, exit ); err = setsockopt( sockV6, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, (char *) &(int){ 1 }, (socklen_t) sizeof( int ) ); err = map_socket_noerr_errno( sockV6, err ); require_noerr( err, exit ); memset( &mcastAddr6, 0, sizeof( mcastAddr6 ) ); SIN6_LEN_SET( &mcastAddr6 ); mcastAddr6.sin6_family = AF_INET6; mcastAddr6.sin6_port = htons( kMDNSPort ); mcastAddr6.sin6_addr.s6_addr[ 0 ] = 0xFF; // mDNS IPv6 multicast address FF02::FB mcastAddr6.sin6_addr.s6_addr[ 1 ] = 0x02; mcastAddr6.sin6_addr.s6_addr[ 15 ] = 0xFB; if( !context->isQU && ( context->localPort == kMDNSPort ) ) { err = SocketJoinMulticast( sockV6, &mcastAddr6, ifNamePtr, context->ifIndex ); require_noerr( err, exit ); } } // Craft mDNS query message. check_compile_time_code( sizeof( context->msgBuf ) >= kDNSQueryMessageMaxLen ); err = WriteDNSQueryMessage( context->msgBuf, kDefaultMDNSMessageID, kDefaultMDNSQueryFlags, context->qnameStr, context->qtype, context->isQU ? ( kDNSServiceClass_IN | kQClassUnicastResponseBit ) : kDNSServiceClass_IN, &msgLen ); require_noerr( err, exit ); // Print prologue. MDNSQueryPrintPrologue( context ); // Send mDNS query message. sendCount = 0; if( IsValidSocket( sockV4 ) ) { n = sendto( sockV4, context->msgBuf, msgLen, 0, (struct sockaddr *) &mcastAddr4, (socklen_t) sizeof( mcastAddr4 ) ); err = map_socket_value_errno( sockV4, n == (ssize_t) msgLen, n ); if( err ) { FPrintF( stderr, "*** Failed to send query on IPv4 socket with error %#m\n", err ); ForgetSocket( &sockV4 ); } else { ++sendCount; } } if( IsValidSocket( sockV6 ) ) { n = sendto( sockV6, context->msgBuf, msgLen, 0, (struct sockaddr *) &mcastAddr6, (socklen_t) sizeof( mcastAddr6 ) ); err = map_socket_value_errno( sockV6, n == (ssize_t) msgLen, n ); if( err ) { 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; err = SocketContextCreate( sockV4, context, &sockCtx ); require_noerr( err, exit ); sockV4 = kInvalidSocketRef; err = DispatchReadSourceCreate( sockCtx->sock, NULL, MDNSQueryReadHandler, SocketContextCancelHandler, sockCtx, &context->readSourceV4 ); if( err ) ForgetSocketContext( &sockCtx ); require_noerr( err, exit ); dispatch_resume( context->readSourceV4 ); } if( IsValidSocket( sockV6 ) ) { SocketContext * sockCtx; err = SocketContextCreate( sockV6, context, &sockCtx ); require_noerr( err, exit ); sockV6 = kInvalidSocketRef; err = DispatchReadSourceCreate( sockCtx->sock, NULL, MDNSQueryReadHandler, SocketContextCancelHandler, sockCtx, &context->readSourceV6 ); if( err ) ForgetSocketContext( &sockCtx ); require_noerr( err, exit ); dispatch_resume( context->readSourceV6 ); } 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 ); if( err ) exit( 1 ); } //=========================================================================================================================== // 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%#.*{du:dnsmsg}", msgLen, context->printRawRData ? 1 : 0, context->msgBuf, msgLen ); } exit: if( err ) exit( 1 ); } //=========================================================================================================================== // PIDToUUIDCmd //=========================================================================================================================== static void PIDToUUIDCmd( void ) { OSStatus err; int n; struct proc_uniqidentifierinfo info; n = proc_pidinfo( gPIDToUUID_PID, PROC_PIDUNIQIDENTIFIERINFO, 1, &info, sizeof( info ) ); require_action_quiet( n == (int) sizeof( info ), exit, err = kUnknownErr ); FPrintF( stdout, "%#U\n", info.p_uuid ); err = kNoErr; exit: if( err ) exit( 1 ); } //=========================================================================================================================== // DNSServerCmd //=========================================================================================================================== typedef uint32_t DNSServerEventType; #define kDNSServerEvent_Started 1 #define kDNSServerEvent_Stopped 2 typedef struct DNSServerPrivate * DNSServerRef; typedef struct { DNSServerRef server; // Reference to the DNS server. dispatch_source_t sigIntSource; // Dispatch SIGINT source. dispatch_source_t sigTermSource; // Dispatch SIGTERM source. #if( TARGET_OS_DARWIN ) dispatch_source_t processMonitor; // Process monitor source for process being followed, if any. pid_t followPID; // PID of process being followed (we exit when they exit), if any. Boolean resolverRegistered; // True if system DNS settings contains a resolver entry for server. #endif Boolean loopbackOnly; // True if the server should be bound to the loopback interface. Boolean serverStarted; // True if the server was successfully started. Boolean calledStop; // True if the server was explicitly stopped. } DNSServerCmdContext; typedef void ( *DNSServerEventHandler_f )( DNSServerEventType inType, void *inContext ); CFTypeID DNSServerGetTypeID( void ); static OSStatus DNSServerCreate( dispatch_queue_t inQueue, DNSServerEventHandler_f inEventHandler, void * inEventContext, int inResponseDelayMs, Boolean inLoopbackOnly, DNSServerRef * outServer ); static void DNSServerStart( DNSServerRef inServer ); static void DNSServerStop( DNSServerRef inServer ); static void DNSServerCmdContextFree( DNSServerCmdContext *inContext ); static void DNSServerCmdEventHandler( DNSServerEventType inType, void *inContext ); static void DNSServerCmdSigIntHandler( void *inContext ); static void DNSServerCmdSigTermHandler( void *inContext ); #if( TARGET_OS_DARWIN ) static void DNSServerCmdFollowedProcessHandler( void *inContext ); #endif ulog_define_ex( "com.apple.dnssdutil", DNSServer, kLogLevelInfo, kLogFlags_None, "DNSServer", NULL ); #define ds_ulog( LEVEL, ... ) ulog( &log_category_from_name( DNSServer ), (LEVEL), __VA_ARGS__ ) static void DNSServerCmd( void ) { OSStatus err; DNSServerCmdContext * context; context = (DNSServerCmdContext *) calloc( 1, sizeof( *context ) ); require_action( context, exit, err = kNoMemoryErr ); context->loopbackOnly = gDNSServer_LoopbackOnly ? true : false; #if( TARGET_OS_DARWIN ) if( gDNSServer_FollowPID ) { long long value; err = StringToLongLong( gDNSServer_FollowPID, &value ); if( !err && ( value < 0 ) ) err = kValueErr; if( err ) { FPrintF( stderr, "Invalid followPID argument \"%s\".\n", gDNSServer_FollowPID ); goto exit; } context->followPID = (pid_t) value; err = DispatchProcessMonitorCreate( context->followPID, DISPATCH_PROC_EXIT, dispatch_get_main_queue(), DNSServerCmdFollowedProcessHandler, NULL, context, &context->processMonitor ); require_noerr( err, exit ); dispatch_resume( context->processMonitor ); } else { context->followPID = -1; } #endif signal( SIGINT, SIG_IGN ); err = DispatchSignalSourceCreate( SIGINT, DNSServerCmdSigIntHandler, context, &context->sigIntSource ); require_noerr( err, exit ); dispatch_resume( context->sigIntSource ); signal( SIGTERM, SIG_IGN ); err = DispatchSignalSourceCreate( SIGTERM, DNSServerCmdSigTermHandler, context, &context->sigTermSource ); require_noerr( err, exit ); dispatch_resume( context->sigTermSource ); if( gDNSServer_Foreground ) { LogControl( "DNSServer:output=file;stdout,DNSServer:flags=time;prefix" ); } if( ( gDNSServer_DefaultTTL < 0 ) || ( gDNSServer_DefaultTTL > INT32_MAX ) ) { ds_ulog( kLogLevelError, "The default TTL %d provided by user is out-of-range. Will use %d instead.\n", gDNSServer_DefaultTTL, kDNSServerDefaultTTL ); gDNSServer_DefaultTTL = kDNSServerDefaultTTL; } err = DNSServerCreate( dispatch_get_main_queue(), DNSServerCmdEventHandler, context, gDNSServer_ResponseDelayMs, context->loopbackOnly, &context->server ); require_noerr( err, exit ); DNSServerStart( context->server ); dispatch_main(); exit: ds_ulog( kLogLevelError, "Failed to start DNS server: %#m\n", err ); if( context ) DNSServerCmdContextFree( context ); if( err ) exit( 1 ); } //=========================================================================================================================== // DNSServerCmdContextFree //=========================================================================================================================== static void DNSServerCmdContextFree( DNSServerCmdContext *inContext ) { ForgetCF( &inContext->server ); dispatch_source_forget( &inContext->sigIntSource ); dispatch_source_forget( &inContext->sigTermSource ); dispatch_source_forget( &inContext->processMonitor ); free( inContext ); } //=========================================================================================================================== // DNSServerCmdEventHandler //=========================================================================================================================== #if( TARGET_OS_DARWIN ) static OSStatus _DNSServerCmdRegisterResolver( void ); static OSStatus _DNSServerCmdUnregisterResolver( void ); #endif static void DNSServerCmdEventHandler( DNSServerEventType inType, void *inContext ) { DNSServerCmdContext * const context = (DNSServerCmdContext *) inContext; #if( TARGET_OS_DARWIN ) OSStatus err; #endif if( inType == kDNSServerEvent_Started ) { context->serverStarted = true; #if( TARGET_OS_DARWIN ) err = _DNSServerCmdRegisterResolver(); if( err ) { ds_ulog( kLogLevelError, "Failed to add resolver to DNS configuration for \"d.test.\" domain: %#m\n", err ); if( context->loopbackOnly ) exit( 1 ); } else { context->resolverRegistered = true; } #endif } else if( inType == kDNSServerEvent_Stopped ) { #if( TARGET_OS_DARWIN ) if( context->resolverRegistered ) { err = _DNSServerCmdUnregisterResolver(); if( err ) { ds_ulog( kLogLevelError, "Failed to remove resolver from DNS configuration: %#m\n", err ); } else { context->resolverRegistered = false; } } if( !context->calledStop ) { ds_ulog( kLogLevelError, "The server stopped unexpectedly.\n" ); exit( 1 ); } #endif DNSServerCmdContextFree( context ); } } #if( TARGET_OS_DARWIN ) //=========================================================================================================================== // _DNSServerCmdRegisterResolver //=========================================================================================================================== static OSStatus _DNSServerCmdRegisterResolver( void ) { OSStatus err; SCDynamicStoreRef store; CFPropertyListRef plist = NULL; CFStringRef key = NULL; const uint32_t loopbackV4 = htonl( INADDR_LOOPBACK ); Boolean success; store = SCDynamicStoreCreate( NULL, CFSTR( "com.apple.dnssdutil" ), NULL, NULL ); err = map_scerror( store ); require_noerr( err, exit ); err = CFPropertyListCreateFormatted( kCFAllocatorDefault, &plist, "{" "%kO=" "[" "%s" "]" "%kO=" "[" "%.4a" "%.16a" "]" "}", kSCPropNetDNSSupplementalMatchDomains, "d.test.", kSCPropNetDNSServerAddresses, &loopbackV4, in6addr_loopback.s6_addr ); require_noerr( err, exit ); key = SCDynamicStoreKeyCreateNetworkServiceEntity( NULL, kSCDynamicStoreDomainState, CFSTR( "com.apple.dnssdutil.server" ), kSCEntNetDNS ); require_action( key, exit, err = kUnknownErr ); success = SCDynamicStoreSetValue( store, key, plist ); require_action( success, exit, err = kUnknownErr ); exit: CFReleaseNullSafe( store ); CFReleaseNullSafe( plist ); CFReleaseNullSafe( key ); return( err ); } //=========================================================================================================================== // _DNSServerCmdUnregisterResolver //=========================================================================================================================== static OSStatus _DNSServerCmdUnregisterResolver( void ) { OSStatus err; SCDynamicStoreRef store; CFStringRef key = NULL; Boolean success; store = SCDynamicStoreCreate( NULL, CFSTR( "com.apple.dnssdutil" ), NULL, NULL ); err = map_scerror( store ); require_noerr( err, exit ); key = SCDynamicStoreKeyCreateNetworkServiceEntity( NULL, kSCDynamicStoreDomainState, CFSTR( "com.apple.dnssdutil.server" ), kSCEntNetDNS ); require_action( key, exit, err = kUnknownErr ); success = SCDynamicStoreRemoveValue( store, key ); require_action( success, exit, err = kUnknownErr ); exit: CFReleaseNullSafe( store ); CFReleaseNullSafe( key ); return( err ); } #endif //=========================================================================================================================== // DNSServerCmdSigIntHandler //=========================================================================================================================== static void _DNSServerCmdExternalExit( DNSServerCmdContext *inContext, int inSignal ); static void DNSServerCmdSigIntHandler( void *inContext ) { _DNSServerCmdExternalExit( (DNSServerCmdContext *) inContext, SIGINT ); } //=========================================================================================================================== // DNSServerCmdSigTermHandler //=========================================================================================================================== static void DNSServerCmdSigTermHandler( void *inContext ) { _DNSServerCmdExternalExit( (DNSServerCmdContext *) inContext, SIGTERM ); } #if( TARGET_OS_DARWIN ) //=========================================================================================================================== // DNSServerCmdFollowedProcessHandler //=========================================================================================================================== static void DNSServerCmdFollowedProcessHandler( void *inContext ) { DNSServerCmdContext * const context = (DNSServerCmdContext *) inContext; if( dispatch_source_get_data( context->processMonitor ) & DISPATCH_PROC_EXIT ) { _DNSServerCmdExternalExit( context, 0 ); } } #endif //=========================================================================================================================== // _DNSServerCmdExternalExit //=========================================================================================================================== #define SignalNumberToString( X ) ( \ ( (X) == SIGINT ) ? "SIGINT" : \ ( (X) == SIGTERM ) ? "SIGTERM" : \ "???" ) static void _DNSServerCmdExternalExit( DNSServerCmdContext *inContext, int inSignal ) { OSStatus err; #if( TARGET_OS_DARWIN ) if( inSignal == 0 ) { ds_ulog( kLogLevelNotice, "Exiting: followed process (%lld) exited\n", (int64_t) inContext->followPID ); } else #endif { ds_ulog( kLogLevelNotice, "Exiting: received signal %d (%s)\n", inSignal, SignalNumberToString( inSignal ) ); } #if( TARGET_OS_DARWIN ) if( inContext->resolverRegistered ) { err = _DNSServerCmdUnregisterResolver(); if( err ) { ds_ulog( kLogLevelError, "Failed to remove resolver from DNS configuration: %#m\n", err ); goto exit; } inContext->resolverRegistered = false; } #endif if( inContext->serverStarted ) { DNSServerStop( inContext->server ); inContext->calledStop = true; } err = kNoErr; exit: exit( err ? 1 : 0 ); } //=========================================================================================================================== // DNSServerCreate //=========================================================================================================================== typedef struct DNSDelayedResponse DNSDelayedResponse; struct DNSDelayedResponse { DNSDelayedResponse * next; sockaddr_ip clientAddr; uint64_t targetTicks; uint8_t * msgPtr; size_t msgLen; }; #define DNSScheduledResponseFree( X ) do { ForgetMem( &(X)->msgPtr ) ; free( X ); } while( 0 ) struct DNSServerPrivate { CFRuntimeBase base; // CF object base. dispatch_queue_t queue; // Queue for DNS server's events. dispatch_source_t readSourceUDPv4; // Read source for IPv4 UDP socket. dispatch_source_t readSourceUDPv6; // Read source for IPv6 UDP socket. dispatch_source_t readSourceTCPv4; // Read source for IPv4 TCP socket. dispatch_source_t readSourceTCPv6; // Read source for IPv6 TCP socket. DNSServerEventHandler_f eventHandler; void * eventContext; DNSDelayedResponse * responseList; int responseDelayMs; dispatch_source_t responseTimer; Boolean loopbackOnly; Boolean stopped; }; CF_CLASS_DEFINE( DNSServer ); static OSStatus DNSServerCreate( dispatch_queue_t inQueue, DNSServerEventHandler_f inEventHandler, void * inEventContext, int inResponseDelayMs, Boolean inLoopbackOnly, DNSServerRef * outServer ) { OSStatus err; DNSServerRef obj = NULL; CF_OBJECT_CREATE( DNSServer, obj, err, exit ); ReplaceDispatchQueue( &obj->queue, inQueue ); obj->eventHandler = inEventHandler; obj->eventContext = inEventContext; obj->responseDelayMs = inResponseDelayMs; if( inLoopbackOnly ) obj->loopbackOnly = true; *outServer = obj; obj = NULL; err = kNoErr; exit: CFReleaseNullSafe( obj ); return( err ); } //=========================================================================================================================== // _DNSServerFinalize //=========================================================================================================================== static void _DNSServerFinalize( CFTypeRef inObj ) { DNSServerRef const me = (DNSServerRef) inObj; check( !me->readSourceUDPv4 ); check( !me->readSourceUDPv6 ); check( !me->readSourceTCPv4 ); check( !me->readSourceTCPv6 ); check( !me->responseTimer ); dispatch_forget( &me->queue ); } //=========================================================================================================================== // DNSServerStart //=========================================================================================================================== static void _DNSServerStart( void *inContext ); static void _DNSServerUDPReadHandler( void *inContext ); static void _DNSServerTCPReadHandler( void *inContext ); static void DNSServerStart( DNSServerRef me ) { CFRetain( me ); dispatch_async_f( me->queue, me, _DNSServerStart ); } static void _DNSServerStart( void *inContext ) { OSStatus err; DNSServerRef const me = (DNSServerRef) inContext; SocketRef sock = kInvalidSocketRef; SocketContext * sockCtx = NULL; const uint32_t loopbackV4 = htonl( INADDR_LOOPBACK ); // Create IPv4 UDP socket. err = _ServerSocketOpenEx2( AF_INET, SOCK_DGRAM, IPPROTO_UDP, me->loopbackOnly ? &loopbackV4 : NULL, kDNSPort, NULL, kSocketBufferSize_DontSet, me->loopbackOnly ? true : false, &sock ); require_noerr( err, exit ); // Create read source for IPv4 UDP socket. err = SocketContextCreate( sock, me, &sockCtx ); require_noerr( err, exit ); sock = kInvalidSocketRef; err = DispatchReadSourceCreate( sockCtx->sock, me->queue, _DNSServerUDPReadHandler, SocketContextCancelHandler, sockCtx, &me->readSourceUDPv4 ); require_noerr( err, exit ); dispatch_resume( me->readSourceUDPv4 ); sockCtx = NULL; // Create IPv6 UDP socket. err = _ServerSocketOpenEx2( AF_INET6, SOCK_DGRAM, IPPROTO_UDP, me->loopbackOnly ? &in6addr_loopback : NULL, kDNSPort, NULL, kSocketBufferSize_DontSet, me->loopbackOnly ? true : false, &sock ); require_noerr( err, exit ); // Create read source for IPv6 UDP socket. err = SocketContextCreate( sock, me, &sockCtx ); require_noerr( err, exit ); sock = kInvalidSocketRef; err = DispatchReadSourceCreate( sockCtx->sock, me->queue, _DNSServerUDPReadHandler, SocketContextCancelHandler, sockCtx, &me->readSourceUDPv6 ); require_noerr( err, exit ); dispatch_resume( me->readSourceUDPv6 ); sockCtx = NULL; // Create IPv4 TCP socket. err = _ServerSocketOpenEx2( AF_INET, SOCK_STREAM, IPPROTO_TCP, me->loopbackOnly ? &loopbackV4 : NULL, kDNSPort, NULL, kSocketBufferSize_DontSet, false, &sock ); require_noerr( err, exit ); // Create read source for IPv4 TCP socket. err = SocketContextCreate( sock, me, &sockCtx ); require_noerr( err, exit ); sock = kInvalidSocketRef; err = DispatchReadSourceCreate( sockCtx->sock, me->queue, _DNSServerTCPReadHandler, SocketContextCancelHandler, sockCtx, &me->readSourceTCPv4 ); require_noerr( err, exit ); dispatch_resume( me->readSourceTCPv4 ); sockCtx = NULL; // Create IPv6 TCP socket. err = _ServerSocketOpenEx2( AF_INET6, SOCK_STREAM, IPPROTO_TCP, me->loopbackOnly ? &in6addr_loopback : NULL, kDNSPort, NULL, kSocketBufferSize_DontSet, false, &sock ); require_noerr( err, exit ); // Create read source for IPv6 TCP socket. err = SocketContextCreate( sock, me, &sockCtx ); require_noerr( err, exit ); sock = kInvalidSocketRef; err = DispatchReadSourceCreate( sockCtx->sock, me->queue, _DNSServerTCPReadHandler, SocketContextCancelHandler, sockCtx, &me->readSourceTCPv6 ); require_noerr( err, exit ); dispatch_resume( me->readSourceTCPv6 ); sockCtx = NULL; CFRetain( me ); if( me->eventHandler ) me->eventHandler( kDNSServerEvent_Started, me->eventContext ); exit: ForgetSocket( &sock ); if( sockCtx ) SocketContextRelease( sockCtx ); if( err ) DNSServerStop( me ); CFRelease( me ); } //=========================================================================================================================== // DNSServerStop //=========================================================================================================================== static void _DNSServerStop( void *inContext ); static void _DNSServerStop2( void *inContext ); static void DNSServerStop( DNSServerRef me ) { CFRetain( me ); dispatch_async_f( me->queue, me, _DNSServerStop ); } static void _DNSServerStop( void *inContext ) { DNSServerRef const me = (DNSServerRef) inContext; DNSDelayedResponse * resp; dispatch_source_forget( &me->readSourceUDPv4 ); dispatch_source_forget( &me->readSourceUDPv6 ); dispatch_source_forget( &me->readSourceTCPv4 ); dispatch_source_forget( &me->readSourceTCPv6 ); dispatch_source_forget( &me->responseTimer ); while( ( resp = me->responseList ) != NULL ) { me->responseList = resp->next; DNSScheduledResponseFree( resp ); } dispatch_async_f( me->queue, me, _DNSServerStop2 ); } static void _DNSServerStop2( void *inContext ) { DNSServerRef const me = (DNSServerRef) inContext; if( !me->stopped ) { me->stopped = true; if( me->eventHandler ) me->eventHandler( kDNSServerEvent_Stopped, me->eventContext ); CFRelease( me ); } CFRelease( me ); } //=========================================================================================================================== // _DNSServerUDPReadHandler //=========================================================================================================================== static OSStatus _DNSServerAnswerQuery( const uint8_t * inQueryPtr, size_t inQueryLen, Boolean inForTCP, uint8_t ** outResponsePtr, size_t * outResponseLen ); #define _DNSServerAnswerQueryForUDP( IN_QUERY_PTR, IN_QUERY_LEN, IN_RESPONSE_PTR, IN_RESPONSE_LEN ) \ _DNSServerAnswerQuery( IN_QUERY_PTR, IN_QUERY_LEN, false, IN_RESPONSE_PTR, IN_RESPONSE_LEN ) #define _DNSServerAnswerQueryForTCP( IN_QUERY_PTR, IN_QUERY_LEN, IN_RESPONSE_PTR, IN_RESPONSE_LEN ) \ _DNSServerAnswerQuery( IN_QUERY_PTR, IN_QUERY_LEN, true, IN_RESPONSE_PTR, IN_RESPONSE_LEN ) static void _DNSServerUDPDelayedSend( void *inContext ); static void _DNSServerUDPReadHandler( void *inContext ) { OSStatus err; SocketContext * const sockCtx = (SocketContext *) inContext; DNSServerRef const me = (DNSServerRef) sockCtx->userContext; struct timeval now; ssize_t n; sockaddr_ip clientAddr; socklen_t clientAddrLen; uint8_t * responsePtr = NULL; // malloc'd size_t responseLen; uint8_t msg[ 512 ]; gettimeofday( &now, NULL ); // Receive message. clientAddrLen = (socklen_t) sizeof( clientAddr ); n = recvfrom( sockCtx->sock, (char *) msg, sizeof( msg ), 0, &clientAddr.sa, &clientAddrLen ); err = map_socket_value_errno( sockCtx->sock, n >= 0, n ); require_noerr( err, exit ); ds_ulog( kLogLevelInfo, "UDP server received %zd bytes from %##a at %{du:time}.\n", n, &clientAddr, &now ); if( n < kDNSHeaderLength ) { ds_ulog( kLogLevelInfo, "UDP DNS message is too small (%zd < %d).\n", n, kDNSHeaderLength ); goto exit; } ds_ulog( kLogLevelInfo, "UDP received message:\n\n%1{du:dnsmsg}", msg, (size_t) n ); // Create response. err = _DNSServerAnswerQueryForUDP( msg, (size_t) n, &responsePtr, &responseLen ); require_noerr_quiet( err, exit ); // Schedule response. if( me->responseDelayMs > 0 ) { DNSDelayedResponse * resp; DNSDelayedResponse ** ptr; DNSDelayedResponse * newResp; newResp = (DNSDelayedResponse *) calloc( 1, sizeof( *newResp ) ); require_action( newResp, exit, err = kNoMemoryErr ); SockAddrCopy( &clientAddr, &newResp->clientAddr ); newResp->targetTicks = UpTicks() + MillisecondsToUpTicks( (uint64_t) me->responseDelayMs ); newResp->msgLen = responseLen; newResp->msgPtr = responsePtr; responsePtr = NULL; for( ptr = &me->responseList; ( resp = *ptr ) != NULL; ptr = &resp->next ) { if( newResp->targetTicks < resp->targetTicks ) break; } newResp->next = resp; *ptr = newResp; if( me->responseList == newResp ) { dispatch_source_forget( &me->responseTimer ); err = DispatchTimerCreate( dispatch_time_milliseconds( me->responseDelayMs ), DISPATCH_TIME_FOREVER, ( (uint64_t) me->responseDelayMs ) * kNanosecondsPerMillisecond / 10, me->queue, _DNSServerUDPDelayedSend, NULL, sockCtx, &me->responseTimer ); require_noerr( err, exit ); dispatch_resume( me->responseTimer ); } } else { ds_ulog( kLogLevelInfo, "UDP sending %zu byte response:\n\n%1{du:dnsmsg}", responseLen, responsePtr, responseLen ); n = sendto( sockCtx->sock, (char *) responsePtr, responseLen, 0, &clientAddr.sa, clientAddrLen ); err = map_socket_value_errno( sockCtx->sock, n == (ssize_t) responseLen, n ); require_noerr( err, exit ); } exit: FreeNullSafe( responsePtr ); return; } static void _DNSServerUDPDelayedSend( void *inContext ) { OSStatus err; SocketContext * const sockCtx = (SocketContext *) inContext; DNSServerRef const me = (DNSServerRef) sockCtx->userContext; DNSDelayedResponse * resp; ssize_t n; uint64_t nowTicks; DNSDelayedResponse * freeList = NULL; dispatch_source_forget( &me->responseTimer ); nowTicks = UpTicks(); while( ( resp = me->responseList ) != NULL ) { if( resp->targetTicks > nowTicks ) break; me->responseList = resp->next; ds_ulog( kLogLevelInfo, "UDP sending %zu byte response (delayed):\n\n%1{du:dnsmsg}", resp->msgLen, resp->msgPtr, resp->msgLen ); n = sendto( sockCtx->sock, (char *) resp->msgPtr, resp->msgLen, 0, &resp->clientAddr.sa, SockAddrGetSize( &resp->clientAddr ) ); err = map_socket_value_errno( sockCtx->sock, n == (ssize_t) resp->msgLen, n ); check_noerr( err ); resp->next = freeList; freeList = resp; nowTicks = UpTicks(); } if( ( resp = me->responseList ) != NULL ) { uint64_t remainingNs; remainingNs = UpTicksToNanoseconds( resp->targetTicks - nowTicks ); if( remainingNs > INT64_MAX ) remainingNs = INT64_MAX; err = DispatchTimerCreate( dispatch_time( DISPATCH_TIME_NOW, (int64_t) remainingNs ), DISPATCH_TIME_FOREVER, 0, me->queue, _DNSServerUDPDelayedSend, NULL, sockCtx, &me->responseTimer ); require_noerr( err, exit ); dispatch_resume( me->responseTimer ); } exit: while( ( resp = freeList ) != NULL ) { freeList = resp->next; DNSScheduledResponseFree( resp ); } } //=========================================================================================================================== // _DNSServerAnswerQuery //=========================================================================================================================== #define kLabelPrefix_Alias "alias" #define kLabelPrefix_AliasTTL "alias-ttl" #define kLabelPrefix_Count "count" #define kLabelPrefix_TTL "ttl" #define kLabel_IPv4 "ipv4" #define kLabel_IPv6 "ipv6" #define kMaxAliasTTLCount ( ( kDomainLabelLengthMax - sizeof_string( kLabelPrefix_AliasTTL ) ) / 2 ) static OSStatus _DNSServerInitializeResponseMessage( DataBuffer * inDB, unsigned int inID, unsigned int inFlags, const uint8_t * inQName, unsigned int inQType, unsigned int inQClass ); static OSStatus _DNSServerAnswerQueryDynamically( const uint8_t * inQName, unsigned int inQType, unsigned int inQClass, Boolean inForTCP, DataBuffer * inDB ); static OSStatus _DNSServerAnswerQuery( const uint8_t * const inQueryPtr, const size_t inQueryLen, Boolean inForTCP, uint8_t ** outResponsePtr, size_t * outResponseLen ) { OSStatus err; DataBuffer dataBuf; const uint8_t * ptr; const uint8_t * const queryEnd = &inQueryPtr[ inQueryLen ]; const DNSHeader * qhdr; unsigned int msgID, qflags, qtype, qclass, rflags; uint8_t qname[ kDomainNameLengthMax ]; DataBuffer_Init( &dataBuf, NULL, 0, kDNSMaxTCPMessageSize ); require_action_quiet( inQueryLen >= kDNSHeaderLength, exit, err = kUnderrunErr ); qhdr = (const DNSHeader *) inQueryPtr; msgID = DNSHeaderGetID( qhdr ); qflags = DNSHeaderGetFlags( qhdr ); // Minimal checking of the query message's header. if( ( qflags & kDNSHeaderFlag_Response ) || // The message must be a query, not a response. ( DNSFlagsGetOpCode( qflags ) != kDNSOpCode_Query ) || // OPCODE must be QUERY (standard query). ( DNSHeaderGetQuestionCount( qhdr ) != 1 ) ) // There should be a single question. { err = kRequestErr; goto exit; } // Get QNAME. ptr = (const uint8_t *) &qhdr[ 1 ]; err = DNSMessageExtractDomainName( inQueryPtr, inQueryLen, ptr, qname, &ptr ); require_noerr( err, exit ); // Get QTYPE and QCLASS. require_action_quiet( ( queryEnd - ptr ) >= 4, exit, err = kUnderrunErr ); qtype = DNSQuestionFixedFieldsGetType( (const DNSQuestionFixedFields *) ptr ); qclass = DNSQuestionFixedFieldsGetClass( (const DNSQuestionFixedFields *) ptr ); ptr += 4; // Create a tentative response message. rflags = kDNSHeaderFlag_Response; if( qflags & kDNSHeaderFlag_RecursionDesired ) rflags |= kDNSHeaderFlag_RecursionDesired; DNSFlagsSetOpCode( rflags, kDNSOpCode_Query ); err = _DNSServerInitializeResponseMessage( &dataBuf, msgID, rflags, qname, qtype, qclass ); require_noerr( err, exit ); err = _DNSServerAnswerQueryDynamically( qname, qtype, qclass, inForTCP, &dataBuf ); if( err ) { DNSFlagsSetRCode( rflags, kDNSRCode_ServerFailure ); err = _DNSServerInitializeResponseMessage( &dataBuf, msgID, rflags, qname, qtype, qclass ); require_noerr( err, exit ); } err = DataBuffer_Detach( &dataBuf, outResponsePtr, outResponseLen ); require_noerr( err, exit ); exit: DataBuffer_Free( &dataBuf ); return( err ); } static OSStatus _DNSServerInitializeResponseMessage( DataBuffer * inDB, unsigned int inID, unsigned int inFlags, const uint8_t * inQName, unsigned int inQType, unsigned int inQClass ) { OSStatus err; DNSHeader header; DNSQuestionFixedFields fields; 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_Append( inDB, inQName, DomainNameLength( inQName ) ); require_noerr( err, exit ); DNSQuestionFixedFieldsInit( &fields, inQType, inQClass ); err = DataBuffer_Append( inDB, &fields, sizeof( fields ) ); require_noerr( err, exit ); exit: return( err ); } static OSStatus _DNSServerAnswerQueryDynamically( const uint8_t * const inQName, const unsigned int inQType, const unsigned int inQClass, const Boolean inForTCP, DataBuffer * const inDB ) { OSStatus err; // General-purpose error variable. const uint8_t * labelPtr; // QNAME label pointer. size_t labelLen; // QNAME label length. DNSHeader * hdr; // Response header pointer. unsigned int flags; // Response header flags. unsigned int rcode; // Response header response code. unsigned int answerCount = 0; // Number of answers contained in response. int32_t aliasCount = -1; // Arg from "alias" label. Valid values are in [2 .. 2^31 - 1]. int count = -1; // First arg from "count" label. Valid values are in [1 .. 255]. int randCount = -1; // Second arg from "count" label. Valid values are in [1 .. 255]. int32_t ttl = -1; // Arg from "ttl" label. Valid values are in [0 .. 2^31 - 1]. uint32_t aliasTTLs[ kMaxAliasTTLCount ]; // Args from "alias-ttl" label. Valid values are in [0 .. 2^31 - 1]. int i; // General-purpose array index. Boolean useAliasTTLs = false; // True if QNAME contained a valid "alias-ttl" label. Boolean nameExists = false; // True if name specified by QNAME exists. Boolean nameHasA = false; // True if name specified by QNAME has an A record. Boolean nameHasAAAA = false; // True if name specified by QNAME has a AAAA record. Boolean notImplemented = false; // True if the kind of the query is not supported. Boolean truncated = false; // True if the response message is truncated. uint8_t namePtr[ 2 ]; // Name compression pointer. if( inQClass != kDNSServiceClass_IN ) { notImplemented = true; goto done; } for( labelPtr = inQName; ( labelLen = *labelPtr ) != 0; labelPtr += ( 1 + labelLen ) ) { const char * const labelStr = (const char *) &labelPtr[ 1 ]; const char * next; long long arg; int n; require_action( labelLen <= kDomainNameLengthMax, exit, err = kUnexpectedErr ); // Check if the first label is a valid alias TTL sequence label. if( ( labelPtr == inQName ) && ( strnicmp_prefix( labelStr, labelLen, kLabelPrefix_AliasTTL ) == 0 ) ) { const char * src = &labelStr[ sizeof_string( kLabelPrefix_AliasTTL ) ]; const char * const end = &labelStr[ labelLen ]; int argCount = 0; while( src < end ) { n = SNScanF( src, (size_t)( end - src ), "-%10lld%#n", &arg, &next ); if( n != 1 ) break; if( ( arg < 0 ) || ( arg > INT32_MAX ) ) break; // TTL must be >= 0 and <= (2^31 - 1). aliasTTLs[ argCount++ ] = (uint32_t) arg; src = next; } if( ( argCount > 0 ) && ( src == end ) ) { aliasCount = argCount; useAliasTTLs = true; continue; } } // Check if the first label is a valid alias label. if( ( labelPtr == inQName ) && ( strnicmp_prefix( labelStr, labelLen, kLabelPrefix_Alias ) == 0 ) ) { const char * src = &labelStr[ sizeof_string( kLabelPrefix_Alias ) ]; const char * const end = &labelStr[ labelLen ]; if( src == end ) { aliasCount = 1; continue; } n = SNScanF( src, (size_t)( end - src ), "-%10lld%#n", &arg, &next ); if( ( n == 1 ) && ( next == end ) ) { if( ( arg < 2 ) || ( arg > INT32_MAX ) ) break; // Alias count must be >= 2 and <= (2^31 - 1). aliasCount = (int32_t) arg; continue; } } // Check if the label is a valid count label. if( strnicmp_prefix( labelStr, labelLen, kLabelPrefix_Count ) == 0 ) { const char * src = &labelStr[ sizeof_string( kLabelPrefix_Count ) ]; const char * const end = &labelStr[ labelLen ]; n = SNScanF( src, (size_t)( end - src ), "-%3lld%#n", &arg, &next ); if( n == 1 ) { if( count > 0 ) break; // Count cannot be specified more than once. if( ( arg < 1 ) || ( arg > 255 ) ) break; // Count must be >= 1 and <= 255. count = (int) arg; src = next; if( src < end ) { n = SNScanF( src, (size_t)( end - src ), "-%3lld%#n", &arg, &next ); if( ( n != 1 ) || ( next != end ) ) break; if( ( arg < count ) || ( arg > 255 ) ) break; // Rand count must be >= count and <= 255. randCount = (int) arg; } continue; } } // Check if the label is a valid tag label. if( strnicmp_prefix( labelStr, labelLen, "tag-" ) == 0 ) continue; // Check if the label is a valid TTL label. if( strnicmp_prefix( labelStr, labelLen, kLabelPrefix_TTL ) == 0 ) { const char * src = &labelStr[ sizeof_string( kLabelPrefix_TTL ) ]; const char * const end = &labelStr[ labelLen ]; n = SNScanF( src, (size_t)( end - src ), "-%10lld%#n", &arg, &next ); if( ( n == 1 ) && ( next == end ) ) { if( ttl >= 0 ) break; // TTL cannot be specified more than once. if( ( arg < 0 ) || ( arg > INT32_MAX ) ) break; // TTL must be >= 0 and <= (2^31 - 1). ttl = (int32_t) arg; continue; } } // Check if the label is a valid IPv4 or IPv6 label. if( MemIEqual( labelStr, labelLen, kLabel_IPv4, sizeof_string( kLabel_IPv4 ) ) ) { if( nameHasA || nameHasAAAA ) break; // Valid names have at most one IPv4 or IPv6 label. nameHasA = true; continue; } if( MemIEqual( labelStr, labelLen, kLabel_IPv6, sizeof_string( kLabel_IPv6 ) ) ) { if( nameHasA || nameHasAAAA ) break; // Valid names have at most one IPv4 or IPv6 label. nameHasAAAA = true; continue; } // If the remaining labels are equal to "d.test.", the name exists. if( DomainNameEqual( labelPtr, (const uint8_t *) "\x01" "d" "\x04" "test" ) ) nameExists = true; break; } require_quiet( nameExists, done ); // Set default values for count and TTL, if those labels were present. if( count <= 0 ) count = 1; check( ( gDNSServer_DefaultTTL >= 0 ) && ( gDNSServer_DefaultTTL <= INT32_MAX ) ); if( ttl < 0 ) ttl = gDNSServer_DefaultTTL; // Names that don't specify v4 or v6 have both A and AAAA records. if( !nameHasA && !nameHasAAAA ) { nameHasA = true; nameHasAAAA = true; } check( ( count >= 1 ) && ( count <= 255 ) ); check( ( randCount <= 0 ) || ( ( randCount >= count ) && ( randCount <= 255 ) ) ); if( aliasCount > 0 ) { size_t nameOffset; uint8_t rdataLabel[ 1 + kDomainLabelLengthMax + 1 ]; // If aliasCount is non-zero, then the first label of QNAME is either "alias" or "alias-". superPtr is a name // compression pointer to the second label of QNAME, i.e., the immediate superdomain 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. const uint8_t superPtr[ 2 ] = { 0xC0, (uint8_t)( kDNSHeaderLength + 1 + inQName[ 0 ] ) }; // The name of the first CNAME record is equal to QNAME, so nameOffset is set to offset of QNAME. nameOffset = kDNSHeaderLength; for( i = aliasCount; i >= 1; --i ) { size_t nameLen; size_t rdataLen; int j; uint32_t aliasTTL; uint8_t nameLabel[ 1 + kDomainLabelLengthMax + 1 ]; DNSRecordFixedFields fields; if( nameOffset <= kDNSCompressionOffsetMax ) { namePtr[ 0 ] = (uint8_t)( ( ( nameOffset >> 8 ) & 0x3F ) | 0xC0 ); namePtr[ 1 ] = (uint8_t)( nameOffset & 0xFF ); nameLen = sizeof( namePtr ); } else { memcpy( nameLabel, rdataLabel, 1 + rdataLabel[ 0 ] ); nameLen = 1 + nameLabel[ 0 ] + sizeof( superPtr ); } if( i >= 2 ) { char * dst = (char *) &rdataLabel[ 1 ]; char * const end = (char *) &rdataLabel[ countof( rdataLabel ) ]; if( useAliasTTLs ) { err = SNPrintF_Add( &dst, end, kLabelPrefix_AliasTTL ); require_noerr( err, exit ); for( j = aliasCount - ( i - 1 ); j < aliasCount; ++j ) { err = SNPrintF_Add( &dst, end, "-%u", aliasTTLs[ j ] ); require_noerr( err, exit ); } } else { err = SNPrintF_Add( &dst, end, kLabelPrefix_Alias "%?{end}-%u", i == 2, i - 1 ); require_noerr( err, exit ); } rdataLabel[ 0 ] = (uint8_t)( dst - (char *) &rdataLabel[ 1 ] ); rdataLen = 1 + rdataLabel[ 0 ] + sizeof( superPtr ); } else { rdataLen = sizeof( superPtr ); } if( !inForTCP ) { size_t recordLen = nameLen + sizeof( fields ) + rdataLen; if( ( DataBuffer_GetLen( inDB ) + recordLen ) > kDNSMaxUDPMessageSize ) { truncated = true; goto done; } } ++answerCount; // Set CNAME record's NAME. if( nameOffset <= kDNSCompressionOffsetMax ) { err = DataBuffer_Append( inDB, namePtr, sizeof( namePtr ) ); require_noerr( err, exit ); } else { err = DataBuffer_Append( inDB, nameLabel, 1 + nameLabel[ 0 ] ); require_noerr( err, exit ); err = DataBuffer_Append( inDB, superPtr, sizeof( superPtr ) ); require_noerr( err, exit ); } // Set CNAME record's TYPE, CLASS, TTL, and RDLENGTH. aliasTTL = useAliasTTLs ? aliasTTLs[ aliasCount - i ] : ( (uint32_t) gDNSServer_DefaultTTL ); DNSRecordFixedFieldsInit( &fields, kDNSServiceType_CNAME, kDNSServiceClass_IN, aliasTTL, rdataLen ); err = DataBuffer_Append( inDB, &fields, sizeof( fields ) ); 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 ); // Set CNAME record's RDATA. if( i >= 2 ) { err = DataBuffer_Append( inDB, rdataLabel, 1 + rdataLabel[ 0 ] ); require_noerr( err, exit ); } err = DataBuffer_Append( inDB, superPtr, sizeof( superPtr ) ); require_noerr( err, exit ); } namePtr[ 0 ] = superPtr[ 0 ]; namePtr[ 1 ] = superPtr[ 1 ]; } else { // There are no aliases, so initialize the name compression pointer to point to QNAME. namePtr[ 0 ] = 0xC0; namePtr[ 1 ] = kDNSHeaderLength; } if( ( ( inQType == kDNSServiceType_A ) && nameHasA ) || ( ( inQType == kDNSServiceType_AAAA ) && nameHasAAAA ) ) { uint8_t * lsb; // Pointer to the least significant byte of record data. size_t recordLen; // Length of the entire record. size_t rdataLen; // Length of record's RDATA. uint8_t rdata[ 16 ]; // A buffer that's big enough for either A or AAAA RDATA. uint8_t randItegers[ 255 ]; // Array for random integers in [1 .. 255]. DNSRecordFixedFields fields; if( inQType == kDNSServiceType_A ) { rdataLen = 4; WriteBig32( rdata, kTestDNSServerBaseAddrV4 ); lsb = &rdata[ 3 ]; } else { rdataLen = 16; memcpy( rdata, kTestDNSServerBaseAddrV6, 16 ); lsb = &rdata[ 15 ]; } if( randCount > 0 ) { // Populate the array with all integers between 1 and , inclusive. for( i = 0; i < randCount; ++i ) randItegers[ i ] = (uint8_t)( i + 1 ); // 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 < count; ++i ) { uint8_t tmp; int j; j = (int) RandomRange( i, randCount - 1 ); if( i != j ) { tmp = randItegers[ i ]; randItegers[ i ] = randItegers[ j ]; randItegers[ j ] = tmp; } } } recordLen = sizeof( namePtr ) + sizeof( fields ) + rdataLen; for( i = 0; i < count; ++i ) { if( !inForTCP && ( ( DataBuffer_GetLen( inDB ) + recordLen ) > kDNSMaxUDPMessageSize ) ) { truncated = true; goto done; } ++answerCount; // Set record NAME. err = DataBuffer_Append( inDB, namePtr, sizeof( namePtr ) ); require_noerr( err, exit ); // Set record TYPE, CLASS, TTL, and RDLENGTH. DNSRecordFixedFieldsInit( &fields, inQType, kDNSServiceClass_IN, ttl, rdataLen ); err = DataBuffer_Append( inDB, &fields, sizeof( fields ) ); require_noerr( err, exit ); // Set record RDATA. *lsb = ( randCount > 0 ) ? randItegers[ i ] : ( *lsb + 1 ); err = DataBuffer_Append( inDB, rdata, rdataLen ); require_noerr( err, exit ); } } done: hdr = (DNSHeader *) DataBuffer_GetPtr( inDB ); flags = DNSHeaderGetFlags( hdr ); if( truncated ) flags |= kDNSHeaderFlag_Truncation; if( notImplemented ) { rcode = kDNSRCode_NotImplemented; } else { flags |= kDNSHeaderFlag_AuthAnswer; rcode = nameExists ? kDNSRCode_NoError : kDNSRCode_NXDomain; } DNSFlagsSetRCode( flags, rcode ); DNSHeaderSetFlags( hdr, flags ); DNSHeaderSetAnswerCount( hdr, answerCount ); err = kNoErr; exit: return( err ); } //=========================================================================================================================== // _DNSServerTCPReadHandler //=========================================================================================================================== typedef struct { sockaddr_ip clientAddr; // Client's address. dispatch_source_t readSource; // Dispatch read source for client socket. dispatch_source_t writeSource; // Dispatch write source for client socket. size_t offset; // Offset into receive buffer. void * msgPtr; // Pointer to dynamically allocated message buffer. size_t msgLen; // Length of message buffer. Boolean readSuspended; // True if the read source is currently suspended. Boolean writeSuspended; // True if the write source is currently suspended. Boolean receivedLength; // True if receiving DNS message as opposed to the message length. uint8_t lenBuf[ 2 ]; // Buffer for two-octet message length field. iovec_t iov[ 2 ]; // IO vector for writing response message. iovec_t * iovPtr; // Vector pointer for SocketWriteData(). int iovCount; // Vector count for SocketWriteData(). } TCPConnectionContext; static void TCPConnectionStop( TCPConnectionContext *inContext ); static void TCPConnectionContextFree( TCPConnectionContext *inContext ); static void TCPConnectionReadHandler( void *inContext ); static void TCPConnectionWriteHandler( void *inContext ); #define TCPConnectionForget( X ) ForgetCustomEx( X, TCPConnectionStop, TCPConnectionContextFree ) static void _DNSServerTCPReadHandler( void *inContext ) { OSStatus err; SocketContext * const sockCtx = (SocketContext *) inContext; TCPConnectionContext * connection; socklen_t clientAddrLen; SocketRef newSock = kInvalidSocketRef; SocketContext * newSockCtx = NULL; connection = (TCPConnectionContext *) calloc( 1, sizeof( *connection ) ); require_action( connection, exit, err = kNoMemoryErr ); clientAddrLen = (socklen_t) sizeof( connection->clientAddr ); newSock = accept( sockCtx->sock, &connection->clientAddr.sa, &clientAddrLen ); err = map_socket_creation_errno( newSock ); require_noerr( err, exit ); err = SocketContextCreate( newSock, connection, &newSockCtx ); require_noerr( err, exit ); newSock = kInvalidSocketRef; err = DispatchReadSourceCreate( newSockCtx->sock, NULL, TCPConnectionReadHandler, SocketContextCancelHandler, newSockCtx, &connection->readSource ); require_noerr( err, exit ); SocketContextRetain( newSockCtx ); dispatch_resume( connection->readSource ); err = DispatchWriteSourceCreate( newSockCtx->sock, NULL, TCPConnectionWriteHandler, SocketContextCancelHandler, newSockCtx, &connection->writeSource ); require_noerr( err, exit ); SocketContextRetain( newSockCtx ); connection->writeSuspended = true; connection = NULL; exit: ForgetSocket( &newSock ); SocketContextRelease( newSockCtx ); TCPConnectionForget( &connection ); } //=========================================================================================================================== // TCPConnectionStop //=========================================================================================================================== static void TCPConnectionStop( TCPConnectionContext *inContext ) { dispatch_source_forget_ex( &inContext->readSource, &inContext->readSuspended ); dispatch_source_forget_ex( &inContext->writeSource, &inContext->writeSuspended ); } //=========================================================================================================================== // TCPConnectionContextFree //=========================================================================================================================== static void TCPConnectionContextFree( TCPConnectionContext *inContext ) { check( !inContext->readSource ); check( !inContext->writeSource ); ForgetMem( &inContext->msgPtr ); free( inContext ); } //=========================================================================================================================== // TCPConnectionReadHandler //=========================================================================================================================== static void TCPConnectionReadHandler( void *inContext ) { OSStatus err; SocketContext * const sockCtx = (SocketContext *) inContext; TCPConnectionContext * connection = (TCPConnectionContext *) sockCtx->userContext; struct timeval now; uint8_t * responsePtr = NULL; // malloc'd size_t responseLen; // Receive message length. if( !connection->receivedLength ) { err = SocketReadData( sockCtx->sock, connection->lenBuf, sizeof( connection->lenBuf ), &connection->offset ); if( err == EWOULDBLOCK ) goto exit; require_noerr( err, exit ); connection->offset = 0; connection->msgLen = ReadBig16( connection->lenBuf ); connection->msgPtr = malloc( connection->msgLen ); require_action( connection->msgPtr, exit, err = kNoMemoryErr ); connection->receivedLength = true; } // Receive message. err = SocketReadData( sockCtx->sock, connection->msgPtr, connection->msgLen, &connection->offset ); if( err == EWOULDBLOCK ) goto exit; require_noerr( err, exit ); gettimeofday( &now, NULL ); dispatch_suspend( connection->readSource ); connection->readSuspended = true; ds_ulog( kLogLevelInfo, "TCP server received %zu bytes from %##a at %{du:time}.\n", connection->msgLen, &connection->clientAddr, &now ); if( connection->msgLen < kDNSHeaderLength ) { ds_ulog( kLogLevelInfo, "TCP DNS message is too small (%zu < %d).\n", connection->msgLen, kDNSHeaderLength ); goto exit; } ds_ulog( kLogLevelInfo, "TCP received message:\n\n%1{du:dnsmsg}", connection->msgPtr, connection->msgLen ); // Create response. err = _DNSServerAnswerQueryForTCP( connection->msgPtr, connection->msgLen, &responsePtr, &responseLen ); require_noerr_quiet( err, exit ); // Send response. ds_ulog( kLogLevelInfo, "TCP sending %zu byte response:\n\n%1{du:dnsmsg}", responseLen, responsePtr, responseLen ); free( connection->msgPtr ); connection->msgPtr = responsePtr; connection->msgLen = responseLen; responsePtr = NULL; check( connection->msgLen <= UINT16_MAX ); WriteBig16( connection->lenBuf, connection->msgLen ); connection->iov[ 0 ].iov_base = connection->lenBuf; connection->iov[ 0 ].iov_len = sizeof( connection->lenBuf ); connection->iov[ 1 ].iov_base = connection->msgPtr; connection->iov[ 1 ].iov_len = connection->msgLen; connection->iovPtr = connection->iov; connection->iovCount = 2; check( connection->writeSuspended ); dispatch_resume( connection->writeSource ); connection->writeSuspended = false; exit: FreeNullSafe( responsePtr ); if( err && ( err != EWOULDBLOCK ) ) TCPConnectionForget( &connection ); } //=========================================================================================================================== // TCPConnectionWriteHandler //=========================================================================================================================== static void TCPConnectionWriteHandler( void *inContext ) { OSStatus err; SocketContext * const sockCtx = (SocketContext *) inContext; TCPConnectionContext * connection = (TCPConnectionContext *) sockCtx->userContext; err = SocketWriteData( sockCtx->sock, &connection->iovPtr, &connection->iovCount ); if( err == EWOULDBLOCK ) goto exit; check_noerr( err ); TCPConnectionForget( &connection ); exit: return; } //=========================================================================================================================== // GAIPerfCmd //=========================================================================================================================== #define kGAIPerfStandardTTL ( 1 * kSecondsPerHour ) typedef struct GAITesterPrivate * GAITesterRef; typedef struct GAITestCase GAITestCase; typedef uint32_t GAITesterEventType; #define kGAITesterEvent_Started 1 #define kGAITesterEvent_Stopped 2 typedef struct { const char * name; // Domain name that was resolved. int64_t connectionTimeUs; // Time in microseconds that it took to create a DNS-SD connection. int64_t firstTimeUs; // Time in microseconds that it took to get the first address result. int64_t timeUs; // Time in microseconds that it took to get all expected address results. } GAITestItemResult; typedef void ( *GAITesterEventHandler_f )( GAITesterEventType inType, void *inContext ); typedef void ( *GAITesterResultsHandler_f )( const char * inCaseTitle, MicroTime64 inCaseStartTime, MicroTime64 inCaseEndTime, const GAITestItemResult * inResults, size_t inResultCount, size_t inItemCount, void * inContext ); typedef unsigned int GAITestAddrType; #define kGAITestAddrType_None 0 #define kGAITestAddrType_IPv4 ( 1U << 0 ) #define kGAITestAddrType_IPv6 ( 1U << 1 ) #define kGAITestAddrType_Both ( kGAITestAddrType_IPv4 | kGAITestAddrType_IPv6 ) #define GAITestAddrTypeIsValid( X ) \ ( ( (X) & kGAITestAddrType_Both ) && ( ( (X) & ~kGAITestAddrType_Both ) == 0 ) ) typedef enum { kGAIPerfOutputFormat_JSON = 1, kGAIPerfOutputFormat_XML = 2, kGAIPerfOutputFormat_Binary = 3 } GAIPerfOutputFormatType; typedef struct { GAITesterRef tester; // GAI tester object. CFMutableArrayRef caseResults; // Array of test case results. char * outputFilePath; // File to write test results to. If NULL, then write to stdout. GAIPerfOutputFormatType outputFormat; // Format of test results output. unsigned int callDelayMs; // Amount of time to wait before calling DNSServiceGetAddrInfo(). unsigned int serverDelayMs; // Amount of additional time to have server delay its responses. unsigned int defaultIterCount; // Default test case iteration count. dispatch_source_t sigIntSource; // Dispatch source for SIGINT. dispatch_source_t sigTermSource; // Dispatch source for SIGTERM. Boolean gotSignal; // True if SIGINT or SIGTERM was caught. Boolean testerStarted; // True if the GAI tester was started. Boolean appendNewLine; // True if a newline character should be appended to JSON output. } GAIPerfContext; static void GAIPerfContextFree( GAIPerfContext *inContext ); static OSStatus GAIPerfAddAdvancedTestCases( GAIPerfContext *inContext ); static OSStatus GAIPerfAddBasicTestCases( GAIPerfContext *inContext ); static void GAIPerfEventHandler( GAITesterEventType inType, void *inContext ); static void GAIPerfResultsHandler( const char * inCaseTitle, MicroTime64 inCaseStartTime, MicroTime64 inCaseEndTime, const GAITestItemResult * inResults, size_t inResultCount, size_t inItemCount, void * inContext ); static void GAIPerfSignalHandler( void *inContext ); CFTypeID GAITesterGetTypeID( void ); static OSStatus GAITesterCreate( dispatch_queue_t inQueue, int inCallDelayMs, int inServerDelayMs, int inServerDefaultTTL, GAITesterRef * outTester ); static void GAITesterStart( GAITesterRef inTester ); static void GAITesterStop( GAITesterRef inTester ); static void GAITesterAddCase( GAITesterRef inTester, GAITestCase *inCase ); static void GAITesterSetEventHandler( GAITesterRef inTester, GAITesterEventHandler_f inEventHandler, void * inEventContext ); static void GAITesterSetResultsHandler( GAITesterRef inTester, GAITesterResultsHandler_f inResultsHandler, void * inResultsContext ); static OSStatus GAITestCaseCreate( const char *inTitle, unsigned int inTimeLimitMs, GAITestCase **outSet ); static void GAITestCaseFree( GAITestCase *inCase ); static OSStatus GAITestCaseAddItem( GAITestCase * inCase, unsigned int inAliasCount, unsigned int inAddressCount, int inTTL, GAITestAddrType inHasAddrs, GAITestAddrType inWantAddrs, unsigned int inItemCount ); static OSStatus GAITestCaseAddLocalHostItem( GAITestCase *inCase, GAITestAddrType inWantAddrs, unsigned int inItemCount ); #define kGAIPerfTestSuite_Basic 1 #define kGAIPerfTestSuite_Advanced 2 static void GAIPerfCmd( void ) { OSStatus err; GAIPerfContext * context; int suiteValue; context = (GAIPerfContext *) calloc( 1, sizeof( *context ) ); require_action( context, exit, err = kNoMemoryErr ); context->caseResults = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks ); require_action( context->caseResults, exit, err = kNoMemoryErr ); context->outputFormat = (GAIPerfOutputFormatType) CLIArgToValue( "format", gGAIPerf_OutputFormat, &err, "json", kGAIPerfOutputFormat_JSON, "xml", kGAIPerfOutputFormat_XML, "binary", kGAIPerfOutputFormat_Binary, NULL ); require_noerr_quiet( err, exit ); context->callDelayMs = ( gGAIPerf_CallDelayMs >= 0 ) ? (unsigned int) gGAIPerf_CallDelayMs : 0; context->serverDelayMs = ( gGAIPerf_ServerDelayMs >= 0 ) ? (unsigned int) gGAIPerf_ServerDelayMs : 0; context->defaultIterCount = ( gGAIPerf_DefaultIterCount >= 0 ) ? (unsigned int) gGAIPerf_DefaultIterCount : 0; context->appendNewLine = gGAIPerf_OutputAppendNewLine ? true : false; if( gGAIPerf_OutputFilePath ) { context->outputFilePath = strdup( gGAIPerf_OutputFilePath ); require_action( context->outputFilePath, exit, err = kNoMemoryErr ); } err = GAITesterCreate( dispatch_get_main_queue(), (int) context->callDelayMs, (int) context->serverDelayMs, kGAIPerfStandardTTL, &context->tester ); require_noerr( err, exit ); check( gGAIPerf_TestSuite ); suiteValue = CLIArgToValue( "suite", gGAIPerf_TestSuite, &err, "basic", kGAIPerfTestSuite_Basic, "advanced", kGAIPerfTestSuite_Advanced, NULL ); require_noerr_quiet( err, exit ); switch( suiteValue ) { case kGAIPerfTestSuite_Basic: err = GAIPerfAddBasicTestCases( context ); require_noerr( err, exit ); break; case kGAIPerfTestSuite_Advanced: err = GAIPerfAddAdvancedTestCases( context ); require_noerr( err, exit ); break; default: err = kValueErr; break; } GAITesterSetEventHandler( context->tester, GAIPerfEventHandler, context ); GAITesterSetResultsHandler( context->tester, GAIPerfResultsHandler, context ); signal( SIGINT, SIG_IGN ); err = DispatchSignalSourceCreate( SIGINT, GAIPerfSignalHandler, context, &context->sigIntSource ); require_noerr( err, exit ); dispatch_resume( context->sigIntSource ); signal( SIGTERM, SIG_IGN ); err = DispatchSignalSourceCreate( SIGTERM, GAIPerfSignalHandler, context, &context->sigTermSource ); require_noerr( err, exit ); dispatch_resume( context->sigTermSource ); GAITesterStart( context->tester ); dispatch_main(); exit: if( context ) GAIPerfContextFree( context ); if( err ) exit( 1 ); } //=========================================================================================================================== // GAIPerfContextFree //=========================================================================================================================== static void GAIPerfContextFree( GAIPerfContext *inContext ) { ForgetCF( &inContext->tester ); ForgetCF( &inContext->caseResults ); 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 ); static unsigned int _GAIPerfTimeLimitMs( unsigned int inCallDelayMs, unsigned int inServerDelayMs, unsigned int inIterationCount ); #define kGAIPerfAdvancedTestSuite_MaxAliasCount 4 #define kGAIPerfAdvancedTestSuite_MaxAddrCount 8 static OSStatus GAIPerfAddAdvancedTestCases( GAIPerfContext *inContext ) { OSStatus err; unsigned int aliasCount, addressCount, timeLimitMs, 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 ); timeLimitMs = _GAIPerfTimeLimitMs( inContext->callDelayMs, inContext->serverDelayMs, inContext->defaultIterCount ); err = GAITestCaseCreate( title, timeLimitMs, &testCase ); require_noerr( err, exit ); for( i = 0; i < inContext->defaultIterCount; ++i ) { err = GAITestCaseAddItem( testCase, aliasCount, addressCount, kGAIPerfStandardTTL, kGAITestAddrType_Both, kGAITestAddrType_Both, 1 ); require_noerr( err, exit ); } GAITesterAddCase( inContext->tester, testCase ); 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 ); timeLimitMs = _GAIPerfTimeLimitMs( inContext->callDelayMs, inContext->serverDelayMs, 1 ) + _GAIPerfTimeLimitMs( inContext->callDelayMs, 0, inContext->defaultIterCount ); err = GAITestCaseCreate( title, timeLimitMs, &testCase ); require_noerr( err, exit ); err = GAITestCaseAddItem( testCase, aliasCount, addressCount, kGAIPerfStandardTTL, kGAITestAddrType_Both, kGAITestAddrType_Both, inContext->defaultIterCount + 1 ); require_noerr( err, exit ); GAITesterAddCase( inContext->tester, testCase ); testCase = NULL; } if( aliasCount == 0 ) aliasCount = 1; else aliasCount *= 2; } // Finally, add a test case to resolve localhost to its IPv4 and IPv6 addresses. _GAIPerfWriteLocalHostTestCaseTitle( title, kGAITestAddrType_Both, inContext->defaultIterCount ); timeLimitMs = _GAIPerfTimeLimitMs( inContext->callDelayMs, 0, inContext->defaultIterCount ); err = GAITestCaseCreate( title, timeLimitMs, &testCase ); require_noerr( err, exit ); err = GAITestCaseAddLocalHostItem( testCase, kGAITestAddrType_Both, inContext->defaultIterCount ); require_noerr( err, exit ); GAITesterAddCase( inContext->tester, testCase ); 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 ); } //=========================================================================================================================== // _GAIPerfTimeLimitMs //=========================================================================================================================== static unsigned int _GAIPerfTimeLimitMs( unsigned int inCallDelayMs, unsigned int inServerDelayMs, unsigned int inIterationCount ) { // Allow each iteration 20 ms to complete (in addition to the call and server delay times). return( ( inCallDelayMs + inServerDelayMs + 20 ) * inIterationCount ); } //=========================================================================================================================== // GAIPerfAddBasicTestCases //=========================================================================================================================== #define kGAIPerfBasicTestSuite_AliasCount 2 #define kGAIPerfBasicTestSuite_AddrCount 4 static OSStatus GAIPerfAddBasicTestCases( GAIPerfContext *inContext ) { OSStatus err; GAITestCase * testCase = NULL; char title[ kTestCaseTitleBufferSize ]; unsigned int timeLimitMs, 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 ); timeLimitMs = _GAIPerfTimeLimitMs( inContext->callDelayMs, inContext->serverDelayMs, inContext->defaultIterCount ); err = GAITestCaseCreate( title, timeLimitMs, &testCase ); require_noerr( err, exit ); for( i = 0; i < inContext->defaultIterCount; ++i ) { err = GAITestCaseAddItem( testCase, kGAIPerfBasicTestSuite_AliasCount, kGAIPerfBasicTestSuite_AddrCount, kGAIPerfStandardTTL, kGAITestAddrType_Both, kGAITestAddrType_Both, 1 ); require_noerr( err, exit ); } GAITesterAddCase( inContext->tester, testCase ); testCase = NULL; // Test Case #2: // Resolve a domain name with // // 2 CNAME records, 4 A records, and 4 AAAA records // // to its IPv4 and IPv6 addresses. A preliminary iteration resolves a unique instance of such a domain name, which // requires server queries. Each of the subsequent iterations resolves the same domain name as the preliminary // iteration, which should ideally require no additional server queries, i.e., the results should come from the cache. _GAIPerfWriteTestCaseTitle( title, kGAIPerfBasicTestSuite_AliasCount, kGAIPerfBasicTestSuite_AddrCount, kGAIPerfBasicTestSuite_AddrCount, kGAITestAddrType_Both, inContext->defaultIterCount, false ); timeLimitMs = _GAIPerfTimeLimitMs( inContext->callDelayMs, inContext->serverDelayMs, 1 ) + _GAIPerfTimeLimitMs( inContext->callDelayMs, 0, inContext->defaultIterCount ); err = GAITestCaseCreate( title, timeLimitMs, &testCase ); require_noerr( err, exit ); err = GAITestCaseAddItem( testCase, kGAIPerfBasicTestSuite_AliasCount, kGAIPerfBasicTestSuite_AddrCount, kGAIPerfStandardTTL, kGAITestAddrType_Both, kGAITestAddrType_Both, inContext->defaultIterCount + 1 ); require_noerr( err, exit ); GAITesterAddCase( inContext->tester, testCase ); testCase = NULL; // Test Case #3: // Each iteration resolves localhost to its IPv4 and IPv6 addresses. _GAIPerfWriteLocalHostTestCaseTitle( title, kGAITestAddrType_Both, inContext->defaultIterCount ); timeLimitMs = _GAIPerfTimeLimitMs( inContext->callDelayMs, 0, inContext->defaultIterCount ); err = GAITestCaseCreate( title, timeLimitMs, &testCase ); require_noerr( err, exit ); err = GAITestCaseAddLocalHostItem( testCase, kGAITestAddrType_Both, inContext->defaultIterCount ); require_noerr( err, exit ); GAITesterAddCase( inContext->tester, testCase ); testCase = NULL; exit: if( testCase ) GAITestCaseFree( testCase ); return( err ); } //=========================================================================================================================== // GAIPerfEventHandler //=========================================================================================================================== static void _GAIPerfOutputResultsAndExit( GAIPerfContext *inContext ) ATTRIBUTE_NORETURN; static void GAIPerfEventHandler( GAITesterEventType inType, void *inContext ) { GAIPerfContext * const context = (GAIPerfContext *) inContext; if( inType == kGAITesterEvent_Started ) { context->testerStarted = true; } else if( inType == kGAITesterEvent_Stopped ) { if( context->gotSignal ) exit( 1 ); _GAIPerfOutputResultsAndExit( context ); } } //=========================================================================================================================== // _GAIPerfOutputResultsAndExit //=========================================================================================================================== #define kGAIPerfResultsKey_TestCases CFSTR( "testCases" ) #define kGAIPerfResultsKey_Info CFSTR( "info" ) #define kGAIPerfInfoKey_CallDelay CFSTR( "callDelayMs" ) #define kGAIPerfInfoKey_ServerDelay CFSTR( "serverDelayMs" ) static void _GAIPerfOutputResultsAndExit( GAIPerfContext *inContext ) { OSStatus err; CFPropertyListRef plist = NULL; CFDataRef results = NULL; FILE * file = NULL; err = CFPropertyListCreateFormatted( kCFAllocatorDefault, &plist, "{" "%kO=%O" "%kO=" "{" "%kO=%lli" "%kO=%lli" "}" "}", kGAIPerfResultsKey_TestCases, inContext->caseResults, kGAIPerfResultsKey_Info, kGAIPerfInfoKey_CallDelay, (int64_t) inContext->callDelayMs, kGAIPerfInfoKey_ServerDelay, (int64_t) inContext->serverDelayMs ); require_noerr( err, exit ); // Convert results to a specific format. switch( inContext->outputFormat ) { case kGAIPerfOutputFormat_JSON: results = CFCreateJSONData( plist, kJSONFlags_None, NULL ); require_action( results, exit, err = kUnknownErr ); break; case kGAIPerfOutputFormat_XML: results = CFPropertyListCreateData( NULL, plist, kCFPropertyListXMLFormat_v1_0, 0, NULL ); require_action( results, exit, err = kUnknownErr ); break; case kGAIPerfOutputFormat_Binary: results = CFPropertyListCreateData( NULL, plist, kCFPropertyListBinaryFormat_v1_0, 0, NULL ); require_action( results, exit, err = kUnknownErr ); break; default: err = kValueErr; goto exit; } // Write formatted results to file or stdout. if( inContext->outputFilePath ) { file = fopen( inContext->outputFilePath, "wb" ); err = map_global_value_errno( file, file ); require_noerr( err, exit ); } else { file = stdout; } err = WriteANSIFile( file, CFDataGetBytePtr( results ), (size_t) CFDataGetLength( results ) ); require_noerr( err, exit ); // Write a trailing newline for JSON-formatted results if requested. if( ( inContext->outputFormat == kGAIPerfOutputFormat_JSON ) && inContext->appendNewLine ) { err = WriteANSIFile( file, "\n", 1 ); require_noerr( err, exit ); } exit: CFReleaseNullSafe( plist ); CFReleaseNullSafe( results ); if( file && ( file != stdout ) ) fclose( file ); GAIPerfContextFree( inContext ); exit( err ? 1 : 0 ); } //=========================================================================================================================== // GAIPerfResultsHandler //=========================================================================================================================== // Keys for test case dictionary #define kGAIPerfTestCaseKey_Title CFSTR( "title" ) #define kGAIPerfTestCaseKey_StartTime CFSTR( "startTimeUs" ) #define kGAIPerfTestCaseKey_EndTime CFSTR( "endTimeUs" ) #define kGAIPerfTestCaseKey_Results CFSTR( "results" ) #define kGAIPerfTestCaseKey_FirstStats CFSTR( "firstStats" ) #define kGAIPerfTestCaseKey_ConnectionStats CFSTR( "connectionStats" ) #define kGAIPerfTestCaseKey_Stats CFSTR( "stats" ) #define kGAIPerfTestCaseKey_TimedOut CFSTR( "timedOut" ) // Keys for test case results array entry dictionaries #define kGAIPerfTestCaseResultKey_Name CFSTR( "name" ) #define kGAIPerfTestCaseResultKey_ConnectionTime CFSTR( "connectionTimeUs" ) #define kGAIPerfTestCaseResultKey_FirstTime CFSTR( "firstTimeUs" ) #define kGAIPerfTestCaseResultKey_Time CFSTR( "timeUs" ) // Keys for test case stats dictionaries #define kGAIPerfTestCaseStatsKey_Count CFSTR( "count" ) #define kGAIPerfTestCaseStatsKey_Min CFSTR( "min" ) #define kGAIPerfTestCaseStatsKey_Max CFSTR( "max" ) #define kGAIPerfTestCaseStatsKey_Mean CFSTR( "mean" ) #define kGAIPerfTestCaseStatsKey_StdDev CFSTR( "sd" ) typedef struct { double min; double max; double mean; double stdDev; } GAIPerfStats; #define GAIPerfStatsInit( X ) \ do { (X)->min = DBL_MAX; (X)->max = DBL_MIN; (X)->mean = 0.0; (X)->stdDev = 0.0; } while( 0 ) static void GAIPerfResultsHandler( const char * inCaseTitle, MicroTime64 inCaseStartTime, MicroTime64 inCaseEndTime, const GAITestItemResult * inResults, size_t inResultCount, size_t inItemCount, void * inContext ) { OSStatus err; GAIPerfContext * const context = (GAIPerfContext *) inContext; int namesAreDynamic, namesAreUnique; const char * ptr; size_t count, startIndex; CFMutableArrayRef results = NULL; GAIPerfStats stats, firstStats, connStats; double sum, firstSum, connSum, value, diff; size_t keyValueLen, i; char keyValue[ 16 ]; // Size must be at least strlen( "name=dynamic" ) + 1 bytes. // If this test case resolves the same "d.test." name in each iteration (title contains the "name=dynamic" key-value // pair, but not the "unique" key), then don't count the first iteration, whose purpose is to populate the cache with the // domain name's CNAME, A, and AAAA records. namesAreDynamic = false; namesAreUnique = false; ptr = inCaseTitle; while( ParseQuotedEscapedString( ptr, NULL, ",", keyValue, sizeof( keyValue ), &keyValueLen, NULL, &ptr ) ) { if( strnicmpx( keyValue, keyValueLen, "name=dynamic" ) == 0 ) { namesAreDynamic = true; } else if( strnicmpx( keyValue, keyValueLen, "unique" ) == 0 ) { namesAreUnique = true; } if( namesAreDynamic && namesAreUnique ) break; } if( namesAreDynamic && !namesAreUnique && ( inItemCount > 0 ) ) { count = ( inResultCount > 0 ) ? ( inResultCount - 1 ) : 0; startIndex = 1; } else { count = inResultCount; startIndex = 0; } results = CFArrayCreateMutable( NULL, (CFIndex) count, &kCFTypeArrayCallBacks ); require_action( results, exit, err = kNoMemoryErr ); GAIPerfStatsInit( &stats ); GAIPerfStatsInit( &firstStats ); GAIPerfStatsInit( &connStats ); sum = 0.0; firstSum = 0.0; connSum = 0.0; for( i = startIndex; i < count; ++i ) { value = (double) inResults[ i ].timeUs; if( value < stats.min ) stats.min = value; if( value > stats.max ) stats.max = value; sum += value; value = (double) inResults[ i ].firstTimeUs; if( value < firstStats.min ) firstStats.min = value; if( value > firstStats.max ) firstStats.max = value; firstSum += value; value = (double) inResults[ i ].connectionTimeUs; if( value < connStats.min ) connStats.min = value; if( value > connStats.max ) connStats.max = value; connSum += value; err = CFPropertyListAppendFormatted( kCFAllocatorDefault, results, "{" "%kO=%s" "%kO=%lli" "%kO=%lli" "%kO=%lli" "}", kGAIPerfTestCaseResultKey_Name, inResults[ i ].name, kGAIPerfTestCaseResultKey_ConnectionTime, inResults[ i ].connectionTimeUs, kGAIPerfTestCaseResultKey_FirstTime, inResults[ i ].firstTimeUs, kGAIPerfTestCaseResultKey_Time, inResults[ i ].timeUs ); require_noerr( err, exit ); } if( count > 0 ) { stats.mean = sum / count; firstStats.mean = firstSum / count; connStats.mean = connSum / count; sum = 0.0; firstSum = 0.0; connSum = 0.0; for( i = startIndex; i < count; ++i ) { diff = stats.mean - (double) inResults[ i ].timeUs; sum += ( diff * diff ); diff = firstStats.mean - (double) inResults[ i ].firstTimeUs; firstSum += ( diff * diff ); diff = connStats.mean - (double) inResults[ i ].connectionTimeUs; connSum += ( diff * diff ); } stats.stdDev = sqrt( sum / count ); firstStats.stdDev = sqrt( firstSum / count ); connStats.stdDev = sqrt( connSum / count ); } err = CFPropertyListAppendFormatted( kCFAllocatorDefault, context->caseResults, "{" "%kO=%s" "%kO=%lli" "%kO=%lli" "%kO=%O" "%kO=" "{" "%kO=%lli" "%kO=%f" "%kO=%f" "%kO=%f" "%kO=%f" "}" "%kO=" "{" "%kO=%lli" "%kO=%f" "%kO=%f" "%kO=%f" "%kO=%f" "}" "%kO=" "{" "%kO=%lli" "%kO=%f" "%kO=%f" "%kO=%f" "%kO=%f" "}" "%kO=%b" "}", kGAIPerfTestCaseKey_Title, inCaseTitle, kGAIPerfTestCaseKey_StartTime, (int64_t) inCaseStartTime, kGAIPerfTestCaseKey_EndTime, (int64_t) inCaseEndTime, kGAIPerfTestCaseKey_Results, results, kGAIPerfTestCaseKey_Stats, kGAIPerfTestCaseStatsKey_Count, (int64_t) count, kGAIPerfTestCaseStatsKey_Min, stats.min, kGAIPerfTestCaseStatsKey_Max, stats.max, kGAIPerfTestCaseStatsKey_Mean, stats.mean, kGAIPerfTestCaseStatsKey_StdDev, stats.stdDev, kGAIPerfTestCaseKey_FirstStats, kGAIPerfTestCaseStatsKey_Count, (int64_t) count, kGAIPerfTestCaseStatsKey_Min, firstStats.min, kGAIPerfTestCaseStatsKey_Max, firstStats.max, kGAIPerfTestCaseStatsKey_Mean, firstStats.mean, kGAIPerfTestCaseStatsKey_StdDev, firstStats.stdDev, kGAIPerfTestCaseKey_ConnectionStats, kGAIPerfTestCaseStatsKey_Count, (int64_t) count, kGAIPerfTestCaseStatsKey_Min, connStats.min, kGAIPerfTestCaseStatsKey_Max, connStats.max, kGAIPerfTestCaseStatsKey_Mean, connStats.mean, kGAIPerfTestCaseStatsKey_StdDev, connStats.stdDev, kGAIPerfTestCaseKey_TimedOut, ( inResultCount < inItemCount ) ? true : false ); require_noerr( err, exit ); exit: CFReleaseNullSafe( results ); } //=========================================================================================================================== // GAIPerfSignalHandler //=========================================================================================================================== static void GAIPerfSignalHandler( void *inContext ) { GAIPerfContext * const context = (GAIPerfContext *) inContext; context->gotSignal = true; if( context->tester && context->testerStarted ) { GAITesterStop( context->tester ); } else { exit( 1 ); } } //=========================================================================================================================== // GAITesterCreate //=========================================================================================================================== typedef enum { kGAITestConnType_UseMainConnection = 1, kGAITestConnType_OwnSharedConnection = 2 } GAITestConnType; typedef struct GAITestItem GAITestItem; struct GAITestItem { GAITestItem * next; // Next test item in list. char * name; // Domain name to resolve. int64_t connectionTimeUs; // Time in microseconds that it took to create a DNS-SD connection. int64_t firstTimeUs; // Time in microseconds that it took to get the first address result. int64_t timeUs; // Time in microseconds that it took to get all expected address results. unsigned int addressCount; // Address count of the domain name, i.e., the Count label argument. Boolean hasV4; // True if the domain name has one or more IPv4 addresses. Boolean hasV6; // True if the domain name has one or more IPv6 addresses. Boolean wantV4; // True if DNSServiceGetAddrInfo() should be called to get IPv4 addresses. Boolean wantV6; // True if DNSServiceGetAddrInfo() should be called to get IPv6 addresses. }; struct GAITestCase { GAITestCase * next; // Next test case in list. GAITestItem * itemList; // List of test items. char * title; // Title of the test case. unsigned int timeLimitMs; // Time limit in milliseconds for the test case's completion. }; struct GAITesterPrivate { CFRuntimeBase base; // CF object base. dispatch_queue_t queue; // Serial work queue. DNSServiceRef mainRef; // Reference to the main shared DNS-SD connection. DNSServiceRef opRef; // Reference to the current DNSServiceGetAddrInfo operation. GAITestCase * caseList; // List of test cases. GAITestCase * currentCase; // Pointer to the current test case. GAITestItem * currentItem; // Pointer to the current test item. MicroTime64 caseStartTime; // Start time of current test case in Unix time as microseconds. MicroTime64 caseEndTime; // End time of current test case in Unix time as microseconds. Boolean started; // True if the tester has been successfully started. Boolean stopped; // True if the tester has been stopped. int callDelayMs; // Amount of time to wait before calling DNSServiceGetAddrInfo(). dispatch_source_t caseTimer; // Timer for enforcing a test case time limits. pcap_t * pcap; // Captures traffic between mDNSResponder and test DNS server. pid_t serverPID; // PID of the test DNS server. int serverDelayMs; // Additional time to have the server delay its responses by. int serverDefaultTTL; // Default TTL for the server's records. GAITesterEventHandler_f eventHandler; // User's event handler. void * eventContext; // User's event handler context. GAITesterResultsHandler_f resultsHandler; // User's results handler. void * resultsContext; // User's results handler context. // 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 _GAITesterRun( void *inContext ); static OSStatus _GAITesterCreatePacketCapture( pcap_t **outPCap ); static void _GAITesterTimeout( void *inContext ); static void _GAITesterAdvanceCurrentItem( GAITesterRef inTester ); static void _GAITesterAdvanceCurrentSet( GAITesterRef inTester ); static void _GAITesterInitializeCurrentTest( GAITesterRef inTester ); static void DNSSD_API _GAITesterGetAddrInfoCallback( DNSServiceRef inSDRef, DNSServiceFlags inFlags, uint32_t inInterfaceIndex, DNSServiceErrorType inError, const char * inHostname, const struct sockaddr * inSockAddr, uint32_t inTTL, void * inContext ); static void _GAITesterCompleteCurrentTest( GAITesterRef inTester, Boolean inTimedOut ); #define ForgetPacketCapture( X ) ForgetCustom( X, pcap_close ) static OSStatus GAITestItemCreate( const char * inName, unsigned int inAddressCount, GAITestAddrType inHasAddrs, GAITestAddrType inWantAddrs, GAITestItem ** outItem ); static OSStatus GAITestItemDuplicate( const GAITestItem *inItem, GAITestItem **outItem ); static void GAITestItemFree( GAITestItem *inItem ); static OSStatus GAITesterCreate( dispatch_queue_t inQueue, int inCallDelayMs, int inServerDelayMs, int inServerDefaultTTL, GAITesterRef * outTester ) { OSStatus err; GAITesterRef obj = NULL; CF_OBJECT_CREATE( GAITester, obj, err, exit ); ReplaceDispatchQueue( &obj->queue, inQueue ); obj->callDelayMs = inCallDelayMs; obj->serverPID = -1; obj->serverDelayMs = inServerDelayMs; obj->serverDefaultTTL = inServerDefaultTTL; *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->opRef ); check( !me->mainRef ); check( !me->caseTimer ); 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 ); static void GAITesterStart( GAITesterRef me ) { CFRetain( me ); dispatch_async_f( me->queue, me, _GAITesterStart ); } extern char ** environ; static void _GAITesterStart( void *inContext ) { OSStatus err; GAITesterRef const me = (GAITesterRef) inContext; char * argv[ 4 ]; char * ptr; char * end; char command[ 128 ]; ptr = &command[ 0 ]; end = &command[ countof( command ) ]; SNPrintF_Add( &ptr, end, "dnssdutil server --loopback --followPID %lld", (int64_t) getpid() ); if( me->serverDefaultTTL >= 0 ) SNPrintF_Add( &ptr, end, " --defaultTTL %d", me->serverDefaultTTL ); if( me->serverDelayMs >= 0 ) SNPrintF_Add( &ptr, end, " --responseDelay %d", me->serverDelayMs ); argv[ 0 ] = "/bin/sh"; argv[ 1 ] = "-c"; argv[ 2 ] = command; argv[ 3 ] = NULL; err = posix_spawn( &me->serverPID, argv[ 0 ], NULL, NULL, argv, environ ); require_noerr( err, exit ); me->currentCase = me->caseList; me->currentItem = me->currentCase ? me->currentCase->itemList : NULL; _GAITesterInitializeCurrentTest( me ); // Hack: The first tester run is delayed for three seconds to allow the test DNS server to start up. // A better way to handle this is to issue an asynchronous query for something in the d.test. domain. As soon as an // expected response is received, the server can be considered to be up and running. CFRetain( me ); dispatch_after_f( dispatch_time_seconds( 3 ), me->queue, me, _GAITesterRun ); CFRetain( me ); me->started = true; if( me->eventHandler ) me->eventHandler( kGAITesterEvent_Started, me->eventContext ); exit: if( err ) _GAITesterStop( me ); CFRelease( me ); } //=========================================================================================================================== // 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 ); CFRelease( me ); } static void _GAITesterStop( GAITesterRef me ) { OSStatus err; DNSServiceForget( &me->opRef ); DNSServiceForget( &me->mainRef ); ForgetPacketCapture( &me->pcap ); dispatch_source_forget( &me->caseTimer ); if( me->serverPID != -1 ) { err = kill( me->serverPID, SIGTERM ); err = map_global_noerr_errno( err ); check_noerr( err ); } if( !me->stopped ) { me->stopped = true; if( me->eventHandler ) me->eventHandler( kGAITesterEvent_Stopped, me->eventContext ); if( me->started ) CFRelease( me ); } } //=========================================================================================================================== // GAITesterAddCase //=========================================================================================================================== static void GAITesterAddCase( GAITesterRef me, GAITestCase *inCase ) { GAITestCase ** ptr; for( ptr = &me->caseList; *ptr != NULL; ptr = &( *ptr )->next ) {} *ptr = inCase; } //=========================================================================================================================== // GAITesterSetEventHandler //=========================================================================================================================== static void GAITesterSetEventHandler( GAITesterRef me, GAITesterEventHandler_f inEventHandler, void *inEventContext ) { me->eventHandler = inEventHandler; me->eventContext = inEventContext; } //=========================================================================================================================== // GAITesterSetResultsHandler //=========================================================================================================================== static void GAITesterSetResultsHandler( GAITesterRef me, GAITesterResultsHandler_f inResultsHandler, void *inResultsContext ) { me->resultsHandler = inResultsHandler; me->resultsContext = inResultsContext; } //=========================================================================================================================== // _GAITesterRun //=========================================================================================================================== static void _GAITesterRun( void *inContext ) { OSStatus err; GAITesterRef const me = (GAITesterRef) inContext; GAITestItem * item; GAITestItemResult * results = NULL; require_action_quiet( !me->stopped, exit, err = kNoErr ); for( ;; ) { item = me->currentItem; if( item ) { DNSServiceProtocol protocols; check( !me->opRef ); check( ( me->bitmapV4 != 0 ) || ( me->bitmapV6 != 0 ) ); // Perform preliminary tasks if this is the start of a new test case. if( item == me->currentCase->itemList ) { // Flush mDNSResponder's cache. err = systemf( NULL, "killall -HUP mDNSResponder" ); require_noerr( err, exit ); usleep( kMicrosecondsPerSecond ); // Start a packet capture. check( !me->pcap ); err = _GAITesterCreatePacketCapture( &me->pcap ); require_noerr( err, exit ); // Start the test case time limit timer. check( !me->caseTimer ); if( me->currentCase->timeLimitMs > 0 ) { const int64_t timeLimitSecs = ( me->currentCase->timeLimitMs + 999 ) / 1000; err = DispatchTimerCreate( dispatch_time_seconds( timeLimitSecs ), DISPATCH_TIME_FOREVER, ( (uint64_t) timeLimitSecs ) * kNanosecondsPerSecond / 10, me->queue, _GAITesterTimeout, NULL, me, &me->caseTimer ); require_noerr( err, exit ); dispatch_resume( me->caseTimer ); } me->caseStartTime = GetCurrentMicroTime(); } // Call DNSServiceGetAddrInfo(). if( me->callDelayMs > 0 ) usleep( ( (useconds_t) me->callDelayMs ) * kMicrosecondsPerMillisecond ); protocols = 0; if( item->wantV4 ) protocols |= kDNSServiceProtocol_IPv4; if( item->wantV6 ) protocols |= kDNSServiceProtocol_IPv6; check( !me->mainRef ); me->startTicks = UpTicks(); err = DNSServiceCreateConnection( &me->mainRef ); require_noerr( err, exit ); err = DNSServiceSetDispatchQueue( me->mainRef, me->queue ); require_noerr( err, exit ); me->connTicks = UpTicks(); me->opRef = me->mainRef; err = DNSServiceGetAddrInfo( &me->opRef, kDNSServiceFlagsShareConnection | kDNSServiceFlagsReturnIntermediates, kDNSServiceInterfaceIndexAny, protocols, item->name, _GAITesterGetAddrInfoCallback, me ); require_noerr( err, exit ); break; } else { // No more test items means that this test case has completed (or timed out). me->caseEndTime = GetCurrentMicroTime(); dispatch_source_forget( &me->caseTimer ); ForgetPacketCapture( &me->pcap ); if( me->resultsHandler ) { size_t resultCount, itemCount, i; int timedOut; itemCount = 0; resultCount = 0; timedOut = false; for( item = me->currentCase->itemList; item; item = item->next ) { if( !timedOut ) { if( item->timeUs < 0 ) { timedOut = true; } else { ++resultCount; } } ++itemCount; } if( resultCount > 0 ) { results = (GAITestItemResult *) calloc( resultCount, sizeof( *results ) ); require_action( results, exit, err = kNoMemoryErr ); item = me->currentCase->itemList; for( i = 0; i < resultCount; ++i ) { results[ i ].name = item->name; results[ i ].connectionTimeUs = item->connectionTimeUs; results[ i ].firstTimeUs = item->firstTimeUs; results[ i ].timeUs = item->timeUs; item = item->next; } } me->resultsHandler( me->currentCase->title, me->caseStartTime, me->caseEndTime, results, resultCount, itemCount, me->resultsContext ); ForgetMem( &results ); } _GAITesterAdvanceCurrentSet( me ); require_action_quiet( me->currentCase, exit, err = kEndingErr ); } } exit: FreeNullSafe( results ); if( err ) _GAITesterStop( me ); CFRelease( me ); } //=========================================================================================================================== // _GAITesterCreatePacketCapture //=========================================================================================================================== static OSStatus _GAITesterCreatePacketCapture( pcap_t **outPCap ) { OSStatus err; pcap_t * pcap; struct bpf_program program; char errBuf[ PCAP_ERRBUF_SIZE ]; pcap = pcap_create( "lo0", errBuf ); require_action_string( pcap, exit, err = kUnknownErr, errBuf ); err = pcap_set_buffer_size( pcap, 512 * kBytesPerKiloByte ); require_noerr_action( err, exit, err = kUnknownErr ); err = pcap_set_snaplen( pcap, 512 ); require_noerr_action( err, exit, err = kUnknownErr ); err = pcap_set_immediate_mode( pcap, 0 ); require_noerr_action_string( err, exit, err = kUnknownErr, pcap_geterr( pcap ) ); err = pcap_activate( pcap ); require_noerr_action_string( err, exit, err = kUnknownErr, pcap_geterr( pcap ) ); err = pcap_setdirection( pcap, PCAP_D_INOUT ); require_noerr_action_string( err, exit, err = kUnknownErr, pcap_geterr( pcap ) ); err = pcap_setnonblock( pcap, 1, errBuf ); require_noerr_action_string( err, exit, err = kUnknownErr, pcap_geterr( pcap ) ); err = pcap_compile( pcap, &program, "udp port 53", 1, PCAP_NETMASK_UNKNOWN ); require_noerr_action_string( err, exit, err = kUnknownErr, pcap_geterr( pcap ) ); err = pcap_setfilter( pcap, &program ); pcap_freecode( &program ); require_noerr_action_string( err, exit, err = kUnknownErr, pcap_geterr( pcap ) ); *outPCap = pcap; pcap = NULL; exit: if( pcap ) pcap_close( pcap ); return( err ); } //=========================================================================================================================== // _GAITesterTimeout //=========================================================================================================================== static void _GAITesterTimeout( void *inContext ) { GAITesterRef const me = (GAITesterRef) inContext; dispatch_source_forget( &me->caseTimer ); _GAITesterCompleteCurrentTest( me, true ); } //=========================================================================================================================== // _GAITesterAdvanceCurrentItem //=========================================================================================================================== static void _GAITesterAdvanceCurrentItem( GAITesterRef me ) { if( me->currentItem ) { me->currentItem = me->currentItem->next; _GAITesterInitializeCurrentTest( me ); } } //=========================================================================================================================== // _GAITesterAdvanceCurrentSet //=========================================================================================================================== static void _GAITesterAdvanceCurrentSet( GAITesterRef me ) { if( me->currentCase ) { me->caseStartTime = 0; me->caseEndTime = 0; me->currentCase = me->currentCase->next; if( me->currentCase ) { me->currentItem = me->currentCase->itemList; _GAITesterInitializeCurrentTest( me ); } } } //=========================================================================================================================== // _GAITesterInitializeCurrentTest //=========================================================================================================================== static void _GAITesterInitializeCurrentTest( GAITesterRef me ) { GAITestItem * const item = me->currentItem; if( item ) { check( item->addressCount > 0 ); if( item->wantV4 ) { me->bitmapV4 = item->hasV4 ? ( ( UINT64_C( 1 ) << item->addressCount ) - 1 ) : 1; } else { me->bitmapV4 = 0; } if( item->wantV6 ) { me->bitmapV6 = item->hasV6 ? ( ( UINT64_C( 1 ) << item->addressCount ) - 1 ) : 1; } else { me->bitmapV6 = 0; } me->gotFirstResult = false; } } //=========================================================================================================================== // _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 ) { GAITesterRef const me = (GAITesterRef) inContext; GAITestItem * const item = me->currentItem; const sockaddr_ip * const sip = (const sockaddr_ip *) inSockAddr; uint64_t nowTicks; uint64_t * bitmapPtr; uint64_t bitmask; unsigned int addrOffset; Unused( inSDRef ); Unused( inInterfaceIndex ); Unused( inHostname ); Unused( inTTL ); nowTicks = UpTicks(); require_quiet( inFlags & kDNSServiceFlagsAdd, exit ); require_quiet( !inError || ( inError == kDNSServiceErr_NoSuchRecord ), exit ); bitmapPtr = NULL; bitmask = 0; if( ( sip->sa.sa_family == AF_INET ) && item->wantV4 ) { if( item->hasV4 ) { if( !inError ) { const uint32_t addrV4 = ntohl( sip->v4.sin_addr.s_addr ); if( strcasecmp( item->name, "localhost." ) == 0 ) { if( addrV4 == INADDR_LOOPBACK ) { bitmask = 1; bitmapPtr = &me->bitmapV4; } } else { addrOffset = addrV4 - kTestDNSServerBaseAddrV4; if( ( addrOffset >= 1 ) && ( addrOffset <= item->addressCount ) ) { bitmask = UINT64_C( 1 ) << ( addrOffset - 1 ); bitmapPtr = &me->bitmapV4; } } } } else if( inError == kDNSServiceErr_NoSuchRecord ) { bitmask = 1; bitmapPtr = &me->bitmapV4; } } else if( ( sip->sa.sa_family == AF_INET6 ) && item->wantV6 ) { if( item->hasV6 ) { if( !inError ) { const uint8_t * const addrV6 = sip->v6.sin6_addr.s6_addr; if( strcasecmp( item->name, "localhost." ) == 0 ) { if( memcmp( addrV6, in6addr_loopback.s6_addr, 16 ) == 0 ) { bitmask = 1; bitmapPtr = &me->bitmapV6; } } else if( memcmp( addrV6, kTestDNSServerBaseAddrV6, 15 ) == 0 ) { addrOffset = addrV6[ 15 ]; if( ( addrOffset >= 1 ) && ( addrOffset <= item->addressCount ) ) { bitmask = UINT64_C( 1 ) << ( addrOffset - 1 ); bitmapPtr = &me->bitmapV6; } } } } else if( inError == kDNSServiceErr_NoSuchRecord ) { bitmask = 1; bitmapPtr = &me->bitmapV6; } } if( bitmapPtr && ( *bitmapPtr & bitmask ) ) { *bitmapPtr &= ~bitmask; if( !me->gotFirstResult ) { me->firstTicks = nowTicks; me->gotFirstResult = true; } if( ( me->bitmapV4 == 0 ) && ( me->bitmapV6 == 0 ) ) { me->endTicks = nowTicks; _GAITesterCompleteCurrentTest( me, false ); } } exit: return; } //=========================================================================================================================== // _GAITesterCompleteCurrentTest //=========================================================================================================================== static OSStatus _GAITesterGetDNSMessageFromPacket( const uint8_t * inPacketPtr, size_t inPacketLen, const uint8_t ** outMsgPtr, size_t * outMsgLen ); static void _GAITesterCompleteCurrentTest( GAITesterRef me, Boolean inTimedOut ) { OSStatus err; GAITestItem * item; struct timeval * tsQA = NULL; struct timeval * tsQAAAA = NULL; struct timeval * tsRA = NULL; struct timeval * tsRAAAA = NULL; struct timeval timeStamps[ 4 ]; struct timeval * tsPtr = &timeStamps[ 0 ]; struct timeval * tsQ; struct timeval * tsR; int64_t idleTimeUs; uint8_t name[ kDomainNameLengthMax ]; DNSServiceForget( &me->opRef ); DNSServiceForget( &me->mainRef ); if( inTimedOut ) { for( item = me->currentItem; item; item = item->next ) { item->firstTimeUs = -1; item->timeUs = -1; } me->currentItem = NULL; CFRetain( me ); dispatch_async_f( me->queue, me, _GAITesterRun ); return; } item = me->currentItem; err = DomainNameFromString( name, item->name, NULL ); require_noerr( err, exit ); for( ;; ) { int status; struct pcap_pkthdr * pktHdr; const uint8_t * packet; const uint8_t * msgPtr; size_t msgLen; const DNSHeader * hdr; unsigned int flags; const uint8_t * ptr; const DNSQuestionFixedFields * qfields; unsigned int qtype; uint8_t qname[ kDomainNameLengthMax ]; status = pcap_next_ex( me->pcap, &pktHdr, &packet ); if( status != 1 ) break; if( _GAITesterGetDNSMessageFromPacket( packet, pktHdr->caplen, &msgPtr, &msgLen ) != kNoErr ) continue; if( msgLen < kDNSHeaderLength ) continue; hdr = (const DNSHeader *) msgPtr; flags = DNSHeaderGetFlags( hdr ); if( DNSFlagsGetOpCode( flags ) != kDNSOpCode_Query ) continue; if( DNSHeaderGetQuestionCount( hdr ) < 1 ) continue; ptr = (const uint8_t *) &hdr[ 1 ]; if( DNSMessageExtractDomainName( msgPtr, msgLen, ptr, qname, &ptr ) != kNoErr ) continue; if( !DomainNameEqual( qname, name ) ) continue; qfields = (const DNSQuestionFixedFields *) ptr; if( DNSQuestionFixedFieldsGetClass( qfields ) != kDNSServiceClass_IN ) continue; qtype = DNSQuestionFixedFieldsGetType( qfields ); if( item->wantV4 && ( qtype == kDNSServiceType_A ) ) { if( flags & kDNSHeaderFlag_Response ) { if( tsQA && !tsRA ) { tsRA = tsPtr++; *tsRA = pktHdr->ts; } } else if( !tsQA ) { tsQA = tsPtr++; *tsQA = pktHdr->ts; } } else if( item->wantV6 && ( qtype == kDNSServiceType_AAAA ) ) { if( flags & kDNSHeaderFlag_Response ) { if( tsQAAAA && !tsRAAAA ) { tsRAAAA = tsPtr++; *tsRAAAA = pktHdr->ts; } } else if( !tsQAAAA ) { tsQAAAA = tsPtr++; *tsQAAAA = pktHdr->ts; } } } if( tsQA && tsQAAAA ) tsQ = TIMEVAL_GT( *tsQA, *tsQAAAA ) ? tsQA : tsQAAAA; else tsQ = tsQA ? tsQA : tsQAAAA; if( tsRA && tsRAAAA ) tsR = TIMEVAL_LT( *tsRA, *tsRAAAA ) ? tsRA : tsRAAAA; else tsR = tsQA ? tsQA : tsQAAAA; if( tsQ && tsR ) { idleTimeUs = TIMEVAL_USEC64_DIFF( *tsR, *tsQ ); if( idleTimeUs < 0 ) idleTimeUs = 0; } else { idleTimeUs = 0; } item->connectionTimeUs = (int64_t) UpTicksToMicroseconds( me->connTicks - me->startTicks ); item->firstTimeUs = (int64_t)( UpTicksToMicroseconds( me->firstTicks - me->connTicks ) - (uint64_t) idleTimeUs ); item->timeUs = (int64_t)( UpTicksToMicroseconds( me->endTicks - me->connTicks ) - (uint64_t) idleTimeUs ); _GAITesterAdvanceCurrentItem( me ); CFRetain( me ); dispatch_async_f( me->queue, me, _GAITesterRun ); exit: if( err ) _GAITesterStop( me ); } //=========================================================================================================================== // _GAITesterGetDNSMessageFromPacket //=========================================================================================================================== #define kHeaderSizeNullLink 4 #define kHeaderSizeIPv4Min 20 #define kHeaderSizeIPv6 40 #define kHeaderSizeUDP 8 #define kIPProtocolUDP 0x11 static OSStatus _GAITesterGetDNSMessageFromPacket( const uint8_t * inPacketPtr, size_t inPacketLen, const uint8_t ** outMsgPtr, size_t * outMsgLen ) { OSStatus err; const uint8_t * nullLink; uint32_t addressFamily; const uint8_t * ip; int ipHeaderLen; int protocol; const uint8_t * msg; const uint8_t * const end = &inPacketPtr[ inPacketLen ]; 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, unsigned int inTimeLimitMs, GAITestCase **outSet ) { 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 ); obj->timeLimitMs = inTimeLimitMs; *outSet = 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 //=========================================================================================================================== // A character set of lower-case alphabet characters and digits and a string length of six allows for 36^6 = 2,176,782,336 // possible strings to use in the Tag label. #define kUniqueStringCharSet "abcdefghijklmnopqrstuvwxyz0123456789" #define kUniqueStringCharSetLen sizeof_string( kUniqueStringCharSet ) #define kUniqueStringLen 6 static OSStatus GAITestCaseAddItem( GAITestCase * inCase, unsigned int inAliasCount, unsigned int inAddressCount, int inTTL, GAITestAddrType inHasAddrs, GAITestAddrType inWantAddrs, 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 uniqueStr[ kUniqueStringLen + 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. RandomString( kUniqueStringCharSet, kUniqueStringCharSetLen, kUniqueStringLen, kUniqueStringLen, uniqueStr ); SNPrintF_Add( &ptr, end, "tag-%s.", uniqueStr ); // Add IPv4 or IPv6 label if necessary. switch( inHasAddrs ) { case kGAITestAddrType_IPv4: SNPrintF_Add( &ptr, end, "ipv4." ); break; case kGAITestAddrType_IPv6: SNPrintF_Add( &ptr, end, "ipv6." ); break; } // Add d.test. labels. SNPrintF_Add( &ptr, end, "d.test." ); // Create item. err = GAITestItemCreate( name, inAddressCount, inHasAddrs, inWantAddrs, &item ); require_noerr( err, exit ); newItemList = item; itemPtr = &item->next; // Create repeat items. for( i = 1; i < inItemCount; ++i ) { err = GAITestItemDuplicate( item, &item2 ); require_noerr( err, exit ); *itemPtr = item2; itemPtr = &item2->next; } // Append to test case's item list. for( itemPtr = &inCase->itemList; *itemPtr; itemPtr = &( *itemPtr )->next ) {} *itemPtr = newItemList; newItemList = NULL; exit: while( ( item = newItemList ) != NULL ) { newItemList = item->next; GAITestItemFree( item ); } return( err ); } //=========================================================================================================================== // GAITestCaseAddLocalHostItem //=========================================================================================================================== static OSStatus GAITestCaseAddLocalHostItem( GAITestCase *inCase, GAITestAddrType inWantAddrs, unsigned int inItemCount ) { OSStatus err; GAITestItem * item; GAITestItem * item2; GAITestItem * newItemList = NULL; GAITestItem ** itemPtr; unsigned int i; require_action_quiet( inItemCount > 1, exit, err = kNoErr ); err = GAITestItemCreate( "localhost.", 1, kGAITestAddrType_Both, inWantAddrs, &item ); require_noerr( err, exit ); newItemList = item; itemPtr = &item->next; // Create repeat items. for( i = 1; i < inItemCount; ++i ) { err = GAITestItemDuplicate( item, &item2 ); require_noerr( err, exit ); *itemPtr = item2; itemPtr = &item2->next; } for( itemPtr = &inCase->itemList; *itemPtr; itemPtr = &( *itemPtr )->next ) {} *itemPtr = newItemList; newItemList = NULL; exit: while( ( item = newItemList ) != NULL ) { newItemList = item->next; GAITestItemFree( item ); } return( err ); } //=========================================================================================================================== // GAITestItemCreate //=========================================================================================================================== static OSStatus GAITestItemCreate( const char * inName, unsigned int inAddressCount, GAITestAddrType inHasAddrs, GAITestAddrType inWantAddrs, GAITestItem ** outItem ) { OSStatus err; GAITestItem * obj = NULL; require_action_quiet( inAddressCount >= 1, exit, err = kCountErr ); require_action_quiet( GAITestAddrTypeIsValid( inHasAddrs ), exit, err = kValueErr ); require_action_quiet( GAITestAddrTypeIsValid( inWantAddrs ), exit, err = kValueErr ); obj = (GAITestItem *) calloc( 1, sizeof( *obj ) ); require_action( obj, exit, err = kNoMemoryErr ); obj->name = strdup( inName ); require_action( obj->name, exit, err = kNoMemoryErr ); obj->addressCount = inAddressCount; obj->hasV4 = ( inHasAddrs & kGAITestAddrType_IPv4 ) ? true : false; obj->hasV6 = ( inHasAddrs & kGAITestAddrType_IPv6 ) ? true : false; obj->wantV4 = ( inWantAddrs & kGAITestAddrType_IPv4 ) ? true : false; obj->wantV6 = ( inWantAddrs & kGAITestAddrType_IPv6 ) ? true : false; *outItem = obj; obj = NULL; err = kNoErr; exit: if( obj ) GAITestItemFree( obj ); return( err ); } //=========================================================================================================================== // GAITestItemDuplicate //=========================================================================================================================== static OSStatus GAITestItemDuplicate( const GAITestItem *inItem, GAITestItem **outItem ) { OSStatus err; GAITestItem * obj; obj = (GAITestItem *) calloc( 1, sizeof( *obj ) ); require_action( obj, exit, err = kNoMemoryErr ); *obj = *inItem; obj->next = NULL; if( inItem->name ) { obj->name = strdup( inItem->name ); require_action( obj->name, exit, err = kNoMemoryErr ); } *outItem = obj; obj = NULL; err = kNoErr; exit: if( obj ) GAITestItemFree( obj ); return( err ); } //=========================================================================================================================== // GAITestItemFree //=========================================================================================================================== static void GAITestItemFree( GAITestItem *inItem ) { ForgetMem( &inItem->name ); free( inItem ); } //=========================================================================================================================== // SSDPDiscoverCmd //=========================================================================================================================== #define kSSDPPort 1900 typedef struct { HTTPHeader header; // HTTP header object for sending and receiving. dispatch_source_t readSourceV4; // Read dispatch source for IPv4 socket. dispatch_source_t readSourceV6; // Read dispatch source for IPv6 socket. int receiveSecs; // After send, the amount of time to spend receiving. uint32_t ifindex; // Index of the interface over which to send the query. Boolean useIPv4; // True if the query should be sent via IPv4 multicast. Boolean useIPv6; // True if the query should be sent via IPv6 multicast. } SSDPDiscoverContext; static void SSDPDiscoverPrintPrologue( const SSDPDiscoverContext *inContext ); static void SSDPDiscoverReadHandler( void *inContext ); static int SocketToPortNumber( SocketRef inSock ); static OSStatus WriteSSDPSearchRequest( HTTPHeader *inHeader, const void *inHostSA, int inMX, const char *inST ); static void SSDPDiscoverCmd( void ) { OSStatus err; struct timeval now; SSDPDiscoverContext * context; dispatch_source_t signalSource = NULL; SocketRef sockV4 = kInvalidSocketRef; SocketRef sockV6 = kInvalidSocketRef; ssize_t n; int sendCount; // Set up SIGINT handler. signal( SIGINT, SIG_IGN ); err = DispatchSignalSourceCreate( SIGINT, Exit, kExitReason_SIGINT, &signalSource ); require_noerr( err, exit ); dispatch_resume( signalSource ); // Check command parameters. if( gSSDPDiscover_ReceiveSecs < -1 ) { FPrintF( stdout, "Invalid receive time: %d seconds.\n", gSSDPDiscover_ReceiveSecs ); err = kParamErr; goto exit; } // Create context. context = (SSDPDiscoverContext *) calloc( 1, sizeof( *context ) ); require_action( context, exit, err = kNoMemoryErr ); context->receiveSecs = gSSDPDiscover_ReceiveSecs; context->useIPv4 = ( gSSDPDiscover_UseIPv4 || !gSSDPDiscover_UseIPv6 ) ? true : false; context->useIPv6 = ( gSSDPDiscover_UseIPv6 || !gSSDPDiscover_UseIPv4 ) ? true : false; err = InterfaceIndexFromArgString( gInterface, &context->ifindex ); require_noerr_quiet( err, exit ); // Set up IPv4 socket. if( context->useIPv4 ) { int port; err = UDPClientSocketOpen( AF_INET, NULL, 0, -1, &port, &sockV4 ); require_noerr( err, exit ); err = SocketSetMulticastInterface( sockV4, NULL, context->ifindex ); require_noerr( err, exit ); err = setsockopt( sockV4, IPPROTO_IP, IP_MULTICAST_LOOP, (char *) &(uint8_t){ 1 }, (socklen_t) sizeof( uint8_t ) ); err = map_socket_noerr_errno( sockV4, err ); require_noerr( err, exit ); } // Set up IPv6 socket. if( context->useIPv6 ) { err = UDPClientSocketOpen( AF_INET6, NULL, 0, -1, NULL, &sockV6 ); require_noerr( err, exit ); err = SocketSetMulticastInterface( sockV6, NULL, context->ifindex ); require_noerr( err, exit ); err = setsockopt( sockV6, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, (char *) &(int){ 1 }, (socklen_t) sizeof( int ) ); err = map_socket_noerr_errno( sockV6, err ); require_noerr( err, exit ); } // Print prologue. SSDPDiscoverPrintPrologue( context ); // Send mDNS query message. sendCount = 0; if( IsValidSocket( sockV4 ) ) { struct sockaddr_in mcastAddr4; memset( &mcastAddr4, 0, sizeof( mcastAddr4 ) ); SIN_LEN_SET( &mcastAddr4 ); mcastAddr4.sin_family = AF_INET; mcastAddr4.sin_port = htons( kSSDPPort ); mcastAddr4.sin_addr.s_addr = htonl( 0xEFFFFFFA ); // 239.255.255.250 err = WriteSSDPSearchRequest( &context->header, &mcastAddr4, gSSDPDiscover_MX, gSSDPDiscover_ST ); require_noerr( err, exit ); n = sendto( sockV4, context->header.buf, context->header.len, 0, (const struct sockaddr *) &mcastAddr4, (socklen_t) sizeof( mcastAddr4 ) ); err = map_socket_value_errno( sockV4, n == (ssize_t) context->header.len, n ); if( err ) { FPrintF( stderr, "*** Failed to send query on IPv4 socket with error %#m\n", err ); ForgetSocket( &sockV4 ); } else { if( gSSDPDiscover_Verbose ) { gettimeofday( &now, NULL ); FPrintF( stdout, "---\n" ); FPrintF( stdout, "Send time: %{du:time}\n", &now ); FPrintF( stdout, "Source Port: %d\n", SocketToPortNumber( sockV4 ) ); FPrintF( stdout, "Destination: %##a\n", &mcastAddr4 ); FPrintF( stdout, "Message size: %zu\n", context->header.len ); FPrintF( stdout, "HTTP header:\n%1{text}", context->header.buf, context->header.len ); } ++sendCount; } } if( IsValidSocket( sockV6 ) ) { struct sockaddr_in6 mcastAddr6; memset( &mcastAddr6, 0, sizeof( mcastAddr6 ) ); SIN6_LEN_SET( &mcastAddr6 ); mcastAddr6.sin6_family = AF_INET6; mcastAddr6.sin6_port = htons( kSSDPPort ); mcastAddr6.sin6_addr.s6_addr[ 0 ] = 0xFF; // SSDP IPv6 link-local multicast address FF02::C mcastAddr6.sin6_addr.s6_addr[ 1 ] = 0x02; mcastAddr6.sin6_addr.s6_addr[ 15 ] = 0x0C; err = WriteSSDPSearchRequest( &context->header, &mcastAddr6, gSSDPDiscover_MX, gSSDPDiscover_ST ); require_noerr( err, exit ); n = sendto( sockV6, context->header.buf, context->header.len, 0, (const struct sockaddr *) &mcastAddr6, (socklen_t) sizeof( mcastAddr6 ) ); err = map_socket_value_errno( sockV6, n == (ssize_t) context->header.len, n ); if( err ) { FPrintF( stderr, "*** Failed to send query on IPv6 socket with error %#m\n", err ); ForgetSocket( &sockV6 ); } else { if( gSSDPDiscover_Verbose ) { gettimeofday( &now, NULL ); FPrintF( stdout, "---\n" ); FPrintF( stdout, "Send time: %{du:time}\n", &now ); FPrintF( stdout, "Source Port: %d\n", SocketToPortNumber( sockV6 ) ); FPrintF( stdout, "Destination: %##a\n", &mcastAddr6 ); FPrintF( stdout, "Message size: %zu\n", context->header.len ); FPrintF( stdout, "HTTP header:\n%1{text}", context->header.buf, context->header.len ); } ++sendCount; } } require_action_quiet( sendCount > 0, exit, err = kUnexpectedErr ); // If there's no wait period after the send, then exit. if( context->receiveSecs == 0 ) goto exit; // Create dispatch read sources for socket(s). if( IsValidSocket( sockV4 ) ) { SocketContext * sockCtx; err = SocketContextCreate( sockV4, context, &sockCtx ); require_noerr( err, exit ); sockV4 = kInvalidSocketRef; err = DispatchReadSourceCreate( sockCtx->sock, NULL, SSDPDiscoverReadHandler, SocketContextCancelHandler, sockCtx, &context->readSourceV4 ); if( err ) ForgetSocketContext( &sockCtx ); require_noerr( err, exit ); dispatch_resume( context->readSourceV4 ); } if( IsValidSocket( sockV6 ) ) { SocketContext * sockCtx; err = SocketContextCreate( sockV6, context, &sockCtx ); require_noerr( err, exit ); sockV6 = kInvalidSocketRef; err = DispatchReadSourceCreate( sockCtx->sock, NULL, SSDPDiscoverReadHandler, SocketContextCancelHandler, sockCtx, &context->readSourceV6 ); if( err ) ForgetSocketContext( &sockCtx ); require_noerr( err, exit ); dispatch_resume( context->readSourceV6 ); } if( context->receiveSecs > 0 ) { dispatch_after_f( dispatch_time_seconds( context->receiveSecs ), dispatch_get_main_queue(), kExitReason_Timeout, Exit ); } dispatch_main(); exit: ForgetSocket( &sockV4 ); ForgetSocket( &sockV6 ); dispatch_source_forget( &signalSource ); if( err ) exit( 1 ); } static int SocketToPortNumber( SocketRef inSock ) { OSStatus err; sockaddr_ip sip; socklen_t len; len = (socklen_t) sizeof( sip ); err = getsockname( inSock, &sip.sa, &len ); err = map_socket_noerr_errno( inSock, err ); check_noerr( err ); return( err ? -1 : SockAddrGetPort( &sip ) ); } static OSStatus WriteSSDPSearchRequest( HTTPHeader *inHeader, const void *inHostSA, int inMX, const char *inST ) { OSStatus err; err = HTTPHeader_InitRequest( inHeader, "M-SEARCH", "*", "HTTP/1.1" ); require_noerr( err, exit ); err = HTTPHeader_SetField( inHeader, "Host", "%##a", inHostSA ); require_noerr( err, exit ); err = HTTPHeader_SetField( inHeader, "ST", "%s", inST ? inST : "ssdp:all" ); require_noerr( err, exit ); err = HTTPHeader_SetField( inHeader, "Man", "\"ssdp:discover\"" ); require_noerr( err, exit ); err = HTTPHeader_SetField( inHeader, "MX", "%d", inMX ); require_noerr( err, exit ); err = HTTPHeader_Commit( inHeader ); require_noerr( err, exit ); exit: return( err ); } //=========================================================================================================================== // SSDPDiscoverPrintPrologue //=========================================================================================================================== static void SSDPDiscoverPrintPrologue( const SSDPDiscoverContext *inContext ) { const int receiveSecs = inContext->receiveSecs; const char * ifName; char ifNameBuf[ IF_NAMESIZE + 1 ]; NetTransportType ifType; ifName = if_indextoname( inContext->ifindex, ifNameBuf ); ifType = kNetTransportType_Undefined; if( ifName ) SocketGetInterfaceInfo( kInvalidSocketRef, ifName, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &ifType ); FPrintF( stdout, "Interface: %s/%d/%s\n", ifName ? ifName : "?", inContext->ifindex, NetTransportTypeToString( ifType ) ); FPrintF( stdout, "IP protocols: %?s%?s%?s\n", inContext->useIPv4, "IPv4", ( inContext->useIPv4 && inContext->useIPv6 ), ", ", inContext->useIPv6, "IPv6" ); FPrintF( stdout, "Receive duration: " ); if( receiveSecs >= 0 ) FPrintF( stdout, "%d second%?c\n", receiveSecs, receiveSecs != 1, 's' ); else FPrintF( stdout, "∞\n" ); FPrintF( stdout, "Start time: %{du:time}\n", NULL ); } //=========================================================================================================================== // SSDPDiscoverReadHandler //=========================================================================================================================== static void SSDPDiscoverReadHandler( void *inContext ) { OSStatus err; struct timeval now; SocketContext * const sockCtx = (SocketContext *) inContext; SSDPDiscoverContext * const context = (SSDPDiscoverContext *) sockCtx->userContext; HTTPHeader * const header = &context->header; sockaddr_ip fromAddr; size_t msgLen; gettimeofday( &now, NULL ); err = SocketRecvFrom( sockCtx->sock, header->buf, sizeof( header->buf ), &msgLen, &fromAddr, sizeof( fromAddr ), NULL, NULL, NULL, NULL ); require_noerr( err, exit ); FPrintF( stdout, "---\n" ); FPrintF( stdout, "Receive time: %{du:time}\n", &now ); FPrintF( stdout, "Source: %##a\n", &fromAddr ); FPrintF( stdout, "Message size: %zu\n", msgLen ); header->len = msgLen; if( HTTPHeader_Validate( header ) ) { FPrintF( stdout, "HTTP header:\n%1{text}", header->buf, header->len ); if( header->extraDataLen > 0 ) { FPrintF( stdout, "HTTP body: %1.1H", header->extraDataPtr, (int) header->extraDataLen, INT_MAX ); } } else { FPrintF( stdout, "Invalid HTTP message:\n%1.1H", header->buf, (int) msgLen, INT_MAX ); goto exit; } exit: if( err ) exit( 1 ); } //=========================================================================================================================== // HTTPHeader_Validate // // Parses for the end of an HTTP header and updates the HTTPHeader structure so it's ready to parse. Returns true if valid. // This assumes the "buf" and "len" fields are set. The other fields are set by this function. // // Note: This was copied from CoreUtils because the HTTPHeader_Validate function is currently not exported in the framework. //=========================================================================================================================== Boolean HTTPHeader_Validate( HTTPHeader *inHeader ) { const char * src; const char * end; // Check for interleaved binary data (4 byte header that begins with $). See RFC 2326 section 10.12. require( inHeader->len < sizeof( inHeader->buf ), exit ); src = inHeader->buf; end = src + inHeader->len; if( ( ( end - src ) >= 4 ) && ( src[ 0 ] == '$' ) ) { src += 4; } else { // Search for an empty line (HTTP-style header/body separator). CRLFCRLF, LFCRLF, or LFLF accepted. // $$$ TO DO: Start from the last search location to avoid re-searching the same data over and over. for( ;; ) { while( ( src < end ) && ( src[ 0 ] != '\n' ) ) ++src; if( src >= end ) goto exit; ++src; if( ( ( end - src ) >= 2 ) && ( src[ 0 ] == '\r' ) && ( src[ 1 ] == '\n' ) ) // CFLFCRLF or LFCRLF { src += 2; break; } else if( ( ( end - src ) >= 1 ) && ( src[ 0 ] == '\n' ) ) // LFLF { src += 1; break; } } } inHeader->extraDataPtr = src; inHeader->extraDataLen = (size_t)( end - src ); inHeader->len = (size_t)( src - inHeader->buf ); return( true ); exit: return( false ); } #if( TARGET_OS_DARWIN ) //=========================================================================================================================== // ResQueryCmd //=========================================================================================================================== // res_query() from libresolv is actually called res_9_query (see /usr/include/resolv.h). SOFT_LINK_LIBRARY_EX( "/usr/lib", resolv ); SOFT_LINK_FUNCTION_EX( resolv, res_9_query, int, ( const char *dname, int class, int type, u_char *answer, int anslen ), ( dname, class, type, answer, anslen ) ); // res_query() from libinfo SOFT_LINK_LIBRARY_EX( "/usr/lib", info ); SOFT_LINK_FUNCTION_EX( info, res_query, int, ( const char *dname, int class, int type, u_char *answer, int anslen ), ( dname, class, type, answer, anslen ) ); typedef int ( *res_query_f )( const char *dname, int class, int type, u_char *answer, int anslen ); static void ResQueryCmd( void ) { OSStatus err; res_query_f res_query_ptr; int n; uint16_t type, class; uint8_t answer[ 1024 ]; // Get pointer to one of the res_query() functions. if( gResQuery_UseLibInfo ) { if( !SOFT_LINK_HAS_FUNCTION( info, res_query ) ) { FPrintF( stderr, "Failed to soft link res_query from libinfo.\n" ); err = kNotFoundErr; goto exit; } res_query_ptr = soft_res_query; } else { if( !SOFT_LINK_HAS_FUNCTION( resolv, res_9_query ) ) { FPrintF( stderr, "Failed to soft link res_query from libresolv.\n" ); err = kNotFoundErr; goto exit; } res_query_ptr = soft_res_9_query; } // Get record type. err = RecordTypeFromArgString( gResQuery_Type, &type ); require_noerr( err, exit ); // Get record class. if( gResQuery_Class ) { err = RecordClassFromArgString( gResQuery_Class, &class ); require_noerr( err, exit ); } else { class = kDNSServiceClass_IN; } // Print prologue. FPrintF( stdout, "Name: %s\n", gResQuery_Name ); FPrintF( stdout, "Type: %s (%u)\n", RecordTypeToString( type ), type ); FPrintF( stdout, "Class: %s (%u)\n", ( class == kDNSServiceClass_IN ) ? "IN" : "???", class ); FPrintF( stdout, "Start time: %{du:time}\n", NULL ); FPrintF( stdout, "---\n" ); // Call res_query(). n = res_query_ptr( gResQuery_Name, class, type, (u_char *) answer, (int) sizeof( answer ) ); if( n < 0 ) { FPrintF( stderr, "res_query() failed with error: %d (%s).\n", h_errno, hstrerror( h_errno ) ); err = kUnknownErr; goto exit; } // Print result. FPrintF( stdout, "Message size: %d\n\n%{du:dnsmsg}", n, answer, (size_t) n ); exit: if( err ) exit( 1 ); } //=========================================================================================================================== // ResolvDNSQueryCmd //=========================================================================================================================== // dns_handle_t is defined as a pointer to a privately-defined struct in /usr/include/dns.h. It's defined as a void * here to // avoid including the header file. typedef void * dns_handle_t; SOFT_LINK_FUNCTION_EX( resolv, dns_open, dns_handle_t, ( const char *path ), ( path ) ); SOFT_LINK_FUNCTION_VOID_RETURN_EX( resolv, dns_free, ( dns_handle_t *dns ), ( dns ) ); SOFT_LINK_FUNCTION_EX( resolv, dns_query, int32_t, ( dns_handle_t dns, const char * name, uint32_t dnsclass, uint32_t dnstype, char * buf, uint32_t len, struct sockaddr * from, uint32_t * fromlen ), ( dns, name, dnsclass, dnstype, buf, len, from, fromlen ) ); static void ResolvDNSQueryCmd( void ) { OSStatus err; int n; dns_handle_t dns = NULL; uint16_t type, class; sockaddr_ip from; uint32_t fromLen; uint8_t answer[ 1024 ]; // Make sure that the required symbols are available. if( !SOFT_LINK_HAS_FUNCTION( resolv, dns_open ) ) { FPrintF( stderr, "Failed to soft link dns_open from libresolv.\n" ); err = kNotFoundErr; goto exit; } if( !SOFT_LINK_HAS_FUNCTION( resolv, dns_free ) ) { FPrintF( stderr, "Failed to soft link dns_free from libresolv.\n" ); err = kNotFoundErr; goto exit; } if( !SOFT_LINK_HAS_FUNCTION( resolv, dns_query ) ) { FPrintF( stderr, "Failed to soft link dns_query from libresolv.\n" ); err = kNotFoundErr; goto exit; } // Get record type. err = RecordTypeFromArgString( gResolvDNSQuery_Type, &type ); require_noerr( err, exit ); // Get record class. if( gResolvDNSQuery_Class ) { err = RecordClassFromArgString( gResolvDNSQuery_Class, &class ); require_noerr( err, exit ); } else { class = kDNSServiceClass_IN; } // Get dns handle. dns = soft_dns_open( gResolvDNSQuery_Path ); if( !dns ) { FPrintF( stderr, "dns_open( %s ) failed.\n", gResolvDNSQuery_Path ); err = kUnknownErr; goto exit; } // Print prologue. FPrintF( stdout, "Name: %s\n", gResolvDNSQuery_Name ); FPrintF( stdout, "Type: %s (%u)\n", RecordTypeToString( type ), type ); FPrintF( stdout, "Class: %s (%u)\n", ( class == kDNSServiceClass_IN ) ? "IN" : "???", class ); FPrintF( stdout, "Path: %s\n", gResolvDNSQuery_Path ? gResolvDNSQuery_Name : "" ); FPrintF( stdout, "Start time: %{du:time}\n", NULL ); FPrintF( stdout, "---\n" ); // Call dns_query(). memset( &from, 0, sizeof( from ) ); fromLen = (uint32_t) sizeof( from ); n = soft_dns_query( dns, gResolvDNSQuery_Name, class, type, (char *) answer, (uint32_t) sizeof( answer ), &from.sa, &fromLen ); if( n < 0 ) { FPrintF( stderr, "dns_query() failed with error: %d (%s).\n", h_errno, hstrerror( h_errno ) ); err = kUnknownErr; goto exit; } // Print result. FPrintF( stdout, "From: %##a\n", &from ); FPrintF( stdout, "Message size: %d\n\n%{du:dnsmsg}", n, answer, (size_t) n ); exit: if( dns ) soft_dns_free( dns ); if( err ) exit( 1 ); } //=========================================================================================================================== // CFHostCmd //=========================================================================================================================== static void _CFHostResolveCallback( CFHostRef inHost, CFHostInfoType inInfoType, const CFStreamError * inError, void * inInfo ); static void CFHostCmd( void ) { OSStatus err; CFStringRef name; Boolean success; CFHostRef host = NULL; CFHostClientContext context; CFStreamError streamErr; name = CFStringCreateWithCString( kCFAllocatorDefault, gCFHost_Name, kCFStringEncodingUTF8 ); require_action( name, exit, err = kUnknownErr ); host = CFHostCreateWithName( kCFAllocatorDefault, name ); ForgetCF( &name ); require_action( host, exit, err = kUnknownErr ); memset( &context, 0, sizeof( context ) ); success = CFHostSetClient( host, _CFHostResolveCallback, &context ); require_action( success, exit, err = kUnknownErr ); CFHostScheduleWithRunLoop( host, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode ); // Print prologue. FPrintF( stdout, "Hostname: %s\n", gCFHost_Name ); FPrintF( stdout, "Start time: %{du:time}\n", NULL ); FPrintF( stdout, "---\n" ); success = CFHostStartInfoResolution( host, kCFHostAddresses, &streamErr ); require_action( success, exit, err = kUnknownErr ); err = kNoErr; CFRunLoopRun(); exit: CFReleaseNullSafe( host ); if( err ) exit( 1 ); } static void _CFHostResolveCallback( CFHostRef inHost, CFHostInfoType inInfoType, const CFStreamError *inError, void *inInfo ) { OSStatus err; struct timeval now; gettimeofday( &now, NULL ); Unused( inInfoType ); Unused( inInfo ); if( inError && ( inError->domain != 0 ) && ( inError->error ) ) { err = inError->error; if( inError->domain == kCFStreamErrorDomainNetDB ) { FPrintF( stderr, "Error %d: %s.\n", err, gai_strerror( err ) ); } else { FPrintF( stderr, "Error %#m\n", err ); } } else { CFArrayRef addresses; CFIndex count, i; CFDataRef addrData; const struct sockaddr * sockAddr; Boolean wasResolved = false; addresses = CFHostGetAddressing( inHost, &wasResolved ); check( wasResolved ); if( addresses ) { count = CFArrayGetCount( addresses ); for( i = 0; i < count; ++i ) { addrData = CFArrayGetCFDataAtIndex( addresses, i, &err ); require_noerr( err, exit ); sockAddr = (const struct sockaddr *) CFDataGetBytePtr( addrData ); FPrintF( stdout, "%##a\n", sockAddr ); } } err = kNoErr; } FPrintF( stdout, "---\n" ); FPrintF( stdout, "End time: %{du:time}\n", &now ); if( gCFHost_WaitSecs > 0 ) sleep( (unsigned int) gCFHost_WaitSecs ); exit: exit( err ? 1 : 0 ); } //=========================================================================================================================== // DNSConfigAddCmd // // Note: Based on ajn's supplemental test tool. //=========================================================================================================================== static void DNSConfigAddCmd( void ) { OSStatus err; CFMutableDictionaryRef dict = NULL; CFMutableArrayRef array = NULL; size_t i; SCDynamicStoreRef store = NULL; CFStringRef key = NULL; Boolean success; if( geteuid() != 0 ) { FPrintF( stderr, "error: This command must to be run as root.\n" ); err = kIDErr; goto exit; } // Create dictionary. dict = CFDictionaryCreateMutable( NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks ); require_action( dict, exit, err = kNoMemoryErr ); // Add DNS server IP addresses. array = CFArrayCreateMutable( NULL, (CFIndex) gDNSConfigAdd_IPAddrCount, &kCFTypeArrayCallBacks ); require_action( array, exit, err = kNoMemoryErr ); for( i = 0; i < gDNSConfigAdd_IPAddrCount; ++i ) { CFStringRef addrStr; addrStr = CFStringCreateWithCString( NULL, gDNSConfigAdd_IPAddrArray[ i ], kCFStringEncodingUTF8 ); require_action( addrStr, exit, err = kUnknownErr ); CFArrayAppendValue( array, addrStr ); CFRelease( addrStr ); } CFDictionarySetValue( dict, kSCPropNetDNSServerAddresses, array ); ForgetCF( &array ); // Add domains, if any. array = CFArrayCreateMutable( NULL, (CFIndex) Min( gDNSConfigAdd_DomainCount, 1 ), &kCFTypeArrayCallBacks ); require_action( array, exit, err = kNoMemoryErr ); if( gDNSConfigAdd_DomainCount > 0 ) { for( i = 0; i < gDNSConfigAdd_DomainCount; ++i ) { CFStringRef domainStr; domainStr = CFStringCreateWithCString( NULL, gDNSConfigAdd_DomainArray[ i ], kCFStringEncodingUTF8 ); require_action( domainStr, exit, err = kUnknownErr ); CFArrayAppendValue( array, domainStr ); CFRelease( domainStr ); } } else { // There are no domains, but the domain array needs to be non-empty, so add a zero-length string to the array. CFArrayAppendValue( array, CFSTR( "" ) ); } CFDictionarySetValue( dict, kSCPropNetDNSSupplementalMatchDomains, array ); ForgetCF( &array ); // Add interface, if any. if( gDNSConfigAdd_Interface ) { err = CFDictionarySetCString( dict, kSCPropInterfaceName, gDNSConfigAdd_Interface, kSizeCString ); require_noerr( err, exit ); CFDictionarySetValue( dict, kSCPropNetDNSConfirmedServiceID, gDNSConfigAdd_ID ); } // Set dictionary in dynamic store. store = SCDynamicStoreCreate( NULL, CFSTR( "com.apple.dnssdutil" ), NULL, NULL ); err = map_scerror( store ); require_noerr( err, exit ); key = SCDynamicStoreKeyCreateNetworkServiceEntity( NULL, kSCDynamicStoreDomainState, gDNSConfigAdd_ID, kSCEntNetDNS ); require_action( key, exit, err = kUnknownErr ); success = SCDynamicStoreSetValue( store, key, dict ); require_action( success, exit, err = kUnknownErr ); exit: CFReleaseNullSafe( dict ); CFReleaseNullSafe( array ); CFReleaseNullSafe( store ); CFReleaseNullSafe( key ); gExitCode = err ? 1 : 0; } //=========================================================================================================================== // DNSConfigRemoveCmd //=========================================================================================================================== static void DNSConfigRemoveCmd( void ) { OSStatus err; SCDynamicStoreRef store = NULL; CFStringRef key = NULL; Boolean success; if( geteuid() != 0 ) { FPrintF( stderr, "error: This command must to be run as root.\n" ); err = kIDErr; goto exit; } store = SCDynamicStoreCreate( NULL, CFSTR( "com.apple.dnssdutil" ), NULL, NULL ); err = map_scerror( store ); require_noerr( err, exit ); key = SCDynamicStoreKeyCreateNetworkServiceEntity( NULL, kSCDynamicStoreDomainState, gDNSConfigRemove_ID, kSCEntNetDNS ); require_action( key, exit, err = kUnknownErr ); success = SCDynamicStoreRemoveValue( store, key ); require_action( success, exit, err = kUnknownErr ); exit: CFReleaseNullSafe( store ); CFReleaseNullSafe( key ); gExitCode = err ? 1 : 0; } #endif // TARGET_OS_DARWIN //=========================================================================================================================== // DaemonVersionCmd //=========================================================================================================================== static void DaemonVersionCmd( void ) { OSStatus err; uint32_t size, version; char strBuf[ 16 ]; size = (uint32_t) sizeof( version ); err = DNSServiceGetProperty( kDNSServiceProperty_DaemonVersion, &version, &size ); require_noerr( err, exit ); FPrintF( stdout, "Daemon version: %s\n", SourceVersionToCString( version, strBuf ) ); exit: if( err ) exit( 1 ); } //=========================================================================================================================== // Exit //=========================================================================================================================== static void Exit( void *inContext ) { const char * const reason = (const char *) inContext; FPrintF( stdout, "---\n" ); FPrintF( stdout, "End time: %{du:time}\n", NULL ); if( reason ) FPrintF( stdout, "End reason: %s\n", reason ); exit( gExitCode ); } //=========================================================================================================================== // PrintFTimestampHandler //=========================================================================================================================== static int PrintFTimestampHandler( PrintFContext * inContext, PrintFFormat * inFormat, PrintFVAList * inArgs, void * inUserContext ) { struct timeval now; const struct timeval * tv; struct tm * localTime; size_t len; int n; char dateTimeStr[ 32 ]; Unused( inUserContext ); tv = va_arg( inArgs->args, const struct timeval * ); require_action_quiet( !inFormat->suppress, exit, n = 0 ); if( !tv ) { gettimeofday( &now, NULL ); tv = &now; } localTime = localtime( &tv->tv_sec ); len = strftime( dateTimeStr, sizeof( dateTimeStr ), "%Y-%m-%d %H:%M:%S", localTime ); if( len == 0 ) dateTimeStr[ 0 ] = '\0'; n = PrintFCore( inContext, "%s.%06u", dateTimeStr, (unsigned int) tv->tv_usec ); exit: return( n ); } //=========================================================================================================================== // PrintFDNSMessageHandler //=========================================================================================================================== static int PrintFDNSMessageHandler( PrintFContext * inContext, PrintFFormat * inFormat, PrintFVAList * inArgs, void * inUserContext ) { OSStatus err; const void * msgPtr; size_t msgLen; char * text; int n; Boolean isMDNS; Boolean printRawRData; Unused( inUserContext ); msgPtr = va_arg( inArgs->args, const void * ); msgLen = va_arg( inArgs->args, size_t ); require_action_quiet( !inFormat->suppress, exit, n = 0 ); isMDNS = ( inFormat->altForm > 0 ) ? true : false; if( inFormat->precision == 0 ) printRawRData = false; else if( inFormat->precision == 1 ) printRawRData = true; else { n = PrintFCore( inContext, "<< BAD %%{du:dnsmsg} PRECISION >>" ); goto exit; } err = DNSMessageToText( msgPtr, msgLen, isMDNS, printRawRData, &text ); if( !err ) { n = PrintFCore( inContext, "%*{text}", inFormat->fieldWidth, text, kSizeCString ); free( text ); } else { n = PrintFCore( inContext, "%*.1H", inFormat->fieldWidth, msgPtr, (int) msgLen, (int) msgLen ); } exit: return( n ); } //=========================================================================================================================== // GetDNSSDFlagsFromOpts //=========================================================================================================================== static DNSServiceFlags GetDNSSDFlagsFromOpts( void ) { DNSServiceFlags flags; flags = (DNSServiceFlags) gDNSSDFlags; if( flags & kDNSServiceFlagsShareConnection ) { FPrintF( stderr, "*** Warning: kDNSServiceFlagsShareConnection (0x%X) is explicitly set in flag parameters.\n", kDNSServiceFlagsShareConnection ); } if( gDNSSDFlag_BrowseDomains ) flags |= kDNSServiceFlagsBrowseDomains; if( gDNSSDFlag_DenyCellular ) flags |= kDNSServiceFlagsDenyCellular; if( gDNSSDFlag_DenyExpensive ) flags |= kDNSServiceFlagsDenyExpensive; if( gDNSSDFlag_ForceMulticast ) flags |= kDNSServiceFlagsForceMulticast; if( gDNSSDFlag_IncludeAWDL ) flags |= kDNSServiceFlagsIncludeAWDL; if( gDNSSDFlag_NoAutoRename ) flags |= kDNSServiceFlagsNoAutoRename; if( gDNSSDFlag_PathEvaluationDone ) flags |= kDNSServiceFlagsPathEvaluationDone; if( gDNSSDFlag_RegistrationDomains ) flags |= kDNSServiceFlagsRegistrationDomains; if( gDNSSDFlag_ReturnIntermediates ) flags |= kDNSServiceFlagsReturnIntermediates; if( gDNSSDFlag_Shared ) flags |= kDNSServiceFlagsShared; if( gDNSSDFlag_SuppressUnusable ) flags |= kDNSServiceFlagsSuppressUnusable; if( gDNSSDFlag_Timeout ) flags |= kDNSServiceFlagsTimeout; if( gDNSSDFlag_UnicastResponse ) flags |= kDNSServiceFlagsUnicastResponse; if( gDNSSDFlag_Unique ) flags |= kDNSServiceFlagsUnique; if( gDNSSDFlag_WakeOnResolve ) flags |= kDNSServiceFlagsWakeOnResolve; return( flags ); } //=========================================================================================================================== // CreateConnectionFromArgString //=========================================================================================================================== static OSStatus CreateConnectionFromArgString( const char * inString, dispatch_queue_t inQueue, DNSServiceRef * outSDRef, ConnectionDesc * outDesc ) { OSStatus err; DNSServiceRef sdRef = NULL; ConnectionType type; int32_t pid = -1; // Initializing because the analyzer claims pid may be used uninitialized. uint8_t uuid[ 16 ]; if( strcasecmp( inString, kConnectionArg_Normal ) == 0 ) { err = DNSServiceCreateConnection( &sdRef ); require_noerr( err, exit ); type = kConnectionType_Normal; } else if( stricmp_prefix( inString, kConnectionArgPrefix_PID ) == 0 ) { const char * const pidStr = inString + sizeof_string( kConnectionArgPrefix_PID ); err = StringToInt32( pidStr, &pid ); if( err ) { FPrintF( stderr, "Invalid delegate connection PID value: %s\n", pidStr ); err = kParamErr; goto exit; } memset( uuid, 0, sizeof( uuid ) ); err = DNSServiceCreateDelegateConnection( &sdRef, pid, uuid ); if( err ) { FPrintF( stderr, "DNSServiceCreateDelegateConnection() returned %#m for PID %d\n", err, pid ); goto exit; } type = kConnectionType_DelegatePID; } else if( stricmp_prefix( inString, kConnectionArgPrefix_UUID ) == 0 ) { const char * const uuidStr = inString + sizeof_string( kConnectionArgPrefix_UUID ); check_compile_time_code( sizeof( uuid ) == sizeof( uuid_t ) ); err = StringToUUID( uuidStr, kSizeCString, false, uuid ); if( err ) { FPrintF( stderr, "Invalid delegate connection UUID value: %s\n", uuidStr ); err = kParamErr; goto exit; } err = DNSServiceCreateDelegateConnection( &sdRef, 0, uuid ); if( err ) { FPrintF( stderr, "DNSServiceCreateDelegateConnection() returned %#m for UUID %#U\n", err, uuid ); goto exit; } type = kConnectionType_DelegateUUID; } else { FPrintF( stderr, "Unrecognized connection string \"%s\".\n", inString ); err = kParamErr; goto exit; } err = DNSServiceSetDispatchQueue( sdRef, inQueue ); require_noerr( err, exit ); *outSDRef = sdRef; if( outDesc ) { outDesc->type = type; if( type == kConnectionType_DelegatePID ) outDesc->delegate.pid = pid; else if( type == kConnectionType_DelegateUUID ) memcpy( outDesc->delegate.uuid, uuid, 16 ); } sdRef = NULL; exit: if( sdRef ) DNSServiceRefDeallocate( sdRef ); return( err ); } //=========================================================================================================================== // InterfaceIndexFromArgString //=========================================================================================================================== static OSStatus InterfaceIndexFromArgString( const char *inString, uint32_t *outIndex ) { OSStatus err; uint32_t ifIndex; if( inString ) { ifIndex = if_nametoindex( inString ); if( ifIndex == 0 ) { err = StringToUInt32( inString, &ifIndex ); if( err ) { FPrintF( stderr, "Invalid interface value: %s\n", inString ); err = kParamErr; goto exit; } } } else { ifIndex = 0; } *outIndex = ifIndex; err = kNoErr; exit: return( err ); } //=========================================================================================================================== // RecordDataFromArgString //=========================================================================================================================== #define kRDataMaxLen UINT16_C( 0xFFFF ) static OSStatus StringToSRVRData( const char *inString, uint8_t **outPtr, size_t *outLen ); static OSStatus StringToTXTRData( const char *inString, char inDelimiter, uint8_t **outPtr, size_t *outLen ); static OSStatus RecordDataFromArgString( const char *inString, uint8_t **outDataPtr, size_t *outDataLen ) { OSStatus err; 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 <= kRDataMaxLen, 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 <= kRDataMaxLen, 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 = StringToSRVRData( 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 <= kRDataMaxLen, 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 = StringToTXTRData( str, ',', &dataPtr, &dataLen ); require_noerr( err, exit ); } // Unrecognized format else { FPrintF( stderr, "Unrecognized record data string \"%s\".\n", inString ); err = kParamErr; goto exit; } err = kNoErr; *outDataLen = dataLen; *outDataPtr = dataPtr; dataPtr = NULL; exit: FreeNullSafe( dataPtr ); return( err ); } static OSStatus StringToSRVRData( const char *inString, uint8_t **outPtr, size_t *outLen ) { OSStatus err; DataBuffer dataBuf; const char * ptr; int i; uint8_t * end; uint8_t target[ kDomainNameLengthMax ]; DataBuffer_Init( &dataBuf, NULL, 0, ( 3 * 2 ) + kDomainNameLengthMax ); // Parse and set the priority, weight, and port values (all three are unsigned 16-bit values). ptr = inString; for( i = 0; i < 3; ++i ) { char * next; long value; uint8_t buf[ 2 ]; value = strtol( ptr, &next, 0 ); require_action_quiet( ( next != ptr ) && ( *next == ',' ), exit, err = kMalformedErr ); require_action_quiet( ( value >= 0 ) && ( value <= UINT16_MAX ), exit, err = kRangeErr ); ptr = next + 1; WriteBig16( buf, value ); err = DataBuffer_Append( &dataBuf, buf, sizeof( buf ) ); require_noerr( err, exit ); } // Set the target domain name. err = DomainNameFromString( target, ptr, &end ); require_noerr_quiet( err, exit ); err = DataBuffer_Append( &dataBuf, target, (size_t)( end - target ) ); require_noerr( err, exit ); err = DataBuffer_Detach( &dataBuf, outPtr, outLen ); require_noerr( err, exit ); exit: DataBuffer_Free( &dataBuf ); return( err ); } static OSStatus StringToTXTRData( const char *inString, char inDelimiter, uint8_t **outPtr, size_t *outLen ) { OSStatus err; DataBuffer dataBuf; const char * src; uint8_t txtStr[ 256 ]; // Buffer for single TXT string: 1 length byte + up to 255 bytes of data. DataBuffer_Init( &dataBuf, NULL, 0, kRDataMaxLen ); src = inString; for( ;; ) { uint8_t * dst = &txtStr[ 1 ]; const uint8_t * const lim = &txtStr[ 256 ]; int c; while( *src && ( *src != inDelimiter ) ) { if( ( c = *src++ ) == '\\' ) { require_action_quiet( *src != '\0', exit, err = kUnderrunErr ); c = *src++; } require_action_quiet( dst < lim, exit, err = kOverrunErr ); *dst++ = (uint8_t) c; } txtStr[ 0 ] = (uint8_t)( dst - &txtStr[ 1 ] ); err = DataBuffer_Append( &dataBuf, txtStr, 1 + txtStr[ 0 ] ); require_noerr( err, exit ); if( *src == '\0' ) break; ++src; } err = DataBuffer_Detach( &dataBuf, outPtr, outLen ); require_noerr( err, exit ); exit: DataBuffer_Free( &dataBuf ); return( err ); } //=========================================================================================================================== // RecordTypeFromArgString //=========================================================================================================================== typedef struct { uint16_t value; // Record type's numeric value. const char * name; // Record type's name as a string (e.g., "A", "PTR", "SRV"). } RecordType; static const RecordType kRecordTypes[] = { // Common types. { kDNSServiceType_A, "A" }, { kDNSServiceType_AAAA, "AAAA" }, { kDNSServiceType_PTR, "PTR" }, { kDNSServiceType_SRV, "SRV" }, { kDNSServiceType_TXT, "TXT" }, { kDNSServiceType_CNAME, "CNAME" }, { kDNSServiceType_SOA, "SOA" }, { kDNSServiceType_NSEC, "NSEC" }, { kDNSServiceType_NS, "NS" }, { kDNSServiceType_MX, "MX" }, { kDNSServiceType_ANY, "ANY" }, { kDNSServiceType_OPT, "OPT" }, // Less common types. { kDNSServiceType_MD, "MD" }, { kDNSServiceType_NS, "NS" }, { kDNSServiceType_MD, "MD" }, { kDNSServiceType_MF, "MF" }, { kDNSServiceType_MB, "MB" }, { kDNSServiceType_MG, "MG" }, { kDNSServiceType_MR, "MR" }, { kDNSServiceType_NULL, "NULL" }, { kDNSServiceType_WKS, "WKS" }, { kDNSServiceType_HINFO, "HINFO" }, { kDNSServiceType_MINFO, "MINFO" }, { kDNSServiceType_RP, "RP" }, { kDNSServiceType_AFSDB, "AFSDB" }, { kDNSServiceType_X25, "X25" }, { kDNSServiceType_ISDN, "ISDN" }, { kDNSServiceType_RT, "RT" }, { kDNSServiceType_NSAP, "NSAP" }, { kDNSServiceType_NSAP_PTR, "NSAP_PTR" }, { kDNSServiceType_SIG, "SIG" }, { kDNSServiceType_KEY, "KEY" }, { kDNSServiceType_PX, "PX" }, { kDNSServiceType_GPOS, "GPOS" }, { kDNSServiceType_LOC, "LOC" }, { kDNSServiceType_NXT, "NXT" }, { kDNSServiceType_EID, "EID" }, { kDNSServiceType_NIMLOC, "NIMLOC" }, { kDNSServiceType_ATMA, "ATMA" }, { kDNSServiceType_NAPTR, "NAPTR" }, { kDNSServiceType_KX, "KX" }, { kDNSServiceType_CERT, "CERT" }, { kDNSServiceType_A6, "A6" }, { kDNSServiceType_DNAME, "DNAME" }, { kDNSServiceType_SINK, "SINK" }, { kDNSServiceType_APL, "APL" }, { kDNSServiceType_DS, "DS" }, { kDNSServiceType_SSHFP, "SSHFP" }, { kDNSServiceType_IPSECKEY, "IPSECKEY" }, { kDNSServiceType_RRSIG, "RRSIG" }, { kDNSServiceType_DNSKEY, "DNSKEY" }, { kDNSServiceType_DHCID, "DHCID" }, { kDNSServiceType_NSEC3, "NSEC3" }, { kDNSServiceType_NSEC3PARAM, "NSEC3PARAM" }, { kDNSServiceType_HIP, "HIP" }, { kDNSServiceType_SPF, "SPF" }, { kDNSServiceType_UINFO, "UINFO" }, { kDNSServiceType_UID, "UID" }, { kDNSServiceType_GID, "GID" }, { kDNSServiceType_UNSPEC, "UNSPEC" }, { kDNSServiceType_TKEY, "TKEY" }, { kDNSServiceType_TSIG, "TSIG" }, { kDNSServiceType_IXFR, "IXFR" }, { kDNSServiceType_AXFR, "AXFR" }, { kDNSServiceType_MAILB, "MAILB" }, { kDNSServiceType_MAILA, "MAILA" } }; static OSStatus RecordTypeFromArgString( const char *inString, uint16_t *outValue ) { OSStatus err; int32_t i32; const RecordType * type; const RecordType * const end = kRecordTypes + countof( kRecordTypes ); for( type = kRecordTypes; type < end; ++type ) { if( strcasecmp( type->name, inString ) == 0 ) { *outValue = type->value; return( kNoErr ); } } err = StringToInt32( inString, &i32 ); require_noerr_quiet( err, exit ); require_action_quiet( ( i32 >= 0 ) && ( i32 <= UINT16_MAX ), exit, err = kParamErr ); *outValue = (uint16_t) i32; exit: return( err ); } //=========================================================================================================================== // RecordClassFromArgString //=========================================================================================================================== static OSStatus RecordClassFromArgString( const char *inString, uint16_t *outValue ) { OSStatus err; int32_t i32; if( strcasecmp( inString, "IN" ) == 0 ) { *outValue = kDNSServiceClass_IN; err = kNoErr; goto exit; } err = StringToInt32( inString, &i32 ); require_noerr_quiet( err, exit ); require_action_quiet( ( i32 >= 0 ) && ( i32 <= UINT16_MAX ), exit, err = kParamErr ); *outValue = (uint16_t) i32; exit: return( err ); } //=========================================================================================================================== // InterfaceIndexToName //=========================================================================================================================== static char * InterfaceIndexToName( uint32_t inIfIndex, char inNameBuf[ kInterfaceNameBufLen ] ) { switch( inIfIndex ) { case kDNSServiceInterfaceIndexAny: strlcpy( inNameBuf, "Any", kInterfaceNameBufLen ); break; case kDNSServiceInterfaceIndexLocalOnly: strlcpy( inNameBuf, "LocalOnly", kInterfaceNameBufLen ); break; case kDNSServiceInterfaceIndexUnicast: strlcpy( inNameBuf, "Unicast", kInterfaceNameBufLen ); break; case kDNSServiceInterfaceIndexP2P: strlcpy( inNameBuf, "P2P", kInterfaceNameBufLen ); break; #if( defined( kDNSServiceInterfaceIndexBLE ) ) case kDNSServiceInterfaceIndexBLE: strlcpy( inNameBuf, "BLE", kInterfaceNameBufLen ); break; #endif default: { const char * name; name = if_indextoname( inIfIndex, inNameBuf ); if( !name ) strlcpy( inNameBuf, "NO NAME", kInterfaceNameBufLen ); break; } } return( inNameBuf ); } //=========================================================================================================================== // RecordTypeToString //=========================================================================================================================== static const char * RecordTypeToString( unsigned int inValue ) { const RecordType * type; const RecordType * const end = kRecordTypes + countof( kRecordTypes ); for( type = kRecordTypes; type < end; ++type ) { if( type->value == inValue ) return( type->name ); } return( "???" ); } //=========================================================================================================================== // DNSMessageExtractDomainName //=========================================================================================================================== #define IsCompressionByte( X ) ( ( ( X ) & 0xC0 ) == 0xC0 ) static OSStatus DNSMessageExtractDomainName( const uint8_t * inMsgPtr, size_t inMsgLen, const uint8_t * inNamePtr, uint8_t inBuf[ kDomainNameLengthMax ], const uint8_t ** outNextPtr ) { OSStatus err; const uint8_t * label; uint8_t labelLen; const uint8_t * nextLabel; const uint8_t * const msgEnd = inMsgPtr + inMsgLen; uint8_t * dst = inBuf; const uint8_t * const dstLim = inBuf ? ( inBuf + kDomainNameLengthMax ) : NULL; const uint8_t * nameEnd = NULL; require_action( ( inNamePtr >= inMsgPtr ) && ( inNamePtr < msgEnd ), exit, err = kRangeErr ); for( label = inNamePtr; ( labelLen = label[ 0 ] ) != 0; label = nextLabel ) { if( labelLen <= kDomainLabelLengthMax ) { nextLabel = label + 1 + labelLen; require_action( nextLabel < msgEnd, exit, err = kUnderrunErr ); if( dst ) { require_action( ( dstLim - dst ) > ( 1 + labelLen ), exit, err = kOverrunErr ); memcpy( dst, label, 1 + labelLen ); dst += ( 1 + labelLen ); } } else if( IsCompressionByte( labelLen ) ) { uint16_t offset; require_action( ( msgEnd - label ) >= 2, exit, err = kUnderrunErr ); if( !nameEnd ) { nameEnd = label + 2; if( !dst ) break; } offset = (uint16_t)( ( ( label[ 0 ] & 0x3F ) << 8 ) | label[ 1 ] ); nextLabel = inMsgPtr + offset; require_action( nextLabel < msgEnd, exit, err = kUnderrunErr ); require_action( !IsCompressionByte( nextLabel[ 0 ] ), exit, err = kMalformedErr ); } else { dlogassert( "Unhandled label length 0x%02X\n", labelLen ); err = kMalformedErr; goto exit; } } if( dst ) *dst = 0; if( !nameEnd ) nameEnd = label + 1; if( outNextPtr ) *outNextPtr = nameEnd; err = kNoErr; exit: return( err ); } //=========================================================================================================================== // DNSMessageExtractDomainNameString //=========================================================================================================================== static OSStatus DNSMessageExtractDomainNameString( const void * inMsgPtr, size_t inMsgLen, const void * inNamePtr, char inBuf[ kDNSServiceMaxDomainName ], const uint8_t ** outNextPtr ) { OSStatus err; const uint8_t * nextPtr; uint8_t domainName[ kDomainNameLengthMax ]; err = DNSMessageExtractDomainName( inMsgPtr, inMsgLen, inNamePtr, domainName, &nextPtr ); require_noerr( err, exit ); err = DomainNameToString( domainName, NULL, inBuf, NULL ); require_noerr( err, exit ); if( outNextPtr ) *outNextPtr = nextPtr; exit: return( err ); } //=========================================================================================================================== // DNSMessageExtractRecord //=========================================================================================================================== typedef struct { uint8_t type[ 2 ]; uint8_t class[ 2 ]; uint8_t ttl[ 4 ]; uint8_t rdLength[ 2 ]; uint8_t rdata[ 1 ]; } DNSRecordFields; check_compile_time( offsetof( DNSRecordFields, rdata ) == 10 ); static OSStatus DNSMessageExtractRecord( const uint8_t * inMsgPtr, size_t inMsgLen, const uint8_t * inPtr, uint8_t inNameBuf[ kDomainNameLengthMax ], uint16_t * outType, uint16_t * outClass, uint32_t * outTTL, const uint8_t ** outRDataPtr, size_t * outRDataLen, const uint8_t ** outPtr ) { OSStatus err; const uint8_t * const msgEnd = inMsgPtr + inMsgLen; const uint8_t * ptr; const DNSRecordFields * record; size_t rdLength; err = DNSMessageExtractDomainName( inMsgPtr, inMsgLen, inPtr, inNameBuf, &ptr ); require_noerr_quiet( err, exit ); require_action_quiet( (size_t)( msgEnd - ptr ) >= offsetof( DNSRecordFields, rdata ), exit, err = kUnderrunErr ); record = (DNSRecordFields *) ptr; rdLength = ReadBig16( record->rdLength ); require_action_quiet( (size_t)( msgEnd - record->rdata ) >= rdLength , exit, err = kUnderrunErr ); if( outType ) *outType = ReadBig16( record->type ); if( outClass ) *outClass = ReadBig16( record->class ); if( outTTL ) *outTTL = ReadBig32( record->ttl ); if( outRDataPtr ) *outRDataPtr = record->rdata; if( outRDataLen ) *outRDataLen = rdLength; if( outPtr ) *outPtr = record->rdata + rdLength; exit: return( err ); } //=========================================================================================================================== // DNSMessageGetAnswerSection //=========================================================================================================================== static OSStatus DNSMessageGetAnswerSection( const uint8_t *inMsgPtr, size_t inMsgLen, const uint8_t **outPtr ) { OSStatus err; const uint8_t * const msgEnd = inMsgPtr + inMsgLen; unsigned int questionCount, i; const DNSHeader * hdr; const uint8_t * ptr; require_action_quiet( inMsgLen >= kDNSHeaderLength, exit, err = kSizeErr ); hdr = (DNSHeader *) inMsgPtr; questionCount = DNSHeaderGetQuestionCount( hdr ); ptr = (uint8_t *)( hdr + 1 ); for( i = 0; i < questionCount; ++i ) { err = DNSMessageExtractDomainName( inMsgPtr, inMsgLen, ptr, NULL, &ptr ); require_noerr( err, exit ); require_action_quiet( ( msgEnd - ptr ) >= 4, exit, err = kUnderrunErr ); ptr += 4; } if( outPtr ) *outPtr = ptr; err = kNoErr; exit: return( err ); } //=========================================================================================================================== // DNSRecordDataToString //=========================================================================================================================== static OSStatus DNSRecordDataToString( const void * inRDataPtr, size_t inRDataLen, unsigned int inRDataType, const void * inMsgPtr, size_t inMsgLen, char ** outString ) { OSStatus err; const uint8_t * const rdataPtr = (uint8_t *) inRDataPtr; const uint8_t * const rdataEnd = rdataPtr + inRDataLen; char * rdataStr; const uint8_t * ptr; int n; char domainNameStr[ kDNSServiceMaxDomainName ]; rdataStr = NULL; if( inRDataType == kDNSServiceType_A ) { require_action_quiet( inRDataLen == 4, exit, err = kMalformedErr ); ASPrintF( &rdataStr, "%.4a", rdataPtr ); require_action( rdataStr, exit, err = kNoMemoryErr ); } else if( inRDataType == kDNSServiceType_AAAA ) { require_action_quiet( inRDataLen == 16, exit, err = kMalformedErr ); ASPrintF( &rdataStr, "%.16a", rdataPtr ); require_action( rdataStr, exit, err = kNoMemoryErr ); } else if( ( inRDataType == kDNSServiceType_PTR ) || ( inRDataType == kDNSServiceType_CNAME ) || ( inRDataType == kDNSServiceType_NS ) ) { if( inMsgPtr ) { err = DNSMessageExtractDomainNameString( inMsgPtr, inMsgLen, rdataPtr, domainNameStr, NULL ); require_noerr( err, exit ); } else { err = DomainNameToString( rdataPtr, rdataEnd, domainNameStr, NULL ); require_noerr( err, exit ); } rdataStr = strdup( domainNameStr ); require_action( rdataStr, exit, err = kNoMemoryErr ); } else if( inRDataType == kDNSServiceType_SRV ) { uint16_t priority, weight, port; const uint8_t * target; require_action_quiet( ( rdataPtr + 6 ) < rdataEnd, exit, err = kMalformedErr ); priority = ReadBig16( rdataPtr ); weight = ReadBig16( rdataPtr + 2 ); port = ReadBig16( rdataPtr + 4 ); target = rdataPtr + 6; if( inMsgPtr ) { err = DNSMessageExtractDomainNameString( inMsgPtr, inMsgLen, target, domainNameStr, NULL ); require_noerr( err, exit ); } else { err = DomainNameToString( target, rdataEnd, domainNameStr, NULL ); require_noerr( err, exit ); } ASPrintF( &rdataStr, "%u %u %u %s", priority, weight, port, domainNameStr ); require_action( rdataStr, exit, err = kNoMemoryErr ); } else if( inRDataType == kDNSServiceType_TXT ) { require_action_quiet( inRDataLen > 0, exit, err = kMalformedErr ); if( inRDataLen == 1 ) { ASPrintF( &rdataStr, "%#H", rdataPtr, (int) inRDataLen, INT_MAX ); require_action( rdataStr, exit, err = kNoMemoryErr ); } else { ASPrintF( &rdataStr, "%#{txt}", rdataPtr, inRDataLen ); require_action( rdataStr, exit, err = kNoMemoryErr ); } } else if( inRDataType == kDNSServiceType_SOA ) { uint32_t serial, refresh, retry, expire, minimum; if( inMsgPtr ) { err = DNSMessageExtractDomainNameString( inMsgPtr, inMsgLen, rdataPtr, domainNameStr, &ptr ); require_noerr( err, exit ); require_action_quiet( ptr < rdataEnd, exit, err = kMalformedErr ); rdataStr = strdup( domainNameStr ); require_action( rdataStr, exit, err = kNoMemoryErr ); err = DNSMessageExtractDomainNameString( inMsgPtr, inMsgLen, ptr, domainNameStr, &ptr ); require_noerr( err, exit ); } else { err = DomainNameToString( rdataPtr, rdataEnd, domainNameStr, &ptr ); require_noerr( err, exit ); rdataStr = strdup( domainNameStr ); require_action( rdataStr, exit, err = kNoMemoryErr ); err = DomainNameToString( ptr, rdataEnd, domainNameStr, &ptr ); require_noerr( err, exit ); } require_action_quiet( ( ptr + 20 ) == rdataEnd, exit, err = kMalformedErr ); serial = ReadBig32( ptr ); refresh = ReadBig32( ptr + 4 ); retry = ReadBig32( ptr + 8 ); expire = ReadBig32( ptr + 12 ); minimum = ReadBig32( ptr + 16 ); n = AppendPrintF( &rdataStr, " %s %u %u %u %u %u\n", domainNameStr, serial, refresh, retry, expire, minimum ); require_action( n > 0, exit, err = kUnknownErr ); } else if( inRDataType == kDNSServiceType_NSEC ) { unsigned int windowBlock, bitmapLen, i, recordType; const uint8_t * bitmapPtr; if( inMsgPtr ) { err = DNSMessageExtractDomainNameString( inMsgPtr, inMsgLen, rdataPtr, domainNameStr, &ptr ); require_noerr( err, exit ); } else { err = DomainNameToString( rdataPtr, rdataEnd, domainNameStr, &ptr ); require_noerr( err, exit ); } require_action_quiet( ptr < rdataEnd, exit, err = kMalformedErr ); rdataStr = strdup( domainNameStr ); require_action( rdataStr, exit, err = kNoMemoryErr ); for( ; ptr < rdataEnd; ptr += ( 2 + bitmapLen ) ) { require_action_quiet( ( ptr + 2 ) < rdataEnd, exit, err = kMalformedErr ); windowBlock = ptr[ 0 ]; bitmapLen = ptr[ 1 ]; bitmapPtr = &ptr[ 2 ]; require_action_quiet( ( bitmapLen >= 1 ) && ( bitmapLen <= 32 ) , exit, err = kMalformedErr ); require_action_quiet( ( bitmapPtr + bitmapLen ) <= rdataEnd, exit, err = kMalformedErr ); for( i = 0; i < BitArray_MaxBits( bitmapLen ); ++i ) { if( BitArray_GetBit( bitmapPtr, bitmapLen, i ) ) { recordType = ( windowBlock * 256 ) + i; n = AppendPrintF( &rdataStr, " %s", RecordTypeToString( recordType ) ); require_action( n > 0, exit, err = kUnknownErr ); } } } } else if( inRDataType == kDNSServiceType_MX ) { uint16_t preference; const uint8_t * exchange; require_action_quiet( ( rdataPtr + 2 ) < rdataEnd, exit, err = kMalformedErr ); preference = ReadBig16( rdataPtr ); exchange = &rdataPtr[ 2 ]; if( inMsgPtr ) { err = DNSMessageExtractDomainNameString( inMsgPtr, inMsgLen, exchange, domainNameStr, NULL ); require_noerr( err, exit ); } else { err = DomainNameToString( exchange, rdataEnd, domainNameStr, NULL ); require_noerr( err, exit ); } n = ASPrintF( &rdataStr, "%u %s", preference, domainNameStr ); require_action( n > 0, exit, err = kUnknownErr ); } else { err = kNotHandledErr; goto exit; } check( rdataStr ); *outString = rdataStr; rdataStr = NULL; err = kNoErr; exit: FreeNullSafe( rdataStr ); return( err ); } //=========================================================================================================================== // DomainNameAppendString //=========================================================================================================================== static OSStatus DomainNameAppendString( uint8_t inDomainName[ kDomainNameLengthMax ], const char * inString, uint8_t ** outEndPtr ) { OSStatus err; const char * src; uint8_t * root; const uint8_t * const nameLim = inDomainName + kDomainNameLengthMax; for( root = inDomainName; ( root < nameLim ) && *root; root += ( 1 + *root ) ) {} require_action_quiet( root < nameLim, exit, err = kMalformedErr ); // If the string is a single dot, denoting the root domain, then there are no non-empty labels. src = inString; if( ( src[ 0 ] == '.' ) && ( src[ 1 ] == '\0' ) ) ++src; while( *src ) { uint8_t * const label = root; const uint8_t * const labelLim = Min( &label[ 1 + kDomainLabelLengthMax ], nameLim - 1 ); uint8_t * dst; int c; size_t labelLen; dst = &label[ 1 ]; while( *src && ( ( c = *src++ ) != '.' ) ) { if( c == '\\' ) { require_action_quiet( *src != '\0', exit, err = kUnderrunErr ); c = *src++; if( isdigit_safe( c ) && isdigit_safe( src[ 0 ] ) && isdigit_safe( src[ 1 ] ) ) { const int decimal = ( ( c - '0' ) * 100 ) + ( ( src[ 0 ] - '0' ) * 10 ) + ( src[ 1 ] - '0' ); if( decimal <= 255 ) { c = decimal; src += 2; } } } require_action_quiet( dst < labelLim, exit, err = kOverrunErr ); *dst++ = (uint8_t) c; } labelLen = (size_t)( dst - &label[ 1 ] ); require_action_quiet( labelLen > 0, exit, err = kMalformedErr ); label[ 0 ] = (uint8_t) labelLen; root = dst; *root = 0; } if( outEndPtr ) *outEndPtr = root + 1; err = kNoErr; exit: return( err ); } //=========================================================================================================================== // DomainNameEqual //=========================================================================================================================== static Boolean DomainNameEqual( const uint8_t *inName1, const uint8_t *inName2 ) { const uint8_t * p1 = inName1; const uint8_t * p2 = inName2; unsigned int len; for( ;; ) { if( ( len = *p1++ ) != *p2++ ) return( false ); if( len == 0 ) break; for( ; len > 0; ++p1, ++p2, --len ) { if( tolower_safe( *p1 ) != tolower_safe( *p2 ) ) return( false ); } } return( true ); } //=========================================================================================================================== // DomainNameLength //=========================================================================================================================== static size_t DomainNameLength( const uint8_t * const inName ) { const uint8_t * ptr; for( ptr = inName; *ptr != 0; ptr += ( 1 + *ptr ) ) {} return( (size_t)( ptr - inName ) + 1 ); } //=========================================================================================================================== // DomainNameFromString //=========================================================================================================================== static OSStatus DomainNameFromString( uint8_t inDomainName[ kDomainNameLengthMax ], const char * inString, uint8_t ** outEndPtr ) { inDomainName[ 0 ] = 0; return( DomainNameAppendString( inDomainName, inString, outEndPtr ) ); } //=========================================================================================================================== // DomainNameToString //=========================================================================================================================== static OSStatus DomainNameToString( const uint8_t * inDomainName, const uint8_t * inEnd, char inBuf[ kDNSServiceMaxDomainName ], const uint8_t ** outNextPtr ) { OSStatus err; const uint8_t * label; uint8_t labelLen; const uint8_t * nextLabel; char * dst; const uint8_t * src; require_action( !inEnd || ( inDomainName < inEnd ), exit, err = kUnderrunErr ); // Convert each label up until the root label, i.e., the zero-length label. dst = inBuf; for( label = inDomainName; ( labelLen = label[ 0 ] ) != 0; label = nextLabel ) { require_action( labelLen <= kDomainLabelLengthMax, exit, err = kMalformedErr ); nextLabel = &label[ 1 ] + labelLen; require_action( ( nextLabel - inDomainName ) < kDomainNameLengthMax, exit, err = kMalformedErr ); require_action( !inEnd || ( nextLabel < inEnd ), exit, err = kUnderrunErr ); for( src = &label[ 1 ]; src < nextLabel; ++src ) { if( isprint_safe( *src ) ) { if( ( *src == '.' ) || ( *src == '\\' ) || ( *src == ' ' ) ) *dst++ = '\\'; *dst++ = (char) *src; } else { *dst++ = '\\'; *dst++ = '0' + ( *src / 100 ); *dst++ = '0' + ( ( *src / 10 ) % 10 ); *dst++ = '0' + ( *src % 10 ); } } *dst++ = '.'; } // At this point, label points to the root label. // If the root label was the only label, then write a dot for it. if( label == inDomainName ) *dst++ = '.'; *dst = '\0'; if( outNextPtr ) *outNextPtr = label + 1; err = kNoErr; exit: return( err ); } //=========================================================================================================================== // DNSMessageToText //=========================================================================================================================== #define DNSFlagsOpCodeToString( X ) ( \ ( (X) == kDNSOpCode_Query ) ? "Query" : \ ( (X) == kDNSOpCode_InverseQuery ) ? "IQuery" : \ ( (X) == kDNSOpCode_Status ) ? "Status" : \ ( (X) == kDNSOpCode_Notify ) ? "Notify" : \ ( (X) == kDNSOpCode_Update ) ? "Update" : \ "Unassigned" ) #define DNSFlagsRCodeToString( X ) ( \ ( (X) == kDNSRCode_NoError ) ? "NoError" : \ ( (X) == kDNSRCode_FormatError ) ? "FormErr" : \ ( (X) == kDNSRCode_ServerFailure ) ? "ServFail" : \ ( (X) == kDNSRCode_NXDomain ) ? "NXDomain" : \ ( (X) == kDNSRCode_NotImplemented ) ? "NotImp" : \ ( (X) == kDNSRCode_Refused ) ? "Refused" : \ "???" ) static OSStatus DNSMessageToText( const uint8_t * inMsgPtr, size_t inMsgLen, const Boolean inMDNS, const Boolean inPrintRaw, char ** outText ) { OSStatus err; DataBuffer dataBuf; size_t len; const DNSHeader * hdr; const uint8_t * const msgEnd = inMsgPtr + inMsgLen; const uint8_t * ptr; unsigned int id, flags, opcode, rcode; unsigned int questionCount, answerCount, authorityCount, additionalCount, i, totalRRCount; char nameStr[ kDNSServiceMaxDomainName ]; DataBuffer_Init( &dataBuf, NULL, 0, SIZE_MAX ); #define _Append( ... ) do { err = DataBuffer_AppendF( &dataBuf, __VA_ARGS__ ); require_noerr( err, exit ); } while( 0 ) require_action_quiet( inMsgLen >= kDNSHeaderLength, exit, err = kSizeErr ); hdr = (DNSHeader *) inMsgPtr; id = DNSHeaderGetID( hdr ); flags = DNSHeaderGetFlags( hdr ); questionCount = DNSHeaderGetQuestionCount( hdr ); answerCount = DNSHeaderGetAnswerCount( hdr ); authorityCount = DNSHeaderGetAuthorityCount( hdr ); additionalCount = DNSHeaderGetAdditionalCount( hdr ); opcode = DNSFlagsGetOpCode( flags ); rcode = DNSFlagsGetRCode( flags ); _Append( "ID: 0x%04X (%u)\n", id, id ); _Append( "Flags: 0x%04X %c/%s %cAA%cTC%cRD%cRA%?s%?s %s\n", flags, ( flags & kDNSHeaderFlag_Response ) ? 'R' : 'Q', DNSFlagsOpCodeToString( opcode ), ( flags & kDNSHeaderFlag_AuthAnswer ) ? ' ' : '!', ( flags & kDNSHeaderFlag_Truncation ) ? ' ' : '!', ( flags & kDNSHeaderFlag_RecursionDesired ) ? ' ' : '!', ( flags & kDNSHeaderFlag_RecursionAvailable ) ? ' ' : '!', !inMDNS, ( flags & kDNSHeaderFlag_AuthenticData ) ? " AD" : "!AD", !inMDNS, ( flags & kDNSHeaderFlag_CheckingDisabled ) ? " CD" : "!CD", DNSFlagsRCodeToString( rcode ) ); _Append( "Question count: %u\n", questionCount ); _Append( "Answer count: %u\n", answerCount ); _Append( "Authority count: %u\n", authorityCount ); _Append( "Additional count: %u\n", additionalCount ); ptr = (const uint8_t *) &hdr[ 1 ]; for( i = 0; i < questionCount; ++i ) { unsigned int qtype, qclass; Boolean isQU; err = DNSMessageExtractDomainNameString( inMsgPtr, inMsgLen, ptr, nameStr, &ptr ); require_noerr( err, exit ); if( ( msgEnd - ptr ) < 4 ) { err = kUnderrunErr; goto exit; } qtype = DNSQuestionFixedFieldsGetType( (const DNSQuestionFixedFields *) ptr ); qclass = DNSQuestionFixedFieldsGetClass( (const DNSQuestionFixedFields *) ptr ); ptr += 4; isQU = ( inMDNS && ( qclass & kQClassUnicastResponseBit ) ) ? true : false; if( inMDNS ) qclass &= ~kQClassUnicastResponseBit; if( i == 0 ) _Append( "\nQUESTION SECTION\n" ); _Append( "%s %2s %?2s%?2u %-5s\n", nameStr, inMDNS ? ( isQU ? "QU" : "QM" ) : "", ( qclass == kDNSServiceClass_IN ), "IN", ( qclass != kDNSServiceClass_IN ), qclass, RecordTypeToString( qtype ) ); } totalRRCount = answerCount + authorityCount + additionalCount; for( i = 0; i < totalRRCount; ++i ) { uint16_t type; uint16_t class; uint32_t ttl; const uint8_t * rdataPtr; size_t rdataLen; char * rdataStr; Boolean cacheFlush; uint8_t name[ kDomainNameLengthMax ]; err = DNSMessageExtractRecord( inMsgPtr, inMsgLen, ptr, name, &type, &class, &ttl, &rdataPtr, &rdataLen, &ptr ); require_noerr( err, exit ); err = DomainNameToString( name, NULL, nameStr, NULL ); require_noerr( err, exit ); cacheFlush = ( inMDNS && ( class & kRRClassCacheFlushBit ) ) ? true : false; if( inMDNS ) class &= ~kRRClassCacheFlushBit; rdataStr = NULL; if( !inPrintRaw ) DNSRecordDataToString( rdataPtr, rdataLen, type, inMsgPtr, inMsgLen, &rdataStr ); if( !rdataStr ) { ASPrintF( &rdataStr, "%#H", rdataPtr, (int) rdataLen, INT_MAX ); require_action( rdataStr, exit, err = kNoMemoryErr ); } if( answerCount && ( i == 0 ) ) _Append( "\nANSWER SECTION\n" ); else if( authorityCount && ( i == answerCount ) ) _Append( "\nAUTHORITY SECTION\n" ); else if( additionalCount && ( i == ( answerCount + authorityCount ) ) ) _Append( "\nADDITIONAL SECTION\n" ); _Append( "%-42s %6u %2s %?2s%?2u %-5s %s\n", nameStr, ttl, cacheFlush ? "CF" : "", ( class == kDNSServiceClass_IN ), "IN", ( class != kDNSServiceClass_IN ), class, RecordTypeToString( type ), rdataStr ); free( rdataStr ); } _Append( "\n" ); err = DataBuffer_Append( &dataBuf, "", 1 ); require_noerr( err, exit ); err = DataBuffer_Detach( &dataBuf, (uint8_t **) outText, &len ); require_noerr( err, exit ); exit: DataBuffer_Free( &dataBuf ); return( err ); } //=========================================================================================================================== // WriteDNSQueryMessage //=========================================================================================================================== static OSStatus WriteDNSQueryMessage( uint8_t inMsg[ kDNSQueryMessageMaxLen ], uint16_t inMsgID, uint16_t inFlags, const char * inQName, uint16_t inQType, uint16_t inQClass, size_t * outMsgLen ) { OSStatus err; DNSHeader * const hdr = (DNSHeader *) inMsg; uint8_t * ptr; size_t msgLen; memset( hdr, 0, sizeof( *hdr ) ); DNSHeaderSetID( hdr, inMsgID ); DNSHeaderSetFlags( hdr, inFlags ); DNSHeaderSetQuestionCount( hdr, 1 ); ptr = (uint8_t *)( hdr + 1 ); err = DomainNameFromString( ptr, inQName, &ptr ); require_noerr_quiet( err, exit ); DNSQuestionFixedFieldsInit( (DNSQuestionFixedFields *) ptr, inQType, inQClass ); ptr += 4; msgLen = (size_t)( ptr - inMsg ); check( msgLen <= kDNSQueryMessageMaxLen ); if( outMsgLen ) *outMsgLen = msgLen; exit: return( err ); } //=========================================================================================================================== // DispatchSignalSourceCreate //=========================================================================================================================== static OSStatus DispatchSignalSourceCreate( int inSignal, DispatchHandler inEventHandler, void * inContext, dispatch_source_t * outSource ) { OSStatus err; dispatch_source_t source; source = dispatch_source_create( DISPATCH_SOURCE_TYPE_SIGNAL, (uintptr_t) inSignal, 0, dispatch_get_main_queue() ); require_action( source, exit, err = kUnknownErr ); dispatch_set_context( source, inContext ); dispatch_source_set_event_handler_f( source, inEventHandler ); *outSource = source; err = kNoErr; exit: return( err ); } //=========================================================================================================================== // DispatchSocketSourceCreate //=========================================================================================================================== static OSStatus DispatchSocketSourceCreate( SocketRef inSock, dispatch_source_type_t inType, dispatch_queue_t inQueue, DispatchHandler inEventHandler, DispatchHandler inCancelHandler, void * inContext, dispatch_source_t * outSource ) { OSStatus err; dispatch_source_t source; source = dispatch_source_create( inType, (uintptr_t) inSock, 0, inQueue ? inQueue : dispatch_get_main_queue() ); require_action( source, exit, err = kUnknownErr ); dispatch_set_context( source, inContext ); dispatch_source_set_event_handler_f( source, inEventHandler ); dispatch_source_set_cancel_handler_f( source, inCancelHandler ); *outSource = source; err = kNoErr; exit: return( err ); } //=========================================================================================================================== // DispatchTimerCreate //=========================================================================================================================== static OSStatus DispatchTimerCreate( dispatch_time_t inStart, uint64_t inIntervalNs, uint64_t inLeewayNs, dispatch_queue_t inQueue, DispatchHandler inEventHandler, DispatchHandler inCancelHandler, void * inContext, dispatch_source_t * outTimer ) { OSStatus err; dispatch_source_t timer; timer = dispatch_source_create( DISPATCH_SOURCE_TYPE_TIMER, 0, 0, inQueue ? inQueue : dispatch_get_main_queue() ); require_action( timer, exit, err = kUnknownErr ); dispatch_source_set_timer( timer, inStart, inIntervalNs, inLeewayNs ); dispatch_set_context( timer, inContext ); dispatch_source_set_event_handler_f( timer, inEventHandler ); dispatch_source_set_cancel_handler_f( timer, inCancelHandler ); *outTimer = timer; err = kNoErr; exit: return( err ); } //=========================================================================================================================== // DispatchProcessMonitorCreate //=========================================================================================================================== static OSStatus DispatchProcessMonitorCreate( pid_t inPID, unsigned long inFlags, dispatch_queue_t inQueue, DispatchHandler inEventHandler, DispatchHandler inCancelHandler, void * inContext, dispatch_source_t * outMonitor ) { OSStatus err; dispatch_source_t monitor; 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 ); } //=========================================================================================================================== // ServiceTypeDescription //=========================================================================================================================== typedef struct { const char * name; // Name of the service type in two-label "_service._proto" format. const char * description; // Description of the service type. } ServiceType; // A Non-comprehensive table of DNS-SD service types static const ServiceType kServiceTypes[] = { { "_acp-sync._tcp", "AirPort Base Station Sync" }, { "_adisk._tcp", "Automatic Disk Discovery" }, { "_afpovertcp._tcp", "Apple File Sharing" }, { "_airdrop._tcp", "AirDrop" }, { "_airplay._tcp", "AirPlay" }, { "_airport._tcp", "AirPort Base Station" }, { "_daap._tcp", "Digital Audio Access Protocol (iTunes)" }, { "_eppc._tcp", "Remote AppleEvents" }, { "_ftp._tcp", "File Transfer Protocol" }, { "_home-sharing._tcp", "Home Sharing" }, { "_homekit._tcp", "HomeKit" }, { "_http._tcp", "World Wide Web HTML-over-HTTP" }, { "_https._tcp", "HTTP over SSL/TLS" }, { "_ipp._tcp", "Internet Printing Protocol" }, { "_ldap._tcp", "Lightweight Directory Access Protocol" }, { "_mediaremotetv._tcp", "Media Remote" }, { "_net-assistant._tcp", "Apple Remote Desktop" }, { "_od-master._tcp", "OpenDirectory Master" }, { "_nfs._tcp", "Network File System" }, { "_presence._tcp", "Peer-to-peer messaging / Link-Local Messaging" }, { "_pdl-datastream._tcp", "Printer Page Description Language Data Stream" }, { "_raop._tcp", "Remote Audio Output Protocol" }, { "_rfb._tcp", "Remote Frame Buffer" }, { "_scanner._tcp", "Bonjour Scanning" }, { "_smb._tcp", "Server Message Block over TCP/IP" }, { "_sftp-ssh._tcp", "Secure File Transfer Protocol over SSH" }, { "_sleep-proxy._udp", "Sleep Proxy Server" }, { "_ssh._tcp", "SSH Remote Login Protocol" }, { "_teleport._tcp", "teleport" }, { "_tftp._tcp", "Trivial File Transfer Protocol" }, { "_workstation._tcp", "Workgroup Manager" }, { "_webdav._tcp", "World Wide Web Distributed Authoring and Versioning (WebDAV)" }, { "_webdavs._tcp", "WebDAV over SSL/TLS" } }; static const char * ServiceTypeDescription( const char *inName ) { const ServiceType * serviceType; const ServiceType * const end = kServiceTypes + countof( kServiceTypes ); for( serviceType = kServiceTypes; serviceType < end; ++serviceType ) { if( strcasecmp( inName, serviceType->name ) == 0 ) return( serviceType->description ); } return( NULL ); } //=========================================================================================================================== // SocketContextCreate //=========================================================================================================================== static OSStatus SocketContextCreate( SocketRef inSock, void * inUserContext, SocketContext **outContext ) { 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; *outContext = context; err = kNoErr; exit: return( err ); } //=========================================================================================================================== // SocketContextRetain //=========================================================================================================================== static SocketContext * SocketContextRetain( SocketContext *inContext ) { ++inContext->refCount; return( inContext ); } //=========================================================================================================================== // SocketContextRelease //=========================================================================================================================== static void SocketContextRelease( SocketContext *inContext ) { if( --inContext->refCount == 0 ) { ForgetSocket( &inContext->sock ); free( inContext ); } } //=========================================================================================================================== // SocketContextCancelHandler //=========================================================================================================================== static void SocketContextCancelHandler( void *inContext ) { SocketContextRelease( (SocketContext *) inContext ); } //=========================================================================================================================== // StringToInt32 //=========================================================================================================================== static OSStatus StringToInt32( const char *inString, int32_t *outValue ) { OSStatus err; long value; char * endPtr; 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 ); } //=========================================================================================================================== // StringToLongLong //=========================================================================================================================== static OSStatus StringToLongLong( const char *inString, long long *outValue ) { OSStatus err; long long value; char * endPtr; set_errno_compat( 0 ); value = strtol( inString, &endPtr, 0 ); err = errno_compat(); if( ( ( value == LLONG_MIN ) || ( value == LLONG_MAX ) ) && ( err == ERANGE ) ) goto exit; require_action_quiet( ( *endPtr == '\0' ) && ( endPtr != inString ), exit, err = kParamErr ); *outValue = value; err = kNoErr; exit: return( err ); } //=========================================================================================================================== // 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 //=========================================================================================================================== // GetCurrentMicroTime //=========================================================================================================================== static MicroTime64 GetCurrentMicroTime( void ) { struct timeval now; TIMEVAL_ZERO( now ); gettimeofday( &now, NULL ); return( (MicroTime64) TIMEVAL_USEC64( now ) ); } //=========================================================================================================================== // SocketWriteAll // // Note: This was copied from CoreUtils because the SocketWriteAll function is currently not exported in the framework. //=========================================================================================================================== OSStatus SocketWriteAll( SocketRef inSock, const void *inData, size_t inSize, int32_t inTimeoutSecs ) { OSStatus err; const uint8_t * src; const uint8_t * end; fd_set writeSet; struct timeval timeout; ssize_t n; FD_ZERO( &writeSet ); src = (const uint8_t *) inData; end = src + inSize; while( src < end ) { FD_SET( inSock, &writeSet ); timeout.tv_sec = inTimeoutSecs; timeout.tv_usec = 0; n = select( (int)( inSock + 1 ), NULL, &writeSet, NULL, &timeout ); if( n == 0 ) { err = kTimeoutErr; goto exit; } err = map_socket_value_errno( inSock, n > 0, n ); require_noerr( err, exit ); n = send( inSock, (char *) src, (size_t)( end - src ), 0 ); err = map_socket_value_errno( inSock, n >= 0, n ); if( err == EINTR ) continue; require_noerr( err, exit ); src += n; } err = kNoErr; exit: return( err ); } //=========================================================================================================================== // ParseIPv4Address // // Warning: "inBuffer" may be modified even in error cases. // // Note: This was copied from CoreUtils because the StringToIPv4Address function is currently not exported in the framework. //=========================================================================================================================== static OSStatus ParseIPv4Address( const char *inStr, uint8_t inBuffer[ 4 ], const char **outStr ) { OSStatus err; uint8_t * dst; int segments; int sawDigit; int c; int v; check( inBuffer ); check( outStr ); dst = inBuffer; *dst = 0; sawDigit = 0; segments = 0; for( ; ( c = *inStr ) != '\0'; ++inStr ) { if( isdigit_safe( c ) ) { v = ( *dst * 10 ) + ( c - '0' ); require_action_quiet( v <= 255, exit, err = kRangeErr ); *dst = (uint8_t) v; if( !sawDigit ) { ++segments; require_action_quiet( segments <= 4, exit, err = kOverrunErr ); sawDigit = 1; } } else if( ( c == '.' ) && sawDigit ) { require_action_quiet( segments < 4, exit, err = kMalformedErr ); *++dst = 0; sawDigit = 0; } else { break; } } require_action_quiet( segments == 4, exit, err = kUnderrunErr ); *outStr = inStr; err = kNoErr; exit: return( err ); } //=========================================================================================================================== // StringToIPv4Address // // Note: This was copied from CoreUtils because the StringToIPv4Address function is currently not exported in the framework. //=========================================================================================================================== OSStatus StringToIPv4Address( const char * inStr, StringToIPAddressFlags inFlags, uint32_t * outIP, int * outPort, uint32_t * outSubnet, uint32_t * outRouter, const char ** outStr ) { OSStatus err; uint8_t buf[ 4 ]; int c; uint32_t ip; int hasPort; int port; int hasPrefix; int prefix; uint32_t subnetMask; uint32_t router; require_action( inStr, exit, err = kParamErr ); // Parse the address-only part of the address (e.g. "1.2.3.4"). err = ParseIPv4Address( inStr, buf, &inStr ); require_noerr_quiet( err, exit ); ip = (uint32_t)( ( buf[ 0 ] << 24 ) | ( buf[ 1 ] << 16 ) | ( buf[ 2 ] << 8 ) | buf[ 3 ] ); c = *inStr; // Parse the port (if any). hasPort = 0; port = 0; if( c == ':' ) { require_action_quiet( !( inFlags & kStringToIPAddressFlagsNoPort ), exit, err = kUnexpectedErr ); while( ( ( c = *( ++inStr ) ) != '\0' ) && ( ( c >= '0' ) && ( c <= '9' ) ) ) port = ( port * 10 ) + ( c - '0' ); require_action_quiet( port <= 65535, exit, err = kRangeErr ); hasPort = 1; } // Parse the prefix length (if any). hasPrefix = 0; prefix = 0; subnetMask = 0; router = 0; if( c == '/' ) { require_action_quiet( !( inFlags & kStringToIPAddressFlagsNoPrefix ), exit, err = kUnexpectedErr ); while( ( ( c = *( ++inStr ) ) != '\0' ) && ( ( c >= '0' ) && ( c <= '9' ) ) ) prefix = ( prefix * 10 ) + ( c - '0' ); require_action_quiet( ( prefix >= 0 ) && ( prefix <= 32 ), exit, err = kRangeErr ); hasPrefix = 1; subnetMask = ( prefix > 0 ) ? ( UINT32_C( 0xFFFFFFFF ) << ( 32 - prefix ) ) : 0; router = ( ip & subnetMask ) | 1; } // Return the results. Only fill in port/prefix/router results if the info was found to allow for defaults. if( outIP ) *outIP = ip; if( outPort && hasPort ) *outPort = port; if( outSubnet && hasPrefix ) *outSubnet = subnetMask; if( outRouter && hasPrefix ) *outRouter = router; if( outStr ) *outStr = inStr; err = kNoErr; exit: return( err ); } //=========================================================================================================================== // ParseIPv6Address // // Note: Parsed according to the rules specified in RFC 3513. // Warning: "inBuffer" may be modified even in error cases. // // Note: This was copied from CoreUtils because the StringToIPv6Address function is currently not exported in the framework. //=========================================================================================================================== static OSStatus ParseIPv6Address( const char *inStr, int inAllowV4Mapped, uint8_t inBuffer[ 16 ], const char **outStr ) { // Table to map uppercase hex characters - '0' to their numeric values. // 0 1 2 3 4 5 6 7 8 9 : ; < = > ? @ A B C D E F static const uint8_t kASCIItoHexTable[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, 0, 10, 11, 12, 13, 14, 15 }; OSStatus err; const char * ptr; uint8_t * dst; uint8_t * lim; uint8_t * colonPtr; int c; int sawDigit; unsigned int v; int i; int n; // Pre-zero the address to simplify handling of compressed addresses (e.g. "::1"). for( i = 0; i < 16; ++i ) inBuffer[ i ] = 0; // Special case leading :: (e.g. "::1") to simplify processing later. if( *inStr == ':' ) { ++inStr; require_action_quiet( *inStr == ':', exit, err = kMalformedErr ); } // Parse the address. ptr = inStr; dst = inBuffer; lim = dst + 16; colonPtr = NULL; sawDigit = 0; v = 0; while( ( ( c = *inStr++ ) != '\0' ) && ( c != '%' ) && ( c != '/' ) && ( c != ']' ) ) { if( ( c >= 'a' ) && ( c <= 'f' ) ) c -= ( 'a' - 'A' ); if( ( ( c >= '0' ) && ( c <= '9' ) ) || ( ( c >= 'A' ) && ( c <= 'F' ) ) ) { c -= '0'; check( c < (int) countof( kASCIItoHexTable ) ); v = ( v << 4 ) | kASCIItoHexTable[ c ]; require_action_quiet( v <= 0xFFFF, exit, err = kRangeErr ); sawDigit = 1; continue; } if( c == ':' ) { ptr = inStr; if( !sawDigit ) { require_action_quiet( !colonPtr, exit, err = kMalformedErr ); colonPtr = dst; continue; } require_action_quiet( *inStr != '\0', exit, err = kUnderrunErr ); require_action_quiet( ( dst + 2 ) <= lim, exit, err = kOverrunErr ); *dst++ = (uint8_t)( ( v >> 8 ) & 0xFF ); *dst++ = (uint8_t)( v & 0xFF ); sawDigit = 0; v = 0; continue; } // Handle IPv4-mapped/compatible addresses (e.g. ::FFFF:1.2.3.4). if( inAllowV4Mapped && ( c == '.' ) && ( ( dst + 4 ) <= lim ) ) { err = ParseIPv4Address( ptr, dst, &inStr ); require_noerr_quiet( err, exit ); dst += 4; sawDigit = 0; ++inStr; // Increment because the code below expects the end to be at "inStr - 1". } break; } if( sawDigit ) { require_action_quiet( ( dst + 2 ) <= lim, exit, err = kOverrunErr ); *dst++ = (uint8_t)( ( v >> 8 ) & 0xFF ); *dst++ = (uint8_t)( v & 0xFF ); } check( dst <= lim ); if( colonPtr ) { require_action_quiet( dst < lim, exit, err = kOverrunErr ); n = (int)( dst - colonPtr ); for( i = 1; i <= n; ++i ) { lim[ -i ] = colonPtr[ n - i ]; colonPtr[ n - i ] = 0; } dst = lim; } require_action_quiet( dst == lim, exit, err = kUnderrunErr ); *outStr = inStr - 1; err = kNoErr; exit: return( err ); } //=========================================================================================================================== // ParseIPv6Scope // // Note: This was copied from CoreUtils because the StringToIPv6Address function is currently not exported in the framework. //=========================================================================================================================== static OSStatus ParseIPv6Scope( const char *inStr, uint32_t *outScope, const char **outStr ) { #if( TARGET_OS_POSIX ) OSStatus err; char scopeStr[ 64 ]; char * dst; char * lim; int c; uint32_t scope; const char * ptr; // Copy into a local NULL-terminated string since that is what if_nametoindex expects. dst = scopeStr; lim = dst + ( countof( scopeStr ) - 1 ); while( ( ( c = *inStr ) != '\0' ) && ( c != ':' ) && ( c != '/' ) && ( c != ']' ) && ( dst < lim ) ) { *dst++ = *inStr++; } *dst = '\0'; check( dst <= lim ); // First try to map as a name and if that fails, treat it as a numeric scope. scope = if_nametoindex( scopeStr ); if( scope == 0 ) { for( ptr = scopeStr; ( ( c = *ptr ) >= '0' ) && ( c <= '9' ); ++ptr ) { scope = ( scope * 10 ) + ( ( (uint8_t) c ) - '0' ); } require_action_quiet( c == '\0', exit, err = kMalformedErr ); require_action_quiet( ( ptr != scopeStr ) && ( ( (int)( ptr - scopeStr ) ) <= 10 ), exit, err = kMalformedErr ); } *outScope = scope; *outStr = inStr; err = kNoErr; exit: return( err ); #else OSStatus err; uint32_t scope; const char * start; int c; scope = 0; for( start = inStr; ( ( c = *inStr ) >= '0' ) && ( c <= '9' ); ++inStr ) { scope = ( scope * 10 ) + ( c - '0' ); } require_action_quiet( ( inStr != start ) && ( ( (int)( inStr - start ) ) <= 10 ), exit, err = kMalformedErr ); *outScope = scope; *outStr = inStr; err = kNoErr; exit: return( err ); #endif } //=========================================================================================================================== // StringToIPv6Address // // Note: This was copied from CoreUtils because the StringToIPv6Address function is currently not exported in the framework. //=========================================================================================================================== OSStatus StringToIPv6Address( const char * inStr, StringToIPAddressFlags inFlags, uint8_t outIPv6[ 16 ], uint32_t * outScope, int * outPort, int * outPrefix, const char ** outStr ) { OSStatus err; uint8_t ipv6[ 16 ]; int c; int hasScope; uint32_t scope; int hasPort; int port; int hasPrefix; int prefix; int hasBracket; int i; require_action( inStr, exit, err = kParamErr ); if( *inStr == '[' ) ++inStr; // Skip a leading bracket for []-wrapped addresses (e.g. "[::1]:80"). // Parse the address-only part of the address (e.g. "1::1"). err = ParseIPv6Address( inStr, !( inFlags & kStringToIPAddressFlagsNoIPv4Mapped ), ipv6, &inStr ); require_noerr_quiet( err, exit ); c = *inStr; // Parse the scope, port, or prefix length. hasScope = 0; scope = 0; hasPort = 0; port = 0; hasPrefix = 0; prefix = 0; hasBracket = 0; for( ;; ) { if( c == '%' ) // Scope (e.g. "%en0" or "%5") { require_action_quiet( !hasScope, exit, err = kMalformedErr ); require_action_quiet( !( inFlags & kStringToIPAddressFlagsNoScope ), exit, err = kUnexpectedErr ); ++inStr; err = ParseIPv6Scope( inStr, &scope, &inStr ); require_noerr_quiet( err, exit ); hasScope = 1; c = *inStr; } else if( c == ':' ) // Port (e.g. ":80") { require_action_quiet( !hasPort, exit, err = kMalformedErr ); require_action_quiet( !( inFlags & kStringToIPAddressFlagsNoPort ), exit, err = kUnexpectedErr ); while( ( ( c = *( ++inStr ) ) != '\0' ) && ( ( c >= '0' ) && ( c <= '9' ) ) ) port = ( port * 10 ) + ( c - '0' ); require_action_quiet( port <= 65535, exit, err = kRangeErr ); hasPort = 1; } else if( c == '/' ) // Prefix Length (e.g. "/64") { require_action_quiet( !hasPrefix, exit, err = kMalformedErr ); require_action_quiet( !( inFlags & kStringToIPAddressFlagsNoPrefix ), exit, err = kUnexpectedErr ); while( ( ( c = *( ++inStr ) ) != '\0' ) && ( ( c >= '0' ) && ( c <= '9' ) ) ) prefix = ( prefix * 10 ) + ( c - '0' ); require_action_quiet( ( prefix >= 0 ) && ( prefix <= 128 ), exit, err = kRangeErr ); hasPrefix = 1; } else if( c == ']' ) { require_action_quiet( !hasBracket, exit, err = kMalformedErr ); hasBracket = 1; c = *( ++inStr ); } else { break; } } // Return the results. Only fill in scope/port/prefix results if the info was found to allow for defaults. if( outIPv6 ) for( i = 0; i < 16; ++i ) outIPv6[ i ] = ipv6[ i ]; if( outScope && hasScope ) *outScope = scope; if( outPort && hasPort ) *outPort = port; if( outPrefix && hasPrefix ) *outPrefix = prefix; if( outStr ) *outStr = inStr; err = kNoErr; exit: return( err ); } //=========================================================================================================================== // StringArray_Free // // Note: This was copied from CoreUtils because the StringArray_Free function is currently not exported in the framework. //=========================================================================================================================== void StringArray_Free( char **inArray, size_t inCount ) { size_t i; for( i = 0; i < inCount; ++i ) { free( inArray[ i ] ); } if( inCount > 0 ) free( inArray ); } //=========================================================================================================================== // ParseQuotedEscapedString // // Note: This was copied from CoreUtils because it's currently not exported in the framework. //=========================================================================================================================== Boolean ParseQuotedEscapedString( const char * inSrc, const char * inEnd, const char * inDelimiters, char * inBuf, size_t inMaxLen, size_t * outCopiedLen, size_t * outTotalLen, const char ** outSrc ) { const unsigned char * src; const unsigned char * end; unsigned char * dst; unsigned char * lim; unsigned char c; unsigned char c2; size_t totalLen; Boolean singleQuote; Boolean doubleQuote; if( inEnd == NULL ) inEnd = inSrc + strlen( inSrc ); src = (const unsigned char *) inSrc; end = (const unsigned char *) inEnd; dst = (unsigned char *) inBuf; lim = dst + inMaxLen; while( ( src < end ) && isspace_safe( *src ) ) ++src; // Skip leading spaces. if( src >= end ) return( false ); // Parse each argument from the string. // // See 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. //=========================================================================================================================== 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 ); } #if( !TARGET_OS_WINDOWS ) //=========================================================================================================================== // memicmp // // Note: This was copied from CoreUtils because it's currently not exported in the framework. //=========================================================================================================================== 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 ); } #endif