summaryrefslogtreecommitdiffstats
path: root/mDNSResponder/Clients/dnssdutil.c
diff options
context:
space:
mode:
authorSebastian Huber <sebastian.huber@embedded-brains.de>2020-06-18 13:07:18 +0200
committerSebastian Huber <sebastian.huber@embedded-brains.de>2020-06-23 18:17:22 +0200
commita6b1d332b1d22fb47edd711a82e1857e42110b62 (patch)
treec25f556baa10418f7225b8a5e8cee401352ea2a3 /mDNSResponder/Clients/dnssdutil.c
parentmDNSResponder: Update to v878.70.2 (diff)
downloadrtems-libbsd-a6b1d332b1d22fb47edd711a82e1857e42110b62.tar.bz2
mDNSResponder: Update to v878.200.35
The sources can be obtained via: https://opensource.apple.com/tarballs/mDNSResponder/mDNSResponder-878.200.35.tar.gz Update #4010.
Diffstat (limited to 'mDNSResponder/Clients/dnssdutil.c')
-rw-r--r--mDNSResponder/Clients/dnssdutil.c5565
1 files changed, 5134 insertions, 431 deletions
diff --git a/mDNSResponder/Clients/dnssdutil.c b/mDNSResponder/Clients/dnssdutil.c
index 1bde0daf..206e2b6c 100644
--- a/mDNSResponder/Clients/dnssdutil.c
+++ b/mDNSResponder/Clients/dnssdutil.c
@@ -6,10 +6,13 @@
#include <CoreUtils/CommonServices.h> // Include early.
#include <CoreUtils/AsyncConnection.h>
+#include <CoreUtils/CFUtils.h>
#include <CoreUtils/CommandLineUtils.h>
#include <CoreUtils/DataBufferUtils.h>
#include <CoreUtils/DebugServices.h>
#include <CoreUtils/HTTPUtils.h>
+#include <CoreUtils/JSONUtils.h>
+#include <CoreUtils/LogUtils.h>
#include <CoreUtils/MiscUtils.h>
#include <CoreUtils/NetUtils.h>
#include <CoreUtils/PrintFUtils.h>
@@ -20,10 +23,17 @@
#include <dns_sd.h>
#include <dns_sd_private.h>
+#include CF_RUNTIME_HEADER
+
#if( TARGET_OS_DARWIN )
+ #include <CFNetwork/CFHost.h>
+ #include <CoreFoundation/CoreFoundation.h>
+ #include <SystemConfiguration/SCPrivate.h>
#include <dnsinfo.h>
#include <libproc.h>
#include <netdb.h>
+ #include <pcap.h>
+ #include <spawn.h>
#include <sys/proc_info.h>
#endif
@@ -36,17 +46,19 @@
#endif
//===========================================================================================================================
-// Global Constants
+// Versioning
//===========================================================================================================================
-// Versioning
-
#define kDNSSDUtilNumVersion NumVersionBuild( 2, 0, 0, kVersionStageBeta, 0 )
#if( !MDNSRESPONDER_PROJECT && !defined( DNSSDUTIL_SOURCE_VERSION ) )
#define DNSSDUTIL_SOURCE_VERSION "0.0.0"
#endif
+//===========================================================================================================================
+// DNS-SD
+//===========================================================================================================================
+
// DNS-SD API flag descriptors
#define kDNSServiceFlagsDescriptors \
@@ -90,19 +102,76 @@
"\x05" "TCP\0" \
"\x00"
-// (m)DNS
+#define kBadDNSServiceRef ( (DNSServiceRef)(intptr_t) -1 )
+
+//===========================================================================================================================
+// DNS
+//===========================================================================================================================
+
+#define kDNSPort 53
+#define kDNSCompressionOffsetMax 0x3FFF
+#define kDNSMaxUDPMessageSize 512
+#define kDNSMaxTCPMessageSize UINT16_MAX
+
+#define kDomainLabelLengthMax 63
+#define kDomainNameLengthMax 256
+
+typedef struct
+{
+ uint8_t id[ 2 ];
+ uint8_t flags[ 2 ];
+ uint8_t questionCount[ 2 ];
+ uint8_t answerCount[ 2 ];
+ uint8_t authorityCount[ 2 ];
+ uint8_t additionalCount[ 2 ];
+
+} DNSHeader;
+
+#define kDNSHeaderLength 12
+check_compile_time( sizeof( DNSHeader ) == kDNSHeaderLength );
+
+#define DNSHeaderGetID( HDR ) ReadBig16( ( HDR )->id )
+#define DNSHeaderGetFlags( HDR ) ReadBig16( ( HDR )->flags )
+#define DNSHeaderGetQuestionCount( HDR ) ReadBig16( ( HDR )->questionCount )
+#define DNSHeaderGetAnswerCount( HDR ) ReadBig16( ( HDR )->answerCount )
+#define DNSHeaderGetAuthorityCount( HDR ) ReadBig16( ( HDR )->authorityCount )
+#define DNSHeaderGetAdditionalCount( HDR ) ReadBig16( ( HDR )->additionalCount )
+
+#define DNSHeaderSetID( HDR, X ) WriteBig16( ( HDR )->id, (X) )
+#define DNSHeaderSetFlags( HDR, X ) WriteBig16( ( HDR )->flags, (X) )
+#define DNSHeaderSetQuestionCount( HDR, X ) WriteBig16( ( HDR )->questionCount, (X) )
+#define DNSHeaderSetAnswerCount( HDR, X ) WriteBig16( ( HDR )->answerCount, (X) )
+#define DNSHeaderSetAuthorityCount( HDR, X ) WriteBig16( ( HDR )->authorityCount, (X) )
+#define DNSHeaderSetAdditionalCount( HDR, X ) WriteBig16( ( HDR )->additionalCount, (X) )
+
+// Single-bit DNS header fields
-#define kDNSHeaderFlag_Response ( 1 << 15 )
-#define kDNSHeaderFlag_AuthAnswer ( 1 << 10 )
-#define kDNSHeaderFlag_Truncation ( 1 << 9 )
-#define kDNSHeaderFlag_RecursionDesired ( 1 << 8 )
-#define kDNSHeaderFlag_RecursionAvailable ( 1 << 7 )
+#define kDNSHeaderFlag_Response ( 1 << 15 ) // QR (bit 15), Query (0)/Response (1)
+#define kDNSHeaderFlag_AuthAnswer ( 1 << 10 ) // AA (bit 10), Authoritative Answer
+#define kDNSHeaderFlag_Truncation ( 1 << 9 ) // TC (bit 9), TrunCation
+#define kDNSHeaderFlag_RecursionDesired ( 1 << 8 ) // RD (bit 8), Recursion Desired
+#define kDNSHeaderFlag_RecursionAvailable ( 1 << 7 ) // RA (bit 7), Recursion Available
+#define kDNSHeaderFlag_Z ( 1 << 6 ) // Z (bit 6), Reserved (must be zero)
+#define kDNSHeaderFlag_AuthenticData ( 1 << 5 ) // AD (bit 5), Authentic Data (RFC 2535, Section 6)
+#define kDNSHeaderFlag_CheckingDisabled ( 1 << 4 ) // CD (bit 4), Checking Disabled (RFC 2535, Section 6)
-#define kDNSOpCode_Query 0
-#define kDNSOpCode_InverseQuery 1
-#define kDNSOpCode_Status 2
-#define kDNSOpCode_Notify 4
-#define kDNSOpCode_Update 5
+// OPCODE (bits 14-11), Operation Code
+
+#define DNSFlagsGetOpCode( FLAGS ) ( ( (FLAGS) >> 11 ) & 0x0FU )
+#define DNSFlagsSetOpCode( FLAGS, OPCODE ) \
+ do{ (FLAGS) = ( (FLAGS) & ~0x7800U ) | ( ( (OPCODE) & 0x0FU ) << 11 ); } while( 0 )
+
+#define kDNSOpCode_Query 0 // QUERY (standard query)
+#define kDNSOpCode_InverseQuery 1 // IQUERY (inverse query)
+#define kDNSOpCode_Status 2 // STATUS
+#define kDNSOpCode_Notify 4 // NOTIFY
+#define kDNSOpCode_Update 5 // UPDATE
+
+// RCODE (bits 3-0), Response Code
+
+#define DNSFlagsGetRCode( FLAGS ) ( (FLAGS) & 0x0FU )
+#define DNSFlagsSetRCode( FLAGS, RCODE ) \
+ do{ (FLAGS) = ( (FLAGS) & ~0x000FU ) | ( (RCODE) & 0x0FU ); } while( 0 )
#define kDNSRCode_NoError 0
#define kDNSRCode_FormatError 1
@@ -111,11 +180,66 @@
#define kDNSRCode_NotImplemented 4
#define kDNSRCode_Refused 5
-#define kQClassUnicastResponseBit ( 1U << 15 )
-#define kRRClassCacheFlushBit ( 1U << 15 )
+typedef struct
+{
+ uint8_t type[ 2 ];
+ uint8_t class[ 2 ];
+
+} DNSQuestionFixedFields;
+
+check_compile_time( sizeof( DNSQuestionFixedFields ) == 4 );
-#define kDomainLabelLengthMax 63
-#define kDomainNameLengthMax 256
+#define DNSQuestionFixedFieldsInit( FIELDS, QTYPE, QCLASS ) \
+ do { WriteBig16( (FIELDS)->type, QTYPE ); WriteBig16( (FIELDS)->class, QCLASS ); } while( 0 )
+
+#define DNSQuestionFixedFieldsGetType( FIELDS ) ReadBig16( (FIELDS)->type )
+#define DNSQuestionFixedFieldsGetClass( FIELDS ) ReadBig16( (FIELDS)->class )
+
+typedef struct
+{
+ uint8_t type[ 2 ];
+ uint8_t class[ 2 ];
+ uint8_t ttl[ 4 ];
+ uint8_t rdlength[ 2 ];
+
+} DNSRecordFixedFields;
+
+check_compile_time( sizeof( DNSRecordFixedFields ) == 10 );
+
+#define DNSRecordFixedFieldsInit( FIELDS, TYPE, CLASS, TTL, RDLENGTH ) \
+ do \
+ { \
+ WriteBig16( (FIELDS)->type, TYPE ); \
+ WriteBig16( (FIELDS)->class, CLASS ); \
+ WriteBig32( (FIELDS)->ttl, TTL ); \
+ WriteBig16( (FIELDS)->rdlength, RDLENGTH ); \
+ \
+ } while( 0 )
+
+//===========================================================================================================================
+// mDNS
+//===========================================================================================================================
+
+#define kMDNSPort 5353
+
+#define kDefaultMDNSMessageID 0
+#define kDefaultMDNSQueryFlags 0
+
+#define kQClassUnicastResponseBit ( 1U << 15 )
+#define kRRClassCacheFlushBit ( 1U << 15 )
+
+//===========================================================================================================================
+// Test DNS Server
+//===========================================================================================================================
+
+// IPv4 address block 203.0.113.0/24 (TEST-NET-3) is reserved for documentation. See <https://tools.ietf.org/html/rfc5737>.
+
+#define kTestDNSServerBaseAddrV4 UINT32_C( 0xCB007100 ) // 203.0.113.0
+
+// IPv6 address block 2001:db8::/32 is reserved for documentation. See <https://tools.ietf.org/html/rfc3849>.
+
+#define kTestDNSServerBaseAddrV6 \
+ ( (const uint8_t *) "\x20\x01\x0D\xB8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" )
//===========================================================================================================================
// Gerneral Command Options
@@ -153,6 +277,11 @@
#define StringOption( SHORT_CHAR, LONG_NAME, VAL_PTR, ARG_HELP, SHORT_HELP, IS_REQUIRED ) \
StringOptionEx( SHORT_CHAR, LONG_NAME, VAL_PTR, ARG_HELP, SHORT_HELP, IS_REQUIRED, NULL )
+#define CFStringOption( SHORT_CHAR, LONG_NAME, VAL_PTR, ARG_HELP, SHORT_HELP, IS_REQUIRED ) \
+ CLI_OPTION_CFSTRING_EX( SHORT_CHAR, LONG_NAME, VAL_PTR, ARG_HELP, \
+ (IS_REQUIRED) ? SHORT_HELP kRequiredOptionSuffix : SHORT_HELP, \
+ (IS_REQUIRED) ? kCLIOptionFlags_Required : kCLIOptionFlags_None, NULL )
+
// DNS-SD API flag options
static int gDNSSDFlags = 0;
@@ -634,7 +763,6 @@ static CLIOption kPortMappingOpts[] =
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_MaxConnectTimeSecs = 0;
@@ -654,6 +782,36 @@ static CLIOption kBrowseAllOpts[] =
};
//===========================================================================================================================
+// GetNameInfo Command Options
+//===========================================================================================================================
+
+static void GetNameInfoCmd( void );
+
+static char * gGetNameInfo_IPAddress = NULL;
+static int gGetNameInfoFlag_DGram = false;
+static int gGetNameInfoFlag_NameReqd = false;
+static int gGetNameInfoFlag_NoFQDN = false;
+static int gGetNameInfoFlag_NumericHost = false;
+static int gGetNameInfoFlag_NumericScope = false;
+static int gGetNameInfoFlag_NumericServ = false;
+
+static CLIOption kGetNameInfoOpts[] =
+{
+ StringOption( 'a', "address", &gGetNameInfo_IPAddress, "IP address", "IPv4 or IPv6 address to use in sockaddr structure.", true ),
+
+ CLI_OPTION_GROUP( "Flags" ),
+ BooleanOption( 0 , "flag-dgram", &gGetNameInfoFlag_DGram, "Use NI_DGRAM flag." ),
+ BooleanOption( 0 , "flag-namereqd", &gGetNameInfoFlag_NameReqd, "Use NI_NAMEREQD flag." ),
+ BooleanOption( 0 , "flag-nofqdn", &gGetNameInfoFlag_NoFQDN, "Use NI_NOFQDN flag." ),
+ BooleanOption( 0 , "flag-numerichost", &gGetNameInfoFlag_NumericHost, "Use NI_NUMERICHOST flag." ),
+ BooleanOption( 0 , "flag-numericscope", &gGetNameInfoFlag_NumericScope, "Use NI_NUMERICSCOPE flag." ),
+ BooleanOption( 0 , "flag-numericserv", &gGetNameInfoFlag_NumericServ, "Use NI_NUMERICSERV flag." ),
+
+ CLI_SECTION( "Notes", "See getnameinfo(3) man page for more details.\n" ),
+ CLI_OPTION_END()
+};
+
+//===========================================================================================================================
// GetAddrInfoStress Command Options
//===========================================================================================================================
@@ -782,6 +940,298 @@ static CLIOption kPIDToUUIDOpts[] =
};
//===========================================================================================================================
+// DNSServer Command Options
+//===========================================================================================================================
+
+#define kDNSServerInfoText_Intro \
+ "The DNS server answers certain queries in the d.test. domain. Responses are dynamically generated based on the\n" \
+ "presence of special labels in the query's QNAME. There are currently seven types of special labels that can be\n" \
+ "used to generate specific responses: Alias labels, Alias-TTL labels, Count labels, Tag labels, TTL labels, the\n" \
+ "IPv4 label, and the IPv6 label.\n"
+
+#define kDNSServerInfoText_NameExistence \
+ "A name is considered to exist if and only if it ends in d.test., and the other labels, if\n" \
+ "any, consist of\n" \
+ "\n" \
+ " 1. at most one Alias or Alias-TTL label as the first label;\n" \
+ " 2. at most one Count label;\n" \
+ " 3. zero or more Tag labels;\n" \
+ " 4. at most one TTL label; and\n" \
+ " 5. at most one IPv4 or IPv6 label.\n"
+
+#define kDNSServerInfoText_ResourceRecords \
+ "Currently, the server only provides CNAME, A, and AAAA records.\n" \
+ "\n" \
+ "Names that exist and begin with an Alias or Alias-TTL label are aliases of canonical names, i.e., they're the\n" \
+ "names of CNAME records. See \"Alias Labels\" and \"Alias-TTL Labels\" for details.\n" \
+ "\n" \
+ "Names that exist and have an IPv4 label have at least one A record, but no AAAA records. Names that exist and\n" \
+ "have an IPv6 label, have at least one AAAA record, but no A records. All other names that exist have at least\n" \
+ "one A record and at least one AAAA record. See \"Count Labels\" for how the number of address records for a\n" \
+ "given name is determined.\n" \
+ "\n" \
+ "A records contain IPv4 addresses in the 203.0.113.0/24 block, while AAAA records contain IPv6 addresses in the\n" \
+ "2001:db8::/32 block. Both of these address blocks are reserved for documentation.\n" \
+ "See <https://tools.ietf.org/html/rfc5737> and <https://tools.ietf.org/html/rfc3849>.\n" \
+ "\n" \
+ "Unless otherwise specified, all resource records will use a default TTL. The default TTL can be set with the\n" \
+ "--defaultTTL option. See \"Alias-TTL Labels\" and \"TTL Labels\" for details on how to query for records with\n" \
+ "specific TTL values.\n"
+
+#define kDNSServerInfoText_AliasLabel \
+ "Alias labels are of the form \"alias\" or \"alias-N\", where N is an integer in [2 .. 2^31 - 1].\n" \
+ "\n" \
+ "If QNAME exist and its first label is Alias label \"alias-N\", then the response will contain exactly N CNAME\n" \
+ "records:\n" \
+ "\n" \
+ " 1. For each i in [3 .. N], the response will contain a CNAME record whose name is identical to QNAME,\n" \
+ " except that the first label is \"alias-i\" instead, and whose RDATA is the name of the other CNAME\n" \
+ " record whose name has \"alias-(i - 1)\" as its first label.\n" \
+ "\n" \
+ " 2. The response will contain a CNAME record whose name is identical to QNAME, except that the first label\n" \
+ " is \"alias-2\" instead, and whose RDATA is the name identical to QNAME, except that the first label is\n" \
+ " \"alias\" instead.\n" \
+ "\n" \
+ " 3. The response will contain a CNAME record whose name is identical to QNAME, except that the first label\n" \
+ " is \"alias\" instead, and whose RDATA is the name identical to QNAME stripped of its first label.\n" \
+ "\n" \
+ "If QNAME exist and its first label is Alias label \"alias\", then the response will contain a single CNAME\n" \
+ "record. The CNAME record's name will be equal to QNAME and its RDATA will be the name identical to QNAME\n" \
+ "stripped of its first label.\n" \
+ "\n" \
+ "Example. A response to a query with a QNAME of alias-3.count-5.d.test will contain the following CNAME\n" \
+ "records:\n" \
+ "\n" \
+ " alias-4.count-5.d.test. 60 IN CNAME alias-3.count-5.d.test.\n" \
+ " alias-3.count-5.d.test. 60 IN CNAME alias-2.count-5.d.test.\n" \
+ " alias-2.count-5.d.test. 60 IN CNAME alias.count-5.d.test.\n" \
+ " alias.count-5.d.test. 60 IN CNAME count-5.d.test.\n"
+
+#define kDNSServerInfoText_AliasTTLLabel \
+ "Alias-TTL labels are of the form \"alias-ttl-T_1[-T_2[...-T_N]]\", where each T_i is an integer in\n" \
+ "[0 .. 2^31 - 1] and N is a positive integer bounded by the size of the maximum legal label length (63 octets).\n" \
+ "\n" \
+ "If QNAME exists and its first label is Alias-TTL label \"alias-ttl-T_1...-T_N\", then the response will contain\n" \
+ "exactly N CNAME records:\n" \
+ "\n" \
+ " 1. For each i in [1 .. N - 1], the response will contain a CNAME record whose name is identical to QNAME,\n" \
+ " except that the first label is \"alias-ttl-T_i...-T_N\" instead, whose TTL value is T_i, and whose RDATA\n" \
+ " is the name of the other CNAME record whose name has \"alias-ttl-T_(i+1)...-T_N\" as its first label.\n" \
+ "\n" \
+ " 2. The response will contain a CNAME record whose name is identical to QNAME, except that the first label\n" \
+ " is \"alias-ttl-T_N\", whose TTL is T_N, and whose RDATA is identical to QNAME stripped of its first\n" \
+ " label.\n" \
+ "\n" \
+ "Example. A response to a query with a QNAME of alias-ttl-20-40-80.count-5.d.test will contain the following\n" \
+ "CNAME records:\n" \
+ "\n" \
+ " alias-ttl-20-40-80.count-5.d.test. 20 IN CNAME alias-ttl-40-80.count-5.d.test.\n" \
+ " alias-ttl-40-80.count-5.d.test. 40 IN CNAME alias-ttl-80.count-5.d.test.\n" \
+ " alias-ttl-80.count-5.d.test. 80 IN CNAME count-5.d.test.\n"
+
+#define kDNSServerInfoText_CountLabel \
+ "Count labels are of the form \"count-N_1\" or \"count-N_1-N_2\", where N_1 is an integer in [1 .. 255] and N_2\n" \
+ "is an integer in [N_1 .. 255].\n" \
+ "\n" \
+ "If QNAME exists, contains Count label \"count-N\", and has the type of address records specified by QTYPE, then\n" \
+ "the response will contain exactly N address records:\n" \
+ "\n" \
+ " 1. For i in [1 .. N], the response will contain an address record of type QTYPE whose name is equal to\n" \
+ " QNAME and whose RDATA is an address equal to a constant base address + i.\n" \
+ "\n" \
+ " 2. The address records will be ordered by the address contained in RDATA in ascending order.\n" \
+ "\n" \
+ "Example. A response to an A record query with a QNAME of alias.count-3.d.test will contain the following A\n" \
+ "records:\n" \
+ "\n" \
+ " count-3.d.test. 60 IN A 203.0.113.1\n" \
+ " count-3.d.test. 60 IN A 203.0.113.2\n" \
+ " count-3.d.test. 60 IN A 203.0.113.3\n" \
+ "\n" \
+ "If QNAME exists, contains Count label \"count-N_1-N_2\", and has the type of address records specified by\n" \
+ "QTYPE, then the response will contain exactly N_1 address records:\n" \
+ "\n" \
+ " 1. Each of the address records will be of type QTYPE, have name equal to QNAME, and have as its RDATA a\n" \
+ " unique address equal to a constant base address + i, where i is a randomly chosen integer in [1 .. N_2].\n" \
+ "\n" \
+ " 2. The order of the address records will be random.\n" \
+ "\n" \
+ "Example. A response to a AAAA record query with a QNAME of count-3-100.ttl-20.d.test could contain the\n" \
+ "following AAAA records:\n" \
+ "\n" \
+ " count-3-100.ttl-20.d.test. 20 IN AAAA 2001:db8::c\n" \
+ " count-3-100.ttl-20.d.test. 20 IN AAAA 2001:db8::3a\n" \
+ " count-3-100.ttl-20.d.test. 20 IN AAAA 2001:db8::4f\n" \
+ "\n" \
+ "If QNAME exists, but doesn't have the type of address records specified by QTYPE, then the response will\n" \
+ "contain no address records, regardless of whether it contains a Count label.\n" \
+ "\n" \
+ "QNAMEs that exist, but don't have a Count label are treated as though they contain a count label equal to\n" \
+ "\"count-1\".\n"
+
+#define kDNSServerInfoText_TagLabel \
+ "Tag labels are labels prefixed with \"tag-\" and contain zero or more arbitrary octets after the prefix.\n" \
+ "\n" \
+ "This type of label exists to allow testers to \"uniquify\" domain names. Tag labels can also serve as padding\n" \
+ "to increase the sizes of domain names.\n"
+
+#define kDNSServerInfoText_TTLLabel \
+ "TTL labels are of the form \"ttl-T\", where T is an integer in [0 .. 2^31 - 1].\n" \
+ "\n" \
+ "If the name specified by QNAME exists, and contains TTL label \"ttl-T\", then all non-CNAME records contained\n" \
+ "in the response will have a TTL value equal to T.\n"
+
+#define kDNSServerInfoText_IPv4Label \
+ "The IPv4 label is \"ipv4\". See \"Resource Records\" for the affect of this label.\n"
+
+#define kDNSServerInfoText_IPv6Label \
+ "The IPv6 label is \"ipv6\". See \"Resource Records\" for the affect of this label.\n"
+
+#define kDNSServerDefaultTTL 60
+
+static int gDNSServer_LoopbackOnly = false;
+static int gDNSServer_Foreground = false;
+static int gDNSServer_ResponseDelayMs = 0;
+static int gDNSServer_DefaultTTL = kDNSServerDefaultTTL;
+#if( TARGET_OS_DARWIN )
+static const char * gDNSServer_FollowPID = NULL;
+#endif
+
+static CLIOption kDNSServerOpts[] =
+{
+ BooleanOption( 'l', "loopback", &gDNSServer_LoopbackOnly, "Bind to to the loopback interface." ),
+ BooleanOption( 'f', "foreground", &gDNSServer_Foreground, "Directlog output to stdout instead of system logging." ),
+ IntegerOption( 'd', "responseDelay", &gDNSServer_ResponseDelayMs, "ms", "The amount of additional delay in milliseconds to apply to responses. (default: 0)", false ),
+ IntegerOption( 0 , "defaultTTL", &gDNSServer_DefaultTTL, "seconds", "Resource record TTL value to use when unspecified. (default: 60)", false ),
+#if( TARGET_OS_DARWIN )
+ StringOption( 0 , "followPID", &gDNSServer_FollowPID, "pid", "Exit when the process (usually the parent proccess) specified by PID exits.", false ),
+#endif
+
+ CLI_SECTION( "Intro", kDNSServerInfoText_Intro ),
+ CLI_SECTION( "Name Existence", kDNSServerInfoText_NameExistence ),
+ CLI_SECTION( "Resource Records", kDNSServerInfoText_ResourceRecords ),
+ CLI_SECTION( "Alias Labels", kDNSServerInfoText_AliasLabel ),
+ CLI_SECTION( "Alias-TTL Labels", kDNSServerInfoText_AliasTTLLabel ),
+ CLI_SECTION( "Count Labels", kDNSServerInfoText_CountLabel ),
+ CLI_SECTION( "Tag Labels", kDNSServerInfoText_TagLabel ),
+ CLI_SECTION( "TTL Labels", kDNSServerInfoText_TTLLabel ),
+ CLI_SECTION( "IPv4 Label", kDNSServerInfoText_IPv4Label ),
+ CLI_SECTION( "IPv6 Label", kDNSServerInfoText_IPv6Label ),
+ CLI_OPTION_END()
+};
+
+static void DNSServerCmd( void );
+
+//===========================================================================================================================
+// Test Command Options
+//===========================================================================================================================
+
+static const char * gGAIPerf_TestSuite = NULL;
+static int gGAIPerf_CallDelayMs = 10;
+static int gGAIPerf_ServerDelayMs = 10;
+static int gGAIPerf_DefaultIterCount = 100;
+static const char * gGAIPerf_OutputFilePath = NULL;
+static const char * gGAIPerf_OutputFormat = "json";
+static int gGAIPerf_OutputAppendNewLine = false;
+
+static void GAIPerfCmd( void );
+
+#define kGAIPerfSectionTitle_TestSuiteBasic "Test Suite \"Basic\""
+#define kGAIPerfSectionText_TestSuiteBasic \
+ "This test suite consists of the following three test cases:\n" \
+ "\n" \
+ "Test Case #1: Resolve a domain name with\n" \
+ "\n" \
+ " 2 CNAME records, 4 A records, and 4 AAAA records\n" \
+ "\n" \
+ "to its IPv4 and IPv6 addresses. Each iteration resolves a unique instance of such a domain name, which requires\n" \
+ "server queries.\n" \
+ "\n" \
+ "Test Case #2: Resolve a domain name with\n" \
+ "\n" \
+ " 2 CNAME records, 4 A records, and 4 AAAA records\n" \
+ "\n" \
+ "to its IPv4 and IPv6 addresses. A preliminary iteration resolves a unique instance of such a domain name, which\n" \
+ "requires server queries. Each subsequent iteration resolves the same domain name as the preliminary iteration,\n" \
+ "which should ideally require no additional server queries, i.e., the results should come from the cache.\n" \
+ "\n" \
+ "Unlike the preceding test case, this test case is concerned with DNSServiceGetAddrInfo() performance when the\n" \
+ "records of the domain name being resolved are already in the cache. Therefore, the time required to resolve the\n" \
+ "domain name in the preliminary iteration isn't counted in the performance stats.\n" \
+ "\n" \
+ "Test Case #3: Each iteration resolves localhost to its IPv4 and IPv6 addresses.\n"
+
+#define kGAIPerfSectionTitle_TestSuiteAdvanced "Test Suite \"Advanced\""
+#define kGAIPerfSectionText_TestSuiteAdvanced \
+ "This test suite consists of 33 test cases. Test cases 1 through 32 can be described in the following way\n" \
+ "\n" \
+ "Test Case #N (where N is in [1, 32] and odd): Resolve a domain name with\n" \
+ "\n" \
+ " N_c CNAME records, N_a A records, and N_a AAAA records\n" \
+ "\n" \
+ "to its IPv4 and IPv6 addresses. Each iteration resolves a unique instance of such a domain name, which requires\n" \
+ "server queries.\n" \
+ "\n" \
+ "Test Case #N (where N is in [1, 32] and even): Resolve a domain name with\n" \
+ "\n" \
+ " N_c CNAME records, N_a A records, and N_a AAAA records\n" \
+ "\n" \
+ "to its IPv4 and IPv6 addresses. A preliminary iteration resolves a unique instance of such a domain name, which\n" \
+ "requires server queries. Each subsequent iteration resolves the same domain name as the preliminary iteration,\n" \
+ "which should ideally require no additional server queries, i.e., the results should come from the cache.\n" \
+ "\n" \
+ "Unlike the preceding test case, this test case is concerned with DNSServiceGetAddrInfo() performance when the\n" \
+ "records of the domain name being resolved are already in the cache. Therefore, the time required to resolve the\n" \
+ "domain name in the preliminary iteration isn't counted in the performance stats.\n" \
+ "\n" \
+ "N_c and N_a take on the following values, depending on the value of N:\n" \
+ "\n" \
+ " N_c is 0 if N is in [1, 8].\n" \
+ " N_c is 1 if N is in [9, 16].\n" \
+ " N_c is 2 if N is in [17, 24].\n" \
+ " N_c is 4 if N is in [25, 32].\n" \
+ "\n" \
+ " N_a is 1 if N mod 8 is 1 or 2.\n" \
+ " N_a is 2 if N mod 8 is 3 or 4.\n" \
+ " N_a is 4 if N mod 8 is 5 or 6.\n" \
+ " N_a is 8 if N mod 8 is 7 or 0.\n" \
+ "\n" \
+ "Finally,\n" \
+ "\n" \
+ "Test Case #33: Each iteration resolves localhost to its IPv4 and IPv6 addresses.\n"
+
+static CLIOption kGAIPerfOpts[] =
+{
+ StringOptionEx( 's', "suite", &gGAIPerf_TestSuite, "name", "Name of the predefined test suite to run.", true,
+ "\n"
+ "There are currently two predefined test suites, 'basic' and 'advanced', which are described below.\n"
+ "\n"
+ ),
+ StringOption( 'o', "output", &gGAIPerf_OutputFilePath, "path", "Path of the file to write test results to instead of standard output (stdout).", false ),
+ StringOptionEx( 'f', "format", &gGAIPerf_OutputFormat, "format", "Specifies the test results output format. (default: json)", false,
+ "\n"
+ "Use 'json' for JavaScript Object Notation (JSON).\n"
+ "Use 'xml' for property list XML version 1.0.\n"
+ "Use 'binary' for property list binary version 1.0.\n"
+ "\n"
+ ),
+ BooleanOption( 'n', "appendNewline", &gGAIPerf_OutputAppendNewLine, "If the output format is JSON, output a trailing newline character." ),
+ IntegerOption( 0 , "callDelay", &gGAIPerf_CallDelayMs, "ms", "Time to wait before calling DNSServiceGetAddrInfo() in milliseconds. (default: 10)", false ),
+ IntegerOption( 0 , "responseDelay", &gGAIPerf_ServerDelayMs, "ms", "Additional delay in milliseconds to have the test DNS server apply to responses. (default: 0)", false ),
+ IntegerOption( 'i', "iterations", &gGAIPerf_DefaultIterCount, "count", "The default number of test case iterations. (default: 100)", false ),
+
+ CLI_SECTION( kGAIPerfSectionTitle_TestSuiteBasic, kGAIPerfSectionText_TestSuiteBasic ),
+ CLI_SECTION( kGAIPerfSectionTitle_TestSuiteAdvanced, kGAIPerfSectionText_TestSuiteAdvanced ),
+ CLI_OPTION_END()
+};
+
+static CLIOption kTestOpts[] =
+{
+ Command( "gaiperf", GAIPerfCmd, kGAIPerfOpts, "Run DNSServiceGetAddrInfo() performance tests.", false ),
+ CLI_OPTION_END()
+};
+
+//===========================================================================================================================
// SSDP Command Options
//===========================================================================================================================
@@ -812,10 +1262,13 @@ static CLIOption kSSDPOpts[] =
CLI_OPTION_END()
};
+#if( TARGET_OS_DARWIN )
//===========================================================================================================================
// res_query Command Options
//===========================================================================================================================
+static void ResQueryCmd( void );
+
static const char * gResQuery_Name = NULL;
static const char * gResQuery_Type = NULL;
static const char * gResQuery_Class = NULL;
@@ -834,6 +1287,8 @@ static CLIOption kResQueryOpts[] =
// dns_query Command Options
//===========================================================================================================================
+static void ResolvDNSQueryCmd( void );
+
static const char * gResolvDNSQuery_Name = NULL;
static const char * gResolvDNSQuery_Type = NULL;
static const char * gResolvDNSQuery_Class = NULL;
@@ -849,6 +1304,78 @@ static CLIOption kResolvDNSQueryOpts[] =
};
//===========================================================================================================================
+// CFHost Command Options
+//===========================================================================================================================
+
+static void CFHostCmd( void );
+
+static const char * gCFHost_Name = NULL;
+static int gCFHost_WaitSecs = 0;
+
+static CLIOption kCFHostOpts[] =
+{
+ StringOption( 'n', "name", &gCFHost_Name, "hostname", "Hostname to resolve.", true ),
+ IntegerOption( 'w', "wait", &gCFHost_WaitSecs, "seconds", "Time in seconds to wait before a normal exit. (default: 0)", false ),
+ CLI_OPTION_END()
+};
+
+static CLIOption kLegacyOpts[] =
+{
+ Command( "res_query", ResQueryCmd, kResQueryOpts, "Uses res_query() from either libresolv or libinfo to query for a record.", true ),
+ Command( "dns_query", ResolvDNSQueryCmd, kResolvDNSQueryOpts, "Uses dns_query() from libresolv to query for a record.", true ),
+ Command( "cfhost", CFHostCmd, kCFHostOpts, "Uses CFHost to resolve a hostname.", true ),
+ CLI_OPTION_END()
+};
+
+//===========================================================================================================================
+// DNSConfigAdd Command Options
+//===========================================================================================================================
+
+static void DNSConfigAddCmd( void );
+
+static CFStringRef gDNSConfigAdd_ID = NULL;
+static char ** gDNSConfigAdd_IPAddrArray = NULL;
+static size_t gDNSConfigAdd_IPAddrCount = 0;
+static char ** gDNSConfigAdd_DomainArray = NULL;
+static size_t gDNSConfigAdd_DomainCount = 0;
+static const char * gDNSConfigAdd_Interface = NULL;
+
+static CLIOption kDNSConfigAddOpts[] =
+{
+ CFStringOption( 0 , "id", &gDNSConfigAdd_ID, "ID", "Arbitrary ID to use for resolver entry.", true ),
+ MultiStringOption( 'a', "address", &gDNSConfigAdd_IPAddrArray, &gDNSConfigAdd_IPAddrCount, "IP address", "DNS server IP address(es). Can be specified more than once.", true ),
+ MultiStringOption( 'd', "domain", &gDNSConfigAdd_DomainArray, &gDNSConfigAdd_DomainCount, "domain", "Specific domain(s) for the resolver entry. Can be specified more than once.", false ),
+ StringOption( 'i', "interface", &gDNSConfigAdd_Interface, "interface name", "Specific interface for the resolver entry.", false ),
+
+ CLI_SECTION( "Notes", "Run 'scutil -d -v --dns' to see the current DNS configuration. See scutil(8) man page for more details.\n" ),
+ CLI_OPTION_END()
+};
+
+//===========================================================================================================================
+// DNSConfigRemove Command Options
+//===========================================================================================================================
+
+static void DNSConfigRemoveCmd( void );
+
+static CFStringRef gDNSConfigRemove_ID = NULL;
+
+static CLIOption kDNSConfigRemoveOpts[] =
+{
+ CFStringOption( 0, "id", &gDNSConfigRemove_ID, "ID", "ID of resolver entry to remove.", true ),
+
+ CLI_SECTION( "Notes", "Run 'scutil -d -v --dns' to see the current DNS configuration. See scutil(8) man page for more details.\n" ),
+ CLI_OPTION_END()
+};
+
+static CLIOption kDNSConfigOpts[] =
+{
+ Command( "add", DNSConfigAddCmd, kDNSConfigAddOpts, "Add a supplemental resolver entry to the system's DNS configuration.", true ),
+ Command( "remove", DNSConfigRemoveCmd, kDNSConfigRemoveOpts, "Remove a supplemental resolver entry from the system's DNS configuration.", true ),
+ CLI_OPTION_END()
+};
+#endif // TARGET_OS_DARWIN
+
+//===========================================================================================================================
// Command Table
//===========================================================================================================================
@@ -872,10 +1399,6 @@ static void DNSCryptCmd( void );
#endif
static void MDNSQueryCmd( void );
static void PIDToUUIDCmd( void );
-#if( TARGET_OS_DARWIN )
-static void ResQueryCmd( void );
-static void ResolvDNSQueryCmd( void );
-#endif
static void DaemonVersionCmd( void );
static CLIOption kGlobalOpts[] =
@@ -900,6 +1423,7 @@ static CLIOption kGlobalOpts[] =
// Uncommon commands.
+ Command( "getnameinfo", GetNameInfoCmd, kGetNameInfoOpts, "Calls getnameinfo() and prints results.", true ),
Command( "getAddrInfoStress", GetAddrInfoStressCmd, kGetAddrInfoStressOpts, "Runs DNSServiceGetAddrInfo() stress testing.", true ),
Command( "DNSQuery", DNSQueryCmd, kDNSQueryOpts, "Crafts and sends a DNS query.", true ),
#if( DNSSDUTIL_INCLUDE_DNSCRYPT )
@@ -907,10 +1431,12 @@ static CLIOption kGlobalOpts[] =
#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( "ssdp", NULL, kSSDPOpts, "Commands for testing with Simple Service Discovery Protocol (SSDP).", true ),
+ Command( "server", DNSServerCmd, kDNSServerOpts, "DNS server for testing.", true ),
+ Command( "test", NULL, kTestOpts, "Commands for testing DNS-SD.", true ),
+ Command( "ssdp", NULL, kSSDPOpts, "Commands for testing Simple Service Discovery Protocol (SSDP).", true ),
#if( TARGET_OS_DARWIN )
- Command( "res_query", ResQueryCmd, kResQueryOpts, "Uses res_query() from either libresolv or libinfo to query for a record.", true ),
- Command( "dns_query", ResolvDNSQueryCmd, kResolvDNSQueryOpts, "Uses dns_query() from libresolv to query for a record.", true ),
+ Command( "legacy", NULL, kLegacyOpts, "Commands for legacy non-DNS-SD API.", true ),
+ Command( "dnsconfig", NULL, kDNSConfigOpts, "Add/remove a supplemental resolver entry to/from the system's DNS configuration.", true ),
#endif
Command( "daemonVersion", DaemonVersionCmd, NULL, "Prints the version of the DNS-SD daemon.", true ),
@@ -930,9 +1456,19 @@ static CLIOption kGlobalOpts[] =
static void Exit( void *inContext ) ATTRIBUTE_NORETURN;
-#define kTimestampBufLen 27
+static int
+ PrintFTimestampHandler(
+ PrintFContext * inContext,
+ PrintFFormat * inFormat,
+ PrintFVAList * inArgs,
+ void * inUserContext );
+static int
+ PrintFDNSMessageHandler(
+ PrintFContext * inContext,
+ PrintFFormat * inFormat,
+ PrintFVAList * inArgs,
+ void * inUserContext );
-static char * GetTimestampStr( char inBuffer[ kTimestampBufLen ] );
static DNSServiceFlags GetDNSSDFlagsFromOpts( void );
typedef enum
@@ -972,29 +1508,6 @@ static OSStatus RecordClassFromArgString( const char *inString, uint16_t *outV
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,
@@ -1036,6 +1549,7 @@ static OSStatus
const char * inString,
uint8_t ** outEndPtr );
static Boolean DomainNameEqual( const uint8_t *inName1, const uint8_t *inName2 );
+static size_t DomainNameLength( const uint8_t *inName );
static OSStatus
DomainNameFromString(
uint8_t inDomainName[ kDomainNameLengthMax ],
@@ -1048,10 +1562,13 @@ static OSStatus
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 )
+static OSStatus
+ DNSMessageToText(
+ const uint8_t * inMsgPtr,
+ size_t inMsgLen,
+ Boolean inIsMDNS,
+ Boolean inPrintRaw,
+ char ** outText );
#define kDNSQueryMessageMaxLen ( kDNSHeaderLength + kDomainNameLengthMax + 4 )
@@ -1076,37 +1593,82 @@ static OSStatus
void * inContext,
dispatch_source_t * outSource );
static OSStatus
- DispatchReadSourceCreate(
- SocketRef inSock,
- DispatchHandler inEventHandler,
- DispatchHandler inCancelHandler,
- void * inContext,
- dispatch_source_t * outSource );
+ DispatchSocketSourceCreate(
+ SocketRef inSock,
+ dispatch_source_type_t inType,
+ dispatch_queue_t inQueue,
+ DispatchHandler inEventHandler,
+ DispatchHandler inCancelHandler,
+ void * inContext,
+ dispatch_source_t * outSource );
+
+#define DispatchReadSourceCreate( SOCK, QUEUE, EVENT_HANDLER, CANCEL_HANDLER, CONTEXT, OUT_SOURCE ) \
+ DispatchSocketSourceCreate( SOCK, DISPATCH_SOURCE_TYPE_READ, QUEUE, EVENT_HANDLER, CANCEL_HANDLER, CONTEXT, OUT_SOURCE )
+
+#define DispatchWriteSourceCreate( SOCK, QUEUE, EVENT_HANDLER, CANCEL_HANDLER, CONTEXT, OUT_SOURCE ) \
+ DispatchSocketSourceCreate( SOCK, DISPATCH_SOURCE_TYPE_WRITE, QUEUE, EVENT_HANDLER, CANCEL_HANDLER, CONTEXT, OUT_SOURCE )
+
static OSStatus
DispatchTimerCreate(
dispatch_time_t inStart,
uint64_t inIntervalNs,
uint64_t inLeewayNs,
+ dispatch_queue_t inQueue,
DispatchHandler inEventHandler,
DispatchHandler inCancelHandler,
void * inContext,
dispatch_source_t * outTimer );
+static OSStatus
+ DispatchProcessMonitorCreate(
+ pid_t inPID,
+ unsigned long inFlags,
+ dispatch_queue_t inQueue,
+ DispatchHandler inEventHandler,
+ DispatchHandler inCancelHandler,
+ void * inContext,
+ dispatch_source_t * outMonitor );
static const char * ServiceTypeDescription( const char *inName );
typedef struct
{
- SocketRef sock;
- void * context;
+ SocketRef sock; // Socket.
+ void * userContext; // User context.
+ int32_t refCount; // Reference count.
} SocketContext;
-static void SocketContextCancelHandler( void *inContext );
+static OSStatus SocketContextCreate( SocketRef inSock, void * inUserContext, SocketContext **outContext );
+static SocketContext * SocketContextRetain( SocketContext *inContext );
+static void SocketContextRelease( SocketContext *inContext );
+static void SocketContextCancelHandler( void *inContext );
+
+#define ForgetSocketContext( X ) ForgetCustom( X, SocketContextRelease )
+
static OSStatus StringToInt32( const char *inString, int32_t *outValue );
static OSStatus StringToUInt32( const char *inString, uint32_t *outValue );
+static OSStatus StringToLongLong( const char *inString, long long *outValue );
+static OSStatus StringToARecordData( const char *inString, uint8_t **outPtr, size_t *outLen );
+static OSStatus StringToAAAARecordData( const char *inString, uint8_t **outPtr, size_t *outLen );
+static OSStatus StringToDomainName( const char *inString, uint8_t **outPtr, size_t *outLen );
#if( TARGET_OS_DARWIN )
static OSStatus GetDefaultDNSServer( sockaddr_ip *outAddr );
#endif
+static OSStatus
+ _ServerSocketOpenEx2(
+ int inFamily,
+ int inType,
+ int inProtocol,
+ const void * inAddr,
+ int inPort,
+ int * outPort,
+ int inRcvBufSize,
+ Boolean inNoPortReuse,
+ SocketRef * outSock );
+
+typedef uint64_t MicroTime64;
+
+static MicroTime64 GetCurrentMicroTime( void ); // Gets the number of milliseconds since 1970-01-01T00:00:00Z
#define AddRmvString( X ) ( ( (X) & kDNSServiceFlagsAdd ) ? "Add" : "Rmv" )
#define Unused( X ) (void)(X)
@@ -1117,12 +1679,17 @@ static OSStatus GetDefaultDNSServer( sockaddr_ip *outAddr );
int main( int argc, const char **argv )
{
+ OSStatus err;
+
// Route DebugServices logging output to stderr.
dlog_control( "DebugServices:output=file;stderr" );
+ PrintFRegisterExtension( "du:time", PrintFTimestampHandler, NULL );
+ PrintFRegisterExtension( "du:dnsmsg", PrintFDNSMessageHandler, NULL );
CLIInit( argc, argv );
- CLIParse( kGlobalOpts, kCLIFlags_None );
+ err = CLIParse( kGlobalOpts, kCLIFlags_None );
+ if( err ) exit( 1 );
return( gExitCode );
}
@@ -1299,7 +1866,7 @@ static void BrowseCmd( void )
{
DNSServiceRef sdRef;
- if( useMainConnection ) sdRef = context->mainRef;
+ sdRef = useMainConnection ? context->mainRef : kBadDNSServiceRef;
err = DNSServiceBrowse( &sdRef, context->flags, context->ifIndex, context->serviceTypes[ i ], context->domain,
BrowseCallback, context );
require_noerr( err, exit );
@@ -1334,23 +1901,22 @@ exit:
static void BrowsePrintPrologue( const BrowseContext *inContext )
{
const int timeLimitSecs = inContext->timeLimitSecs;
- const char * const * serviceType = (const char **) inContext->serviceTypes;
+ const char * const * ptr = (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, "Service types: %s", *ptr++ );
+ while( ptr < end ) FPrintF( stdout, ", %s", *ptr++ );
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, "Start time: %{du:time}\n", NULL );
FPrintF( stdout, "---\n" );
}
@@ -1433,11 +1999,11 @@ static void DNSSD_API
BrowseResolveOp * newOp = NULL;
BrowseResolveOp ** p;
char fullName[ kDNSServiceMaxDomainName ];
- char time[ kTimestampBufLen ];
+ struct timeval now;
Unused( inSDRef );
- GetTimestampStr( time );
+ gettimeofday( &now, NULL );
err = inError;
require_noerr( err, exit );
@@ -1447,8 +2013,8 @@ static void DNSSD_API
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 );
+ FPrintF( stdout, "%{du:time} %-3s %5X %2d %-20s %-20s %s\n",
+ &now, AddRmvString( inFlags ), inFlags, (int32_t) inInterfaceIndex, inDomain, inRegType, inName );
if( !context->doResolve && !context->doResolveTXTOnly ) goto exit;
@@ -1536,22 +2102,22 @@ static void DNSSD_API
uint32_t inTTL,
void * inContext )
{
- OSStatus err;
- char time[ kTimestampBufLen ];
+ OSStatus err;
+ struct timeval now;
Unused( inSDRef );
Unused( inClass );
Unused( inTTL );
Unused( inContext );
- GetTimestampStr( time );
+ gettimeofday( &now, NULL );
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 );
+ FPrintF( stdout, "%{du:time} %s %s TXT on interface %d\n TXT: %#{txt}\n",
+ &now, AddRmvString( inFlags ), inFullName, (int32_t) inInterfaceIndex, inRDataPtr, (size_t) inRDataLen );
exit:
if( err ) exit( 1 );
@@ -1574,19 +2140,19 @@ static void DNSSD_API
const unsigned char * inTXTPtr,
void * inContext )
{
- char time[ kTimestampBufLen ];
- char errorStr[ 64 ];
+ struct timeval now;
+ char errorStr[ 64 ];
Unused( inSDRef );
Unused( inFlags );
Unused( inContext );
- GetTimestampStr( time );
+ gettimeofday( &now, NULL );
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 );
+ FPrintF( stdout, "%{du:time} %s can be reached at %s:%u (interface %d)%?s\n",
+ &now, inFullName, inHostname, ntohs( inPort ), (int32_t) inInterfaceIndex, inError, errorStr );
if( inTXTLen == 1 )
{
FPrintF( stdout, " TXT record: %#H\n", inTXTPtr, (int) inTXTLen, INT_MAX );
@@ -1701,7 +2267,7 @@ static void GetAddrInfoCmd( void )
// Start operation.
- if( useMainConnection ) sdRef = context->mainRef;
+ sdRef = useMainConnection ? context->mainRef : kBadDNSServiceRef;
err = DNSServiceGetAddrInfo( &sdRef, context->flags, context->ifIndex, context->protocols, context->name,
GetAddrInfoCallback, context );
require_noerr( err, exit );
@@ -1736,19 +2302,18 @@ 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, "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, "Start time: %{du:time}\n", NULL );
FPrintF( stdout, "---\n" );
}
@@ -1779,14 +2344,14 @@ static void DNSSD_API
void * inContext )
{
GetAddrInfoContext * const context = (GetAddrInfoContext *) inContext;
+ struct timeval now;
OSStatus err;
const char * addrStr;
char addrStrBuf[ kSockAddrStringMaxSize ];
- char time[ kTimestampBufLen ];
Unused( inSDRef );
- GetTimestampStr( time );
+ gettimeofday( &now, NULL );
switch( inError )
{
@@ -1826,8 +2391,8 @@ static void DNSSD_API
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 );
+ FPrintF( stdout, "%{du:time} %s %5X %2d %-32s %-38s %6u\n",
+ &now, AddRmvString( inFlags ), inFlags, (int32_t) inInterfaceIndex, inHostname, addrStr, inTTL );
if( context->oneShotMode )
{
@@ -1952,7 +2517,7 @@ static void QueryRecordCmd( void )
// Start operation.
- if( useMainConnection ) sdRef = context->mainRef;
+ sdRef = useMainConnection ? context->mainRef : kBadDNSServiceRef;
err = DNSServiceQueryRecord( &sdRef, context->flags, context->ifIndex, context->recordName, context->recordType,
kDNSServiceClass_IN, QueryRecordCallback, context );
require_noerr( err, exit );
@@ -1998,7 +2563,6 @@ static void QueryRecordPrintPrologue( const QueryRecordContext *inContext )
{
const int timeLimitSecs = inContext->timeLimitSecs;
char ifName[ kInterfaceNameBufLen ];
- char time[ kTimestampBufLen ];
InterfaceIndexToName( inContext->ifIndex, ifName );
@@ -2010,7 +2574,7 @@ static void QueryRecordPrintPrologue( const QueryRecordContext *inContext )
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, "Start time: %{du:time}\n", NULL );
FPrintF( stdout, "---\n" );
}
@@ -2034,13 +2598,13 @@ static void DNSSD_API
void * inContext )
{
QueryRecordContext * const context = (QueryRecordContext *) inContext;
+ struct timeval now;
OSStatus err;
char * rdataStr = NULL;
- char time[ kTimestampBufLen ];
Unused( inSDRef );
- GetTimestampStr( time );
+ gettimeofday( &now, NULL );
switch( inError )
{
@@ -2076,8 +2640,8 @@ static void DNSSD_API
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 ),
+ FPrintF( stdout, "%{du:time} %-3s %5X %2d %-32s %-5s %?-5s%?5u %6u %s\n",
+ &now, AddRmvString( inFlags ), inFlags, (int32_t) inInterfaceIndex, inFullName, RecordTypeToString( inType ),
( inClass == kDNSServiceClass_IN ), "IN", ( inClass != kDNSServiceClass_IN ), inClass, inTTL, rdataStr );
if( context->oneShotMode )
@@ -2273,7 +2837,6 @@ static void RegisterPrintPrologue( const RegisterContext *inContext )
size_t i;
int infinite;
char ifName[ kInterfaceNameBufLen ];
- char time[ kTimestampBufLen ];
InterfaceIndexToName( inContext->ifIndex, ifName );
@@ -2304,7 +2867,7 @@ static void RegisterPrintPrologue( const RegisterContext *inContext )
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, "Start time: %{du:time}\n", NULL );
FPrintF( stdout, "---\n" );
}
@@ -2345,19 +2908,19 @@ static void DNSSD_API
{
RegisterContext * const context = (RegisterContext *) inContext;
OSStatus err;
- char time[ kTimestampBufLen ];
+ struct timeval now;
Unused( inSDRef );
- GetTimestampStr( time );
+ gettimeofday( &now, NULL );
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 );
+ FPrintF( stdout, "%{du:time} %-3s %5X %s.%s%s %?#m\n",
+ &now, AddRmvString( inFlags ), inFlags, inName, inType, inDomain, inError, inError );
require_noerr_action_quiet( inError, exit, err = inError );
@@ -2548,7 +3111,6 @@ exit:
static void RegisterRecordPrintPrologue( const RegisterRecordContext *inContext )
{
int infinite;
- char time[ kTimestampBufLen ];
char ifName[ kInterfaceNameBufLen ];
InterfaceIndexToName( inContext->ifIndex, ifName );
@@ -2569,7 +3131,7 @@ static void RegisterRecordPrintPrologue( const RegisterRecordContext *inContext
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, "Start time: %{du:time}\n", NULL );
FPrintF( stdout, "---\n" );
}
@@ -2598,15 +3160,15 @@ static void
void * inContext )
{
RegisterRecordContext * context = (RegisterRecordContext *) inContext;
- char time[ kTimestampBufLen ];
+ struct timeval now;
Unused( inSDRef );
Unused( inRecordRef );
Unused( inFlags );
Unused( context );
- GetTimestampStr( time );
- FPrintF( stdout, "%s Record registration result (error %#m)\n", time, inError );
+ gettimeofday( &now, NULL );
+ FPrintF( stdout, "%{du:time} Record registration result (error %#m)\n", &now, inError );
if( !context->didRegister && !inError )
{
@@ -2749,7 +3311,7 @@ static void ResolveCmd( void )
// Start operation.
- if( useMainConnection ) sdRef = context->mainRef;
+ sdRef = useMainConnection ? context->mainRef : kBadDNSServiceRef;
err = DNSServiceResolve( &sdRef, context->flags, context->ifIndex, context->name, context->type, context->domain,
ResolveCallback, NULL );
require_noerr( err, exit );
@@ -2850,19 +3412,18 @@ 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, "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, "Start time: %{du:time}\n", NULL );
FPrintF( stdout, "---\n" );
}
@@ -2894,19 +3455,19 @@ static void DNSSD_API
const unsigned char * inTXTPtr,
void * inContext )
{
- char time[ kTimestampBufLen ];
- char errorStr[ 64 ];
+ struct timeval now;
+ char errorStr[ 64 ];
Unused( inSDRef );
Unused( inFlags );
Unused( inContext );
- GetTimestampStr( time );
+ gettimeofday( &now, NULL );
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 );
+ FPrintF( stdout, "%{du:time}: %s can be reached at %s:%u (interface %d)%?s\n",
+ &now, inFullName, inHostname, ntohs( inPort ), (int32_t) inInterfaceIndex, inError, errorStr );
if( inTXTLen == 1 )
{
FPrintF( stdout, " TXT record: %#H\n", inTXTPtr, (int) inTXTLen, INT_MAX );
@@ -2958,10 +3519,10 @@ static void GetAddrInfoPOSIXCmd( void )
{
OSStatus err;
struct addrinfo hints;
+ struct timeval now;
const struct addrinfo * addrInfo;
struct addrinfo * addrInfoList = NULL;
const FlagStringPair * pair;
- char time[ kTimestampBufLen ];
memset( &hints, 0, sizeof( hints ) );
hints.ai_socktype = SOCK_STREAM;
@@ -3009,13 +3570,13 @@ static void GetAddrInfoPOSIXCmd( void )
if( ( (unsigned int) hints.ai_flags ) & pair->flag ) FPrintF( stdout, "%s ", pair->str );
}
FPrintF( stdout, ">\n" );
- FPrintF( stdout, "Start time: %s\n", GetTimestampStr( time ) );
+ FPrintF( stdout, "Start time: %{du:time}\n", NULL );
FPrintF( stdout, "---\n" );
// Call getaddrinfo().
err = getaddrinfo( gGAIPOSIX_HostName, gGAIPOSIX_ServName, &hints, &addrInfoList );
- GetTimestampStr( time );
+ gettimeofday( &now, NULL );
if( err )
{
FPrintF( stderr, "Error %d: %s.\n", err, gai_strerror( err ) );
@@ -3033,7 +3594,7 @@ static void GetAddrInfoPOSIXCmd( void )
}
}
FPrintF( stdout, "---\n" );
- FPrintF( stdout, "End time: %s\n", time );
+ FPrintF( stdout, "End time: %{du:time}\n", &now );
exit:
if( addrInfoList ) freeaddrinfo( addrInfoList );
@@ -3151,7 +3712,7 @@ static void ReverseLookupCmd( void )
// Start operation.
- if( useMainConnection ) sdRef = context->mainRef;
+ sdRef = useMainConnection ? context->mainRef : kBadDNSServiceRef;
err = DNSServiceQueryRecord( &sdRef, context->flags, context->ifIndex, context->recordName, context->recordType,
kDNSServiceClass_IN, QueryRecordCallback, context );
require_noerr( err, exit );
@@ -3284,7 +3845,7 @@ static void PortMappingCmd( void )
// Start operation.
- if( useMainConnection ) sdRef = context->mainRef;
+ sdRef = useMainConnection ? context->mainRef : kBadDNSServiceRef;
err = DNSServiceNATPortMappingCreate( &sdRef, context->flags, context->ifIndex, context->protocols,
htons( context->internalPort ), htons( context->externalPort ), context->ttl, PortMappingCallback, context );
require_noerr( err, exit );
@@ -3311,17 +3872,17 @@ exit:
static void PortMappingPrintPrologue( const PortMappingContext *inContext )
{
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, "Internal Port: %u\n", inContext->internalPort );
- FPrintF( stdout, "External Port: %u\n", inContext->externalPort );
- FPrintF( stdout, "TTL: %u%?s\n", inContext->ttl, !inContext->ttl, " (system will use a default value.)" );
- FPrintF( stdout, "Start time: %s\n", GetTimestampStr( time ) );
+ FPrintF( stdout, "Flags: %#{flags}\n", inContext->flags, kDNSServiceFlagsDescriptors );
+ FPrintF( stdout, "Interface: %d (%s)\n", (int32_t) inContext->ifIndex, ifName );
+ FPrintF( stdout, "Protocols: %#{flags}\n", inContext->protocols, kDNSServiceProtocolDescriptors );
+ FPrintF( stdout, "Internal Port: %u\n", inContext->internalPort );
+ FPrintF( stdout, "External Port: %u\n", inContext->externalPort );
+ FPrintF( stdout, "TTL: %u%?s\n", inContext->ttl, !inContext->ttl,
+ " (system will use a default value.)" );
+ FPrintF( stdout, "Start time: %{du:time}\n", NULL );
FPrintF( stdout, "---\n" );
}
@@ -3355,13 +3916,13 @@ static void DNSSD_API
void * inContext )
{
PortMappingContext * const context = (PortMappingContext *) inContext;
- char time[ kTimestampBufLen ];
+ struct timeval now;
char errorStr[ 128 ];
Unused( inSDRef );
Unused( inFlags );
- GetTimestampStr( time );
+ gettimeofday( &now, NULL );
if( inError ) SNPrintF( errorStr, sizeof( errorStr ), " (error: %#m)", inError );
if( !context->printedHeader )
@@ -3369,8 +3930,8 @@ static void DNSSD_API
FPrintF( stdout, "%-26s IF %7s %15s %7s %6s Protocol\n", "Timestamp", "IntPort", "ExtAddr", "ExtPort", "TTL" );
context->printedHeader = true;
}
- FPrintF( stdout, "%-26s %2u %7u %15.4a %7u %6u %#{flags}%?s\n",
- time, inInterfaceIndex, ntohs( inInternalPort), &inExternalIPv4Address, ntohs( inExternalPort ), inTTL,
+ FPrintF( stdout, "%{du:time} %2u %7u %15.4a %7u %6u %#{flags}%?s\n",
+ &now, inInterfaceIndex, ntohs( inInternalPort), &inExternalIPv4Address, ntohs( inExternalPort ), inTTL,
inProtocol, kDNSServiceProtocolDescriptors, inError, errorStr );
}
@@ -3632,7 +4193,7 @@ static void BrowseAllCmd( void )
gBrowseAll_ServiceTypesCount = 0;
context->browseTimeSecs = gBrowseAll_BrowseTimeSecs;
context->maxConnectTimeSecs = gBrowseAll_MaxConnectTimeSecs;
- context->includeAWDL = gBrowseAll_IncludeAWDL ? true : false;
+ context->includeAWDL = gDNSSDFlag_IncludeAWDL ? true : false;
#if( TARGET_OS_POSIX )
context->useColoredText = isatty( STDOUT_FILENO ) ? true : false;
#endif
@@ -3683,7 +4244,6 @@ static void BrowseAllPrintPrologue( const BrowseAllContext *inContext )
{
size_t i;
char ifName[ kInterfaceNameBufLen ];
- char time[ kTimestampBufLen ];
InterfaceIndexToName( inContext->ifIndex, ifName );
@@ -3703,7 +4263,8 @@ static void BrowseAllPrintPrologue( const BrowseAllContext *inContext )
FPrintF( stdout, "Browse time: %d second%?c\n", inContext->browseTimeSecs, inContext->browseTimeSecs != 1, 's' );
FPrintF( stdout, "Max connect time: %d second%?c\n",
inContext->maxConnectTimeSecs, inContext->maxConnectTimeSecs != 1, 's' );
- FPrintF( stdout, "Start time: %s\n", GetTimestampStr( time ) );
+ FPrintF( stdout, "IncludeAWDL: %s\n", inContext->includeAWDL ? "YES" : "NO" );
+ FPrintF( stdout, "Start time: %{du:time}\n", NULL );
FPrintF( stdout, "---\n" );
}
@@ -4063,7 +4624,7 @@ static void BrowseAllStop( void *inContext )
{
check( !context->exitTimer );
err = DispatchTimerCreate( dispatch_time_seconds( context->maxConnectTimeSecs ), DISPATCH_TIME_FOREVER,
- 100 * kNanosecondsPerMillisecond, BrowseAllExit, NULL, context, &context->exitTimer );
+ 100 * kNanosecondsPerMillisecond, NULL, BrowseAllExit, NULL, context, &context->exitTimer );
require_noerr( err, exit );
dispatch_resume( context->exitTimer );
}
@@ -4744,6 +5305,81 @@ static void BrowseIPAddrReleaseList( BrowseIPAddr *inList )
}
//===========================================================================================================================
+// GetNameInfoCmd
+//===========================================================================================================================
+
+const FlagStringPair kGetNameInfoFlagStringPairs[] =
+{
+ CaseFlagStringify( NI_NUMERICSCOPE ),
+ CaseFlagStringify( NI_DGRAM ),
+ CaseFlagStringify( NI_NUMERICSERV ),
+ CaseFlagStringify( NI_NAMEREQD ),
+ CaseFlagStringify( NI_NUMERICHOST ),
+ CaseFlagStringify( NI_NOFQDN ),
+ { 0, NULL }
+};
+
+static void GetNameInfoCmd( void )
+{
+ OSStatus err;
+ sockaddr_ip sip;
+ size_t sockAddrLen;
+ unsigned int flags;
+ const FlagStringPair * pair;
+ struct timeval now;
+ char host[ NI_MAXHOST ];
+ char serv[ NI_MAXSERV ];
+
+ err = StringToSockAddr( gGetNameInfo_IPAddress, &sip, sizeof( sip ), &sockAddrLen );
+ check_noerr( err );
+ if( err )
+ {
+ FPrintF( stderr, "Failed to convert \"%s\" to a sockaddr.\n", gGetNameInfo_IPAddress );
+ goto exit;
+ }
+
+ flags = 0;
+ if( gGetNameInfoFlag_DGram ) flags |= NI_DGRAM;
+ if( gGetNameInfoFlag_NameReqd ) flags |= NI_NAMEREQD;
+ if( gGetNameInfoFlag_NoFQDN ) flags |= NI_NOFQDN;
+ if( gGetNameInfoFlag_NumericHost ) flags |= NI_NUMERICHOST;
+ if( gGetNameInfoFlag_NumericScope ) flags |= NI_NUMERICSCOPE;
+ if( gGetNameInfoFlag_NumericServ ) flags |= NI_NUMERICSERV;
+
+ // Print prologue.
+
+ FPrintF( stdout, "SockAddr: %##a\n", &sip.sa );
+ FPrintF( stdout, "Flags: 0x%X < ", flags );
+ for( pair = kGetNameInfoFlagStringPairs; pair->str != NULL; ++pair )
+ {
+ if( flags & pair->flag ) FPrintF( stdout, "%s ", pair->str );
+ }
+ FPrintF( stdout, ">\n" );
+ FPrintF( stdout, "Start time: %{du:time}\n", NULL );
+ FPrintF( stdout, "---\n" );
+
+ // Call getnameinfo().
+
+ err = getnameinfo( &sip.sa, (socklen_t) sockAddrLen, host, (socklen_t) sizeof( host ), serv, (socklen_t) sizeof( serv ),
+ (int) flags );
+ gettimeofday( &now, NULL );
+ if( err )
+ {
+ FPrintF( stderr, "Error %d: %s.\n", err, gai_strerror( err ) );
+ }
+ else
+ {
+ FPrintF( stdout, "host: %s\n", host );
+ FPrintF( stdout, "serv: %s\n", serv );
+ }
+ FPrintF( stdout, "---\n" );
+ FPrintF( stdout, "End time: %{du:time}\n", &now );
+
+exit:
+ gExitCode = err ? 1 : 0;
+}
+
+//===========================================================================================================================
// GetAddrInfoStressCmd
//===========================================================================================================================
@@ -4782,7 +5418,6 @@ static void GetAddrInfoStressCmd( void )
DNSServiceFlags flags;
uint32_t ifIndex;
char ifName[ kInterfaceNameBufLen ];
- char time[ kTimestampBufLen ];
if( gGAIStress_TestDurationSecs < 0 )
{
@@ -4863,11 +5498,11 @@ static void GetAddrInfoStressCmd( void )
{
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, "Connection count: %d\n", gGAIStress_ConnectionCount );
+ FPrintF( stdout, "Request duration min: %d ms\n", gGAIStress_DurationMinMs );
+ FPrintF( stdout, "Request duration max: %d ms\n", gGAIStress_DurationMaxMs );
+ FPrintF( stdout, "Request count max: %d\n", gGAIStress_RequestCountMax );
+ FPrintF( stdout, "Start time: %{du:time}\n", NULL);
FPrintF( stdout, "---\n" );
dispatch_main();
@@ -4893,7 +5528,6 @@ static void GetAddrInfoStressEvent( void *inContext )
unsigned int nextMs;
char randomStr[ kStressRandStrLen + 1 ];
char hostname[ kStressRandStrLen + 4 + 1 ];
- char time[ kTimestampBufLen ];
Boolean isConnectionNew = false;
static Boolean printedHeader = false;
@@ -4923,8 +5557,8 @@ static void GetAddrInfoStressEvent( void *inContext )
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 );
+ FPrintF( stdout, "%{du:time} %3u%c %9s %8u\n",
+ NULL, context->connectionNumber, isConnectionNew ? '*': ' ', hostname, nextMs );
DNSServiceForget( &context->sdRef );
sdRef = context->mainRef;
@@ -4970,8 +5604,6 @@ static void DNSSD_API
// DNSQueryCmd
//===========================================================================================================================
-#define kDNSPort 53
-
typedef struct
{
sockaddr_ip serverAddr;
@@ -5073,8 +5705,7 @@ static void DNSQueryCmd( void )
if( gDNSQuery_Verbose )
{
- FPrintF( stdout, "DNS message to send:\n\n" );
- PrintUDNSMessage( msgPtr, msgLen, false );
+ FPrintF( stdout, "DNS message to send:\n\n%{du:dnsmsg}", msgPtr, msgLen );
FPrintF( stdout, "---\n" );
}
@@ -5103,7 +5734,7 @@ static void DNSQueryCmd( void )
if( context->timeLimitSecs == 0 ) goto exit;
- err = DispatchReadSourceCreate( context->sock, DNSQueryReadHandler, DNSQueryCancelHandler, context,
+ err = DispatchReadSourceCreate( context->sock, NULL, DNSQueryReadHandler, DNSQueryCancelHandler, context,
&context->readSource );
require_noerr( err, exit );
dispatch_resume( context->readSource );
@@ -5132,7 +5763,6 @@ exit:
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 );
@@ -5141,7 +5771,7 @@ static void DNSQueryPrintPrologue( const DNSQueryContext *inContext )
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, "Start time: %{du:time}\n", NULL );
FPrintF( stdout, "---\n" );
}
@@ -5152,11 +5782,11 @@ static void DNSQueryPrintPrologue( const DNSQueryContext *inContext )
static void DNSQueryReadHandler( void *inContext )
{
OSStatus err;
+ struct timeval now;
const uint64_t nowTicks = UpTicks();
DNSQueryContext * const context = (DNSQueryContext *) inContext;
- char time[ kTimestampBufLen ];
- GetTimestampStr( time );
+ gettimeofday( &now, NULL );
if( context->useTCP )
{
@@ -5198,11 +5828,11 @@ static void DNSQueryReadHandler( void *inContext )
check( SockAddrCompareAddr( &fromAddr, &context->serverAddr ) == 0 );
}
- FPrintF( stdout, "Receive time: %s\n", time );
+ FPrintF( stdout, "Receive time: %{du:time}\n", &now );
FPrintF( stdout, "Source: %##a\n", &context->serverAddr );
FPrintF( stdout, "Message size: %zu\n", context->msgLen );
FPrintF( stdout, "RTT: %llu ms\n\n", UpTicksToMilliseconds( nowTicks - context->sendTicks ) );
- PrintUDNSMessage( context->msgPtr, context->msgLen, context->printRawRData );
+ FPrintF( stdout, "%.*{du:dnsmsg}", context->printRawRData ? 1 : 0, context->msgPtr, context->msgLen );
if( ( context->msgLen >= kDNSHeaderLength ) && ( DNSHeaderGetID( (DNSHeader *) context->msgPtr ) == context->queryID ) )
{
@@ -5336,7 +5966,7 @@ static void DNSCryptCmd( void )
DNSCryptContext * context = NULL;
size_t writtenBytes;
size_t totalBytes;
- SocketContext * sockContext;
+ SocketContext * sockCtx;
SocketRef sock = kInvalidSocketRef;
const char * ptr;
@@ -5403,17 +6033,15 @@ static void DNSCryptCmd( void )
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 = SocketContextCreate( sock, context, &sockCtx );
+ require_noerr( err, exit );
+ sock = kInvalidSocketRef;
- err = DispatchReadSourceCreate( sock, DNSCryptReceiveCertHandler, SocketContextCancelHandler, sockContext,
+ err = DispatchReadSourceCreate( sockCtx->sock, NULL, DNSCryptReceiveCertHandler, SocketContextCancelHandler, sockCtx,
&context->readSource );
- if( err ) ForgetMem( &sockContext );
+ if( err ) ForgetSocketContext( &sockCtx );
require_noerr( err, exit );
- sockContext->context = context;
- sockContext->sock = sock;
- sock = kInvalidSocketRef;
dispatch_resume( context->readSource );
if( context->timeLimitSecs > 0 )
@@ -5436,9 +6064,10 @@ exit:
static void DNSCryptReceiveCertHandler( void *inContext )
{
OSStatus err;
+ struct timeval now;
const uint64_t nowTicks = UpTicks();
- SocketContext * const sockContext = (SocketContext *) inContext;
- DNSCryptContext * const context = (DNSCryptContext *) sockContext->context;
+ SocketContext * const sockCtx = (SocketContext *) inContext;
+ DNSCryptContext * const context = (DNSCryptContext *) sockCtx->userContext;
const DNSHeader * hdr;
sockaddr_ip fromAddr;
const uint8_t * ptr;
@@ -5446,23 +6075,21 @@ static void DNSCryptReceiveCertHandler( void *inContext )
size_t txtLen;
unsigned int answerCount, i;
uint8_t targetName[ kDomainNameLengthMax ];
- char time[ kTimestampBufLen ];
- GetTimestampStr( time );
+ gettimeofday( &now, NULL );
dispatch_source_forget( &context->readSource );
- err = SocketRecvFrom( sockContext->sock, context->msgBuf, sizeof( context->msgBuf ), &context->msgLen,
+ err = SocketRecvFrom( sockCtx->sock, context->msgBuf, sizeof( context->msgBuf ), &context->msgLen,
&fromAddr, sizeof( fromAddr ), NULL, NULL, NULL, NULL );
require_noerr( err, exit );
check( SockAddrCompareAddr( &fromAddr, &context->serverAddr ) == 0 );
- FPrintF( stdout, "Receive time: %s\n", time );
+ FPrintF( stdout, "Receive time: %{du:time}\n", &now );
FPrintF( stdout, "Source: %##a\n", &context->serverAddr );
FPrintF( stdout, "Message size: %zu\n", context->msgLen );
FPrintF( stdout, "RTT: %llu ms\n\n", UpTicksToMilliseconds( nowTicks - context->sendTicks ) );
-
- PrintUDNSMessage( context->msgBuf, context->msgLen, context->printRawRData );
+ FPrintF( stdout, "%.*{du:dnsmsg}", context->printRawRData ? 1 : 0, context->msgBuf, context->msgLen );
require_action_quiet( context->msgLen >= kDNSHeaderLength, exit, err = kSizeErr );
@@ -5521,28 +6148,28 @@ exit:
static void DNSCryptReceiveResponseHandler( void *inContext )
{
OSStatus err;
+ struct timeval now;
const uint64_t nowTicks = UpTicks();
- SocketContext * const sockContext = (SocketContext *) inContext;
- DNSCryptContext * const context = (DNSCryptContext *) sockContext->context;
+ SocketContext * const sockCtx = (SocketContext *) inContext;
+ DNSCryptContext * const context = (DNSCryptContext *) sockCtx->userContext;
sockaddr_ip fromAddr;
DNSCryptResponseHeader * hdr;
const uint8_t * end;
uint8_t * ciphertext;
uint8_t * plaintext;
const uint8_t * response;
- char time[ kTimestampBufLen ];
uint8_t nonce[ crypto_box_NONCEBYTES ];
- GetTimestampStr( time );
+ gettimeofday( &now, NULL );
dispatch_source_forget( &context->readSource );
- err = SocketRecvFrom( sockContext->sock, context->msgBuf, sizeof( context->msgBuf ), &context->msgLen,
+ err = SocketRecvFrom( sockCtx->sock, context->msgBuf, sizeof( context->msgBuf ), &context->msgLen,
&fromAddr, sizeof( fromAddr ), NULL, NULL, NULL, NULL );
require_noerr( err, exit );
check( SockAddrCompareAddr( &fromAddr, &context->serverAddr ) == 0 );
- FPrintF( stdout, "Receive time: %s\n", time );
+ FPrintF( stdout, "Receive time: %{du:time}\n", &now );
FPrintF( stdout, "Source: %##a\n", &context->serverAddr );
FPrintF( stdout, "Message size: %zu\n", context->msgLen );
FPrintF( stdout, "RTT: %llu ms\n\n", UpTicksToMilliseconds( nowTicks - context->sendTicks ) );
@@ -5586,7 +6213,7 @@ static void DNSCryptReceiveResponseHandler( void *inContext )
require_noerr( err, exit );
response = plaintext + crypto_box_ZEROBYTES;
- PrintUDNSMessage( response, (size_t)( end - response ), context->printRawRData );
+ FPrintF( stdout, "%.*{du:dnsmsg}", context->printRawRData ? 1 : 0, response, (size_t)( end - response ) );
Exit( kExitReason_ReceivedResponse );
exit:
@@ -5762,7 +6389,7 @@ exit:
static OSStatus DNSCryptSendQuery( DNSCryptContext *inContext )
{
OSStatus err;
- SocketContext * sockContext;
+ SocketContext * sockCtx;
SocketRef sock = kInvalidSocketRef;
check( inContext->msgLen > 0 );
@@ -5775,18 +6402,15 @@ static OSStatus DNSCryptSendQuery( DNSCryptContext *inContext )
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 = SocketContextCreate( sock, inContext, &sockCtx );
+ require_noerr( err, exit );
+ sock = kInvalidSocketRef;
- err = DispatchReadSourceCreate( sock, DNSCryptReceiveResponseHandler, SocketContextCancelHandler, sockContext,
+ err = DispatchReadSourceCreate( sockCtx->sock, NULL, DNSCryptReceiveResponseHandler, SocketContextCancelHandler, sockCtx,
&inContext->readSource );
- if( err ) ForgetMem( &sockContext );
+ if( err ) ForgetSocketContext( &sockCtx );
require_noerr( err, exit );
- sockContext->context = inContext;
- sockContext->sock = sock;
- sock = kInvalidSocketRef;
-
dispatch_resume( inContext->readSource );
exit:
@@ -5857,11 +6481,6 @@ static char * CertTimeStr( time_t inTime, char inBuffer[ kCertTimeStrBufLen ] )
// 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.
@@ -6039,37 +6658,33 @@ static void MDNSQueryCmd( void )
if( IsValidSocket( sockV4 ) )
{
- SocketContext * sockContext;
+ SocketContext * sockCtx;
- sockContext = (SocketContext *) calloc( 1, sizeof( *sockContext ) );
- require_action( sockContext, exit, err = kNoMemoryErr );
+ err = SocketContextCreate( sockV4, context, &sockCtx );
+ require_noerr( err, exit );
+ sockV4 = kInvalidSocketRef;
- err = DispatchReadSourceCreate( sockV4, MDNSQueryReadHandler, SocketContextCancelHandler, sockContext,
+ err = DispatchReadSourceCreate( sockCtx->sock, NULL, MDNSQueryReadHandler, SocketContextCancelHandler, sockCtx,
&context->readSourceV4 );
- if( err ) ForgetMem( &sockContext );
+ if( err ) ForgetSocketContext( &sockCtx );
require_noerr( err, exit );
- sockContext->context = context;
- sockContext->sock = sockV4;
- sockV4 = kInvalidSocketRef;
dispatch_resume( context->readSourceV4 );
}
if( IsValidSocket( sockV6 ) )
{
- SocketContext * sockContext;
+ SocketContext * sockCtx;
- sockContext = (SocketContext *) calloc( 1, sizeof( *sockContext ) );
- require_action( sockContext, exit, err = kNoMemoryErr );
+ err = SocketContextCreate( sockV6, context, &sockCtx );
+ require_noerr( err, exit );
+ sockV6 = kInvalidSocketRef;
- err = DispatchReadSourceCreate( sockV6, MDNSQueryReadHandler, SocketContextCancelHandler, sockContext,
+ err = DispatchReadSourceCreate( sockCtx->sock, NULL, MDNSQueryReadHandler, SocketContextCancelHandler, sockCtx,
&context->readSourceV6 );
- if( err ) ForgetMem( &sockContext );
+ if( err ) ForgetSocketContext( &sockCtx );
require_noerr( err, exit );
- sockContext->context = context;
- sockContext->sock = sockV6;
- sockV6 = kInvalidSocketRef;
dispatch_resume( context->readSourceV6 );
}
@@ -6093,7 +6708,6 @@ exit:
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 );
@@ -6105,7 +6719,7 @@ static void MDNSQueryPrintPrologue( const MDNSQueryContext *inContext )
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 ) );
+ FPrintF( stdout, "Start time: %{du:time}\n", NULL );
}
//===========================================================================================================================
@@ -6115,16 +6729,16 @@ static void MDNSQueryPrintPrologue( const MDNSQueryContext *inContext )
static void MDNSQueryReadHandler( void *inContext )
{
OSStatus err;
- SocketContext * const sockContext = (SocketContext *) inContext;
- MDNSQueryContext * const context = (MDNSQueryContext *) sockContext->context;
+ struct timeval now;
+ SocketContext * const sockCtx = (SocketContext *) inContext;
+ MDNSQueryContext * const context = (MDNSQueryContext *) sockCtx->userContext;
size_t msgLen;
sockaddr_ip fromAddr;
- char time[ kTimestampBufLen ];
Boolean foundAnswer = false;
- GetTimestampStr( time );
+ gettimeofday( &now, NULL );
- err = SocketRecvFrom( sockContext->sock, context->msgBuf, sizeof( context->msgBuf ), &msgLen, &fromAddr,
+ err = SocketRecvFrom( sockCtx->sock, context->msgBuf, sizeof( context->msgBuf ), &msgLen, &fromAddr,
sizeof( fromAddr ), NULL, NULL, NULL, NULL );
require_noerr( err, exit );
@@ -6162,11 +6776,10 @@ static void MDNSQueryReadHandler( void *inContext )
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 );
+ FPrintF( stdout, "Receive time: %{du:time}\n", &now );
+ FPrintF( stdout, "Source: %##a\n", &fromAddr );
+ FPrintF( stdout, "Message size: %zu\n\n%#.*{du:dnsmsg}",
+ msgLen, context->printRawRData ? 1 : 0, context->msgBuf, msgLen );
}
exit:
@@ -6194,6 +6807,3446 @@ exit:
}
//===========================================================================================================================
+// DNSServerCmd
+//===========================================================================================================================
+
+typedef uint32_t DNSServerEventType;
+#define kDNSServerEvent_Started 1
+#define kDNSServerEvent_Stopped 2
+
+typedef struct DNSServerPrivate * DNSServerRef;
+
+typedef struct
+{
+ DNSServerRef server; // Reference to the DNS server.
+ dispatch_source_t sigIntSource; // Dispatch SIGINT source.
+ dispatch_source_t sigTermSource; // Dispatch SIGTERM source.
+#if( TARGET_OS_DARWIN )
+ dispatch_source_t processMonitor; // Process monitor source for process being followed, if any.
+ pid_t followPID; // PID of process being followed (we exit when they exit), if any.
+ Boolean resolverRegistered; // True if system DNS settings contains a resolver entry for server.
+#endif
+ Boolean loopbackOnly; // True if the server should be bound to the loopback interface.
+ Boolean serverStarted; // True if the server was successfully started.
+ Boolean calledStop; // True if the server was explicitly stopped.
+
+} DNSServerCmdContext;
+
+typedef void ( *DNSServerEventHandler_f )( DNSServerEventType inType, void *inContext );
+
+CFTypeID DNSServerGetTypeID( void );
+static OSStatus
+ DNSServerCreate(
+ dispatch_queue_t inQueue,
+ DNSServerEventHandler_f inEventHandler,
+ void * inEventContext,
+ int inResponseDelayMs,
+ Boolean inLoopbackOnly,
+ DNSServerRef * outServer );
+static void DNSServerStart( DNSServerRef inServer );
+static void DNSServerStop( DNSServerRef inServer );
+
+static void DNSServerCmdContextFree( DNSServerCmdContext *inContext );
+static void DNSServerCmdEventHandler( DNSServerEventType inType, void *inContext );
+static void DNSServerCmdSigIntHandler( void *inContext );
+static void DNSServerCmdSigTermHandler( void *inContext );
+#if( TARGET_OS_DARWIN )
+static void DNSServerCmdFollowedProcessHandler( void *inContext );
+#endif
+
+ulog_define_ex( "com.apple.dnssdutil", DNSServer, kLogLevelInfo, kLogFlags_None, "DNSServer", NULL );
+#define ds_ulog( LEVEL, ... ) ulog( &log_category_from_name( DNSServer ), (LEVEL), __VA_ARGS__ )
+
+static void DNSServerCmd( void )
+{
+ OSStatus err;
+ DNSServerCmdContext * context;
+
+ context = (DNSServerCmdContext *) calloc( 1, sizeof( *context ) );
+ require_action( context, exit, err = kNoMemoryErr );
+
+ context->loopbackOnly = gDNSServer_LoopbackOnly ? true : false;
+
+#if( TARGET_OS_DARWIN )
+ if( gDNSServer_FollowPID )
+ {
+ long long value;
+
+ err = StringToLongLong( gDNSServer_FollowPID, &value );
+ if( !err && ( value < 0 ) ) err = kValueErr;
+ if( err )
+ {
+ FPrintF( stderr, "Invalid followPID argument \"%s\".\n", gDNSServer_FollowPID );
+ goto exit;
+ }
+ context->followPID = (pid_t) value;
+
+ err = DispatchProcessMonitorCreate( context->followPID, DISPATCH_PROC_EXIT, dispatch_get_main_queue(),
+ DNSServerCmdFollowedProcessHandler, NULL, context, &context->processMonitor );
+ require_noerr( err, exit );
+ dispatch_resume( context->processMonitor );
+ }
+ else
+ {
+ context->followPID = -1;
+ }
+#endif
+
+ signal( SIGINT, SIG_IGN );
+ err = DispatchSignalSourceCreate( SIGINT, DNSServerCmdSigIntHandler, context, &context->sigIntSource );
+ require_noerr( err, exit );
+ dispatch_resume( context->sigIntSource );
+
+ signal( SIGTERM, SIG_IGN );
+ err = DispatchSignalSourceCreate( SIGTERM, DNSServerCmdSigTermHandler, context, &context->sigTermSource );
+ require_noerr( err, exit );
+ dispatch_resume( context->sigTermSource );
+
+ if( gDNSServer_Foreground )
+ {
+ LogControl( "DNSServer:output=file;stdout,DNSServer:flags=time;prefix" );
+ }
+
+ if( ( gDNSServer_DefaultTTL < 0 ) || ( gDNSServer_DefaultTTL > INT32_MAX ) )
+ {
+ ds_ulog( kLogLevelError, "The default TTL %d provided by user is out-of-range. Will use %d instead.\n",
+ gDNSServer_DefaultTTL, kDNSServerDefaultTTL );
+ gDNSServer_DefaultTTL = kDNSServerDefaultTTL;
+ }
+
+ err = DNSServerCreate( dispatch_get_main_queue(), DNSServerCmdEventHandler, context, gDNSServer_ResponseDelayMs,
+ context->loopbackOnly, &context->server );
+ require_noerr( err, exit );
+
+ DNSServerStart( context->server );
+ dispatch_main();
+
+exit:
+ ds_ulog( kLogLevelError, "Failed to start DNS server: %#m\n", err );
+ if( context ) DNSServerCmdContextFree( context );
+ if( err ) exit( 1 );
+}
+
+//===========================================================================================================================
+// DNSServerCmdContextFree
+//===========================================================================================================================
+
+static void DNSServerCmdContextFree( DNSServerCmdContext *inContext )
+{
+ ForgetCF( &inContext->server );
+ dispatch_source_forget( &inContext->sigIntSource );
+ dispatch_source_forget( &inContext->sigTermSource );
+ dispatch_source_forget( &inContext->processMonitor );
+ free( inContext );
+}
+
+//===========================================================================================================================
+// DNSServerCmdEventHandler
+//===========================================================================================================================
+
+#if( TARGET_OS_DARWIN )
+static OSStatus _DNSServerCmdRegisterResolver( void );
+static OSStatus _DNSServerCmdUnregisterResolver( void );
+#endif
+
+static void DNSServerCmdEventHandler( DNSServerEventType inType, void *inContext )
+{
+ DNSServerCmdContext * const context = (DNSServerCmdContext *) inContext;
+#if( TARGET_OS_DARWIN )
+ OSStatus err;
+#endif
+
+ if( inType == kDNSServerEvent_Started )
+ {
+ context->serverStarted = true;
+ #if( TARGET_OS_DARWIN )
+ err = _DNSServerCmdRegisterResolver();
+ if( err )
+ {
+ ds_ulog( kLogLevelError, "Failed to add resolver to DNS configuration for \"d.test.\" domain: %#m\n", err );
+ if( context->loopbackOnly ) exit( 1 );
+ }
+ else
+ {
+ context->resolverRegistered = true;
+ }
+ #endif
+ }
+ else if( inType == kDNSServerEvent_Stopped )
+ {
+ #if( TARGET_OS_DARWIN )
+ if( context->resolverRegistered )
+ {
+ err = _DNSServerCmdUnregisterResolver();
+ if( err )
+ {
+ ds_ulog( kLogLevelError, "Failed to remove resolver from DNS configuration: %#m\n", err );
+ }
+ else
+ {
+ context->resolverRegistered = false;
+ }
+ }
+
+ if( !context->calledStop )
+ {
+ ds_ulog( kLogLevelError, "The server stopped unexpectedly.\n" );
+ exit( 1 );
+ }
+ #endif
+ DNSServerCmdContextFree( context );
+ }
+}
+
+#if( TARGET_OS_DARWIN )
+//===========================================================================================================================
+// _DNSServerCmdRegisterResolver
+//===========================================================================================================================
+
+static OSStatus _DNSServerCmdRegisterResolver( void )
+{
+ OSStatus err;
+ SCDynamicStoreRef store;
+ CFPropertyListRef plist = NULL;
+ CFStringRef key = NULL;
+ const uint32_t loopbackV4 = htonl( INADDR_LOOPBACK );
+ Boolean success;
+
+ store = SCDynamicStoreCreate( NULL, CFSTR( "com.apple.dnssdutil" ), NULL, NULL );
+ err = map_scerror( store );
+ require_noerr( err, exit );
+
+ err = CFPropertyListCreateFormatted( kCFAllocatorDefault, &plist,
+ "{"
+ "%kO="
+ "["
+ "%s"
+ "]"
+ "%kO="
+ "["
+ "%.4a"
+ "%.16a"
+ "]"
+ "}",
+ kSCPropNetDNSSupplementalMatchDomains, "d.test.",
+ kSCPropNetDNSServerAddresses, &loopbackV4, in6addr_loopback.s6_addr );
+ require_noerr( err, exit );
+
+ key = SCDynamicStoreKeyCreateNetworkServiceEntity( NULL, kSCDynamicStoreDomainState,
+ CFSTR( "com.apple.dnssdutil.server" ), kSCEntNetDNS );
+ require_action( key, exit, err = kUnknownErr );
+
+ success = SCDynamicStoreSetValue( store, key, plist );
+ require_action( success, exit, err = kUnknownErr );
+
+exit:
+ CFReleaseNullSafe( store );
+ CFReleaseNullSafe( plist );
+ CFReleaseNullSafe( key );
+ return( err );
+}
+
+//===========================================================================================================================
+// _DNSServerCmdUnregisterResolver
+//===========================================================================================================================
+
+static OSStatus _DNSServerCmdUnregisterResolver( void )
+{
+ OSStatus err;
+ SCDynamicStoreRef store;
+ CFStringRef key = NULL;
+ Boolean success;
+
+ store = SCDynamicStoreCreate( NULL, CFSTR( "com.apple.dnssdutil" ), NULL, NULL );
+ err = map_scerror( store );
+ require_noerr( err, exit );
+
+ key = SCDynamicStoreKeyCreateNetworkServiceEntity( NULL, kSCDynamicStoreDomainState,
+ CFSTR( "com.apple.dnssdutil.server" ), kSCEntNetDNS );
+ require_action( key, exit, err = kUnknownErr );
+
+ success = SCDynamicStoreRemoveValue( store, key );
+ require_action( success, exit, err = kUnknownErr );
+
+exit:
+ CFReleaseNullSafe( store );
+ CFReleaseNullSafe( key );
+ return( err );
+}
+#endif
+
+//===========================================================================================================================
+// DNSServerCmdSigIntHandler
+//===========================================================================================================================
+
+static void _DNSServerCmdExternalExit( DNSServerCmdContext *inContext, int inSignal );
+
+static void DNSServerCmdSigIntHandler( void *inContext )
+{
+ _DNSServerCmdExternalExit( (DNSServerCmdContext *) inContext, SIGINT );
+}
+
+//===========================================================================================================================
+// DNSServerCmdSigTermHandler
+//===========================================================================================================================
+
+static void DNSServerCmdSigTermHandler( void *inContext )
+{
+ _DNSServerCmdExternalExit( (DNSServerCmdContext *) inContext, SIGTERM );
+}
+
+#if( TARGET_OS_DARWIN )
+//===========================================================================================================================
+// DNSServerCmdFollowedProcessHandler
+//===========================================================================================================================
+
+static void DNSServerCmdFollowedProcessHandler( void *inContext )
+{
+ DNSServerCmdContext * const context = (DNSServerCmdContext *) inContext;
+
+ if( dispatch_source_get_data( context->processMonitor ) & DISPATCH_PROC_EXIT )
+ {
+ _DNSServerCmdExternalExit( context, 0 );
+ }
+}
+#endif
+
+//===========================================================================================================================
+// _DNSServerCmdExternalExit
+//===========================================================================================================================
+
+#define SignalNumberToString( X ) ( \
+ ( (X) == SIGINT ) ? "SIGINT" : \
+ ( (X) == SIGTERM ) ? "SIGTERM" : \
+ "???" )
+
+static void _DNSServerCmdExternalExit( DNSServerCmdContext *inContext, int inSignal )
+{
+ OSStatus err;
+
+#if( TARGET_OS_DARWIN )
+ if( inSignal == 0 )
+ {
+ ds_ulog( kLogLevelNotice, "Exiting: followed process (%lld) exited\n", (int64_t) inContext->followPID );
+ }
+ else
+#endif
+ {
+ ds_ulog( kLogLevelNotice, "Exiting: received signal %d (%s)\n", inSignal, SignalNumberToString( inSignal ) );
+ }
+
+#if( TARGET_OS_DARWIN )
+ if( inContext->resolverRegistered )
+ {
+ err = _DNSServerCmdUnregisterResolver();
+ if( err )
+ {
+ ds_ulog( kLogLevelError, "Failed to remove resolver from DNS configuration: %#m\n", err );
+ goto exit;
+ }
+ inContext->resolverRegistered = false;
+ }
+#endif
+ if( inContext->serverStarted )
+ {
+ DNSServerStop( inContext->server );
+ inContext->calledStop = true;
+ }
+ err = kNoErr;
+
+exit:
+ exit( err ? 1 : 0 );
+}
+
+//===========================================================================================================================
+// DNSServerCreate
+//===========================================================================================================================
+
+typedef struct DNSDelayedResponse DNSDelayedResponse;
+struct DNSDelayedResponse
+{
+ DNSDelayedResponse * next;
+ sockaddr_ip clientAddr;
+ uint64_t targetTicks;
+ uint8_t * msgPtr;
+ size_t msgLen;
+};
+
+#define DNSScheduledResponseFree( X ) do { ForgetMem( &(X)->msgPtr ) ; free( X ); } while( 0 )
+
+struct DNSServerPrivate
+{
+ CFRuntimeBase base; // CF object base.
+ dispatch_queue_t queue; // Queue for DNS server's events.
+ dispatch_source_t readSourceUDPv4; // Read source for IPv4 UDP socket.
+ dispatch_source_t readSourceUDPv6; // Read source for IPv6 UDP socket.
+ dispatch_source_t readSourceTCPv4; // Read source for IPv4 TCP socket.
+ dispatch_source_t readSourceTCPv6; // Read source for IPv6 TCP socket.
+ DNSServerEventHandler_f eventHandler;
+ void * eventContext;
+ DNSDelayedResponse * responseList;
+ int responseDelayMs;
+ dispatch_source_t responseTimer;
+ Boolean loopbackOnly;
+ Boolean stopped;
+};
+
+CF_CLASS_DEFINE( DNSServer );
+
+static OSStatus
+ DNSServerCreate(
+ dispatch_queue_t inQueue,
+ DNSServerEventHandler_f inEventHandler,
+ void * inEventContext,
+ int inResponseDelayMs,
+ Boolean inLoopbackOnly,
+ DNSServerRef * outServer )
+{
+ OSStatus err;
+ DNSServerRef obj = NULL;
+
+ CF_OBJECT_CREATE( DNSServer, obj, err, exit );
+
+ ReplaceDispatchQueue( &obj->queue, inQueue );
+ obj->eventHandler = inEventHandler;
+ obj->eventContext = inEventContext;
+ obj->responseDelayMs = inResponseDelayMs;
+ if( inLoopbackOnly ) obj->loopbackOnly = true;
+
+ *outServer = obj;
+ obj = NULL;
+ err = kNoErr;
+
+exit:
+ CFReleaseNullSafe( obj );
+ return( err );
+}
+
+//===========================================================================================================================
+// _DNSServerFinalize
+//===========================================================================================================================
+
+static void _DNSServerFinalize( CFTypeRef inObj )
+{
+ DNSServerRef const me = (DNSServerRef) inObj;
+
+ check( !me->readSourceUDPv4 );
+ check( !me->readSourceUDPv6 );
+ check( !me->readSourceTCPv4 );
+ check( !me->readSourceTCPv6 );
+ check( !me->responseTimer );
+ dispatch_forget( &me->queue );
+}
+
+//===========================================================================================================================
+// DNSServerStart
+//===========================================================================================================================
+
+static void _DNSServerStart( void *inContext );
+static void _DNSServerUDPReadHandler( void *inContext );
+static void _DNSServerTCPReadHandler( void *inContext );
+
+static void DNSServerStart( DNSServerRef me )
+{
+ CFRetain( me );
+ dispatch_async_f( me->queue, me, _DNSServerStart );
+}
+
+static void _DNSServerStart( void *inContext )
+{
+ OSStatus err;
+ DNSServerRef const me = (DNSServerRef) inContext;
+ SocketRef sock = kInvalidSocketRef;
+ SocketContext * sockCtx = NULL;
+ const uint32_t loopbackV4 = htonl( INADDR_LOOPBACK );
+
+ // Create IPv4 UDP socket.
+
+ err = _ServerSocketOpenEx2( AF_INET, SOCK_DGRAM, IPPROTO_UDP, me->loopbackOnly ? &loopbackV4 : NULL,
+ kDNSPort, NULL, kSocketBufferSize_DontSet, me->loopbackOnly ? true : false, &sock );
+ require_noerr( err, exit );
+
+ // Create read source for IPv4 UDP socket.
+
+ err = SocketContextCreate( sock, me, &sockCtx );
+ require_noerr( err, exit );
+ sock = kInvalidSocketRef;
+
+ err = DispatchReadSourceCreate( sockCtx->sock, me->queue, _DNSServerUDPReadHandler, SocketContextCancelHandler, sockCtx,
+ &me->readSourceUDPv4 );
+ require_noerr( err, exit );
+ dispatch_resume( me->readSourceUDPv4 );
+ sockCtx = NULL;
+
+ // Create IPv6 UDP socket.
+
+ err = _ServerSocketOpenEx2( AF_INET6, SOCK_DGRAM, IPPROTO_UDP, me->loopbackOnly ? &in6addr_loopback : NULL,
+ kDNSPort, NULL, kSocketBufferSize_DontSet, me->loopbackOnly ? true : false, &sock );
+ require_noerr( err, exit );
+
+ // Create read source for IPv6 UDP socket.
+
+ err = SocketContextCreate( sock, me, &sockCtx );
+ require_noerr( err, exit );
+ sock = kInvalidSocketRef;
+
+ err = DispatchReadSourceCreate( sockCtx->sock, me->queue, _DNSServerUDPReadHandler, SocketContextCancelHandler, sockCtx,
+ &me->readSourceUDPv6 );
+ require_noerr( err, exit );
+ dispatch_resume( me->readSourceUDPv6 );
+ sockCtx = NULL;
+
+ // Create IPv4 TCP socket.
+
+ err = _ServerSocketOpenEx2( AF_INET, SOCK_STREAM, IPPROTO_TCP, me->loopbackOnly ? &loopbackV4 : NULL,
+ kDNSPort, NULL, kSocketBufferSize_DontSet, false, &sock );
+ require_noerr( err, exit );
+
+ // Create read source for IPv4 TCP socket.
+
+ err = SocketContextCreate( sock, me, &sockCtx );
+ require_noerr( err, exit );
+ sock = kInvalidSocketRef;
+
+ err = DispatchReadSourceCreate( sockCtx->sock, me->queue, _DNSServerTCPReadHandler, SocketContextCancelHandler, sockCtx,
+ &me->readSourceTCPv4 );
+ require_noerr( err, exit );
+ dispatch_resume( me->readSourceTCPv4 );
+ sockCtx = NULL;
+
+ // Create IPv6 TCP socket.
+
+ err = _ServerSocketOpenEx2( AF_INET6, SOCK_STREAM, IPPROTO_TCP, me->loopbackOnly ? &in6addr_loopback : NULL,
+ kDNSPort, NULL, kSocketBufferSize_DontSet, false, &sock );
+ require_noerr( err, exit );
+
+ // Create read source for IPv6 TCP socket.
+
+ err = SocketContextCreate( sock, me, &sockCtx );
+ require_noerr( err, exit );
+ sock = kInvalidSocketRef;
+
+ err = DispatchReadSourceCreate( sockCtx->sock, me->queue, _DNSServerTCPReadHandler, SocketContextCancelHandler, sockCtx,
+ &me->readSourceTCPv6 );
+ require_noerr( err, exit );
+ dispatch_resume( me->readSourceTCPv6 );
+ sockCtx = NULL;
+
+ CFRetain( me );
+ if( me->eventHandler ) me->eventHandler( kDNSServerEvent_Started, me->eventContext );
+
+exit:
+ ForgetSocket( &sock );
+ if( sockCtx ) SocketContextRelease( sockCtx );
+ if( err ) DNSServerStop( me );
+ CFRelease( me );
+}
+
+//===========================================================================================================================
+// DNSServerStop
+//===========================================================================================================================
+
+static void _DNSServerStop( void *inContext );
+static void _DNSServerStop2( void *inContext );
+
+static void DNSServerStop( DNSServerRef me )
+{
+ CFRetain( me );
+ dispatch_async_f( me->queue, me, _DNSServerStop );
+}
+
+static void _DNSServerStop( void *inContext )
+{
+ DNSServerRef const me = (DNSServerRef) inContext;
+ DNSDelayedResponse * resp;
+
+ dispatch_source_forget( &me->readSourceUDPv4 );
+ dispatch_source_forget( &me->readSourceUDPv6 );
+ dispatch_source_forget( &me->readSourceTCPv4 );
+ dispatch_source_forget( &me->readSourceTCPv6 );
+ dispatch_source_forget( &me->responseTimer );
+
+ while( ( resp = me->responseList ) != NULL )
+ {
+ me->responseList = resp->next;
+ DNSScheduledResponseFree( resp );
+ }
+
+ dispatch_async_f( me->queue, me, _DNSServerStop2 );
+}
+
+static void _DNSServerStop2( void *inContext )
+{
+ DNSServerRef const me = (DNSServerRef) inContext;
+
+ if( !me->stopped )
+ {
+ me->stopped = true;
+ if( me->eventHandler ) me->eventHandler( kDNSServerEvent_Stopped, me->eventContext );
+ CFRelease( me );
+ }
+ CFRelease( me );
+}
+
+//===========================================================================================================================
+// _DNSServerUDPReadHandler
+//===========================================================================================================================
+
+static OSStatus
+ _DNSServerAnswerQuery(
+ const uint8_t * inQueryPtr,
+ size_t inQueryLen,
+ Boolean inForTCP,
+ uint8_t ** outResponsePtr,
+ size_t * outResponseLen );
+
+#define _DNSServerAnswerQueryForUDP( IN_QUERY_PTR, IN_QUERY_LEN, IN_RESPONSE_PTR, IN_RESPONSE_LEN ) \
+ _DNSServerAnswerQuery( IN_QUERY_PTR, IN_QUERY_LEN, false, IN_RESPONSE_PTR, IN_RESPONSE_LEN )
+
+#define _DNSServerAnswerQueryForTCP( IN_QUERY_PTR, IN_QUERY_LEN, IN_RESPONSE_PTR, IN_RESPONSE_LEN ) \
+ _DNSServerAnswerQuery( IN_QUERY_PTR, IN_QUERY_LEN, true, IN_RESPONSE_PTR, IN_RESPONSE_LEN )
+
+static void _DNSServerUDPDelayedSend( void *inContext );
+
+static void _DNSServerUDPReadHandler( void *inContext )
+{
+ OSStatus err;
+ SocketContext * const sockCtx = (SocketContext *) inContext;
+ DNSServerRef const me = (DNSServerRef) sockCtx->userContext;
+ struct timeval now;
+ ssize_t n;
+ sockaddr_ip clientAddr;
+ socklen_t clientAddrLen;
+ uint8_t * responsePtr = NULL; // malloc'd
+ size_t responseLen;
+ uint8_t msg[ 512 ];
+
+ gettimeofday( &now, NULL );
+
+ // Receive message.
+
+ clientAddrLen = (socklen_t) sizeof( clientAddr );
+ n = recvfrom( sockCtx->sock, (char *) msg, sizeof( msg ), 0, &clientAddr.sa, &clientAddrLen );
+ err = map_socket_value_errno( sockCtx->sock, n >= 0, n );
+ require_noerr( err, exit );
+
+ ds_ulog( kLogLevelInfo, "UDP server received %zd bytes from %##a at %{du:time}.\n", n, &clientAddr, &now );
+
+ if( n < kDNSHeaderLength )
+ {
+ ds_ulog( kLogLevelInfo, "UDP DNS message is too small (%zd < %d).\n", n, kDNSHeaderLength );
+ goto exit;
+ }
+
+ ds_ulog( kLogLevelInfo, "UDP received message:\n\n%1{du:dnsmsg}", msg, (size_t) n );
+
+ // Create response.
+
+ err = _DNSServerAnswerQueryForUDP( msg, (size_t) n, &responsePtr, &responseLen );
+ require_noerr_quiet( err, exit );
+
+ // Schedule response.
+
+ if( me->responseDelayMs > 0 )
+ {
+ DNSDelayedResponse * resp;
+ DNSDelayedResponse ** ptr;
+ DNSDelayedResponse * newResp;
+
+ newResp = (DNSDelayedResponse *) calloc( 1, sizeof( *newResp ) );
+ require_action( newResp, exit, err = kNoMemoryErr );
+
+ SockAddrCopy( &clientAddr, &newResp->clientAddr );
+ newResp->targetTicks = UpTicks() + MillisecondsToUpTicks( (uint64_t) me->responseDelayMs );
+ newResp->msgLen = responseLen;
+ newResp->msgPtr = responsePtr;
+ responsePtr = NULL;
+
+ for( ptr = &me->responseList; ( resp = *ptr ) != NULL; ptr = &resp->next )
+ {
+ if( newResp->targetTicks < resp->targetTicks ) break;
+ }
+
+ newResp->next = resp;
+ *ptr = newResp;
+
+ if( me->responseList == newResp )
+ {
+ dispatch_source_forget( &me->responseTimer );
+
+ err = DispatchTimerCreate( dispatch_time_milliseconds( me->responseDelayMs ), DISPATCH_TIME_FOREVER,
+ ( (uint64_t) me->responseDelayMs ) * kNanosecondsPerMillisecond / 10, me->queue,
+ _DNSServerUDPDelayedSend, NULL, sockCtx, &me->responseTimer );
+ require_noerr( err, exit );
+ dispatch_resume( me->responseTimer );
+ }
+ }
+ else
+ {
+ ds_ulog( kLogLevelInfo, "UDP sending %zu byte response:\n\n%1{du:dnsmsg}", responseLen, responsePtr, responseLen );
+
+ n = sendto( sockCtx->sock, (char *) responsePtr, responseLen, 0, &clientAddr.sa, clientAddrLen );
+ err = map_socket_value_errno( sockCtx->sock, n == (ssize_t) responseLen, n );
+ require_noerr( err, exit );
+ }
+
+exit:
+ FreeNullSafe( responsePtr );
+ return;
+}
+
+static void _DNSServerUDPDelayedSend( void *inContext )
+{
+ OSStatus err;
+ SocketContext * const sockCtx = (SocketContext *) inContext;
+ DNSServerRef const me = (DNSServerRef) sockCtx->userContext;
+ DNSDelayedResponse * resp;
+ ssize_t n;
+ uint64_t nowTicks;
+ DNSDelayedResponse * freeList = NULL;
+
+ dispatch_source_forget( &me->responseTimer );
+
+ nowTicks = UpTicks();
+ while( ( resp = me->responseList ) != NULL )
+ {
+ if( resp->targetTicks > nowTicks ) break;
+ me->responseList = resp->next;
+
+ ds_ulog( kLogLevelInfo, "UDP sending %zu byte response (delayed):\n\n%1{du:dnsmsg}",
+ resp->msgLen, resp->msgPtr, resp->msgLen );
+
+ n = sendto( sockCtx->sock, (char *) resp->msgPtr, resp->msgLen, 0, &resp->clientAddr.sa,
+ SockAddrGetSize( &resp->clientAddr ) );
+ err = map_socket_value_errno( sockCtx->sock, n == (ssize_t) resp->msgLen, n );
+ check_noerr( err );
+
+ resp->next = freeList;
+ freeList = resp;
+ nowTicks = UpTicks();
+ }
+
+ if( ( resp = me->responseList ) != NULL )
+ {
+ uint64_t remainingNs;
+
+ remainingNs = UpTicksToNanoseconds( resp->targetTicks - nowTicks );
+ if( remainingNs > INT64_MAX ) remainingNs = INT64_MAX;
+
+ err = DispatchTimerCreate( dispatch_time( DISPATCH_TIME_NOW, (int64_t) remainingNs ), DISPATCH_TIME_FOREVER, 0,
+ me->queue, _DNSServerUDPDelayedSend, NULL, sockCtx, &me->responseTimer );
+ require_noerr( err, exit );
+ dispatch_resume( me->responseTimer );
+ }
+
+exit:
+ while( ( resp = freeList ) != NULL )
+ {
+ freeList = resp->next;
+ DNSScheduledResponseFree( resp );
+ }
+}
+
+//===========================================================================================================================
+// _DNSServerAnswerQuery
+//===========================================================================================================================
+
+#define kLabelPrefix_Alias "alias"
+#define kLabelPrefix_AliasTTL "alias-ttl"
+#define kLabelPrefix_Count "count"
+#define kLabelPrefix_TTL "ttl"
+#define kLabel_IPv4 "ipv4"
+#define kLabel_IPv6 "ipv6"
+
+#define kMaxAliasTTLCount ( ( kDomainLabelLengthMax - sizeof_string( kLabelPrefix_AliasTTL ) ) / 2 )
+
+static OSStatus
+ _DNSServerInitializeResponseMessage(
+ DataBuffer * inDB,
+ unsigned int inID,
+ unsigned int inFlags,
+ const uint8_t * inQName,
+ unsigned int inQType,
+ unsigned int inQClass );
+static OSStatus
+ _DNSServerAnswerQueryDynamically(
+ const uint8_t * inQName,
+ unsigned int inQType,
+ unsigned int inQClass,
+ Boolean inForTCP,
+ DataBuffer * inDB );
+
+static OSStatus
+ _DNSServerAnswerQuery(
+ const uint8_t * const inQueryPtr,
+ const size_t inQueryLen,
+ Boolean inForTCP,
+ uint8_t ** outResponsePtr,
+ size_t * outResponseLen )
+{
+ OSStatus err;
+ DataBuffer dataBuf;
+ const uint8_t * ptr;
+ const uint8_t * const queryEnd = &inQueryPtr[ inQueryLen ];
+ const DNSHeader * qhdr;
+ unsigned int msgID, qflags, qtype, qclass, rflags;
+ uint8_t qname[ kDomainNameLengthMax ];
+
+ DataBuffer_Init( &dataBuf, NULL, 0, kDNSMaxTCPMessageSize );
+
+ require_action_quiet( inQueryLen >= kDNSHeaderLength, exit, err = kUnderrunErr );
+
+ qhdr = (const DNSHeader *) inQueryPtr;
+ msgID = DNSHeaderGetID( qhdr );
+ qflags = DNSHeaderGetFlags( qhdr );
+
+ // Minimal checking of the query message's header.
+
+ if( ( qflags & kDNSHeaderFlag_Response ) || // The message must be a query, not a response.
+ ( DNSFlagsGetOpCode( qflags ) != kDNSOpCode_Query ) || // OPCODE must be QUERY (standard query).
+ ( DNSHeaderGetQuestionCount( qhdr ) != 1 ) ) // There should be a single question.
+ {
+ err = kRequestErr;
+ goto exit;
+ }
+
+ // Get QNAME.
+
+ ptr = (const uint8_t *) &qhdr[ 1 ];
+ err = DNSMessageExtractDomainName( inQueryPtr, inQueryLen, ptr, qname, &ptr );
+ require_noerr( err, exit );
+
+ // Get QTYPE and QCLASS.
+
+ require_action_quiet( ( queryEnd - ptr ) >= 4, exit, err = kUnderrunErr );
+ qtype = DNSQuestionFixedFieldsGetType( (const DNSQuestionFixedFields *) ptr );
+ qclass = DNSQuestionFixedFieldsGetClass( (const DNSQuestionFixedFields *) ptr );
+ ptr += 4;
+
+ // Create a tentative response message.
+
+ rflags = kDNSHeaderFlag_Response;
+ if( qflags & kDNSHeaderFlag_RecursionDesired ) rflags |= kDNSHeaderFlag_RecursionDesired;
+ DNSFlagsSetOpCode( rflags, kDNSOpCode_Query );
+
+ err = _DNSServerInitializeResponseMessage( &dataBuf, msgID, rflags, qname, qtype, qclass );
+ require_noerr( err, exit );
+
+ err = _DNSServerAnswerQueryDynamically( qname, qtype, qclass, inForTCP, &dataBuf );
+ if( err )
+ {
+ DNSFlagsSetRCode( rflags, kDNSRCode_ServerFailure );
+ err = _DNSServerInitializeResponseMessage( &dataBuf, msgID, rflags, qname, qtype, qclass );
+ require_noerr( err, exit );
+ }
+
+ err = DataBuffer_Detach( &dataBuf, outResponsePtr, outResponseLen );
+ require_noerr( err, exit );
+
+exit:
+ DataBuffer_Free( &dataBuf );
+ return( err );
+}
+
+static OSStatus
+ _DNSServerInitializeResponseMessage(
+ DataBuffer * inDB,
+ unsigned int inID,
+ unsigned int inFlags,
+ const uint8_t * inQName,
+ unsigned int inQType,
+ unsigned int inQClass )
+{
+ OSStatus err;
+ DNSHeader header;
+ DNSQuestionFixedFields fields;
+
+ DataBuffer_Reset( inDB );
+
+ memset( &header, 0, sizeof( header ) );
+ DNSHeaderSetID( &header, inID );
+ DNSHeaderSetFlags( &header, inFlags );
+ DNSHeaderSetQuestionCount( &header, 1 );
+
+ err = DataBuffer_Append( inDB, &header, sizeof( header ) );
+ require_noerr( err, exit );
+
+ err = DataBuffer_Append( inDB, inQName, DomainNameLength( inQName ) );
+ require_noerr( err, exit );
+
+ DNSQuestionFixedFieldsInit( &fields, inQType, inQClass );
+ err = DataBuffer_Append( inDB, &fields, sizeof( fields ) );
+ require_noerr( err, exit );
+
+exit:
+ return( err );
+}
+
+static OSStatus
+ _DNSServerAnswerQueryDynamically(
+ const uint8_t * const inQName,
+ const unsigned int inQType,
+ const unsigned int inQClass,
+ const Boolean inForTCP,
+ DataBuffer * const inDB )
+{
+ OSStatus err; // General-purpose error variable.
+ const uint8_t * labelPtr; // QNAME label pointer.
+ size_t labelLen; // QNAME label length.
+ DNSHeader * hdr; // Response header pointer.
+ unsigned int flags; // Response header flags.
+ unsigned int rcode; // Response header response code.
+ unsigned int answerCount = 0; // Number of answers contained in response.
+ int32_t aliasCount = -1; // Arg from "alias" label. Valid values are in [2 .. 2^31 - 1].
+ int count = -1; // First arg from "count" label. Valid values are in [1 .. 255].
+ int randCount = -1; // Second arg from "count" label. Valid values are in [1 .. 255].
+ int32_t ttl = -1; // Arg from "ttl" label. Valid values are in [0 .. 2^31 - 1].
+ uint32_t aliasTTLs[ kMaxAliasTTLCount ]; // Args from "alias-ttl" label. Valid values are in [0 .. 2^31 - 1].
+ int i; // General-purpose array index.
+ Boolean useAliasTTLs = false; // True if QNAME contained a valid "alias-ttl" label.
+ Boolean nameExists = false; // True if name specified by QNAME exists.
+ Boolean nameHasA = false; // True if name specified by QNAME has an A record.
+ Boolean nameHasAAAA = false; // True if name specified by QNAME has a AAAA record.
+ Boolean notImplemented = false; // True if the kind of the query is not supported.
+ Boolean truncated = false; // True if the response message is truncated.
+ uint8_t namePtr[ 2 ]; // Name compression pointer.
+
+ if( inQClass != kDNSServiceClass_IN )
+ {
+ notImplemented = true;
+ goto done;
+ }
+
+ for( labelPtr = inQName; ( labelLen = *labelPtr ) != 0; labelPtr += ( 1 + labelLen ) )
+ {
+ const char * const labelStr = (const char *) &labelPtr[ 1 ];
+ const char * next;
+ long long arg;
+ int n;
+
+ require_action( labelLen <= kDomainNameLengthMax, exit, err = kUnexpectedErr );
+
+ // Check if the first label is a valid alias TTL sequence label.
+
+ if( ( labelPtr == inQName ) && ( strnicmp_prefix( labelStr, labelLen, kLabelPrefix_AliasTTL ) == 0 ) )
+ {
+ const char * src = &labelStr[ sizeof_string( kLabelPrefix_AliasTTL ) ];
+ const char * const end = &labelStr[ labelLen ];
+ int argCount = 0;
+
+ while( src < end )
+ {
+ n = SNScanF( src, (size_t)( end - src ), "-%10lld%#n", &arg, &next );
+ if( n != 1 ) break;
+ if( ( arg < 0 ) || ( arg > INT32_MAX ) ) break; // TTL must be >= 0 and <= (2^31 - 1).
+ aliasTTLs[ argCount++ ] = (uint32_t) arg;
+ src = next;
+ }
+ if( ( argCount > 0 ) && ( src == end ) )
+ {
+ aliasCount = argCount;
+ useAliasTTLs = true;
+ continue;
+ }
+ }
+
+ // Check if the first label is a valid alias label.
+
+ if( ( labelPtr == inQName ) && ( strnicmp_prefix( labelStr, labelLen, kLabelPrefix_Alias ) == 0 ) )
+ {
+ const char * src = &labelStr[ sizeof_string( kLabelPrefix_Alias ) ];
+ const char * const end = &labelStr[ labelLen ];
+
+ if( src == end )
+ {
+ aliasCount = 1;
+ continue;
+ }
+
+ n = SNScanF( src, (size_t)( end - src ), "-%10lld%#n", &arg, &next );
+ if( ( n == 1 ) && ( next == end ) )
+ {
+ if( ( arg < 2 ) || ( arg > INT32_MAX ) ) break; // Alias count must be >= 2 and <= (2^31 - 1).
+ aliasCount = (int32_t) arg;
+ continue;
+ }
+ }
+
+ // Check if the label is a valid count label.
+
+ if( strnicmp_prefix( labelStr, labelLen, kLabelPrefix_Count ) == 0 )
+ {
+ const char * src = &labelStr[ sizeof_string( kLabelPrefix_Count ) ];
+ const char * const end = &labelStr[ labelLen ];
+
+ n = SNScanF( src, (size_t)( end - src ), "-%3lld%#n", &arg, &next );
+ if( n == 1 )
+ {
+ if( count > 0 ) break; // Count cannot be specified more than once.
+ if( ( arg < 1 ) || ( arg > 255 ) ) break; // Count must be >= 1 and <= 255.
+ count = (int) arg;
+
+ src = next;
+ if( src < end )
+ {
+ n = SNScanF( src, (size_t)( end - src ), "-%3lld%#n", &arg, &next );
+ if( ( n != 1 ) || ( next != end ) ) break;
+ if( ( arg < count ) || ( arg > 255 ) ) break; // Rand count must be >= count and <= 255.
+ randCount = (int) arg;
+ }
+ continue;
+ }
+ }
+
+ // Check if the label is a valid tag label.
+
+ if( strnicmp_prefix( labelStr, labelLen, "tag-" ) == 0 ) continue;
+
+ // Check if the label is a valid TTL label.
+
+ if( strnicmp_prefix( labelStr, labelLen, kLabelPrefix_TTL ) == 0 )
+ {
+ const char * src = &labelStr[ sizeof_string( kLabelPrefix_TTL ) ];
+ const char * const end = &labelStr[ labelLen ];
+
+ n = SNScanF( src, (size_t)( end - src ), "-%10lld%#n", &arg, &next );
+ if( ( n == 1 ) && ( next == end ) )
+ {
+ if( ttl >= 0 ) break; // TTL cannot be specified more than once.
+ if( ( arg < 0 ) || ( arg > INT32_MAX ) ) break; // TTL must be >= 0 and <= (2^31 - 1).
+ ttl = (int32_t) arg;
+ continue;
+ }
+ }
+
+ // Check if the label is a valid IPv4 or IPv6 label.
+
+ if( MemIEqual( labelStr, labelLen, kLabel_IPv4, sizeof_string( kLabel_IPv4 ) ) )
+ {
+ if( nameHasA || nameHasAAAA ) break; // Valid names have at most one IPv4 or IPv6 label.
+ nameHasA = true;
+ continue;
+ }
+ if( MemIEqual( labelStr, labelLen, kLabel_IPv6, sizeof_string( kLabel_IPv6 ) ) )
+ {
+ if( nameHasA || nameHasAAAA ) break; // Valid names have at most one IPv4 or IPv6 label.
+ nameHasAAAA = true;
+ continue;
+ }
+
+ // If the remaining labels are equal to "d.test.", the name exists.
+
+ if( DomainNameEqual( labelPtr, (const uint8_t *) "\x01" "d" "\x04" "test" ) ) nameExists = true;
+ break;
+ }
+ require_quiet( nameExists, done );
+
+ // Set default values for count and TTL, if those labels were present.
+
+ if( count <= 0 ) count = 1;
+ check( ( gDNSServer_DefaultTTL >= 0 ) && ( gDNSServer_DefaultTTL <= INT32_MAX ) );
+ if( ttl < 0 ) ttl = gDNSServer_DefaultTTL;
+
+ // Names that don't specify v4 or v6 have both A and AAAA records.
+
+ if( !nameHasA && !nameHasAAAA )
+ {
+ nameHasA = true;
+ nameHasAAAA = true;
+ }
+
+ check( ( count >= 1 ) && ( count <= 255 ) );
+ check( ( randCount <= 0 ) || ( ( randCount >= count ) && ( randCount <= 255 ) ) );
+
+ if( aliasCount > 0 )
+ {
+ size_t nameOffset;
+ uint8_t rdataLabel[ 1 + kDomainLabelLengthMax + 1 ];
+
+ // If aliasCount is non-zero, then the first label of QNAME is either "alias" or "alias-<N>". superPtr is a name
+ // compression pointer to the second label of QNAME, i.e., the immediate superdomain name of QNAME. It's used for
+ // the RDATA of CNAME records whose canonical name ends with the superdomain name. It may also be used to construct
+ // CNAME record names, when the offset to the previous CNAME's RDATA doesn't fit in a compression pointer.
+
+ const uint8_t superPtr[ 2 ] = { 0xC0, (uint8_t)( kDNSHeaderLength + 1 + inQName[ 0 ] ) };
+
+ // The name of the first CNAME record is equal to QNAME, so nameOffset is set to offset of QNAME.
+
+ nameOffset = kDNSHeaderLength;
+
+ for( i = aliasCount; i >= 1; --i )
+ {
+ size_t nameLen;
+ size_t rdataLen;
+ int j;
+ uint32_t aliasTTL;
+ uint8_t nameLabel[ 1 + kDomainLabelLengthMax + 1 ];
+ DNSRecordFixedFields fields;
+
+ if( nameOffset <= kDNSCompressionOffsetMax )
+ {
+ namePtr[ 0 ] = (uint8_t)( ( ( nameOffset >> 8 ) & 0x3F ) | 0xC0 );
+ namePtr[ 1 ] = (uint8_t)( nameOffset & 0xFF );
+
+ nameLen = sizeof( namePtr );
+ }
+ else
+ {
+ memcpy( nameLabel, rdataLabel, 1 + rdataLabel[ 0 ] );
+ nameLen = 1 + nameLabel[ 0 ] + sizeof( superPtr );
+ }
+
+ if( i >= 2 )
+ {
+ char * dst = (char *) &rdataLabel[ 1 ];
+ char * const end = (char *) &rdataLabel[ countof( rdataLabel ) ];
+
+ if( useAliasTTLs )
+ {
+ err = SNPrintF_Add( &dst, end, kLabelPrefix_AliasTTL );
+ require_noerr( err, exit );
+
+ for( j = aliasCount - ( i - 1 ); j < aliasCount; ++j )
+ {
+ err = SNPrintF_Add( &dst, end, "-%u", aliasTTLs[ j ] );
+ require_noerr( err, exit );
+ }
+ }
+ else
+ {
+ err = SNPrintF_Add( &dst, end, kLabelPrefix_Alias "%?{end}-%u", i == 2, i - 1 );
+ require_noerr( err, exit );
+ }
+ rdataLabel[ 0 ] = (uint8_t)( dst - (char *) &rdataLabel[ 1 ] );
+ rdataLen = 1 + rdataLabel[ 0 ] + sizeof( superPtr );
+ }
+ else
+ {
+ rdataLen = sizeof( superPtr );
+ }
+
+ if( !inForTCP )
+ {
+ size_t recordLen = nameLen + sizeof( fields ) + rdataLen;
+
+ if( ( DataBuffer_GetLen( inDB ) + recordLen ) > kDNSMaxUDPMessageSize )
+ {
+ truncated = true;
+ goto done;
+ }
+ }
+ ++answerCount;
+
+ // Set CNAME record's NAME.
+
+ if( nameOffset <= kDNSCompressionOffsetMax )
+ {
+ err = DataBuffer_Append( inDB, namePtr, sizeof( namePtr ) );
+ require_noerr( err, exit );
+ }
+ else
+ {
+ err = DataBuffer_Append( inDB, nameLabel, 1 + nameLabel[ 0 ] );
+ require_noerr( err, exit );
+
+ err = DataBuffer_Append( inDB, superPtr, sizeof( superPtr ) );
+ require_noerr( err, exit );
+ }
+
+ // Set CNAME record's TYPE, CLASS, TTL, and RDLENGTH.
+
+ aliasTTL = useAliasTTLs ? aliasTTLs[ aliasCount - i ] : ( (uint32_t) gDNSServer_DefaultTTL );
+ DNSRecordFixedFieldsInit( &fields, kDNSServiceType_CNAME, kDNSServiceClass_IN, aliasTTL, rdataLen );
+ err = DataBuffer_Append( inDB, &fields, sizeof( fields ) );
+ require_noerr( err, exit );
+
+ // Save offset of CNAME record's RDATA, which may be used for the name of the next CNAME record.
+
+ nameOffset = DataBuffer_GetLen( inDB );
+
+ // Set CNAME record's RDATA.
+
+ if( i >= 2 )
+ {
+ err = DataBuffer_Append( inDB, rdataLabel, 1 + rdataLabel[ 0 ] );
+ require_noerr( err, exit );
+ }
+ err = DataBuffer_Append( inDB, superPtr, sizeof( superPtr ) );
+ require_noerr( err, exit );
+ }
+
+ namePtr[ 0 ] = superPtr[ 0 ];
+ namePtr[ 1 ] = superPtr[ 1 ];
+ }
+ else
+ {
+ // There are no aliases, so initialize the name compression pointer to point to QNAME.
+
+ namePtr[ 0 ] = 0xC0;
+ namePtr[ 1 ] = kDNSHeaderLength;
+ }
+
+ if( ( ( inQType == kDNSServiceType_A ) && nameHasA ) ||
+ ( ( inQType == kDNSServiceType_AAAA ) && nameHasAAAA ) )
+ {
+ uint8_t * lsb; // Pointer to the least significant byte of record data.
+ size_t recordLen; // Length of the entire record.
+ size_t rdataLen; // Length of record's RDATA.
+ uint8_t rdata[ 16 ]; // A buffer that's big enough for either A or AAAA RDATA.
+ uint8_t randItegers[ 255 ]; // Array for random integers in [1 .. 255].
+ DNSRecordFixedFields fields;
+
+ if( inQType == kDNSServiceType_A )
+ {
+ rdataLen = 4;
+ WriteBig32( rdata, kTestDNSServerBaseAddrV4 );
+ lsb = &rdata[ 3 ];
+ }
+ else
+ {
+ rdataLen = 16;
+ memcpy( rdata, kTestDNSServerBaseAddrV6, 16 );
+ lsb = &rdata[ 15 ];
+ }
+
+ if( randCount > 0 )
+ {
+ // Populate the array with all integers between 1 and <randCount>, inclusive.
+
+ for( i = 0; i < randCount; ++i ) randItegers[ i ] = (uint8_t)( i + 1 );
+
+ // Create a contiguous subarray starting at index 0 that contains <count> randomly chosen integers between
+ // 1 and <randCount>, inclusive.
+ // Loop invariant 1: Array elements with indexes in [0 .. i - 1] have been randomly chosen.
+ // Loop invariant 2: Array elements with indexes in [i .. randCount - 1] are candidates for being chosen.
+
+ for( i = 0; i < count; ++i )
+ {
+ uint8_t tmp;
+ int j;
+
+ j = (int) RandomRange( i, randCount - 1 );
+ if( i != j )
+ {
+ tmp = randItegers[ i ];
+ randItegers[ i ] = randItegers[ j ];
+ randItegers[ j ] = tmp;
+ }
+ }
+ }
+
+ recordLen = sizeof( namePtr ) + sizeof( fields ) + rdataLen;
+ for( i = 0; i < count; ++i )
+ {
+ if( !inForTCP && ( ( DataBuffer_GetLen( inDB ) + recordLen ) > kDNSMaxUDPMessageSize ) )
+ {
+ truncated = true;
+ goto done;
+ }
+ ++answerCount;
+
+ // Set record NAME.
+
+ err = DataBuffer_Append( inDB, namePtr, sizeof( namePtr ) );
+ require_noerr( err, exit );
+
+ // Set record TYPE, CLASS, TTL, and RDLENGTH.
+
+ DNSRecordFixedFieldsInit( &fields, inQType, kDNSServiceClass_IN, ttl, rdataLen );
+ err = DataBuffer_Append( inDB, &fields, sizeof( fields ) );
+ require_noerr( err, exit );
+
+ // Set record RDATA.
+
+ *lsb = ( randCount > 0 ) ? randItegers[ i ] : ( *lsb + 1 );
+
+ err = DataBuffer_Append( inDB, rdata, rdataLen );
+ require_noerr( err, exit );
+ }
+ }
+
+done:
+ hdr = (DNSHeader *) DataBuffer_GetPtr( inDB );
+ flags = DNSHeaderGetFlags( hdr );
+ if( truncated ) flags |= kDNSHeaderFlag_Truncation;
+ if( notImplemented )
+ {
+ rcode = kDNSRCode_NotImplemented;
+ }
+ else
+ {
+ flags |= kDNSHeaderFlag_AuthAnswer;
+ rcode = nameExists ? kDNSRCode_NoError : kDNSRCode_NXDomain;
+ }
+ DNSFlagsSetRCode( flags, rcode );
+ DNSHeaderSetFlags( hdr, flags );
+ DNSHeaderSetAnswerCount( hdr, answerCount );
+ err = kNoErr;
+
+exit:
+ return( err );
+}
+
+//===========================================================================================================================
+// _DNSServerTCPReadHandler
+//===========================================================================================================================
+
+typedef struct
+{
+ sockaddr_ip clientAddr; // Client's address.
+ dispatch_source_t readSource; // Dispatch read source for client socket.
+ dispatch_source_t writeSource; // Dispatch write source for client socket.
+ size_t offset; // Offset into receive buffer.
+ void * msgPtr; // Pointer to dynamically allocated message buffer.
+ size_t msgLen; // Length of message buffer.
+ Boolean readSuspended; // True if the read source is currently suspended.
+ Boolean writeSuspended; // True if the write source is currently suspended.
+ Boolean receivedLength; // True if receiving DNS message as opposed to the message length.
+ uint8_t lenBuf[ 2 ]; // Buffer for two-octet message length field.
+ iovec_t iov[ 2 ]; // IO vector for writing response message.
+ iovec_t * iovPtr; // Vector pointer for SocketWriteData().
+ int iovCount; // Vector count for SocketWriteData().
+
+} TCPConnectionContext;
+
+static void TCPConnectionStop( TCPConnectionContext *inContext );
+static void TCPConnectionContextFree( TCPConnectionContext *inContext );
+static void TCPConnectionReadHandler( void *inContext );
+static void TCPConnectionWriteHandler( void *inContext );
+
+#define TCPConnectionForget( X ) ForgetCustomEx( X, TCPConnectionStop, TCPConnectionContextFree )
+
+static void _DNSServerTCPReadHandler( void *inContext )
+{
+ OSStatus err;
+ SocketContext * const sockCtx = (SocketContext *) inContext;
+ TCPConnectionContext * connection;
+ socklen_t clientAddrLen;
+ SocketRef newSock = kInvalidSocketRef;
+ SocketContext * newSockCtx = NULL;
+
+ connection = (TCPConnectionContext *) calloc( 1, sizeof( *connection ) );
+ require_action( connection, exit, err = kNoMemoryErr );
+
+ clientAddrLen = (socklen_t) sizeof( connection->clientAddr );
+ newSock = accept( sockCtx->sock, &connection->clientAddr.sa, &clientAddrLen );
+ err = map_socket_creation_errno( newSock );
+ require_noerr( err, exit );
+
+ err = SocketContextCreate( newSock, connection, &newSockCtx );
+ require_noerr( err, exit );
+ newSock = kInvalidSocketRef;
+
+ err = DispatchReadSourceCreate( newSockCtx->sock, NULL, TCPConnectionReadHandler, SocketContextCancelHandler,
+ newSockCtx, &connection->readSource );
+ require_noerr( err, exit );
+ SocketContextRetain( newSockCtx );
+ dispatch_resume( connection->readSource );
+
+ err = DispatchWriteSourceCreate( newSockCtx->sock, NULL, TCPConnectionWriteHandler, SocketContextCancelHandler,
+ newSockCtx, &connection->writeSource );
+ require_noerr( err, exit );
+ SocketContextRetain( newSockCtx );
+ connection->writeSuspended = true;
+ connection = NULL;
+
+exit:
+ ForgetSocket( &newSock );
+ SocketContextRelease( newSockCtx );
+ TCPConnectionForget( &connection );
+}
+
+//===========================================================================================================================
+// TCPConnectionStop
+//===========================================================================================================================
+
+static void TCPConnectionStop( TCPConnectionContext *inContext )
+{
+ dispatch_source_forget_ex( &inContext->readSource, &inContext->readSuspended );
+ dispatch_source_forget_ex( &inContext->writeSource, &inContext->writeSuspended );
+}
+
+//===========================================================================================================================
+// TCPConnectionContextFree
+//===========================================================================================================================
+
+static void TCPConnectionContextFree( TCPConnectionContext *inContext )
+{
+ check( !inContext->readSource );
+ check( !inContext->writeSource );
+ ForgetMem( &inContext->msgPtr );
+ free( inContext );
+}
+
+//===========================================================================================================================
+// TCPConnectionReadHandler
+//===========================================================================================================================
+
+static void TCPConnectionReadHandler( void *inContext )
+{
+ OSStatus err;
+ SocketContext * const sockCtx = (SocketContext *) inContext;
+ TCPConnectionContext * connection = (TCPConnectionContext *) sockCtx->userContext;
+ struct timeval now;
+ uint8_t * responsePtr = NULL; // malloc'd
+ size_t responseLen;
+
+ // Receive message length.
+
+ if( !connection->receivedLength )
+ {
+ err = SocketReadData( sockCtx->sock, connection->lenBuf, sizeof( connection->lenBuf ), &connection->offset );
+ if( err == EWOULDBLOCK ) goto exit;
+ require_noerr( err, exit );
+
+ connection->offset = 0;
+ connection->msgLen = ReadBig16( connection->lenBuf );
+ connection->msgPtr = malloc( connection->msgLen );
+ require_action( connection->msgPtr, exit, err = kNoMemoryErr );
+ connection->receivedLength = true;
+ }
+
+ // Receive message.
+
+ err = SocketReadData( sockCtx->sock, connection->msgPtr, connection->msgLen, &connection->offset );
+ if( err == EWOULDBLOCK ) goto exit;
+ require_noerr( err, exit );
+
+ gettimeofday( &now, NULL );
+ dispatch_suspend( connection->readSource );
+ connection->readSuspended = true;
+
+ ds_ulog( kLogLevelInfo, "TCP server received %zu bytes from %##a at %{du:time}.\n",
+ connection->msgLen, &connection->clientAddr, &now );
+
+ if( connection->msgLen < kDNSHeaderLength )
+ {
+ ds_ulog( kLogLevelInfo, "TCP DNS message is too small (%zu < %d).\n", connection->msgLen, kDNSHeaderLength );
+ goto exit;
+ }
+
+ ds_ulog( kLogLevelInfo, "TCP received message:\n\n%1{du:dnsmsg}", connection->msgPtr, connection->msgLen );
+
+ // Create response.
+
+ err = _DNSServerAnswerQueryForTCP( connection->msgPtr, connection->msgLen, &responsePtr, &responseLen );
+ require_noerr_quiet( err, exit );
+
+ // Send response.
+
+ ds_ulog( kLogLevelInfo, "TCP sending %zu byte response:\n\n%1{du:dnsmsg}", responseLen, responsePtr, responseLen );
+
+ free( connection->msgPtr );
+ connection->msgPtr = responsePtr;
+ connection->msgLen = responseLen;
+ responsePtr = NULL;
+
+ check( connection->msgLen <= UINT16_MAX );
+ WriteBig16( connection->lenBuf, connection->msgLen );
+ connection->iov[ 0 ].iov_base = connection->lenBuf;
+ connection->iov[ 0 ].iov_len = sizeof( connection->lenBuf );
+ connection->iov[ 1 ].iov_base = connection->msgPtr;
+ connection->iov[ 1 ].iov_len = connection->msgLen;
+
+ connection->iovPtr = connection->iov;
+ connection->iovCount = 2;
+
+ check( connection->writeSuspended );
+ dispatch_resume( connection->writeSource );
+ connection->writeSuspended = false;
+
+exit:
+ FreeNullSafe( responsePtr );
+ if( err && ( err != EWOULDBLOCK ) ) TCPConnectionForget( &connection );
+}
+
+//===========================================================================================================================
+// TCPConnectionWriteHandler
+//===========================================================================================================================
+
+static void TCPConnectionWriteHandler( void *inContext )
+{
+ OSStatus err;
+ SocketContext * const sockCtx = (SocketContext *) inContext;
+ TCPConnectionContext * connection = (TCPConnectionContext *) sockCtx->userContext;
+
+ err = SocketWriteData( sockCtx->sock, &connection->iovPtr, &connection->iovCount );
+ if( err == EWOULDBLOCK ) goto exit;
+ check_noerr( err );
+
+ TCPConnectionForget( &connection );
+
+exit:
+ return;
+}
+
+//===========================================================================================================================
+// GAIPerfCmd
+//===========================================================================================================================
+
+#define kGAIPerfStandardTTL ( 1 * kSecondsPerHour )
+
+typedef struct GAITesterPrivate * GAITesterRef;
+typedef struct GAITestCase GAITestCase;
+
+typedef uint32_t GAITesterEventType;
+#define kGAITesterEvent_Started 1
+#define kGAITesterEvent_Stopped 2
+
+typedef struct
+{
+ const char * name; // Domain name that was resolved.
+ int64_t connectionTimeUs; // Time in microseconds that it took to create a DNS-SD connection.
+ int64_t firstTimeUs; // Time in microseconds that it took to get the first address result.
+ int64_t timeUs; // Time in microseconds that it took to get all expected address results.
+
+} GAITestItemResult;
+
+typedef void ( *GAITesterEventHandler_f )( GAITesterEventType inType, void *inContext );
+typedef void
+ ( *GAITesterResultsHandler_f )(
+ const char * inCaseTitle,
+ MicroTime64 inCaseStartTime,
+ MicroTime64 inCaseEndTime,
+ const GAITestItemResult * inResults,
+ size_t inResultCount,
+ size_t inItemCount,
+ void * inContext );
+
+typedef unsigned int GAITestAddrType;
+#define kGAITestAddrType_None 0
+#define kGAITestAddrType_IPv4 ( 1U << 0 )
+#define kGAITestAddrType_IPv6 ( 1U << 1 )
+#define kGAITestAddrType_Both ( kGAITestAddrType_IPv4 | kGAITestAddrType_IPv6 )
+
+#define GAITestAddrTypeIsValid( X ) \
+ ( ( (X) & kGAITestAddrType_Both ) && ( ( (X) & ~kGAITestAddrType_Both ) == 0 ) )
+
+typedef enum
+{
+ kGAIPerfOutputFormat_JSON = 1,
+ kGAIPerfOutputFormat_XML = 2,
+ kGAIPerfOutputFormat_Binary = 3
+
+} GAIPerfOutputFormatType;
+
+typedef struct
+{
+ GAITesterRef tester; // GAI tester object.
+ CFMutableArrayRef caseResults; // Array of test case results.
+ char * outputFilePath; // File to write test results to. If NULL, then write to stdout.
+ GAIPerfOutputFormatType outputFormat; // Format of test results output.
+ unsigned int callDelayMs; // Amount of time to wait before calling DNSServiceGetAddrInfo().
+ unsigned int serverDelayMs; // Amount of additional time to have server delay its responses.
+ unsigned int defaultIterCount; // Default test case iteration count.
+ dispatch_source_t sigIntSource; // Dispatch source for SIGINT.
+ dispatch_source_t sigTermSource; // Dispatch source for SIGTERM.
+ Boolean gotSignal; // True if SIGINT or SIGTERM was caught.
+ Boolean testerStarted; // True if the GAI tester was started.
+ Boolean appendNewLine; // True if a newline character should be appended to JSON output.
+
+} GAIPerfContext;
+
+static void GAIPerfContextFree( GAIPerfContext *inContext );
+static OSStatus GAIPerfAddAdvancedTestCases( GAIPerfContext *inContext );
+static OSStatus GAIPerfAddBasicTestCases( GAIPerfContext *inContext );
+static void GAIPerfEventHandler( GAITesterEventType inType, void *inContext );
+static void
+ GAIPerfResultsHandler(
+ const char * inCaseTitle,
+ MicroTime64 inCaseStartTime,
+ MicroTime64 inCaseEndTime,
+ const GAITestItemResult * inResults,
+ size_t inResultCount,
+ size_t inItemCount,
+ void * inContext );
+static void GAIPerfSignalHandler( void *inContext );
+
+CFTypeID GAITesterGetTypeID( void );
+static OSStatus
+ GAITesterCreate(
+ dispatch_queue_t inQueue,
+ int inCallDelayMs,
+ int inServerDelayMs,
+ int inServerDefaultTTL,
+ GAITesterRef * outTester );
+static void GAITesterStart( GAITesterRef inTester );
+static void GAITesterStop( GAITesterRef inTester );
+static void GAITesterAddCase( GAITesterRef inTester, GAITestCase *inCase );
+static void
+ GAITesterSetEventHandler(
+ GAITesterRef inTester,
+ GAITesterEventHandler_f inEventHandler,
+ void * inEventContext );
+static void
+ GAITesterSetResultsHandler(
+ GAITesterRef inTester,
+ GAITesterResultsHandler_f inResultsHandler,
+ void * inResultsContext );
+
+static OSStatus GAITestCaseCreate( const char *inTitle, unsigned int inTimeLimitMs, GAITestCase **outSet );
+static void GAITestCaseFree( GAITestCase *inCase );
+static OSStatus
+ GAITestCaseAddItem(
+ GAITestCase * inCase,
+ unsigned int inAliasCount,
+ unsigned int inAddressCount,
+ int inTTL,
+ GAITestAddrType inHasAddrs,
+ GAITestAddrType inWantAddrs,
+ unsigned int inItemCount );
+static OSStatus GAITestCaseAddLocalHostItem( GAITestCase *inCase, GAITestAddrType inWantAddrs, unsigned int inItemCount );
+
+#define kGAIPerfTestSuite_Basic 1
+#define kGAIPerfTestSuite_Advanced 2
+
+static void GAIPerfCmd( void )
+{
+ OSStatus err;
+ GAIPerfContext * context;
+ int suiteValue;
+
+ context = (GAIPerfContext *) calloc( 1, sizeof( *context ) );
+ require_action( context, exit, err = kNoMemoryErr );
+
+ context->caseResults = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks );
+ require_action( context->caseResults, exit, err = kNoMemoryErr );
+
+ context->outputFormat = (GAIPerfOutputFormatType) CLIArgToValue( "format", gGAIPerf_OutputFormat, &err,
+ "json", kGAIPerfOutputFormat_JSON,
+ "xml", kGAIPerfOutputFormat_XML,
+ "binary", kGAIPerfOutputFormat_Binary,
+ NULL );
+ require_noerr_quiet( err, exit );
+
+ context->callDelayMs = ( gGAIPerf_CallDelayMs >= 0 ) ? (unsigned int) gGAIPerf_CallDelayMs : 0;
+ context->serverDelayMs = ( gGAIPerf_ServerDelayMs >= 0 ) ? (unsigned int) gGAIPerf_ServerDelayMs : 0;
+ context->defaultIterCount = ( gGAIPerf_DefaultIterCount >= 0 ) ? (unsigned int) gGAIPerf_DefaultIterCount : 0;
+ context->appendNewLine = gGAIPerf_OutputAppendNewLine ? true : false;
+
+ if( gGAIPerf_OutputFilePath )
+ {
+ context->outputFilePath = strdup( gGAIPerf_OutputFilePath );
+ require_action( context->outputFilePath, exit, err = kNoMemoryErr );
+ }
+
+ err = GAITesterCreate( dispatch_get_main_queue(), (int) context->callDelayMs, (int) context->serverDelayMs,
+ kGAIPerfStandardTTL, &context->tester );
+ require_noerr( err, exit );
+
+ check( gGAIPerf_TestSuite );
+ suiteValue = CLIArgToValue( "suite", gGAIPerf_TestSuite, &err,
+ "basic", kGAIPerfTestSuite_Basic,
+ "advanced", kGAIPerfTestSuite_Advanced,
+ NULL );
+ require_noerr_quiet( err, exit );
+
+ switch( suiteValue )
+ {
+ case kGAIPerfTestSuite_Basic:
+ err = GAIPerfAddBasicTestCases( context );
+ require_noerr( err, exit );
+ break;
+
+ case kGAIPerfTestSuite_Advanced:
+ err = GAIPerfAddAdvancedTestCases( context );
+ require_noerr( err, exit );
+ break;
+
+ default:
+ err = kValueErr;
+ break;
+ }
+
+ GAITesterSetEventHandler( context->tester, GAIPerfEventHandler, context );
+ GAITesterSetResultsHandler( context->tester, GAIPerfResultsHandler, context );
+
+ signal( SIGINT, SIG_IGN );
+ err = DispatchSignalSourceCreate( SIGINT, GAIPerfSignalHandler, context, &context->sigIntSource );
+ require_noerr( err, exit );
+ dispatch_resume( context->sigIntSource );
+
+ signal( SIGTERM, SIG_IGN );
+ err = DispatchSignalSourceCreate( SIGTERM, GAIPerfSignalHandler, context, &context->sigTermSource );
+ require_noerr( err, exit );
+ dispatch_resume( context->sigTermSource );
+
+ GAITesterStart( context->tester );
+ dispatch_main();
+
+exit:
+ if( context ) GAIPerfContextFree( context );
+ if( err ) exit( 1 );
+}
+
+//===========================================================================================================================
+// GAIPerfContextFree
+//===========================================================================================================================
+
+static void GAIPerfContextFree( GAIPerfContext *inContext )
+{
+ ForgetCF( &inContext->tester );
+ ForgetCF( &inContext->caseResults );
+ ForgetMem( &inContext->outputFilePath );
+ dispatch_source_forget( &inContext->sigIntSource );
+ dispatch_source_forget( &inContext->sigTermSource );
+ free( inContext );
+}
+
+//===========================================================================================================================
+// GAIPerfAddAdvancedTestCases
+//===========================================================================================================================
+
+#define kTestCaseTitleBufferSize 128
+
+static void
+ _GAIPerfWriteTestCaseTitle(
+ char inBuffer[ kTestCaseTitleBufferSize ],
+ unsigned int inCNAMERecordCount,
+ unsigned int inARecordCount,
+ unsigned int inAAAARecordCount,
+ GAITestAddrType inRequested,
+ unsigned int inIterationCount,
+ Boolean inIterationsAreUnique );
+static void
+ _GAIPerfWriteLocalHostTestCaseTitle(
+ char inBuffer[ kTestCaseTitleBufferSize ],
+ GAITestAddrType inRequested,
+ unsigned int inIterationCount );
+static unsigned int
+ _GAIPerfTimeLimitMs(
+ unsigned int inCallDelayMs,
+ unsigned int inServerDelayMs,
+ unsigned int inIterationCount );
+
+#define kGAIPerfAdvancedTestSuite_MaxAliasCount 4
+#define kGAIPerfAdvancedTestSuite_MaxAddrCount 8
+
+static OSStatus GAIPerfAddAdvancedTestCases( GAIPerfContext *inContext )
+{
+ OSStatus err;
+ unsigned int aliasCount, addressCount, timeLimitMs, i;
+ GAITestCase * testCase = NULL;
+ char title[ kTestCaseTitleBufferSize ];
+
+ aliasCount = 0;
+ while( aliasCount <= kGAIPerfAdvancedTestSuite_MaxAliasCount )
+ {
+ for( addressCount = 1; addressCount <= kGAIPerfAdvancedTestSuite_MaxAddrCount; addressCount *= 2 )
+ {
+ // Add a test case to resolve a domain name with
+ //
+ // <aliasCount> CNAME records, <addressCount> A records, and <addressCount> AAAA records
+ //
+ // to its IPv4 and IPv6 addresses. Each iteration resolves a unique instance of such a domain name, which
+ // requires server queries.
+
+ _GAIPerfWriteTestCaseTitle( title, aliasCount, addressCount, addressCount, kGAITestAddrType_Both,
+ inContext->defaultIterCount, true );
+
+ timeLimitMs = _GAIPerfTimeLimitMs( inContext->callDelayMs, inContext->serverDelayMs,
+ inContext->defaultIterCount );
+ err = GAITestCaseCreate( title, timeLimitMs, &testCase );
+ require_noerr( err, exit );
+
+ for( i = 0; i < inContext->defaultIterCount; ++i )
+ {
+ err = GAITestCaseAddItem( testCase, aliasCount, addressCount, kGAIPerfStandardTTL,
+ kGAITestAddrType_Both, kGAITestAddrType_Both, 1 );
+ require_noerr( err, exit );
+ }
+
+ GAITesterAddCase( inContext->tester, testCase );
+ testCase = NULL;
+
+ // Add a test case to resolve a domain name with
+ //
+ // <aliasCount> CNAME records, <addressCount> A records, and <addressCount> AAAA records
+ //
+ // to its IPv4 and IPv6 addresses. A preliminary iteration resolves a unique domain name, which requires a server
+ // query. The subsequent iterations resolve the same domain name as the preliminary iteration, which should
+ // ideally require no server queries, i.e., the results should come from the cache.
+
+ _GAIPerfWriteTestCaseTitle( title, aliasCount, addressCount, addressCount, kGAITestAddrType_Both,
+ inContext->defaultIterCount, false );
+
+ timeLimitMs = _GAIPerfTimeLimitMs( inContext->callDelayMs, inContext->serverDelayMs, 1 ) +
+ _GAIPerfTimeLimitMs( inContext->callDelayMs, 0, inContext->defaultIterCount );
+ err = GAITestCaseCreate( title, timeLimitMs, &testCase );
+ require_noerr( err, exit );
+
+ err = GAITestCaseAddItem( testCase, aliasCount, addressCount, kGAIPerfStandardTTL,
+ kGAITestAddrType_Both, kGAITestAddrType_Both, inContext->defaultIterCount + 1 );
+ require_noerr( err, exit );
+
+ GAITesterAddCase( inContext->tester, testCase );
+ testCase = NULL;
+ }
+
+ if( aliasCount == 0 ) aliasCount = 1;
+ else aliasCount *= 2;
+ }
+
+ // Finally, add a test case to resolve localhost to its IPv4 and IPv6 addresses.
+
+ _GAIPerfWriteLocalHostTestCaseTitle( title, kGAITestAddrType_Both, inContext->defaultIterCount );
+
+ timeLimitMs = _GAIPerfTimeLimitMs( inContext->callDelayMs, 0, inContext->defaultIterCount );
+ err = GAITestCaseCreate( title, timeLimitMs, &testCase );
+ require_noerr( err, exit );
+
+ err = GAITestCaseAddLocalHostItem( testCase, kGAITestAddrType_Both, inContext->defaultIterCount );
+ require_noerr( err, exit );
+
+ GAITesterAddCase( inContext->tester, testCase );
+ testCase = NULL;
+
+exit:
+ if( testCase ) GAITestCaseFree( testCase );
+ return( err );
+}
+
+//===========================================================================================================================
+// _GAIPerfWriteTestCaseTitle
+//===========================================================================================================================
+
+#define GAITestAddrTypeToRequestKeyValue( X ) ( \
+ ( (X) == kGAITestAddrType_Both ) ? "ipv4\\,ipv6" : \
+ ( (X) == kGAITestAddrType_IPv4 ) ? "ipv4" : \
+ ( (X) == kGAITestAddrType_IPv6 ) ? "ipv6" : \
+ "" )
+
+static void
+ _GAIPerfWriteTestCaseTitle(
+ char inBuffer[ kTestCaseTitleBufferSize ],
+ unsigned int inCNAMERecordCount,
+ unsigned int inARecordCount,
+ unsigned int inAAAARecordCount,
+ GAITestAddrType inRequested,
+ unsigned int inIterationCount,
+ Boolean inIterationsAreUnique )
+{
+ SNPrintF( inBuffer, kTestCaseTitleBufferSize, "name=dynamic,cname=%u,a=%u,aaaa=%u,req=%s,iterations=%u%?s",
+ inCNAMERecordCount, inARecordCount, inAAAARecordCount, GAITestAddrTypeToRequestKeyValue( inRequested ),
+ inIterationCount, inIterationsAreUnique, ",unique" );
+}
+
+//===========================================================================================================================
+// _GAIPerfWriteLocalHostTestCaseTitle
+//===========================================================================================================================
+
+static void
+ _GAIPerfWriteLocalHostTestCaseTitle(
+ char inBuffer[ kTestCaseTitleBufferSize ],
+ GAITestAddrType inRequested,
+ unsigned int inIterationCount )
+{
+ SNPrintF( inBuffer, kTestCaseTitleBufferSize, "name=localhost,req=%s,iterations=%u",
+ GAITestAddrTypeToRequestKeyValue( inRequested ), inIterationCount );
+}
+
+//===========================================================================================================================
+// _GAIPerfTimeLimitMs
+//===========================================================================================================================
+
+static unsigned int
+ _GAIPerfTimeLimitMs(
+ unsigned int inCallDelayMs,
+ unsigned int inServerDelayMs,
+ unsigned int inIterationCount )
+{
+ // Allow each iteration 20 ms to complete (in addition to the call and server delay times).
+
+ return( ( inCallDelayMs + inServerDelayMs + 20 ) * inIterationCount );
+}
+
+//===========================================================================================================================
+// GAIPerfAddBasicTestCases
+//===========================================================================================================================
+
+#define kGAIPerfBasicTestSuite_AliasCount 2
+#define kGAIPerfBasicTestSuite_AddrCount 4
+
+static OSStatus GAIPerfAddBasicTestCases( GAIPerfContext *inContext )
+{
+ OSStatus err;
+ GAITestCase * testCase = NULL;
+ char title[ kTestCaseTitleBufferSize ];
+ unsigned int timeLimitMs, i;
+
+ // Test Case #1:
+ // Resolve a domain name with
+ //
+ // 2 CNAME records, 4 A records, and 4 AAAA records
+ //
+ // to its IPv4 and IPv6 addresses. Each of the iterations resolves a unique domain name, which requires server
+ // queries.
+
+ _GAIPerfWriteTestCaseTitle( title, kGAIPerfBasicTestSuite_AliasCount,
+ kGAIPerfBasicTestSuite_AddrCount, kGAIPerfBasicTestSuite_AddrCount, kGAITestAddrType_Both,
+ inContext->defaultIterCount, true );
+
+ timeLimitMs = _GAIPerfTimeLimitMs( inContext->callDelayMs, inContext->serverDelayMs, inContext->defaultIterCount );
+ err = GAITestCaseCreate( title, timeLimitMs, &testCase );
+ require_noerr( err, exit );
+
+ for( i = 0; i < inContext->defaultIterCount; ++i )
+ {
+ err = GAITestCaseAddItem( testCase, kGAIPerfBasicTestSuite_AliasCount, kGAIPerfBasicTestSuite_AddrCount,
+ kGAIPerfStandardTTL, kGAITestAddrType_Both, kGAITestAddrType_Both, 1 );
+ require_noerr( err, exit );
+ }
+
+ GAITesterAddCase( inContext->tester, testCase );
+ testCase = NULL;
+
+ // Test Case #2:
+ // Resolve a domain name with
+ //
+ // 2 CNAME records, 4 A records, and 4 AAAA records
+ //
+ // to its IPv4 and IPv6 addresses. A preliminary iteration resolves a unique instance of such a domain name, which
+ // requires server queries. Each of the subsequent iterations resolves the same domain name as the preliminary
+ // iteration, which should ideally require no additional server queries, i.e., the results should come from the cache.
+
+ _GAIPerfWriteTestCaseTitle( title, kGAIPerfBasicTestSuite_AliasCount,
+ kGAIPerfBasicTestSuite_AddrCount, kGAIPerfBasicTestSuite_AddrCount, kGAITestAddrType_Both,
+ inContext->defaultIterCount, false );
+
+ timeLimitMs = _GAIPerfTimeLimitMs( inContext->callDelayMs, inContext->serverDelayMs, 1 ) +
+ _GAIPerfTimeLimitMs( inContext->callDelayMs, 0, inContext->defaultIterCount );
+ err = GAITestCaseCreate( title, timeLimitMs, &testCase );
+ require_noerr( err, exit );
+
+ err = GAITestCaseAddItem( testCase, kGAIPerfBasicTestSuite_AliasCount, kGAIPerfBasicTestSuite_AddrCount,
+ kGAIPerfStandardTTL, kGAITestAddrType_Both, kGAITestAddrType_Both, inContext->defaultIterCount + 1 );
+ require_noerr( err, exit );
+
+ GAITesterAddCase( inContext->tester, testCase );
+ testCase = NULL;
+
+ // Test Case #3:
+ // Each iteration resolves localhost to its IPv4 and IPv6 addresses.
+
+ _GAIPerfWriteLocalHostTestCaseTitle( title, kGAITestAddrType_Both, inContext->defaultIterCount );
+
+ timeLimitMs = _GAIPerfTimeLimitMs( inContext->callDelayMs, 0, inContext->defaultIterCount );
+ err = GAITestCaseCreate( title, timeLimitMs, &testCase );
+ require_noerr( err, exit );
+
+ err = GAITestCaseAddLocalHostItem( testCase, kGAITestAddrType_Both, inContext->defaultIterCount );
+ require_noerr( err, exit );
+
+ GAITesterAddCase( inContext->tester, testCase );
+ testCase = NULL;
+
+exit:
+ if( testCase ) GAITestCaseFree( testCase );
+ return( err );
+}
+
+//===========================================================================================================================
+// GAIPerfEventHandler
+//===========================================================================================================================
+
+static void _GAIPerfOutputResultsAndExit( GAIPerfContext *inContext ) ATTRIBUTE_NORETURN;
+
+static void GAIPerfEventHandler( GAITesterEventType inType, void *inContext )
+{
+ GAIPerfContext * const context = (GAIPerfContext *) inContext;
+
+ if( inType == kGAITesterEvent_Started )
+ {
+ context->testerStarted = true;
+ }
+ else if( inType == kGAITesterEvent_Stopped )
+ {
+ if( context->gotSignal ) exit( 1 );
+ _GAIPerfOutputResultsAndExit( context );
+ }
+}
+
+//===========================================================================================================================
+// _GAIPerfOutputResultsAndExit
+//===========================================================================================================================
+
+#define kGAIPerfResultsKey_TestCases CFSTR( "testCases" )
+#define kGAIPerfResultsKey_Info CFSTR( "info" )
+
+#define kGAIPerfInfoKey_CallDelay CFSTR( "callDelayMs" )
+#define kGAIPerfInfoKey_ServerDelay CFSTR( "serverDelayMs" )
+
+static void _GAIPerfOutputResultsAndExit( GAIPerfContext *inContext )
+{
+ OSStatus err;
+ CFPropertyListRef plist = NULL;
+ CFDataRef results = NULL;
+ FILE * file = NULL;
+
+ err = CFPropertyListCreateFormatted( kCFAllocatorDefault, &plist,
+ "{"
+ "%kO=%O"
+ "%kO="
+ "{"
+ "%kO=%lli"
+ "%kO=%lli"
+ "}"
+ "}",
+ kGAIPerfResultsKey_TestCases, inContext->caseResults,
+ kGAIPerfResultsKey_Info,
+ kGAIPerfInfoKey_CallDelay, (int64_t) inContext->callDelayMs,
+ kGAIPerfInfoKey_ServerDelay, (int64_t) inContext->serverDelayMs );
+ require_noerr( err, exit );
+
+ // Convert results to a specific format.
+
+ switch( inContext->outputFormat )
+ {
+ case kGAIPerfOutputFormat_JSON:
+ results = CFCreateJSONData( plist, kJSONFlags_None, NULL );
+ require_action( results, exit, err = kUnknownErr );
+ break;
+
+ case kGAIPerfOutputFormat_XML:
+ results = CFPropertyListCreateData( NULL, plist, kCFPropertyListXMLFormat_v1_0, 0, NULL );
+ require_action( results, exit, err = kUnknownErr );
+ break;
+
+ case kGAIPerfOutputFormat_Binary:
+ results = CFPropertyListCreateData( NULL, plist, kCFPropertyListBinaryFormat_v1_0, 0, NULL );
+ require_action( results, exit, err = kUnknownErr );
+ break;
+
+ default:
+ err = kValueErr;
+ goto exit;
+ }
+
+ // Write formatted results to file or stdout.
+
+ if( inContext->outputFilePath )
+ {
+ file = fopen( inContext->outputFilePath, "wb" );
+ err = map_global_value_errno( file, file );
+ require_noerr( err, exit );
+ }
+ else
+ {
+ file = stdout;
+ }
+
+ err = WriteANSIFile( file, CFDataGetBytePtr( results ), (size_t) CFDataGetLength( results ) );
+ require_noerr( err, exit );
+
+ // Write a trailing newline for JSON-formatted results if requested.
+
+ if( ( inContext->outputFormat == kGAIPerfOutputFormat_JSON ) && inContext->appendNewLine )
+ {
+ err = WriteANSIFile( file, "\n", 1 );
+ require_noerr( err, exit );
+ }
+
+exit:
+ CFReleaseNullSafe( plist );
+ CFReleaseNullSafe( results );
+ if( file && ( file != stdout ) ) fclose( file );
+ GAIPerfContextFree( inContext );
+ exit( err ? 1 : 0 );
+}
+
+//===========================================================================================================================
+// GAIPerfResultsHandler
+//===========================================================================================================================
+
+// Keys for test case dictionary
+
+#define kGAIPerfTestCaseKey_Title CFSTR( "title" )
+#define kGAIPerfTestCaseKey_StartTime CFSTR( "startTimeUs" )
+#define kGAIPerfTestCaseKey_EndTime CFSTR( "endTimeUs" )
+#define kGAIPerfTestCaseKey_Results CFSTR( "results" )
+#define kGAIPerfTestCaseKey_FirstStats CFSTR( "firstStats" )
+#define kGAIPerfTestCaseKey_ConnectionStats CFSTR( "connectionStats" )
+#define kGAIPerfTestCaseKey_Stats CFSTR( "stats" )
+#define kGAIPerfTestCaseKey_TimedOut CFSTR( "timedOut" )
+
+// Keys for test case results array entry dictionaries
+
+#define kGAIPerfTestCaseResultKey_Name CFSTR( "name" )
+#define kGAIPerfTestCaseResultKey_ConnectionTime CFSTR( "connectionTimeUs" )
+#define kGAIPerfTestCaseResultKey_FirstTime CFSTR( "firstTimeUs" )
+#define kGAIPerfTestCaseResultKey_Time CFSTR( "timeUs" )
+
+// Keys for test case stats dictionaries
+
+#define kGAIPerfTestCaseStatsKey_Count CFSTR( "count" )
+#define kGAIPerfTestCaseStatsKey_Min CFSTR( "min" )
+#define kGAIPerfTestCaseStatsKey_Max CFSTR( "max" )
+#define kGAIPerfTestCaseStatsKey_Mean CFSTR( "mean" )
+#define kGAIPerfTestCaseStatsKey_StdDev CFSTR( "sd" )
+
+typedef struct
+{
+ double min;
+ double max;
+ double mean;
+ double stdDev;
+
+} GAIPerfStats;
+
+#define GAIPerfStatsInit( X ) \
+ do { (X)->min = DBL_MAX; (X)->max = DBL_MIN; (X)->mean = 0.0; (X)->stdDev = 0.0; } while( 0 )
+
+static void
+ GAIPerfResultsHandler(
+ const char * inCaseTitle,
+ MicroTime64 inCaseStartTime,
+ MicroTime64 inCaseEndTime,
+ const GAITestItemResult * inResults,
+ size_t inResultCount,
+ size_t inItemCount,
+ void * inContext )
+{
+ OSStatus err;
+ GAIPerfContext * const context = (GAIPerfContext *) inContext;
+ int namesAreDynamic, namesAreUnique;
+ const char * ptr;
+ size_t count, startIndex;
+ CFMutableArrayRef results = NULL;
+ GAIPerfStats stats, firstStats, connStats;
+ double sum, firstSum, connSum, value, diff;
+ size_t keyValueLen, i;
+ char keyValue[ 16 ]; // Size must be at least strlen( "name=dynamic" ) + 1 bytes.
+
+ // If this test case resolves the same "d.test." name in each iteration (title contains the "name=dynamic" key-value
+ // pair, but not the "unique" key), then don't count the first iteration, whose purpose is to populate the cache with the
+ // domain name's CNAME, A, and AAAA records.
+
+ namesAreDynamic = false;
+ namesAreUnique = false;
+ ptr = inCaseTitle;
+ while( ParseQuotedEscapedString( ptr, NULL, ",", keyValue, sizeof( keyValue ), &keyValueLen, NULL, &ptr ) )
+ {
+ if( strnicmpx( keyValue, keyValueLen, "name=dynamic" ) == 0 )
+ {
+ namesAreDynamic = true;
+ }
+ else if( strnicmpx( keyValue, keyValueLen, "unique" ) == 0 )
+ {
+ namesAreUnique = true;
+ }
+ if( namesAreDynamic && namesAreUnique ) break;
+ }
+
+ if( namesAreDynamic && !namesAreUnique && ( inItemCount > 0 ) )
+ {
+ count = ( inResultCount > 0 ) ? ( inResultCount - 1 ) : 0;
+ startIndex = 1;
+ }
+ else
+ {
+ count = inResultCount;
+ startIndex = 0;
+ }
+
+ results = CFArrayCreateMutable( NULL, (CFIndex) count, &kCFTypeArrayCallBacks );
+ require_action( results, exit, err = kNoMemoryErr );
+
+ GAIPerfStatsInit( &stats );
+ GAIPerfStatsInit( &firstStats );
+ GAIPerfStatsInit( &connStats );
+
+ sum = 0.0;
+ firstSum = 0.0;
+ connSum = 0.0;
+ for( i = startIndex; i < count; ++i )
+ {
+ value = (double) inResults[ i ].timeUs;
+ if( value < stats.min ) stats.min = value;
+ if( value > stats.max ) stats.max = value;
+ sum += value;
+
+ value = (double) inResults[ i ].firstTimeUs;
+ if( value < firstStats.min ) firstStats.min = value;
+ if( value > firstStats.max ) firstStats.max = value;
+ firstSum += value;
+
+ value = (double) inResults[ i ].connectionTimeUs;
+ if( value < connStats.min ) connStats.min = value;
+ if( value > connStats.max ) connStats.max = value;
+ connSum += value;
+
+ err = CFPropertyListAppendFormatted( kCFAllocatorDefault, results,
+ "{"
+ "%kO=%s"
+ "%kO=%lli"
+ "%kO=%lli"
+ "%kO=%lli"
+ "}",
+ kGAIPerfTestCaseResultKey_Name, inResults[ i ].name,
+ kGAIPerfTestCaseResultKey_ConnectionTime, inResults[ i ].connectionTimeUs,
+ kGAIPerfTestCaseResultKey_FirstTime, inResults[ i ].firstTimeUs,
+ kGAIPerfTestCaseResultKey_Time, inResults[ i ].timeUs );
+ require_noerr( err, exit );
+ }
+
+ if( count > 0 )
+ {
+ stats.mean = sum / count;
+ firstStats.mean = firstSum / count;
+ connStats.mean = connSum / count;
+
+ sum = 0.0;
+ firstSum = 0.0;
+ connSum = 0.0;
+ for( i = startIndex; i < count; ++i )
+ {
+ diff = stats.mean - (double) inResults[ i ].timeUs;
+ sum += ( diff * diff );
+
+ diff = firstStats.mean - (double) inResults[ i ].firstTimeUs;
+ firstSum += ( diff * diff );
+
+ diff = connStats.mean - (double) inResults[ i ].connectionTimeUs;
+ connSum += ( diff * diff );
+ }
+ stats.stdDev = sqrt( sum / count );
+ firstStats.stdDev = sqrt( firstSum / count );
+ connStats.stdDev = sqrt( connSum / count );
+ }
+
+ err = CFPropertyListAppendFormatted( kCFAllocatorDefault, context->caseResults,
+ "{"
+ "%kO=%s"
+ "%kO=%lli"
+ "%kO=%lli"
+ "%kO=%O"
+ "%kO="
+ "{"
+ "%kO=%lli"
+ "%kO=%f"
+ "%kO=%f"
+ "%kO=%f"
+ "%kO=%f"
+ "}"
+ "%kO="
+ "{"
+ "%kO=%lli"
+ "%kO=%f"
+ "%kO=%f"
+ "%kO=%f"
+ "%kO=%f"
+ "}"
+ "%kO="
+ "{"
+ "%kO=%lli"
+ "%kO=%f"
+ "%kO=%f"
+ "%kO=%f"
+ "%kO=%f"
+ "}"
+ "%kO=%b"
+ "}",
+ kGAIPerfTestCaseKey_Title, inCaseTitle,
+ kGAIPerfTestCaseKey_StartTime, (int64_t) inCaseStartTime,
+ kGAIPerfTestCaseKey_EndTime, (int64_t) inCaseEndTime,
+ kGAIPerfTestCaseKey_Results, results,
+ kGAIPerfTestCaseKey_Stats,
+ kGAIPerfTestCaseStatsKey_Count, (int64_t) count,
+ kGAIPerfTestCaseStatsKey_Min, stats.min,
+ kGAIPerfTestCaseStatsKey_Max, stats.max,
+ kGAIPerfTestCaseStatsKey_Mean, stats.mean,
+ kGAIPerfTestCaseStatsKey_StdDev, stats.stdDev,
+ kGAIPerfTestCaseKey_FirstStats,
+ kGAIPerfTestCaseStatsKey_Count, (int64_t) count,
+ kGAIPerfTestCaseStatsKey_Min, firstStats.min,
+ kGAIPerfTestCaseStatsKey_Max, firstStats.max,
+ kGAIPerfTestCaseStatsKey_Mean, firstStats.mean,
+ kGAIPerfTestCaseStatsKey_StdDev, firstStats.stdDev,
+ kGAIPerfTestCaseKey_ConnectionStats,
+ kGAIPerfTestCaseStatsKey_Count, (int64_t) count,
+ kGAIPerfTestCaseStatsKey_Min, connStats.min,
+ kGAIPerfTestCaseStatsKey_Max, connStats.max,
+ kGAIPerfTestCaseStatsKey_Mean, connStats.mean,
+ kGAIPerfTestCaseStatsKey_StdDev, connStats.stdDev,
+ kGAIPerfTestCaseKey_TimedOut, ( inResultCount < inItemCount ) ? true : false );
+ require_noerr( err, exit );
+
+exit:
+ CFReleaseNullSafe( results );
+}
+
+//===========================================================================================================================
+// GAIPerfSignalHandler
+//===========================================================================================================================
+
+static void GAIPerfSignalHandler( void *inContext )
+{
+ GAIPerfContext * const context = (GAIPerfContext *) inContext;
+
+ context->gotSignal = true;
+ if( context->tester && context->testerStarted )
+ {
+ GAITesterStop( context->tester );
+ }
+ else
+ {
+ exit( 1 );
+ }
+}
+
+//===========================================================================================================================
+// GAITesterCreate
+//===========================================================================================================================
+
+typedef enum
+{
+ kGAITestConnType_UseMainConnection = 1,
+ kGAITestConnType_OwnSharedConnection = 2
+
+} GAITestConnType;
+
+typedef struct GAITestItem GAITestItem;
+struct GAITestItem
+{
+ GAITestItem * next; // Next test item in list.
+ char * name; // Domain name to resolve.
+ int64_t connectionTimeUs; // Time in microseconds that it took to create a DNS-SD connection.
+ int64_t firstTimeUs; // Time in microseconds that it took to get the first address result.
+ int64_t timeUs; // Time in microseconds that it took to get all expected address results.
+ unsigned int addressCount; // Address count of the domain name, i.e., the Count label argument.
+ Boolean hasV4; // True if the domain name has one or more IPv4 addresses.
+ Boolean hasV6; // True if the domain name has one or more IPv6 addresses.
+ Boolean wantV4; // True if DNSServiceGetAddrInfo() should be called to get IPv4 addresses.
+ Boolean wantV6; // True if DNSServiceGetAddrInfo() should be called to get IPv6 addresses.
+};
+
+struct GAITestCase
+{
+ GAITestCase * next; // Next test case in list.
+ GAITestItem * itemList; // List of test items.
+ char * title; // Title of the test case.
+ unsigned int timeLimitMs; // Time limit in milliseconds for the test case's completion.
+};
+
+struct GAITesterPrivate
+{
+ CFRuntimeBase base; // CF object base.
+ dispatch_queue_t queue; // Serial work queue.
+ DNSServiceRef mainRef; // Reference to the main shared DNS-SD connection.
+ DNSServiceRef opRef; // Reference to the current DNSServiceGetAddrInfo operation.
+ GAITestCase * caseList; // List of test cases.
+ GAITestCase * currentCase; // Pointer to the current test case.
+ GAITestItem * currentItem; // Pointer to the current test item.
+ MicroTime64 caseStartTime; // Start time of current test case in Unix time as microseconds.
+ MicroTime64 caseEndTime; // End time of current test case in Unix time as microseconds.
+ Boolean started; // True if the tester has been successfully started.
+ Boolean stopped; // True if the tester has been stopped.
+ int callDelayMs; // Amount of time to wait before calling DNSServiceGetAddrInfo().
+ dispatch_source_t caseTimer; // Timer for enforcing a test case time limits.
+ pcap_t * pcap; // Captures traffic between mDNSResponder and test DNS server.
+ pid_t serverPID; // PID of the test DNS server.
+ int serverDelayMs; // Additional time to have the server delay its responses by.
+ int serverDefaultTTL; // Default TTL for the server's records.
+ GAITesterEventHandler_f eventHandler; // User's event handler.
+ void * eventContext; // User's event handler context.
+ GAITesterResultsHandler_f resultsHandler; // User's results handler.
+ void * resultsContext; // User's results handler context.
+
+ // Variables for current test item.
+
+ uint64_t bitmapV4; // Bitmap of IPv4 results that have yet to be received.
+ uint64_t bitmapV6; // Bitmap of IPv6 results that have yet to be received.
+ uint64_t startTicks; // Start ticks of DNSServiceGetAddrInfo().
+ uint64_t connTicks; // Ticks when the connection was created.
+ uint64_t firstTicks; // Ticks when the first DNSServiceGetAddrInfo result was received.
+ uint64_t endTicks; // Ticks when the last DNSServiceGetAddrInfo result was received.
+ Boolean gotFirstResult; // True if the first result has been received.
+};
+
+CF_CLASS_DEFINE( GAITester );
+
+static void _GAITesterRun( void *inContext );
+static OSStatus _GAITesterCreatePacketCapture( pcap_t **outPCap );
+static void _GAITesterTimeout( void *inContext );
+static void _GAITesterAdvanceCurrentItem( GAITesterRef inTester );
+static void _GAITesterAdvanceCurrentSet( GAITesterRef inTester );
+static void _GAITesterInitializeCurrentTest( GAITesterRef inTester );
+static void DNSSD_API
+ _GAITesterGetAddrInfoCallback(
+ DNSServiceRef inSDRef,
+ DNSServiceFlags inFlags,
+ uint32_t inInterfaceIndex,
+ DNSServiceErrorType inError,
+ const char * inHostname,
+ const struct sockaddr * inSockAddr,
+ uint32_t inTTL,
+ void * inContext );
+static void _GAITesterCompleteCurrentTest( GAITesterRef inTester, Boolean inTimedOut );
+
+#define ForgetPacketCapture( X ) ForgetCustom( X, pcap_close )
+
+static OSStatus
+ GAITestItemCreate(
+ const char * inName,
+ unsigned int inAddressCount,
+ GAITestAddrType inHasAddrs,
+ GAITestAddrType inWantAddrs,
+ GAITestItem ** outItem );
+static OSStatus GAITestItemDuplicate( const GAITestItem *inItem, GAITestItem **outItem );
+static void GAITestItemFree( GAITestItem *inItem );
+
+static OSStatus
+ GAITesterCreate(
+ dispatch_queue_t inQueue,
+ int inCallDelayMs,
+ int inServerDelayMs,
+ int inServerDefaultTTL,
+ GAITesterRef * outTester )
+{
+ OSStatus err;
+ GAITesterRef obj = NULL;
+
+ CF_OBJECT_CREATE( GAITester, obj, err, exit );
+
+ ReplaceDispatchQueue( &obj->queue, inQueue );
+ obj->callDelayMs = inCallDelayMs;
+ obj->serverPID = -1;
+ obj->serverDelayMs = inServerDelayMs;
+ obj->serverDefaultTTL = inServerDefaultTTL;
+
+ *outTester = obj;
+ obj = NULL;
+ err = kNoErr;
+
+exit:
+ CFReleaseNullSafe( obj );
+ return( err );
+}
+
+//===========================================================================================================================
+// _GAITesterFinalize
+//===========================================================================================================================
+
+static void _GAITesterFinalize( CFTypeRef inObj )
+{
+ GAITesterRef const me = (GAITesterRef) inObj;
+ GAITestCase * testCase;
+
+ check( !me->opRef );
+ check( !me->mainRef );
+ check( !me->caseTimer );
+ dispatch_forget( &me->queue );
+ while( ( testCase = me->caseList ) != NULL )
+ {
+ me->caseList = testCase->next;
+ GAITestCaseFree( testCase );
+ }
+}
+
+//===========================================================================================================================
+// GAITesterStart
+//===========================================================================================================================
+
+static void _GAITesterStart( void *inContext );
+static void _GAITesterStop( GAITesterRef me );
+
+static void GAITesterStart( GAITesterRef me )
+{
+ CFRetain( me );
+ dispatch_async_f( me->queue, me, _GAITesterStart );
+}
+
+extern char ** environ;
+
+static void _GAITesterStart( void *inContext )
+{
+ OSStatus err;
+ GAITesterRef const me = (GAITesterRef) inContext;
+ char * argv[ 4 ];
+ char * ptr;
+ char * end;
+ char command[ 128 ];
+
+ ptr = &command[ 0 ];
+ end = &command[ countof( command ) ];
+ SNPrintF_Add( &ptr, end, "dnssdutil server --loopback --followPID %lld", (int64_t) getpid() );
+ if( me->serverDefaultTTL >= 0 ) SNPrintF_Add( &ptr, end, " --defaultTTL %d", me->serverDefaultTTL );
+ if( me->serverDelayMs >= 0 ) SNPrintF_Add( &ptr, end, " --responseDelay %d", me->serverDelayMs );
+
+ argv[ 0 ] = "/bin/sh";
+ argv[ 1 ] = "-c";
+ argv[ 2 ] = command;
+ argv[ 3 ] = NULL;
+ err = posix_spawn( &me->serverPID, argv[ 0 ], NULL, NULL, argv, environ );
+ require_noerr( err, exit );
+
+ me->currentCase = me->caseList;
+ me->currentItem = me->currentCase ? me->currentCase->itemList : NULL;
+ _GAITesterInitializeCurrentTest( me );
+
+ // Hack: The first tester run is delayed for three seconds to allow the test DNS server to start up.
+ // A better way to handle this is to issue an asynchronous query for something in the d.test. domain. As soon as an
+ // expected response is received, the server can be considered to be up and running.
+
+ CFRetain( me );
+ dispatch_after_f( dispatch_time_seconds( 3 ), me->queue, me, _GAITesterRun );
+
+ CFRetain( me );
+ me->started = true;
+ if( me->eventHandler ) me->eventHandler( kGAITesterEvent_Started, me->eventContext );
+
+exit:
+ if( err ) _GAITesterStop( me );
+ CFRelease( me );
+}
+
+//===========================================================================================================================
+// GAITesterStop
+//===========================================================================================================================
+
+static void _GAITesterUserStop( void *inContext );
+
+static void GAITesterStop( GAITesterRef me )
+{
+ CFRetain( me );
+ dispatch_async_f( me->queue, me, _GAITesterUserStop );
+}
+
+static void _GAITesterUserStop( void *inContext )
+{
+ GAITesterRef const me = (GAITesterRef) inContext;
+
+ _GAITesterStop( me );
+ CFRelease( me );
+}
+
+static void _GAITesterStop( GAITesterRef me )
+{
+ OSStatus err;
+
+ DNSServiceForget( &me->opRef );
+ DNSServiceForget( &me->mainRef );
+ ForgetPacketCapture( &me->pcap );
+ dispatch_source_forget( &me->caseTimer );
+ if( me->serverPID != -1 )
+ {
+ err = kill( me->serverPID, SIGTERM );
+ err = map_global_noerr_errno( err );
+ check_noerr( err );
+ }
+
+ if( !me->stopped )
+ {
+ me->stopped = true;
+ if( me->eventHandler ) me->eventHandler( kGAITesterEvent_Stopped, me->eventContext );
+ if( me->started ) CFRelease( me );
+ }
+}
+
+//===========================================================================================================================
+// GAITesterAddCase
+//===========================================================================================================================
+
+static void GAITesterAddCase( GAITesterRef me, GAITestCase *inCase )
+{
+ GAITestCase ** ptr;
+
+ for( ptr = &me->caseList; *ptr != NULL; ptr = &( *ptr )->next ) {}
+ *ptr = inCase;
+}
+
+//===========================================================================================================================
+// GAITesterSetEventHandler
+//===========================================================================================================================
+
+static void GAITesterSetEventHandler( GAITesterRef me, GAITesterEventHandler_f inEventHandler, void *inEventContext )
+{
+ me->eventHandler = inEventHandler;
+ me->eventContext = inEventContext;
+}
+
+//===========================================================================================================================
+// GAITesterSetResultsHandler
+//===========================================================================================================================
+
+static void GAITesterSetResultsHandler( GAITesterRef me, GAITesterResultsHandler_f inResultsHandler, void *inResultsContext )
+{
+ me->resultsHandler = inResultsHandler;
+ me->resultsContext = inResultsContext;
+}
+
+//===========================================================================================================================
+// _GAITesterRun
+//===========================================================================================================================
+
+static void _GAITesterRun( void *inContext )
+{
+ OSStatus err;
+ GAITesterRef const me = (GAITesterRef) inContext;
+ GAITestItem * item;
+ GAITestItemResult * results = NULL;
+
+ require_action_quiet( !me->stopped, exit, err = kNoErr );
+
+ for( ;; )
+ {
+ item = me->currentItem;
+ if( item )
+ {
+ DNSServiceProtocol protocols;
+
+ check( !me->opRef );
+ check( ( me->bitmapV4 != 0 ) || ( me->bitmapV6 != 0 ) );
+
+ // Perform preliminary tasks if this is the start of a new test case.
+
+ if( item == me->currentCase->itemList )
+ {
+ // Flush mDNSResponder's cache.
+
+ err = systemf( NULL, "killall -HUP mDNSResponder" );
+ require_noerr( err, exit );
+ usleep( kMicrosecondsPerSecond );
+
+ // Start a packet capture.
+
+ check( !me->pcap );
+ err = _GAITesterCreatePacketCapture( &me->pcap );
+ require_noerr( err, exit );
+
+ // Start the test case time limit timer.
+
+ check( !me->caseTimer );
+ if( me->currentCase->timeLimitMs > 0 )
+ {
+ const int64_t timeLimitSecs = ( me->currentCase->timeLimitMs + 999 ) / 1000;
+
+ err = DispatchTimerCreate( dispatch_time_seconds( timeLimitSecs ), DISPATCH_TIME_FOREVER,
+ ( (uint64_t) timeLimitSecs ) * kNanosecondsPerSecond / 10,
+ me->queue, _GAITesterTimeout, NULL, me, &me->caseTimer );
+ require_noerr( err, exit );
+ dispatch_resume( me->caseTimer );
+ }
+
+ me->caseStartTime = GetCurrentMicroTime();
+ }
+
+ // Call DNSServiceGetAddrInfo().
+
+ if( me->callDelayMs > 0 ) usleep( ( (useconds_t) me->callDelayMs ) * kMicrosecondsPerMillisecond );
+
+ protocols = 0;
+ if( item->wantV4 ) protocols |= kDNSServiceProtocol_IPv4;
+ if( item->wantV6 ) protocols |= kDNSServiceProtocol_IPv6;
+
+ check( !me->mainRef );
+ me->startTicks = UpTicks();
+
+ err = DNSServiceCreateConnection( &me->mainRef );
+ require_noerr( err, exit );
+
+ err = DNSServiceSetDispatchQueue( me->mainRef, me->queue );
+ require_noerr( err, exit );
+
+ me->connTicks = UpTicks();
+
+ me->opRef = me->mainRef;
+ err = DNSServiceGetAddrInfo( &me->opRef, kDNSServiceFlagsShareConnection | kDNSServiceFlagsReturnIntermediates,
+ kDNSServiceInterfaceIndexAny, protocols, item->name, _GAITesterGetAddrInfoCallback, me );
+ require_noerr( err, exit );
+ break;
+ }
+ else
+ {
+ // No more test items means that this test case has completed (or timed out).
+
+ me->caseEndTime = GetCurrentMicroTime();
+ dispatch_source_forget( &me->caseTimer );
+ ForgetPacketCapture( &me->pcap );
+
+ if( me->resultsHandler )
+ {
+ size_t resultCount, itemCount, i;
+ int timedOut;
+
+ itemCount = 0;
+ resultCount = 0;
+ timedOut = false;
+ for( item = me->currentCase->itemList; item; item = item->next )
+ {
+ if( !timedOut )
+ {
+ if( item->timeUs < 0 )
+ {
+ timedOut = true;
+ }
+ else
+ {
+ ++resultCount;
+ }
+ }
+ ++itemCount;
+ }
+ if( resultCount > 0 )
+ {
+ results = (GAITestItemResult *) calloc( resultCount, sizeof( *results ) );
+ require_action( results, exit, err = kNoMemoryErr );
+
+ item = me->currentCase->itemList;
+ for( i = 0; i < resultCount; ++i )
+ {
+ results[ i ].name = item->name;
+ results[ i ].connectionTimeUs = item->connectionTimeUs;
+ results[ i ].firstTimeUs = item->firstTimeUs;
+ results[ i ].timeUs = item->timeUs;
+ item = item->next;
+ }
+ }
+ me->resultsHandler( me->currentCase->title, me->caseStartTime, me->caseEndTime, results, resultCount,
+ itemCount, me->resultsContext );
+ ForgetMem( &results );
+ }
+
+ _GAITesterAdvanceCurrentSet( me );
+ require_action_quiet( me->currentCase, exit, err = kEndingErr );
+ }
+ }
+
+exit:
+ FreeNullSafe( results );
+ if( err ) _GAITesterStop( me );
+ CFRelease( me );
+}
+
+//===========================================================================================================================
+// _GAITesterCreatePacketCapture
+//===========================================================================================================================
+
+static OSStatus _GAITesterCreatePacketCapture( pcap_t **outPCap )
+{
+ OSStatus err;
+ pcap_t * pcap;
+ struct bpf_program program;
+ char errBuf[ PCAP_ERRBUF_SIZE ];
+
+ pcap = pcap_create( "lo0", errBuf );
+ require_action_string( pcap, exit, err = kUnknownErr, errBuf );
+
+ err = pcap_set_buffer_size( pcap, 512 * kBytesPerKiloByte );
+ require_noerr_action( err, exit, err = kUnknownErr );
+
+ err = pcap_set_snaplen( pcap, 512 );
+ require_noerr_action( err, exit, err = kUnknownErr );
+
+ err = pcap_set_immediate_mode( pcap, 0 );
+ require_noerr_action_string( err, exit, err = kUnknownErr, pcap_geterr( pcap ) );
+
+ err = pcap_activate( pcap );
+ require_noerr_action_string( err, exit, err = kUnknownErr, pcap_geterr( pcap ) );
+
+ err = pcap_setdirection( pcap, PCAP_D_INOUT );
+ require_noerr_action_string( err, exit, err = kUnknownErr, pcap_geterr( pcap ) );
+
+ err = pcap_setnonblock( pcap, 1, errBuf );
+ require_noerr_action_string( err, exit, err = kUnknownErr, pcap_geterr( pcap ) );
+
+ err = pcap_compile( pcap, &program, "udp port 53", 1, PCAP_NETMASK_UNKNOWN );
+ require_noerr_action_string( err, exit, err = kUnknownErr, pcap_geterr( pcap ) );
+
+ err = pcap_setfilter( pcap, &program );
+ pcap_freecode( &program );
+ require_noerr_action_string( err, exit, err = kUnknownErr, pcap_geterr( pcap ) );
+
+ *outPCap = pcap;
+ pcap = NULL;
+
+exit:
+ if( pcap ) pcap_close( pcap );
+ return( err );
+}
+
+//===========================================================================================================================
+// _GAITesterTimeout
+//===========================================================================================================================
+
+static void _GAITesterTimeout( void *inContext )
+{
+ GAITesterRef const me = (GAITesterRef) inContext;
+
+ dispatch_source_forget( &me->caseTimer );
+
+ _GAITesterCompleteCurrentTest( me, true );
+}
+
+//===========================================================================================================================
+// _GAITesterAdvanceCurrentItem
+//===========================================================================================================================
+
+static void _GAITesterAdvanceCurrentItem( GAITesterRef me )
+{
+ if( me->currentItem )
+ {
+ me->currentItem = me->currentItem->next;
+ _GAITesterInitializeCurrentTest( me );
+ }
+}
+
+//===========================================================================================================================
+// _GAITesterAdvanceCurrentSet
+//===========================================================================================================================
+
+static void _GAITesterAdvanceCurrentSet( GAITesterRef me )
+{
+ if( me->currentCase )
+ {
+ me->caseStartTime = 0;
+ me->caseEndTime = 0;
+ me->currentCase = me->currentCase->next;
+ if( me->currentCase )
+ {
+ me->currentItem = me->currentCase->itemList;
+ _GAITesterInitializeCurrentTest( me );
+ }
+ }
+}
+
+//===========================================================================================================================
+// _GAITesterInitializeCurrentTest
+//===========================================================================================================================
+
+static void _GAITesterInitializeCurrentTest( GAITesterRef me )
+{
+ GAITestItem * const item = me->currentItem;
+
+ if( item )
+ {
+ check( item->addressCount > 0 );
+ if( item->wantV4 )
+ {
+ me->bitmapV4 = item->hasV4 ? ( ( UINT64_C( 1 ) << item->addressCount ) - 1 ) : 1;
+ }
+ else
+ {
+ me->bitmapV4 = 0;
+ }
+
+ if( item->wantV6 )
+ {
+ me->bitmapV6 = item->hasV6 ? ( ( UINT64_C( 1 ) << item->addressCount ) - 1 ) : 1;
+ }
+ else
+ {
+ me->bitmapV6 = 0;
+ }
+ me->gotFirstResult = false;
+ }
+}
+
+//===========================================================================================================================
+// _GAITesterGetAddrInfoCallback
+//===========================================================================================================================
+
+static void DNSSD_API
+ _GAITesterGetAddrInfoCallback(
+ DNSServiceRef inSDRef,
+ DNSServiceFlags inFlags,
+ uint32_t inInterfaceIndex,
+ DNSServiceErrorType inError,
+ const char * inHostname,
+ const struct sockaddr * inSockAddr,
+ uint32_t inTTL,
+ void * inContext )
+{
+ GAITesterRef const me = (GAITesterRef) inContext;
+ GAITestItem * const item = me->currentItem;
+ const sockaddr_ip * const sip = (const sockaddr_ip *) inSockAddr;
+ uint64_t nowTicks;
+ uint64_t * bitmapPtr;
+ uint64_t bitmask;
+ unsigned int addrOffset;
+
+ Unused( inSDRef );
+ Unused( inInterfaceIndex );
+ Unused( inHostname );
+ Unused( inTTL );
+
+ nowTicks = UpTicks();
+
+ require_quiet( inFlags & kDNSServiceFlagsAdd, exit );
+ require_quiet( !inError || ( inError == kDNSServiceErr_NoSuchRecord ), exit );
+
+ bitmapPtr = NULL;
+ bitmask = 0;
+ if( ( sip->sa.sa_family == AF_INET ) && item->wantV4 )
+ {
+ if( item->hasV4 )
+ {
+ if( !inError )
+ {
+ const uint32_t addrV4 = ntohl( sip->v4.sin_addr.s_addr );
+
+ if( strcasecmp( item->name, "localhost." ) == 0 )
+ {
+ if( addrV4 == INADDR_LOOPBACK )
+ {
+ bitmask = 1;
+ bitmapPtr = &me->bitmapV4;
+ }
+ }
+ else
+ {
+ addrOffset = addrV4 - kTestDNSServerBaseAddrV4;
+ if( ( addrOffset >= 1 ) && ( addrOffset <= item->addressCount ) )
+ {
+ bitmask = UINT64_C( 1 ) << ( addrOffset - 1 );
+ bitmapPtr = &me->bitmapV4;
+ }
+ }
+ }
+ }
+ else if( inError == kDNSServiceErr_NoSuchRecord )
+ {
+ bitmask = 1;
+ bitmapPtr = &me->bitmapV4;
+ }
+ }
+ else if( ( sip->sa.sa_family == AF_INET6 ) && item->wantV6 )
+ {
+ if( item->hasV6 )
+ {
+ if( !inError )
+ {
+ const uint8_t * const addrV6 = sip->v6.sin6_addr.s6_addr;
+
+ if( strcasecmp( item->name, "localhost." ) == 0 )
+ {
+ if( memcmp( addrV6, in6addr_loopback.s6_addr, 16 ) == 0 )
+ {
+ bitmask = 1;
+ bitmapPtr = &me->bitmapV6;
+ }
+ }
+ else if( memcmp( addrV6, kTestDNSServerBaseAddrV6, 15 ) == 0 )
+ {
+ addrOffset = addrV6[ 15 ];
+ if( ( addrOffset >= 1 ) && ( addrOffset <= item->addressCount ) )
+ {
+ bitmask = UINT64_C( 1 ) << ( addrOffset - 1 );
+ bitmapPtr = &me->bitmapV6;
+ }
+ }
+ }
+ }
+ else if( inError == kDNSServiceErr_NoSuchRecord )
+ {
+ bitmask = 1;
+ bitmapPtr = &me->bitmapV6;
+ }
+ }
+
+ if( bitmapPtr && ( *bitmapPtr & bitmask ) )
+ {
+ *bitmapPtr &= ~bitmask;
+ if( !me->gotFirstResult )
+ {
+ me->firstTicks = nowTicks;
+ me->gotFirstResult = true;
+ }
+
+ if( ( me->bitmapV4 == 0 ) && ( me->bitmapV6 == 0 ) )
+ {
+ me->endTicks = nowTicks;
+ _GAITesterCompleteCurrentTest( me, false );
+ }
+ }
+
+exit:
+ return;
+}
+
+//===========================================================================================================================
+// _GAITesterCompleteCurrentTest
+//===========================================================================================================================
+
+static OSStatus
+ _GAITesterGetDNSMessageFromPacket(
+ const uint8_t * inPacketPtr,
+ size_t inPacketLen,
+ const uint8_t ** outMsgPtr,
+ size_t * outMsgLen );
+
+static void _GAITesterCompleteCurrentTest( GAITesterRef me, Boolean inTimedOut )
+{
+ OSStatus err;
+ GAITestItem * item;
+ struct timeval * tsQA = NULL;
+ struct timeval * tsQAAAA = NULL;
+ struct timeval * tsRA = NULL;
+ struct timeval * tsRAAAA = NULL;
+ struct timeval timeStamps[ 4 ];
+ struct timeval * tsPtr = &timeStamps[ 0 ];
+ struct timeval * tsQ;
+ struct timeval * tsR;
+ int64_t idleTimeUs;
+ uint8_t name[ kDomainNameLengthMax ];
+
+ DNSServiceForget( &me->opRef );
+ DNSServiceForget( &me->mainRef );
+
+ if( inTimedOut )
+ {
+ for( item = me->currentItem; item; item = item->next )
+ {
+ item->firstTimeUs = -1;
+ item->timeUs = -1;
+ }
+ me->currentItem = NULL;
+
+ CFRetain( me );
+ dispatch_async_f( me->queue, me, _GAITesterRun );
+ return;
+ }
+
+ item = me->currentItem;
+ err = DomainNameFromString( name, item->name, NULL );
+ require_noerr( err, exit );
+
+ for( ;; )
+ {
+ int status;
+ struct pcap_pkthdr * pktHdr;
+ const uint8_t * packet;
+ const uint8_t * msgPtr;
+ size_t msgLen;
+ const DNSHeader * hdr;
+ unsigned int flags;
+ const uint8_t * ptr;
+ const DNSQuestionFixedFields * qfields;
+ unsigned int qtype;
+ uint8_t qname[ kDomainNameLengthMax ];
+
+ status = pcap_next_ex( me->pcap, &pktHdr, &packet );
+ if( status != 1 ) break;
+ if( _GAITesterGetDNSMessageFromPacket( packet, pktHdr->caplen, &msgPtr, &msgLen ) != kNoErr ) continue;
+ if( msgLen < kDNSHeaderLength ) continue;
+
+ hdr = (const DNSHeader *) msgPtr;
+ flags = DNSHeaderGetFlags( hdr );
+ if( DNSFlagsGetOpCode( flags ) != kDNSOpCode_Query ) continue;
+ if( DNSHeaderGetQuestionCount( hdr ) < 1 ) continue;
+
+ ptr = (const uint8_t *) &hdr[ 1 ];
+ if( DNSMessageExtractDomainName( msgPtr, msgLen, ptr, qname, &ptr ) != kNoErr ) continue;
+ if( !DomainNameEqual( qname, name ) ) continue;
+
+ qfields = (const DNSQuestionFixedFields *) ptr;
+ if( DNSQuestionFixedFieldsGetClass( qfields ) != kDNSServiceClass_IN ) continue;
+
+ qtype = DNSQuestionFixedFieldsGetType( qfields );
+ if( item->wantV4 && ( qtype == kDNSServiceType_A ) )
+ {
+ if( flags & kDNSHeaderFlag_Response )
+ {
+ if( tsQA && !tsRA )
+ {
+ tsRA = tsPtr++;
+ *tsRA = pktHdr->ts;
+ }
+ }
+ else if( !tsQA )
+ {
+ tsQA = tsPtr++;
+ *tsQA = pktHdr->ts;
+ }
+ }
+ else if( item->wantV6 && ( qtype == kDNSServiceType_AAAA ) )
+ {
+ if( flags & kDNSHeaderFlag_Response )
+ {
+ if( tsQAAAA && !tsRAAAA )
+ {
+ tsRAAAA = tsPtr++;
+ *tsRAAAA = pktHdr->ts;
+ }
+ }
+ else if( !tsQAAAA )
+ {
+ tsQAAAA = tsPtr++;
+ *tsQAAAA = pktHdr->ts;
+ }
+ }
+ }
+
+ if( tsQA && tsQAAAA ) tsQ = TIMEVAL_GT( *tsQA, *tsQAAAA ) ? tsQA : tsQAAAA;
+ else tsQ = tsQA ? tsQA : tsQAAAA;
+
+ if( tsRA && tsRAAAA ) tsR = TIMEVAL_LT( *tsRA, *tsRAAAA ) ? tsRA : tsRAAAA;
+ else tsR = tsQA ? tsQA : tsQAAAA;
+
+ if( tsQ && tsR )
+ {
+ idleTimeUs = TIMEVAL_USEC64_DIFF( *tsR, *tsQ );
+ if( idleTimeUs < 0 ) idleTimeUs = 0;
+ }
+ else
+ {
+ idleTimeUs = 0;
+ }
+
+ item->connectionTimeUs = (int64_t) UpTicksToMicroseconds( me->connTicks - me->startTicks );
+ item->firstTimeUs = (int64_t)( UpTicksToMicroseconds( me->firstTicks - me->connTicks ) - (uint64_t) idleTimeUs );
+ item->timeUs = (int64_t)( UpTicksToMicroseconds( me->endTicks - me->connTicks ) - (uint64_t) idleTimeUs );
+
+ _GAITesterAdvanceCurrentItem( me );
+ CFRetain( me );
+ dispatch_async_f( me->queue, me, _GAITesterRun );
+
+exit:
+ if( err ) _GAITesterStop( me );
+}
+
+//===========================================================================================================================
+// _GAITesterGetDNSMessageFromPacket
+//===========================================================================================================================
+
+#define kHeaderSizeNullLink 4
+#define kHeaderSizeIPv4Min 20
+#define kHeaderSizeIPv6 40
+#define kHeaderSizeUDP 8
+
+#define kIPProtocolUDP 0x11
+
+static OSStatus
+ _GAITesterGetDNSMessageFromPacket(
+ const uint8_t * inPacketPtr,
+ size_t inPacketLen,
+ const uint8_t ** outMsgPtr,
+ size_t * outMsgLen )
+{
+ OSStatus err;
+ const uint8_t * nullLink;
+ uint32_t addressFamily;
+ const uint8_t * ip;
+ int ipHeaderLen;
+ int protocol;
+ const uint8_t * msg;
+ const uint8_t * const end = &inPacketPtr[ inPacketLen ];
+
+ nullLink = &inPacketPtr[ 0 ];
+ require_action_quiet( ( end - nullLink ) >= kHeaderSizeNullLink, exit, err = kUnderrunErr );
+ addressFamily = ReadHost32( &nullLink[ 0 ] );
+
+ ip = &nullLink[ kHeaderSizeNullLink ];
+ if( addressFamily == AF_INET )
+ {
+ require_action_quiet( ( end - ip ) >= kHeaderSizeIPv4Min, exit, err = kUnderrunErr );
+ ipHeaderLen = ( ip[ 0 ] & 0x0F ) * 4;
+ protocol = ip[ 9 ];
+ }
+ else if( addressFamily == AF_INET6 )
+ {
+ require_action_quiet( ( end - ip ) >= kHeaderSizeIPv6, exit, err = kUnderrunErr );
+ ipHeaderLen = kHeaderSizeIPv6;
+ protocol = ip[ 6 ];
+ }
+ else
+ {
+ err = kTypeErr;
+ goto exit;
+ }
+ require_action_quiet( protocol == kIPProtocolUDP, exit, err = kTypeErr );
+ require_action_quiet( ( end - ip ) >= ( ipHeaderLen + kHeaderSizeUDP ), exit, err = kUnderrunErr );
+
+ msg = &ip[ ipHeaderLen + kHeaderSizeUDP ];
+
+ *outMsgPtr = msg;
+ *outMsgLen = (size_t)( end - msg );
+ err = kNoErr;
+
+exit:
+ return( err );
+}
+
+//===========================================================================================================================
+// GAITestCaseCreate
+//===========================================================================================================================
+
+static OSStatus GAITestCaseCreate( const char *inTitle, unsigned int inTimeLimitMs, GAITestCase **outSet )
+{
+ OSStatus err;
+ GAITestCase * obj;
+
+ obj = (GAITestCase *) calloc( 1, sizeof( *obj ) );
+ require_action( obj, exit, err = kNoMemoryErr );
+
+ obj->title = strdup( inTitle );
+ require_action( obj->title, exit, err = kNoMemoryErr );
+
+ obj->timeLimitMs = inTimeLimitMs;
+
+ *outSet = obj;
+ obj = NULL;
+ err = kNoErr;
+
+exit:
+ if( obj ) GAITestCaseFree( obj );
+ return( err );
+}
+
+//===========================================================================================================================
+// GAITestCaseFree
+//===========================================================================================================================
+
+static void GAITestCaseFree( GAITestCase *inCase )
+{
+ GAITestItem * item;
+
+ while( ( item = inCase->itemList ) != NULL )
+ {
+ inCase->itemList = item->next;
+ GAITestItemFree( item );
+ }
+ ForgetMem( &inCase->title );
+ free( inCase );
+}
+
+//===========================================================================================================================
+// GAITestCaseAddItem
+//===========================================================================================================================
+
+// A character set of lower-case alphabet characters and digits and a string length of six allows for 36^6 = 2,176,782,336
+// possible strings to use in the Tag label.
+
+#define kUniqueStringCharSet "abcdefghijklmnopqrstuvwxyz0123456789"
+#define kUniqueStringCharSetLen sizeof_string( kUniqueStringCharSet )
+#define kUniqueStringLen 6
+
+static OSStatus
+ GAITestCaseAddItem(
+ GAITestCase * inCase,
+ unsigned int inAliasCount,
+ unsigned int inAddressCount,
+ int inTTL,
+ GAITestAddrType inHasAddrs,
+ GAITestAddrType inWantAddrs,
+ unsigned int inItemCount )
+{
+ OSStatus err;
+ GAITestItem * item;
+ GAITestItem * item2;
+ GAITestItem * newItemList = NULL;
+ GAITestItem ** itemPtr;
+ char * ptr;
+ char * end;
+ unsigned int i;
+ char name[ 64 ];
+ char uniqueStr[ kUniqueStringLen + 1 ];
+
+ require_action_quiet( inItemCount > 0, exit, err = kNoErr );
+
+ // Limit address count to 64 because we use 64-bit bitmaps for keeping track of addresses.
+
+ require_action_quiet( ( inAddressCount >= 1 ) && ( inAddressCount <= 64 ), exit, err = kCountErr );
+ require_action_quiet( ( inAliasCount >= 0 ) && ( inAliasCount <= INT32_MAX ), exit, err = kCountErr );
+ require_action_quiet( GAITestAddrTypeIsValid( inHasAddrs ), exit, err = kValueErr );
+
+ ptr = &name[ 0 ];
+ end = &name[ countof( name ) ];
+
+ // Add Alias label.
+
+ if( inAliasCount == 1 ) SNPrintF_Add( &ptr, end, "alias." );
+ else if( inAliasCount >= 2 ) SNPrintF_Add( &ptr, end, "alias-%u.", inAliasCount );
+
+ // Add Count label.
+
+ SNPrintF_Add( &ptr, end, "count-%u.", inAddressCount );
+
+ // Add TTL label.
+
+ if( inTTL >= 0 ) SNPrintF_Add( &ptr, end, "ttl-%d.", inTTL );
+
+ // Add Tag label.
+
+ RandomString( kUniqueStringCharSet, kUniqueStringCharSetLen, kUniqueStringLen, kUniqueStringLen, uniqueStr );
+ SNPrintF_Add( &ptr, end, "tag-%s.", uniqueStr );
+
+ // Add IPv4 or IPv6 label if necessary.
+
+ switch( inHasAddrs )
+ {
+ case kGAITestAddrType_IPv4:
+ SNPrintF_Add( &ptr, end, "ipv4." );
+ break;
+
+ case kGAITestAddrType_IPv6:
+ SNPrintF_Add( &ptr, end, "ipv6." );
+ break;
+ }
+
+ // Add d.test. labels.
+
+ SNPrintF_Add( &ptr, end, "d.test." );
+
+ // Create item.
+
+ err = GAITestItemCreate( name, inAddressCount, inHasAddrs, inWantAddrs, &item );
+ require_noerr( err, exit );
+
+ newItemList = item;
+ itemPtr = &item->next;
+
+ // Create repeat items.
+
+ for( i = 1; i < inItemCount; ++i )
+ {
+ err = GAITestItemDuplicate( item, &item2 );
+ require_noerr( err, exit );
+
+ *itemPtr = item2;
+ itemPtr = &item2->next;
+ }
+
+ // Append to test case's item list.
+
+ for( itemPtr = &inCase->itemList; *itemPtr; itemPtr = &( *itemPtr )->next ) {}
+ *itemPtr = newItemList;
+ newItemList = NULL;
+
+exit:
+ while( ( item = newItemList ) != NULL )
+ {
+ newItemList = item->next;
+ GAITestItemFree( item );
+ }
+ return( err );
+}
+
+//===========================================================================================================================
+// GAITestCaseAddLocalHostItem
+//===========================================================================================================================
+
+static OSStatus GAITestCaseAddLocalHostItem( GAITestCase *inCase, GAITestAddrType inWantAddrs, unsigned int inItemCount )
+{
+ OSStatus err;
+ GAITestItem * item;
+ GAITestItem * item2;
+ GAITestItem * newItemList = NULL;
+ GAITestItem ** itemPtr;
+ unsigned int i;
+
+ require_action_quiet( inItemCount > 1, exit, err = kNoErr );
+
+ err = GAITestItemCreate( "localhost.", 1, kGAITestAddrType_Both, inWantAddrs, &item );
+ require_noerr( err, exit );
+
+ newItemList = item;
+ itemPtr = &item->next;
+
+ // Create repeat items.
+
+ for( i = 1; i < inItemCount; ++i )
+ {
+ err = GAITestItemDuplicate( item, &item2 );
+ require_noerr( err, exit );
+
+ *itemPtr = item2;
+ itemPtr = &item2->next;
+ }
+
+ for( itemPtr = &inCase->itemList; *itemPtr; itemPtr = &( *itemPtr )->next ) {}
+ *itemPtr = newItemList;
+ newItemList = NULL;
+
+exit:
+ while( ( item = newItemList ) != NULL )
+ {
+ newItemList = item->next;
+ GAITestItemFree( item );
+ }
+ return( err );
+}
+
+//===========================================================================================================================
+// GAITestItemCreate
+//===========================================================================================================================
+
+static OSStatus
+ GAITestItemCreate(
+ const char * inName,
+ unsigned int inAddressCount,
+ GAITestAddrType inHasAddrs,
+ GAITestAddrType inWantAddrs,
+ GAITestItem ** outItem )
+{
+ OSStatus err;
+ GAITestItem * obj = NULL;
+
+ require_action_quiet( inAddressCount >= 1, exit, err = kCountErr );
+ require_action_quiet( GAITestAddrTypeIsValid( inHasAddrs ), exit, err = kValueErr );
+ require_action_quiet( GAITestAddrTypeIsValid( inWantAddrs ), exit, err = kValueErr );
+
+ obj = (GAITestItem *) calloc( 1, sizeof( *obj ) );
+ require_action( obj, exit, err = kNoMemoryErr );
+
+ obj->name = strdup( inName );
+ require_action( obj->name, exit, err = kNoMemoryErr );
+
+ obj->addressCount = inAddressCount;
+ obj->hasV4 = ( inHasAddrs & kGAITestAddrType_IPv4 ) ? true : false;
+ obj->hasV6 = ( inHasAddrs & kGAITestAddrType_IPv6 ) ? true : false;
+ obj->wantV4 = ( inWantAddrs & kGAITestAddrType_IPv4 ) ? true : false;
+ obj->wantV6 = ( inWantAddrs & kGAITestAddrType_IPv6 ) ? true : false;
+
+ *outItem = obj;
+ obj = NULL;
+ err = kNoErr;
+
+exit:
+ if( obj ) GAITestItemFree( obj );
+ return( err );
+}
+
+//===========================================================================================================================
+// GAITestItemDuplicate
+//===========================================================================================================================
+
+static OSStatus GAITestItemDuplicate( const GAITestItem *inItem, GAITestItem **outItem )
+{
+ OSStatus err;
+ GAITestItem * obj;
+
+ obj = (GAITestItem *) calloc( 1, sizeof( *obj ) );
+ require_action( obj, exit, err = kNoMemoryErr );
+
+ *obj = *inItem;
+ obj->next = NULL;
+ if( inItem->name )
+ {
+ obj->name = strdup( inItem->name );
+ require_action( obj->name, exit, err = kNoMemoryErr );
+ }
+
+ *outItem = obj;
+ obj = NULL;
+ err = kNoErr;
+
+exit:
+ if( obj ) GAITestItemFree( obj );
+ return( err );
+}
+
+//===========================================================================================================================
+// GAITestItemFree
+//===========================================================================================================================
+
+static void GAITestItemFree( GAITestItem *inItem )
+{
+ ForgetMem( &inItem->name );
+ free( inItem );
+}
+
+//===========================================================================================================================
// SSDPDiscoverCmd
//===========================================================================================================================
@@ -6219,13 +10272,13 @@ static OSStatus WriteSSDPSearchRequest( HTTPHeader *inHeader, const void *inHost
static void SSDPDiscoverCmd( void )
{
OSStatus err;
+ struct timeval now;
SSDPDiscoverContext * context;
dispatch_source_t signalSource = NULL;
SocketRef sockV4 = kInvalidSocketRef;
SocketRef sockV6 = kInvalidSocketRef;
ssize_t n;
int sendCount;
- char time[ kTimestampBufLen ];
// Set up SIGINT handler.
@@ -6318,13 +10371,13 @@ static void SSDPDiscoverCmd( void )
{
if( gSSDPDiscover_Verbose )
{
- GetTimestampStr( time );
+ gettimeofday( &now, NULL );
FPrintF( stdout, "---\n" );
- FPrintF( stdout, "Send time: %s\n", time );
- FPrintF( stdout, "Source Port: %d\n", SocketToPortNumber( sockV4 ) );
- FPrintF( stdout, "Destination: %##a\n", &mcastAddr4 );
- FPrintF( stdout, "Message size: %zu\n", context->header.len );
- FPrintF( stdout, "HTTP header:\n%1{text}", context->header.buf, context->header.len );
+ FPrintF( stdout, "Send time: %{du:time}\n", &now );
+ FPrintF( stdout, "Source Port: %d\n", SocketToPortNumber( sockV4 ) );
+ FPrintF( stdout, "Destination: %##a\n", &mcastAddr4 );
+ FPrintF( stdout, "Message size: %zu\n", context->header.len );
+ FPrintF( stdout, "HTTP header:\n%1{text}", context->header.buf, context->header.len );
}
++sendCount;
}
@@ -6357,13 +10410,13 @@ static void SSDPDiscoverCmd( void )
{
if( gSSDPDiscover_Verbose )
{
- GetTimestampStr( time );
+ gettimeofday( &now, NULL );
FPrintF( stdout, "---\n" );
- FPrintF( stdout, "Send time: %s\n", time );
- FPrintF( stdout, "Source Port: %d\n", SocketToPortNumber( sockV6 ) );
- FPrintF( stdout, "Destination: %##a\n", &mcastAddr6 );
- FPrintF( stdout, "Message size: %zu\n", context->header.len );
- FPrintF( stdout, "HTTP header:\n%1{text}", context->header.buf, context->header.len );
+ FPrintF( stdout, "Send time: %{du:time}\n", &now );
+ FPrintF( stdout, "Source Port: %d\n", SocketToPortNumber( sockV6 ) );
+ FPrintF( stdout, "Destination: %##a\n", &mcastAddr6 );
+ FPrintF( stdout, "Message size: %zu\n", context->header.len );
+ FPrintF( stdout, "HTTP header:\n%1{text}", context->header.buf, context->header.len );
}
++sendCount;
}
@@ -6378,37 +10431,33 @@ static void SSDPDiscoverCmd( void )
if( IsValidSocket( sockV4 ) )
{
- SocketContext * sockContext;
+ SocketContext * sockCtx;
- sockContext = (SocketContext *) calloc( 1, sizeof( *sockContext ) );
- require_action( sockContext, exit, err = kNoMemoryErr );
+ err = SocketContextCreate( sockV4, context, &sockCtx );
+ require_noerr( err, exit );
+ sockV4 = kInvalidSocketRef;
- err = DispatchReadSourceCreate( sockV4, SSDPDiscoverReadHandler, SocketContextCancelHandler, sockContext,
+ err = DispatchReadSourceCreate( sockCtx->sock, NULL, SSDPDiscoverReadHandler, SocketContextCancelHandler, sockCtx,
&context->readSourceV4 );
- if( err ) ForgetMem( &sockContext );
+ if( err ) ForgetSocketContext( &sockCtx );
require_noerr( err, exit );
- sockContext->context = context;
- sockContext->sock = sockV4;
- sockV4 = kInvalidSocketRef;
dispatch_resume( context->readSourceV4 );
}
if( IsValidSocket( sockV6 ) )
{
- SocketContext * sockContext;
+ SocketContext * sockCtx;
- sockContext = (SocketContext *) calloc( 1, sizeof( *sockContext ) );
- require_action( sockContext, exit, err = kNoMemoryErr );
+ err = SocketContextCreate( sockV6, context, &sockCtx );
+ require_noerr( err, exit );
+ sockV6 = kInvalidSocketRef;
- err = DispatchReadSourceCreate( sockV6, SSDPDiscoverReadHandler, SocketContextCancelHandler, sockContext,
+ err = DispatchReadSourceCreate( sockCtx->sock, NULL, SSDPDiscoverReadHandler, SocketContextCancelHandler, sockCtx,
&context->readSourceV6 );
- if( err ) ForgetMem( &sockContext );
+ if( err ) ForgetSocketContext( &sockCtx );
require_noerr( err, exit );
- sockContext->context = context;
- sockContext->sock = sockV6;
- sockV6 = kInvalidSocketRef;
dispatch_resume( context->readSourceV6 );
}
@@ -6475,7 +10524,6 @@ static void SSDPDiscoverPrintPrologue( const SSDPDiscoverContext *inContext )
const char * ifName;
char ifNameBuf[ IF_NAMESIZE + 1 ];
NetTransportType ifType;
- char time[ kTimestampBufLen ];
ifName = if_indextoname( inContext->ifindex, ifNameBuf );
@@ -6489,7 +10537,7 @@ static void SSDPDiscoverPrintPrologue( const SSDPDiscoverContext *inContext )
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 ) );
+ FPrintF( stdout, "Start time: %{du:time}\n", NULL );
}
//===========================================================================================================================
@@ -6499,23 +10547,23 @@ static void SSDPDiscoverPrintPrologue( const SSDPDiscoverContext *inContext )
static void SSDPDiscoverReadHandler( void *inContext )
{
OSStatus err;
- SocketContext * const sockContext = (SocketContext *) inContext;
- SSDPDiscoverContext * const context = (SSDPDiscoverContext *) sockContext->context;
- HTTPHeader * const header = &context->header;
+ struct timeval now;
+ SocketContext * const sockCtx = (SocketContext *) inContext;
+ SSDPDiscoverContext * const context = (SSDPDiscoverContext *) sockCtx->userContext;
+ HTTPHeader * const header = &context->header;
sockaddr_ip fromAddr;
size_t msgLen;
- char time[ kTimestampBufLen ];
- GetTimestampStr( time );
+ gettimeofday( &now, NULL );
- err = SocketRecvFrom( sockContext->sock, header->buf, sizeof( header->buf ), &msgLen, &fromAddr, sizeof( fromAddr ),
+ err = SocketRecvFrom( sockCtx->sock, header->buf, sizeof( header->buf ), &msgLen, &fromAddr, sizeof( fromAddr ),
NULL, NULL, NULL, NULL );
require_noerr( err, exit );
FPrintF( stdout, "---\n" );
- FPrintF( stdout, "Receive time: %s\n", time );
- FPrintF( stdout, "Source: %##a\n", &fromAddr );
- FPrintF( stdout, "Message size: %zu\n", msgLen );
+ FPrintF( stdout, "Receive time: %{du:time}\n", &now );
+ FPrintF( stdout, "Source: %##a\n", &fromAddr );
+ FPrintF( stdout, "Message size: %zu\n", msgLen );
header->len = msgLen;
if( HTTPHeader_Validate( header ) )
{
@@ -6618,7 +10666,6 @@ static void ResQueryCmd( void )
res_query_f res_query_ptr;
int n;
uint16_t type, class;
- char time[ kTimestampBufLen ];
uint8_t answer[ 1024 ];
// Get pointer to one of the res_query() functions.
@@ -6663,10 +10710,10 @@ static void ResQueryCmd( void )
// Print prologue.
- FPrintF( stdout, "Name: %s\n", gResQuery_Name );
- FPrintF( stdout, "Type: %s (%u)\n", RecordTypeToString( type ), type );
- FPrintF( stdout, "Class: %s (%u)\n", ( class == kDNSServiceClass_IN ) ? "IN" : "???", class );
- FPrintF( stdout, "Start time: %s\n", GetTimestampStr( time ) );
+ FPrintF( stdout, "Name: %s\n", gResQuery_Name );
+ FPrintF( stdout, "Type: %s (%u)\n", RecordTypeToString( type ), type );
+ FPrintF( stdout, "Class: %s (%u)\n", ( class == kDNSServiceClass_IN ) ? "IN" : "???", class );
+ FPrintF( stdout, "Start time: %{du:time}\n", NULL );
FPrintF( stdout, "---\n" );
// Call res_query().
@@ -6681,8 +10728,7 @@ static void ResQueryCmd( void )
// Print result.
- FPrintF( stdout, "Message size: %d\n\n", n );
- PrintUDNSMessage( answer, (size_t) n, false );
+ FPrintF( stdout, "Message size: %d\n\n%{du:dnsmsg}", n, answer, (size_t) n );
exit:
if( err ) exit( 1 );
@@ -6719,7 +10765,6 @@ static void ResolvDNSQueryCmd( void )
uint16_t type, class;
sockaddr_ip from;
uint32_t fromLen;
- char time[ kTimestampBufLen ];
uint8_t answer[ 1024 ];
// Make sure that the required symbols are available.
@@ -6774,11 +10819,11 @@ static void ResolvDNSQueryCmd( void )
// Print prologue.
- FPrintF( stdout, "Name: %s\n", gResolvDNSQuery_Name );
- FPrintF( stdout, "Type: %s (%u)\n", RecordTypeToString( type ), type );
- FPrintF( stdout, "Class: %s (%u)\n", ( class == kDNSServiceClass_IN ) ? "IN" : "???", class );
- FPrintF( stdout, "Path: %s\n", gResolvDNSQuery_Path ? gResolvDNSQuery_Name : "<NULL>" );
- FPrintF( stdout, "Start time: %s\n", GetTimestampStr( time ) );
+ FPrintF( stdout, "Name: %s\n", gResolvDNSQuery_Name );
+ FPrintF( stdout, "Type: %s (%u)\n", RecordTypeToString( type ), type );
+ FPrintF( stdout, "Class: %s (%u)\n", ( class == kDNSServiceClass_IN ) ? "IN" : "???", class );
+ FPrintF( stdout, "Path: %s\n", gResolvDNSQuery_Path ? gResolvDNSQuery_Name : "<NULL>" );
+ FPrintF( stdout, "Start time: %{du:time}\n", NULL );
FPrintF( stdout, "---\n" );
// Call dns_query().
@@ -6797,13 +10842,258 @@ static void ResolvDNSQueryCmd( void )
// Print result.
FPrintF( stdout, "From: %##a\n", &from );
- FPrintF( stdout, "Message size: %d\n\n", n );
- PrintUDNSMessage( answer, (size_t) n, false );
+ FPrintF( stdout, "Message size: %d\n\n%{du:dnsmsg}", n, answer, (size_t) n );
exit:
if( dns ) soft_dns_free( dns );
if( err ) exit( 1 );
}
+
+//===========================================================================================================================
+// CFHostCmd
+//===========================================================================================================================
+
+static void
+ _CFHostResolveCallback(
+ CFHostRef inHost,
+ CFHostInfoType inInfoType,
+ const CFStreamError * inError,
+ void * inInfo );
+
+static void CFHostCmd( void )
+{
+ OSStatus err;
+ CFStringRef name;
+ Boolean success;
+ CFHostRef host = NULL;
+ CFHostClientContext context;
+ CFStreamError streamErr;
+
+ name = CFStringCreateWithCString( kCFAllocatorDefault, gCFHost_Name, kCFStringEncodingUTF8 );
+ require_action( name, exit, err = kUnknownErr );
+
+ host = CFHostCreateWithName( kCFAllocatorDefault, name );
+ ForgetCF( &name );
+ require_action( host, exit, err = kUnknownErr );
+
+ memset( &context, 0, sizeof( context ) );
+ success = CFHostSetClient( host, _CFHostResolveCallback, &context );
+ require_action( success, exit, err = kUnknownErr );
+
+ CFHostScheduleWithRunLoop( host, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode );
+
+ // Print prologue.
+
+ FPrintF( stdout, "Hostname: %s\n", gCFHost_Name );
+ FPrintF( stdout, "Start time: %{du:time}\n", NULL );
+ FPrintF( stdout, "---\n" );
+
+ success = CFHostStartInfoResolution( host, kCFHostAddresses, &streamErr );
+ require_action( success, exit, err = kUnknownErr );
+ err = kNoErr;
+
+ CFRunLoopRun();
+
+exit:
+ CFReleaseNullSafe( host );
+ if( err ) exit( 1 );
+}
+
+static void _CFHostResolveCallback( CFHostRef inHost, CFHostInfoType inInfoType, const CFStreamError *inError, void *inInfo )
+{
+ OSStatus err;
+ struct timeval now;
+
+ gettimeofday( &now, NULL );
+
+ Unused( inInfoType );
+ Unused( inInfo );
+
+ if( inError && ( inError->domain != 0 ) && ( inError->error ) )
+ {
+ err = inError->error;
+ if( inError->domain == kCFStreamErrorDomainNetDB )
+ {
+ FPrintF( stderr, "Error %d: %s.\n", err, gai_strerror( err ) );
+ }
+ else
+ {
+ FPrintF( stderr, "Error %#m\n", err );
+ }
+ }
+ else
+ {
+ CFArrayRef addresses;
+ CFIndex count, i;
+ CFDataRef addrData;
+ const struct sockaddr * sockAddr;
+ Boolean wasResolved = false;
+
+ addresses = CFHostGetAddressing( inHost, &wasResolved );
+ check( wasResolved );
+
+ if( addresses )
+ {
+ count = CFArrayGetCount( addresses );
+ for( i = 0; i < count; ++i )
+ {
+ addrData = CFArrayGetCFDataAtIndex( addresses, i, &err );
+ require_noerr( err, exit );
+
+ sockAddr = (const struct sockaddr *) CFDataGetBytePtr( addrData );
+ FPrintF( stdout, "%##a\n", sockAddr );
+ }
+ }
+ err = kNoErr;
+ }
+
+ FPrintF( stdout, "---\n" );
+ FPrintF( stdout, "End time: %{du:time}\n", &now );
+
+ if( gCFHost_WaitSecs > 0 ) sleep( (unsigned int) gCFHost_WaitSecs );
+
+exit:
+ exit( err ? 1 : 0 );
+}
+
+//===========================================================================================================================
+// DNSConfigAddCmd
+//
+// Note: Based on ajn's supplemental test tool.
+//===========================================================================================================================
+
+static void DNSConfigAddCmd( void )
+{
+ OSStatus err;
+ CFMutableDictionaryRef dict = NULL;
+ CFMutableArrayRef array = NULL;
+ size_t i;
+ SCDynamicStoreRef store = NULL;
+ CFStringRef key = NULL;
+ Boolean success;
+
+ if( geteuid() != 0 )
+ {
+ FPrintF( stderr, "error: This command must to be run as root.\n" );
+ err = kIDErr;
+ goto exit;
+ }
+
+ // Create dictionary.
+
+ dict = CFDictionaryCreateMutable( NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks );
+ require_action( dict, exit, err = kNoMemoryErr );
+
+ // Add DNS server IP addresses.
+
+ array = CFArrayCreateMutable( NULL, (CFIndex) gDNSConfigAdd_IPAddrCount, &kCFTypeArrayCallBacks );
+ require_action( array, exit, err = kNoMemoryErr );
+
+ for( i = 0; i < gDNSConfigAdd_IPAddrCount; ++i )
+ {
+ CFStringRef addrStr;
+
+ addrStr = CFStringCreateWithCString( NULL, gDNSConfigAdd_IPAddrArray[ i ], kCFStringEncodingUTF8 );
+ require_action( addrStr, exit, err = kUnknownErr );
+
+ CFArrayAppendValue( array, addrStr );
+ CFRelease( addrStr );
+ }
+
+ CFDictionarySetValue( dict, kSCPropNetDNSServerAddresses, array );
+ ForgetCF( &array );
+
+ // Add domains, if any.
+
+ array = CFArrayCreateMutable( NULL, (CFIndex) Min( gDNSConfigAdd_DomainCount, 1 ), &kCFTypeArrayCallBacks );
+ require_action( array, exit, err = kNoMemoryErr );
+
+ if( gDNSConfigAdd_DomainCount > 0 )
+ {
+ for( i = 0; i < gDNSConfigAdd_DomainCount; ++i )
+ {
+ CFStringRef domainStr;
+
+ domainStr = CFStringCreateWithCString( NULL, gDNSConfigAdd_DomainArray[ i ], kCFStringEncodingUTF8 );
+ require_action( domainStr, exit, err = kUnknownErr );
+
+ CFArrayAppendValue( array, domainStr );
+ CFRelease( domainStr );
+ }
+ }
+ else
+ {
+ // There are no domains, but the domain array needs to be non-empty, so add a zero-length string to the array.
+
+ CFArrayAppendValue( array, CFSTR( "" ) );
+ }
+
+ CFDictionarySetValue( dict, kSCPropNetDNSSupplementalMatchDomains, array );
+ ForgetCF( &array );
+
+ // Add interface, if any.
+
+ if( gDNSConfigAdd_Interface )
+ {
+ err = CFDictionarySetCString( dict, kSCPropInterfaceName, gDNSConfigAdd_Interface, kSizeCString );
+ require_noerr( err, exit );
+
+ CFDictionarySetValue( dict, kSCPropNetDNSConfirmedServiceID, gDNSConfigAdd_ID );
+ }
+
+ // Set dictionary in dynamic store.
+
+ store = SCDynamicStoreCreate( NULL, CFSTR( "com.apple.dnssdutil" ), NULL, NULL );
+ err = map_scerror( store );
+ require_noerr( err, exit );
+
+ key = SCDynamicStoreKeyCreateNetworkServiceEntity( NULL, kSCDynamicStoreDomainState, gDNSConfigAdd_ID, kSCEntNetDNS );
+ require_action( key, exit, err = kUnknownErr );
+
+ success = SCDynamicStoreSetValue( store, key, dict );
+ require_action( success, exit, err = kUnknownErr );
+
+exit:
+ CFReleaseNullSafe( dict );
+ CFReleaseNullSafe( array );
+ CFReleaseNullSafe( store );
+ CFReleaseNullSafe( key );
+ gExitCode = err ? 1 : 0;
+}
+
+//===========================================================================================================================
+// DNSConfigRemoveCmd
+//===========================================================================================================================
+
+static void DNSConfigRemoveCmd( void )
+{
+ OSStatus err;
+ SCDynamicStoreRef store = NULL;
+ CFStringRef key = NULL;
+ Boolean success;
+
+ if( geteuid() != 0 )
+ {
+ FPrintF( stderr, "error: This command must to be run as root.\n" );
+ err = kIDErr;
+ goto exit;
+ }
+
+ store = SCDynamicStoreCreate( NULL, CFSTR( "com.apple.dnssdutil" ), NULL, NULL );
+ err = map_scerror( store );
+ require_noerr( err, exit );
+
+ key = SCDynamicStoreKeyCreateNetworkServiceEntity( NULL, kSCDynamicStoreDomainState, gDNSConfigRemove_ID, kSCEntNetDNS );
+ require_action( key, exit, err = kUnknownErr );
+
+ success = SCDynamicStoreRemoveValue( store, key );
+ require_action( success, exit, err = kUnknownErr );
+
+exit:
+ CFReleaseNullSafe( store );
+ CFReleaseNullSafe( key );
+ gExitCode = err ? 1 : 0;
+}
#endif // TARGET_OS_DARWIN
//===========================================================================================================================
@@ -6833,33 +11123,98 @@ 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 ) );
+ FPrintF( stdout, "End time: %{du:time}\n", NULL );
if( reason ) FPrintF( stdout, "End reason: %s\n", reason );
exit( gExitCode );
}
//===========================================================================================================================
-// GetTimestampStr
+// PrintFTimestampHandler
//===========================================================================================================================
-static char * GetTimestampStr( char inBuffer[ kTimestampBufLen ] )
+static int
+ PrintFTimestampHandler(
+ PrintFContext * inContext,
+ PrintFFormat * inFormat,
+ PrintFVAList * inArgs,
+ void * inUserContext )
{
- struct timeval now;
- struct tm * tm;
- size_t len;
+ struct timeval now;
+ const struct timeval * tv;
+ struct tm * localTime;
+ size_t len;
+ int n;
+ char dateTimeStr[ 32 ];
- gettimeofday( &now, NULL );
- tm = localtime( &now.tv_sec );
- require_action( tm, exit, *inBuffer = '\0' );
+ Unused( inUserContext );
+
+ tv = va_arg( inArgs->args, const struct timeval * );
+ require_action_quiet( !inFormat->suppress, exit, n = 0 );
- len = strftime( inBuffer, kTimestampBufLen, "%Y-%m-%d %H:%M:%S", tm );
- SNPrintF( &inBuffer[ len ], kTimestampBufLen - len, ".%06u", (unsigned int) now.tv_usec );
+ if( !tv )
+ {
+ gettimeofday( &now, NULL );
+ tv = &now;
+ }
+ localTime = localtime( &tv->tv_sec );
+ len = strftime( dateTimeStr, sizeof( dateTimeStr ), "%Y-%m-%d %H:%M:%S", localTime );
+ if( len == 0 ) dateTimeStr[ 0 ] = '\0';
+
+ n = PrintFCore( inContext, "%s.%06u", dateTimeStr, (unsigned int) tv->tv_usec );
exit:
- return( inBuffer );
+ return( n );
+}
+
+//===========================================================================================================================
+// PrintFDNSMessageHandler
+//===========================================================================================================================
+
+static int
+ PrintFDNSMessageHandler(
+ PrintFContext * inContext,
+ PrintFFormat * inFormat,
+ PrintFVAList * inArgs,
+ void * inUserContext )
+{
+ OSStatus err;
+ const void * msgPtr;
+ size_t msgLen;
+ char * text;
+ int n;
+ Boolean isMDNS;
+ Boolean printRawRData;
+
+ Unused( inUserContext );
+
+ msgPtr = va_arg( inArgs->args, const void * );
+ msgLen = va_arg( inArgs->args, size_t );
+ require_action_quiet( !inFormat->suppress, exit, n = 0 );
+
+ isMDNS = ( inFormat->altForm > 0 ) ? true : false;
+ if( inFormat->precision == 0 ) printRawRData = false;
+ else if( inFormat->precision == 1 ) printRawRData = true;
+ else
+ {
+ n = PrintFCore( inContext, "<< BAD %%{du:dnsmsg} PRECISION >>" );
+ goto exit;
+ }
+
+ err = DNSMessageToText( msgPtr, msgLen, isMDNS, printRawRData, &text );
+ if( !err )
+ {
+ n = PrintFCore( inContext, "%*{text}", inFormat->fieldWidth, text, kSizeCString );
+ free( text );
+ }
+ else
+ {
+ n = PrintFCore( inContext, "%*.1H", inFormat->fieldWidth, msgPtr, (int) msgLen, (int) msgLen );
+ }
+
+exit:
+ return( n );
}
//===========================================================================================================================
@@ -7043,17 +11398,9 @@ static OSStatus RecordDataFromArgString( const char *inString, uint8_t **outData
else if( stricmp_prefix( inString, kRDataArgPrefix_Domain ) == 0 )
{
const char * const str = inString + sizeof_string( kRDataArgPrefix_Domain );
- uint8_t * end;
- uint8_t dname[ kDomainNameLengthMax ];
-
- err = DomainNameFromString( dname, str, &end );
- require_noerr( err, exit );
-
- dataLen = (size_t)( end - dname );
- dataPtr = malloc( dataLen );
- require_action( dataPtr, exit, err = kNoMemoryErr );
- memcpy( dataPtr, dname, dataLen );
+ err = StringToDomainName( str, &dataPtr, &dataLen );
+ require_noerr_quiet( err, exit );
}
// File path
@@ -7083,18 +11430,9 @@ static OSStatus RecordDataFromArgString( const char *inString, uint8_t **outData
else if( stricmp_prefix( inString, kRDataArgPrefix_IPv4 ) == 0 )
{
const char * const str = inString + sizeof_string( kRDataArgPrefix_IPv4 );
- const char * end;
-
- dataLen = 4;
- dataPtr = (uint8_t *) malloc( dataLen );
- require_action( dataPtr, exit, err = kNoMemoryErr );
- err = StringToIPv4Address( str, kStringToIPAddressFlagsNoPort | kStringToIPAddressFlagsNoPrefix,
- (uint32_t *) dataPtr, NULL, NULL, NULL, &end );
- if( !err && ( *end != '\0' ) ) err = kMalformedErr;
- require_noerr( err, exit );
-
- *( (uint32_t *) dataPtr ) = HostToBig32( *( (uint32_t *) dataPtr ) );
+ err = StringToARecordData( str, &dataPtr, &dataLen );
+ require_noerr_quiet( err, exit );
}
// IPv6 address string
@@ -7102,17 +11440,9 @@ static OSStatus RecordDataFromArgString( const char *inString, uint8_t **outData
else if( stricmp_prefix( inString, kRDataArgPrefix_IPv6 ) == 0 )
{
const char * const str = inString + sizeof_string( kRDataArgPrefix_IPv6 );
- const char * end;
- dataLen = 16;
- dataPtr = (uint8_t *) malloc( dataLen );
- require_action( dataPtr, exit, err = kNoMemoryErr );
-
- err = StringToIPv6Address( str,
- kStringToIPAddressFlagsNoPort | kStringToIPAddressFlagsNoPrefix | kStringToIPAddressFlagsNoScope,
- dataPtr, NULL, NULL, NULL, &end );
- if( !err && ( *end != '\0' ) ) err = kMalformedErr;
- require_noerr( err, exit );
+ err = StringToAAAARecordData( str, &dataPtr, &dataLen );
+ require_noerr_quiet( err, exit );
}
// SRV record
@@ -7964,6 +12294,18 @@ static Boolean DomainNameEqual( const uint8_t *inName1, const uint8_t *inName2 )
}
//===========================================================================================================================
+// DomainNameLength
+//===========================================================================================================================
+
+static size_t DomainNameLength( const uint8_t * const inName )
+{
+ const uint8_t * ptr;
+
+ for( ptr = inName; *ptr != 0; ptr += ( 1 + *ptr ) ) {}
+ return( (size_t)( ptr - inName ) + 1 );
+}
+
+//===========================================================================================================================
// DomainNameFromString
//===========================================================================================================================
@@ -8039,7 +12381,7 @@ exit:
}
//===========================================================================================================================
-// PrintDNSMessage
+// DNSMessageToText
//===========================================================================================================================
#define DNSFlagsOpCodeToString( X ) ( \
@@ -8059,12 +12401,17 @@ exit:
( (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 )
+static OSStatus
+ DNSMessageToText(
+ const uint8_t * inMsgPtr,
+ size_t inMsgLen,
+ const Boolean inMDNS,
+ const Boolean inPrintRaw,
+ char ** outText )
{
OSStatus err;
+ DataBuffer dataBuf;
+ size_t len;
const DNSHeader * hdr;
const uint8_t * const msgEnd = inMsgPtr + inMsgLen;
const uint8_t * ptr;
@@ -8072,6 +12419,9 @@ static OSStatus PrintDNSMessage( const uint8_t *inMsgPtr, size_t inMsgLen, const
unsigned int questionCount, answerCount, authorityCount, additionalCount, i, totalRRCount;
char nameStr[ kDNSServiceMaxDomainName ];
+ DataBuffer_Init( &dataBuf, NULL, 0, SIZE_MAX );
+ #define _Append( ... ) do { err = DataBuffer_AppendF( &dataBuf, __VA_ARGS__ ); require_noerr( err, exit ); } while( 0 )
+
require_action_quiet( inMsgLen >= kDNSHeaderLength, exit, err = kSizeErr );
hdr = (DNSHeader *) inMsgPtr;
@@ -8084,24 +12434,26 @@ static OSStatus PrintDNSMessage( const uint8_t *inMsgPtr, size_t inMsgLen, const
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",
+ _Append( "ID: 0x%04X (%u)\n", id, id );
+ _Append( "Flags: 0x%04X %c/%s %cAA%cTC%cRD%cRA%?s%?s %s\n",
flags,
( flags & kDNSHeaderFlag_Response ) ? 'R' : 'Q', DNSFlagsOpCodeToString( opcode ),
( flags & kDNSHeaderFlag_AuthAnswer ) ? ' ' : '!',
( flags & kDNSHeaderFlag_Truncation ) ? ' ' : '!',
( flags & kDNSHeaderFlag_RecursionDesired ) ? ' ' : '!',
( flags & kDNSHeaderFlag_RecursionAvailable ) ? ' ' : '!',
+ !inMDNS, ( flags & kDNSHeaderFlag_AuthenticData ) ? " AD" : "!AD",
+ !inMDNS, ( flags & kDNSHeaderFlag_CheckingDisabled ) ? " CD" : "!CD",
DNSFlagsRCodeToString( rcode ) );
- 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 );
+ _Append( "Question count: %u\n", questionCount );
+ _Append( "Answer count: %u\n", answerCount );
+ _Append( "Authority count: %u\n", authorityCount );
+ _Append( "Additional count: %u\n", additionalCount );
- ptr = (uint8_t *)( hdr + 1 );
+ ptr = (const uint8_t *) &hdr[ 1 ];
for( i = 0; i < questionCount; ++i )
{
- unsigned int qType, qClass;
+ unsigned int qtype, qclass;
Boolean isQU;
err = DNSMessageExtractDomainNameString( inMsgPtr, inMsgLen, ptr, nameStr, &ptr );
@@ -8113,20 +12465,18 @@ static OSStatus PrintDNSMessage( const uint8_t *inMsgPtr, size_t inMsgLen, const
goto exit;
}
- qType = ReadBig16( ptr );
- ptr += 2;
- qClass = ReadBig16( ptr );
- ptr += 2;
+ qtype = DNSQuestionFixedFieldsGetType( (const DNSQuestionFixedFields *) ptr );
+ qclass = DNSQuestionFixedFieldsGetClass( (const DNSQuestionFixedFields *) ptr );
+ ptr += 4;
- isQU = ( inIsMDNS && ( qClass & kQClassUnicastResponseBit ) ) ? true : false;
- if( inIsMDNS ) qClass &= ~kQClassUnicastResponseBit;
+ isQU = ( inMDNS && ( qclass & kQClassUnicastResponseBit ) ) ? true : false;
+ if( inMDNS ) qclass &= ~kQClassUnicastResponseBit;
- if( i == 0 ) FPrintF( stdout, "\nQUESTION SECTION\n" );
+ if( i == 0 ) _Append( "\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 ) );
+ _Append( "%s %2s %?2s%?2u %-5s\n",
+ nameStr, inMDNS ? ( isQU ? "QU" : "QM" ) : "",
+ ( qclass == kDNSServiceClass_IN ), "IN", ( qclass != kDNSServiceClass_IN ), qclass, RecordTypeToString( qtype ) );
}
totalRRCount = answerCount + authorityCount + additionalCount;
@@ -8147,8 +12497,8 @@ static OSStatus PrintDNSMessage( const uint8_t *inMsgPtr, size_t inMsgLen, const
err = DomainNameToString( name, NULL, nameStr, NULL );
require_noerr( err, exit );
- cacheFlush = ( inIsMDNS && ( class & kRRClassCacheFlushBit ) ) ? true : false;
- if( inIsMDNS ) class &= ~kRRClassCacheFlushBit;
+ cacheFlush = ( inMDNS && ( class & kRRClassCacheFlushBit ) ) ? true : false;
+ if( inMDNS ) class &= ~kRRClassCacheFlushBit;
rdataStr = NULL;
if( !inPrintRaw ) DNSRecordDataToString( rdataPtr, rdataLen, type, inMsgPtr, inMsgLen, &rdataStr );
@@ -8158,20 +12508,26 @@ static OSStatus PrintDNSMessage( const uint8_t *inMsgPtr, size_t inMsgLen, const
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" );
+ if( answerCount && ( i == 0 ) ) _Append( "\nANSWER SECTION\n" );
+ else if( authorityCount && ( i == answerCount ) ) _Append( "\nAUTHORITY SECTION\n" );
+ else if( additionalCount && ( i == ( answerCount + authorityCount ) ) ) _Append( "\nADDITIONAL SECTION\n" );
- FPrintF( stdout, "%-42s %6u %2s %?2s%?2u %-5s %s\n",
+ _Append( "%-42s %6u %2s %?2s%?2u %-5s %s\n",
nameStr, ttl, cacheFlush ? "CF" : "",
( class == kDNSServiceClass_IN ), "IN", ( class != kDNSServiceClass_IN ), class,
RecordTypeToString( type ), rdataStr );
free( rdataStr );
}
- FPrintF( stdout, "\n" );
- err = kNoErr;
+ _Append( "\n" );
+
+ err = DataBuffer_Append( &dataBuf, "", 1 );
+ require_noerr( err, exit );
+
+ err = DataBuffer_Detach( &dataBuf, (uint8_t **) outText, &len );
+ require_noerr( err, exit );
exit:
+ DataBuffer_Free( &dataBuf );
return( err );
}
@@ -8194,21 +12550,18 @@ static OSStatus
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 );
+ memset( hdr, 0, sizeof( *hdr ) );
+ DNSHeaderSetID( hdr, inMsgID );
+ DNSHeaderSetFlags( hdr, inFlags );
+ DNSHeaderSetQuestionCount( hdr, 1 );
ptr = (uint8_t *)( hdr + 1 );
err = DomainNameFromString( ptr, inQName, &ptr );
require_noerr_quiet( err, exit );
- WriteBig16( ptr, inQType );
- ptr += 2;
- WriteBig16( ptr, inQClass );
- ptr += 2;
+ DNSQuestionFixedFieldsInit( (DNSQuestionFixedFields *) ptr, inQType, inQClass );
+ ptr += 4;
+
msgLen = (size_t)( ptr - inMsg );
check( msgLen <= kDNSQueryMessageMaxLen );
@@ -8246,21 +12599,23 @@ exit:
}
//===========================================================================================================================
-// DispatchReadSourceCreate
+// DispatchSocketSourceCreate
//===========================================================================================================================
static OSStatus
- DispatchReadSourceCreate(
- SocketRef inSock,
- DispatchHandler inEventHandler,
- DispatchHandler inCancelHandler,
- void * inContext,
- dispatch_source_t * outSource )
+ DispatchSocketSourceCreate(
+ SocketRef inSock,
+ dispatch_source_type_t inType,
+ dispatch_queue_t inQueue,
+ DispatchHandler inEventHandler,
+ DispatchHandler inCancelHandler,
+ void * inContext,
+ dispatch_source_t * outSource )
{
OSStatus err;
dispatch_source_t source;
- source = dispatch_source_create( DISPATCH_SOURCE_TYPE_READ, (uintptr_t) inSock, 0, dispatch_get_main_queue() );
+ source = dispatch_source_create( inType, (uintptr_t) inSock, 0, inQueue ? inQueue : dispatch_get_main_queue() );
require_action( source, exit, err = kUnknownErr );
dispatch_set_context( source, inContext );
@@ -8283,6 +12638,7 @@ static OSStatus
dispatch_time_t inStart,
uint64_t inIntervalNs,
uint64_t inLeewayNs,
+ dispatch_queue_t inQueue,
DispatchHandler inEventHandler,
DispatchHandler inCancelHandler,
void * inContext,
@@ -8291,7 +12647,7 @@ static OSStatus
OSStatus err;
dispatch_source_t timer;
- timer = dispatch_source_create( DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue() );
+ timer = dispatch_source_create( DISPATCH_SOURCE_TYPE_TIMER, 0, 0, inQueue ? inQueue : dispatch_get_main_queue() );
require_action( timer, exit, err = kUnknownErr );
dispatch_source_set_timer( timer, inStart, inIntervalNs, inLeewayNs );
@@ -8307,6 +12663,38 @@ exit:
}
//===========================================================================================================================
+// DispatchProcessMonitorCreate
+//===========================================================================================================================
+
+static OSStatus
+ DispatchProcessMonitorCreate(
+ pid_t inPID,
+ unsigned long inFlags,
+ dispatch_queue_t inQueue,
+ DispatchHandler inEventHandler,
+ DispatchHandler inCancelHandler,
+ void * inContext,
+ dispatch_source_t * outMonitor )
+{
+ OSStatus err;
+ dispatch_source_t monitor;
+
+ monitor = dispatch_source_create( DISPATCH_SOURCE_TYPE_PROC, (uintptr_t) inPID, inFlags,
+ inQueue ? inQueue : dispatch_get_main_queue() );
+ require_action( monitor, exit, err = kUnknownErr );
+
+ dispatch_set_context( monitor, inContext );
+ dispatch_source_set_event_handler_f( monitor, inEventHandler );
+ dispatch_source_set_cancel_handler_f( monitor, inCancelHandler );
+
+ *outMonitor = monitor;
+ err = kNoErr;
+
+exit:
+ return( err );
+}
+
+//===========================================================================================================================
// ServiceTypeDescription
//===========================================================================================================================
@@ -8369,15 +12757,58 @@ static const char * ServiceTypeDescription( const char *inName )
}
//===========================================================================================================================
+// SocketContextCreate
+//===========================================================================================================================
+
+static OSStatus SocketContextCreate( SocketRef inSock, void * inUserContext, SocketContext **outContext )
+{
+ OSStatus err;
+ SocketContext * context;
+
+ context = (SocketContext *) calloc( 1, sizeof( *context ) );
+ require_action( context, exit, err = kNoMemoryErr );
+
+ context->refCount = 1;
+ context->sock = inSock;
+ context->userContext = inUserContext;
+
+ *outContext = context;
+ err = kNoErr;
+
+exit:
+ return( err );
+}
+
+//===========================================================================================================================
+// SocketContextRetain
+//===========================================================================================================================
+
+static SocketContext * SocketContextRetain( SocketContext *inContext )
+{
+ ++inContext->refCount;
+ return( inContext );
+}
+
+//===========================================================================================================================
+// SocketContextRelease
+//===========================================================================================================================
+
+static void SocketContextRelease( SocketContext *inContext )
+{
+ if( --inContext->refCount == 0 )
+ {
+ ForgetSocket( &inContext->sock );
+ free( inContext );
+ }
+}
+
+//===========================================================================================================================
// SocketContextCancelHandler
//===========================================================================================================================
static void SocketContextCancelHandler( void *inContext )
{
- SocketContext * const context = (SocketContext *) inContext;
-
- ForgetSocket( &context->sock );
- free( context );
+ SocketContextRelease( (SocketContext *) inContext );
}
//===========================================================================================================================
@@ -8421,6 +12852,115 @@ exit:
return( err );
}
+//===========================================================================================================================
+// StringToLongLong
+//===========================================================================================================================
+
+static OSStatus StringToLongLong( const char *inString, long long *outValue )
+{
+ OSStatus err;
+ long long value;
+ char * endPtr;
+
+ set_errno_compat( 0 );
+ value = strtol( inString, &endPtr, 0 );
+ err = errno_compat();
+ if( ( ( value == LLONG_MIN ) || ( value == LLONG_MAX ) ) && ( err == ERANGE ) ) goto exit;
+ require_action_quiet( ( *endPtr == '\0' ) && ( endPtr != inString ), exit, err = kParamErr );
+
+ *outValue = value;
+ err = kNoErr;
+
+exit:
+ return( err );
+}
+
+//===========================================================================================================================
+// StringToARecordData
+//===========================================================================================================================
+
+static OSStatus StringToARecordData( const char *inString, uint8_t **outPtr, size_t *outLen )
+{
+ OSStatus err;
+ uint32_t * addrPtr;
+ const size_t addrLen = sizeof( *addrPtr );
+ const char * end;
+
+ addrPtr = (uint32_t *) malloc( addrLen );
+ require_action( addrPtr, exit, err = kNoMemoryErr );
+
+ err = StringToIPv4Address( inString, kStringToIPAddressFlagsNoPort | kStringToIPAddressFlagsNoPrefix, addrPtr,
+ NULL, NULL, NULL, &end );
+ if( !err && ( *end != '\0' ) ) err = kMalformedErr;
+ require_noerr_quiet( err, exit );
+
+ *addrPtr = HostToBig32( *addrPtr );
+
+ *outPtr = (uint8_t *) addrPtr;
+ addrPtr = NULL;
+ *outLen = addrLen;
+
+exit:
+ FreeNullSafe( addrPtr );
+ return( err );
+}
+
+//===========================================================================================================================
+// StringToAAAARecordData
+//===========================================================================================================================
+
+static OSStatus StringToAAAARecordData( const char *inString, uint8_t **outPtr, size_t *outLen )
+{
+ OSStatus err;
+ uint8_t * addrPtr;
+ const size_t addrLen = 16;
+ const char * end;
+
+ addrPtr = (uint8_t *) malloc( addrLen );
+ require_action( addrPtr, exit, err = kNoMemoryErr );
+
+ err = StringToIPv6Address( inString,
+ kStringToIPAddressFlagsNoPort | kStringToIPAddressFlagsNoPrefix | kStringToIPAddressFlagsNoScope,
+ addrPtr, NULL, NULL, NULL, &end );
+ if( !err && ( *end != '\0' ) ) err = kMalformedErr;
+ require_noerr_quiet( err, exit );
+
+ *outPtr = addrPtr;
+ addrPtr = NULL;
+ *outLen = addrLen;
+
+exit:
+ FreeNullSafe( addrPtr );
+ return( err );
+}
+
+//===========================================================================================================================
+// StringToDomainName
+//===========================================================================================================================
+
+static OSStatus StringToDomainName( const char *inString, uint8_t **outPtr, size_t *outLen )
+{
+ OSStatus err;
+ uint8_t * namePtr;
+ size_t nameLen;
+ uint8_t * end;
+ uint8_t nameBuf[ kDomainNameLengthMax ];
+
+ err = DomainNameFromString( nameBuf, inString, &end );
+ require_noerr_quiet( err, exit );
+
+ nameLen = (size_t)( end - nameBuf );
+ namePtr = memdup( nameBuf, nameLen );
+ require_action( namePtr, exit, err = kNoMemoryErr );
+
+ *outPtr = namePtr;
+ namePtr = NULL;
+ if( outLen ) *outLen = nameLen;
+
+exit:
+ return( err );
+}
+
#if( TARGET_OS_DARWIN )
//===========================================================================================================================
// GetDefaultDNSServer
@@ -8446,7 +12986,7 @@ static OSStatus GetDefaultDNSServer( sockaddr_ip *outAddr )
addr = resolver->nameserver[ 0 ];
break;
}
- }
+ }
require_action_quiet( addr, exit, err = kNotFoundErr );
SockAddrCopy( addr, outAddr );
@@ -8459,6 +12999,20 @@ exit:
#endif
//===========================================================================================================================
+// GetCurrentMicroTime
+//===========================================================================================================================
+
+static MicroTime64 GetCurrentMicroTime( void )
+{
+ struct timeval now;
+
+ TIMEVAL_ZERO( now );
+ gettimeofday( &now, NULL );
+
+ return( (MicroTime64) TIMEVAL_USEC64( now ) );
+}
+
+//===========================================================================================================================
// SocketWriteAll
//
// Note: This was copied from CoreUtils because the SocketWriteAll function is currently not exported in the framework.
@@ -8919,47 +13473,6 @@ exit:
}
//===========================================================================================================================
-// 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.
@@ -9150,3 +13663,193 @@ Boolean
if( outSrc ) *outSrc = (const char *) src;
return( true );
}
+
+//===========================================================================================================================
+// _ServerSocketOpenEx2
+//
+// Note: Based on ServerSocketOpenEx() from CoreUtils. Added parameter to not use SO_REUSEPORT.
+//===========================================================================================================================
+
+static OSStatus
+ _ServerSocketOpenEx2(
+ int inFamily,
+ int inType,
+ int inProtocol,
+ const void * inAddr,
+ int inPort,
+ int * outPort,
+ int inRcvBufSize,
+ Boolean inNoPortReuse,
+ SocketRef * outSock )
+{
+ OSStatus err;
+ int port;
+ SocketRef sock;
+ int name;
+ int option;
+ sockaddr_ip sip;
+ socklen_t len;
+
+ port = ( inPort < 0 ) ? -inPort : inPort; // Negated port number means "try this port, but allow dynamic".
+
+ sock = socket( inFamily, inType, inProtocol );
+ err = map_socket_creation_errno( sock );
+ require_noerr_quiet( err, exit );
+
+#if( defined( SO_NOSIGPIPE ) )
+ setsockopt( sock, SOL_SOCKET, SO_NOSIGPIPE, &(int){ 1 }, (socklen_t) sizeof( int ) );
+#endif
+
+ err = SocketMakeNonBlocking( sock );
+ require_noerr( err, exit );
+
+ // Set receive buffer size. This has to be done on the listening socket *before* listen is called because
+ // accept does not return until after the window scale option is exchanged during the 3-way handshake.
+ // Since accept returns a new socket, the only way to use a larger window scale option is to set the buffer
+ // size on the listening socket since SO_RCVBUF is inherited by the accepted socket. See UNPv1e3 Section 7.5.
+
+ err = SocketSetBufferSize( sock, SO_RCVBUF, inRcvBufSize );
+ check_noerr( err );
+
+ // Allow port or address reuse because we may bind separate IPv4 and IPv6 sockets to the same port.
+
+ if( ( inType != SOCK_DGRAM ) || !inNoPortReuse )
+ {
+ option = 1;
+ name = ( inType == SOCK_DGRAM ) ? SO_REUSEPORT : SO_REUSEADDR;
+ err = setsockopt( sock, SOL_SOCKET, name, (char *) &option, (socklen_t) sizeof( option ) );
+ err = map_socket_noerr_errno( sock, err );
+ require_noerr( err, exit );
+ }
+
+ if( inFamily == AF_INET )
+ {
+ // Bind to the port. If it fails, retry with a dynamic port.
+
+ memset( &sip.v4, 0, sizeof( sip.v4 ) );
+ SIN_LEN_SET( &sip.v4 );
+ sip.v4.sin_family = AF_INET;
+ sip.v4.sin_port = htons( (uint16_t) port );
+ sip.v4.sin_addr.s_addr = inAddr ? *( (const uint32_t *) inAddr ) : htonl( INADDR_ANY );
+ err = bind( sock, &sip.sa, (socklen_t) sizeof( sip.v4 ) );
+ err = map_socket_noerr_errno( sock, err );
+ if( err && ( inPort < 0 ) )
+ {
+ sip.v4.sin_port = 0;
+ err = bind( sock, &sip.sa, (socklen_t) sizeof( sip.v4 ) );
+ err = map_socket_noerr_errno( sock, err );
+ }
+ require_noerr( err, exit );
+ }
+#if( defined( AF_INET6 ) )
+ else if( inFamily == AF_INET6 )
+ {
+ // Restrict this socket to IPv6 only because we're going to use a separate socket for IPv4.
+
+ option = 1;
+ err = setsockopt( sock, IPPROTO_IPV6, IPV6_V6ONLY, (char *) &option, (socklen_t) sizeof( option ) );
+ err = map_socket_noerr_errno( sock, err );
+ require_noerr( err, exit );
+
+ // Bind to the port. If it fails, retry with a dynamic port.
+
+ memset( &sip.v6, 0, sizeof( sip.v6 ) );
+ SIN6_LEN_SET( &sip.v6 );
+ sip.v6.sin6_family = AF_INET6;
+ sip.v6.sin6_port = htons( (uint16_t) port );
+ sip.v6.sin6_addr = inAddr ? *( (const struct in6_addr *) inAddr ) : in6addr_any;
+ err = bind( sock, &sip.sa, (socklen_t) sizeof( sip.v6 ) );
+ err = map_socket_noerr_errno( sock, err );
+ if( err && ( inPort < 0 ) )
+ {
+ sip.v6.sin6_port = 0;
+ err = bind( sock, &sip.sa, (socklen_t) sizeof( sip.v6 ) );
+ err = map_socket_noerr_errno( sock, err );
+ }
+ require_noerr( err, exit );
+ }
+#endif
+ else
+ {
+ dlogassert( "Unsupported family: %d", inFamily );
+ err = kUnsupportedErr;
+ goto exit;
+ }
+
+ if( inType == SOCK_STREAM )
+ {
+ err = listen( sock, SOMAXCONN );
+ err = map_socket_noerr_errno( sock, err );
+ if( err )
+ {
+ err = listen( sock, 5 );
+ err = map_socket_noerr_errno( sock, err );
+ require_noerr( err, exit );
+ }
+ }
+
+ if( outPort )
+ {
+ len = (socklen_t) sizeof( sip );
+ err = getsockname( sock, &sip.sa, &len );
+ err = map_socket_noerr_errno( sock, err );
+ require_noerr( err, exit );
+
+ *outPort = SockAddrGetPort( &sip );
+ }
+ *outSock = sock;
+ sock = kInvalidSocketRef;
+
+exit:
+ ForgetSocket( &sock );
+ return( err );
+}
+
+//===========================================================================================================================
+// memdup
+//
+// Note: This was copied from CoreUtils because it's currently not exported in the framework.
+//===========================================================================================================================
+
+void * memdup( const void *inPtr, size_t inLen )
+{
+ void * mem;
+
+ mem = malloc( ( inLen > 0 ) ? inLen : 1 ); // If inLen is 0, use 1 since malloc( 0 ) is not well defined.
+ require( mem, exit );
+ if( inLen > 0 ) memcpy( mem, inPtr, inLen );
+
+exit:
+ return( mem );
+}
+
+#if( !TARGET_OS_WINDOWS )
+//===========================================================================================================================
+// memicmp
+//
+// Note: This was copied from CoreUtils because it's currently not exported in the framework.
+//===========================================================================================================================
+
+int memicmp( const void *inP1, const void *inP2, size_t inLen )
+{
+ const unsigned char * p1;
+ const unsigned char * e1;
+ const unsigned char * p2;
+ int c1;
+ int c2;
+
+ p1 = (const unsigned char *) inP1;
+ e1 = p1 + inLen;
+ p2 = (const unsigned char *) inP2;
+ while( p1 < e1 )
+ {
+ c1 = *p1++;
+ c2 = *p2++;
+ c1 = tolower( c1 );
+ c2 = tolower( c2 );
+ if( c1 < c2 ) return( -1 );
+ if( c1 > c2 ) return( 1 );
+ }
+ return( 0 );
+}
+#endif