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