/*
Copyright (c) 2016-2017 Apple Inc. All rights reserved.
dnssdutil is a command-line utility for testing the DNS-SD API.
*/
#include <CoreUtils/CommonServices.h> // Include early.
#include <CoreUtils/AsyncConnection.h>
#include <CoreUtils/CommandLineUtils.h>
#include <CoreUtils/DataBufferUtils.h>
#include <CoreUtils/DebugServices.h>
#include <CoreUtils/MiscUtils.h>
#include <CoreUtils/NetUtils.h>
#include <CoreUtils/PrintFUtils.h>
#include <CoreUtils/RandomNumberUtils.h>
#include <CoreUtils/StringUtils.h>
#include <CoreUtils/TickUtils.h>
#include <dns_sd.h>
#include <dns_sd_private.h>
#if( TARGET_OS_DARWIN )
#include <dnsinfo.h>
#include <libproc.h>
#include <sys/proc_info.h>
#endif
#if( TARGET_OS_POSIX )
#include <sys/resource.h>
#endif
#if( DNSSDUTIL_INCLUDE_DNSCRYPT )
#include "tweetnacl.h" // TweetNaCl from <https://tweetnacl.cr.yp.to/software.html>.
#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:<PID>\n" \
"\n" \
"to specify the delegator by PID, or use\n" \
"\n" \
" --connection=uuid:<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> string:'\\x09color=red'\n" \
"Hexadecimal string hex:<hex string> hex:c0a80101 or hex:'C0 A8 01 01'\n" \
"TXT record keys and values txt:<comma-delimited keys and values> txt:'key1=x,key2=y\\,z,key3'\n" \
"File containing raw record data file:<file path> 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 : "<NULL> (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 : "<NULL>" );
FPrintF( stdout, "Type: %s\n", inContext->type );
FPrintF( stdout, "Domain: %s\n", inContext->domain ? inContext->domain : "<NULL> (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 <http://resources.mpi-inf.mpg.de/departments/rg1/teaching/unixffb-ss98/quoting-guide.html> 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 );
}