diff options
author | Sebastian Huber <sebastian.huber@embedded-brains.de> | 2014-01-30 13:52:13 +0100 |
---|---|---|
committer | Sebastian Huber <sebastian.huber@embedded-brains.de> | 2014-01-30 16:23:04 +0100 |
commit | 9449f151d0ccf3ac755d5f2bd9b4057ae2b03157 (patch) | |
tree | ada21dc6aa0b146c62a7561a08fb51fe4a8922ee /mDNSResponder/mDNSShared | |
parent | DHCPCD(8): Add MASTER_ONLY option (diff) | |
download | rtems-libbsd-9449f151d0ccf3ac755d5f2bd9b4057ae2b03157.tar.bz2 |
mDNS: Import
The sources can be obtained via:
http://opensource.apple.com/tarballs/mDNSResponder/mDNSResponder-544.tar.gz
Diffstat (limited to 'mDNSResponder/mDNSShared')
39 files changed, 27074 insertions, 0 deletions
diff --git a/mDNSResponder/mDNSShared/CommonServices.h b/mDNSResponder/mDNSShared/CommonServices.h new file mode 100644 index 00000000..342479b9 --- /dev/null +++ b/mDNSResponder/mDNSShared/CommonServices.h @@ -0,0 +1,1537 @@ +/* -*- Mode: C; tab-width: 4 -*- + * + * Copyright (c) 1997-2004 Apple Computer, Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @header CommonServices + + Common Services for Mac OS X, Linux, Palm, VxWorks, Windows, and Windows CE. + */ + +#ifndef __COMMON_SERVICES__ +#define __COMMON_SERVICES__ + +#ifdef __cplusplus +extern "C" { +#endif + +#if 0 +#pragma mark == Target == +#endif + +//=========================================================================================================================== +// Target +//=========================================================================================================================== + +// Macintosh + +#if ( !defined( TARGET_OS_MAC ) ) + #if ( ( macintosh || __MACH__ ) && !KERNEL ) +// ConditionalMacros.h in CoreServices will define this TARGET_* flag. + #else + #define TARGET_OS_MAC 0 + #endif +#endif + +#if ( !defined( TARGET_API_MAC_OSX_KERNEL ) ) + #if ( __MACH__ && KERNEL ) + #define TARGET_API_MAC_OSX_KERNEL 1 + #else + #define TARGET_API_MAC_OSX_KERNEL 0 + #endif +#endif + +// FreeBSD + +#if ( !defined( TARGET_OS_FREEBSD ) ) + #if ( defined( __FreeBSD__ ) ) + #define TARGET_OS_FREEBSD 1 + #else + #define TARGET_OS_FREEBSD 0 + #endif +#endif + +// Linux + +#if ( !defined( TARGET_OS_LINUX ) ) + #if ( defined( __linux__ ) ) + #define TARGET_OS_LINUX 1 + #else + #define TARGET_OS_LINUX 0 + #endif +#endif + +// Solaris + +#if ( !defined( TARGET_OS_SOLARIS ) ) + #if ( defined(solaris) || (defined(__SVR4) && defined(sun)) ) + #define TARGET_OS_SOLARIS 1 + #else + #define TARGET_OS_SOLARIS 0 + #endif +#endif + +// Palm + +#if ( !defined( TARGET_OS_PALM ) ) + #if ( defined( __PALMOS_TRAPS__ ) || defined( __PALMOS_ARMLET__ ) ) + #define TARGET_OS_PALM 1 + #else + #define TARGET_OS_PALM 0 + #endif +#endif + +// VxWorks + +#if ( !defined( TARGET_OS_VXWORKS ) ) + +// No predefined macro for VxWorks so just assume VxWorks if nothing else is set. + + #if ( !macintosh && !__MACH__ && !defined( __FreeBSD__ ) && !defined( __linux__ ) && !defined ( __SVR4 ) && !defined ( __sun ) && !defined( __PALMOS_TRAPS__ ) && !defined( __PALMOS_ARMLET__ ) && !defined( _WIN32 ) ) + #define TARGET_OS_VXWORKS 1 + #else + #define TARGET_OS_VXWORKS 0 + #endif +#endif + +// Windows + +#if ( !defined( TARGET_OS_WIN32 ) ) + #if ( macintosh || __MACH__ ) +// ConditionalMacros.h in CoreServices will define this TARGET_* flag. + #else + #if ( defined( _WIN32 ) ) + #define TARGET_OS_WIN32 1 + #else + #define TARGET_OS_WIN32 0 + #endif + #endif +#endif + +// Windows CE + +#if ( !defined( TARGET_OS_WINDOWS_CE ) ) + #if ( defined( _WIN32_WCE ) ) + #define TARGET_OS_WINDOWS_CE 1 + #else + #define TARGET_OS_WINDOWS_CE 0 + #endif +#endif + +#if 0 +#pragma mark == Includes == +#endif + +//=========================================================================================================================== +// Includes +//=========================================================================================================================== + +#if ( !KERNEL ) + #if defined(WIN32) && !defined(_WSPIAPI_COUNTOF) + #define _WSPIAPI_COUNTOF(_Array) (sizeof(_Array) / sizeof(_Array[0])) + #endif + #include <stddef.h> +#endif + +#if ( ( macintosh || __MACH__ ) && !KERNEL ) + + #if ( defined( __MWERKS__ ) ) + #if ( __option( c9x ) ) + #include <stdbool.h> + #endif + #else + #include <stdbool.h> + #endif + + #include <stdint.h> + + #if ( __MACH__ ) + +// Mac OS X + + #include <sys/types.h> + #include <netinet/in.h> + #include <arpa/inet.h> + #include <fcntl.h> + #include <pthread.h> + #include <sys/ioctl.h> + #include <sys/socket.h> + #include <unistd.h> + + #else + +// Classic Mac OS + + #include <ConditionalMacros.h> + #include <MacTypes.h> + + #endif + +#elif ( KERNEL ) + +// Mac OS X Kernel + + #include <stdint.h> + + #include <libkern/OSTypes.h> + #include <sys/types.h> + +#elif ( TARGET_OS_FREEBSD ) + +// FreeBSD + #include <stdint.h> + #include <pthread.h> + #include <netinet/in.h> + #include <arpa/inet.h> + #include <sys/socket.h> + +#elif ( TARGET_OS_LINUX ) + +// Linux + + #include <stdint.h> + #include <arpa/inet.h> + +#elif ( TARGET_OS_SOLARIS ) + +// Solaris + + #include <stdint.h> + + #include <arpa/inet.h> + #include <arpa/nameser.h> + + #if ( defined( BYTE_ORDER ) && defined( LITTLE_ENDIAN ) && ( BYTE_ORDER == LITTLE_ENDIAN ) ) + #define TARGET_RT_LITTLE_ENDIAN 1 + #endif + #if ( defined( BYTE_ORDER ) && defined( BIG_ENDIAN ) && ( BYTE_ORDER == BIG_ENDIAN ) ) + #define TARGET_RT_BIG_ENDIAN 1 + #endif + +#elif ( TARGET_OS_PALM ) + +// Palm (no special includes yet). + +#elif ( TARGET_OS_VXWORKS ) + +// VxWorks + + #include "vxWorks.h" + +#elif ( TARGET_OS_WIN32 ) + +// Windows + + #if ( !defined( WIN32_WINDOWS ) ) + #define WIN32_WINDOWS 0x0401 + #endif + + #if ( !defined( _WIN32_WINDOWS ) ) + #define _WIN32_WINDOWS 0x0401 + #endif + + #if ( !defined( WIN32_LEAN_AND_MEAN ) ) + #define WIN32_LEAN_AND_MEAN // Needed to avoid redefinitions by Windows interfaces. + #endif + + #if ( defined( __MWERKS__ ) ) + + #if ( __option( c9x ) ) + #include <stdbool.h> + #endif + + #include <stdint.h> + + #elif ( defined( _MSC_VER ) ) + + #pragma warning( disable:4127 ) // Disable "conditional expression is constant" warning for debug macros. + #pragma warning( disable:4706 ) // Disable "assignment within conditional expression" for Microsoft headers. + + #endif + + #include <windows.h> + #include <winsock2.h> + #include <Ws2tcpip.h> + + #if ( defined( _MSC_VER ) ) + #pragma warning( default:4706 ) + #endif + +#else + #error unknown OS - update this file to support your OS +#endif + +#if ( !defined( TARGET_BUILD_MAIN ) ) + #if ( !TARGET_OS_VXWORKS ) + #define TARGET_BUILD_MAIN 1 + #endif +#endif + +#if ( __GNUC__ || !TARGET_OS_VXWORKS ) + #define TARGET_LANGUAGE_C_LIKE 1 +#else + #define TARGET_LANGUAGE_C_LIKE 0 +#endif + +#if 0 +#pragma mark == CPU == +#endif + +//=========================================================================================================================== +// CPU +//=========================================================================================================================== + +// PowerPC + +#if ( !defined( TARGET_CPU_PPC ) ) + #if ( defined( __ppc__ ) || defined( __PPC__ ) || defined( powerpc ) || defined( ppc ) || defined( _M_MPPC ) ) + #define TARGET_CPU_PPC 1 + #else + #define TARGET_CPU_PPC 0 + #endif +#endif + +// x86 + +#if ( !defined( TARGET_CPU_X86 ) ) + #if ( __INTEL__ || defined( __i386__ ) || defined( i386 ) || defined( intel ) || defined( _M_IX86 ) ) + #define TARGET_CPU_X86 1 + #else + #define TARGET_CPU_X86 0 + #endif +#endif + +// MIPS + +#if ( !defined( TARGET_CPU_MIPS ) ) + #if ( __MIPS__ || defined( MIPS32 ) || defined( R3000 ) || defined( R4000 ) || defined( R4650 ) || defined( _M_MRX000 ) ) + #define TARGET_CPU_MIPS 1 + #else + #define TARGET_CPU_MIPS 0 + #endif +#endif + +#if ( !defined( TARGET_CPU_PPC ) && !defined( TARGET_CPU_X86 ) && !defined( TARGET_CPU_MIPS ) ) + #error unknown CPU - update this file to support your CPU +#endif + +#if 0 +#pragma mark == Byte Order == +#endif + +//=========================================================================================================================== +// Byte Order +//=========================================================================================================================== + +// TARGET_RT_LITTLE_ENDIAN + +#if ( !defined( TARGET_RT_LITTLE_ENDIAN ) ) + #if ( MIPSEL || IL_LITTLE_ENDIAN || defined( __LITTLE_ENDIAN__ ) || \ + ( defined( BYTE_ORDER ) && defined( LITTLE_ENDIAN ) && ( BYTE_ORDER == LITTLE_ENDIAN ) ) || \ + ( defined( _BYTE_ORDER ) && defined( _LITTLE_ENDIAN ) && ( _BYTE_ORDER == _LITTLE_ENDIAN ) ) || \ + ( defined( __BYTE_ORDER ) && defined( __LITTLE_ENDIAN ) && ( __BYTE_ORDER == __LITTLE_ENDIAN ) ) || \ + TARGET_CPU_X86 || ( defined( TARGET_RT_BIG_ENDIAN ) && !TARGET_RT_BIG_ENDIAN ) ) + #define TARGET_RT_LITTLE_ENDIAN 1 + #else + #define TARGET_RT_LITTLE_ENDIAN 0 + #endif +#endif + +// TARGET_RT_BIG_ENDIAN + +#if ( !defined( TARGET_RT_BIG_ENDIAN ) ) + #if ( MIPSEB || IL_BIG_ENDIAN || defined( __BIG_ENDIAN__ ) || \ + ( defined( BYTE_ORDER ) && defined( BIG_ENDIAN ) && ( BYTE_ORDER == BIG_ENDIAN ) ) || \ + ( defined( _BYTE_ORDER ) && defined( _BIG_ENDIAN ) && ( _BYTE_ORDER == _BIG_ENDIAN ) ) || \ + ( defined( __BYTE_ORDER ) && defined( __BIG_ENDIAN ) && ( __BYTE_ORDER == __BIG_ENDIAN ) ) || \ + ( defined( TARGET_RT_LITTLE_ENDIAN ) && !TARGET_RT_LITTLE_ENDIAN ) ) + #define TARGET_RT_BIG_ENDIAN 1 + #else + #define TARGET_RT_BIG_ENDIAN 0 + #endif +#endif + +#if ( defined( TARGET_RT_LITTLE_ENDIAN ) && !defined( TARGET_RT_BIG_ENDIAN ) ) + #if ( TARGET_RT_LITTLE_ENDIAN ) + #define TARGET_RT_BIG_ENDIAN 0 + #else + #define TARGET_RT_BIG_ENDIAN 1 + #endif +#endif + +#if ( defined( TARGET_RT_BIG_ENDIAN ) && !defined( TARGET_RT_LITTLE_ENDIAN ) ) + #if ( TARGET_RT_BIG_ENDIAN ) + #define TARGET_RT_LITTLE_ENDIAN 0 + #else + #define TARGET_RT_LITTLE_ENDIAN 1 + #endif +#endif + +#if ( !defined( TARGET_RT_LITTLE_ENDIAN ) || !defined( TARGET_RT_BIG_ENDIAN ) ) + #error unknown byte order - update this file to support your byte order +#endif + +// TARGET_RT_BYTE_ORDER + +#if ( !defined( TARGET_RT_BYTE_ORDER_BIG_ENDIAN ) ) + #define TARGET_RT_BYTE_ORDER_BIG_ENDIAN 1234 +#endif + +#if ( !defined( TARGET_RT_BYTE_ORDER_LITTLE_ENDIAN ) ) + #define TARGET_RT_BYTE_ORDER_LITTLE_ENDIAN 4321 +#endif + +#if ( !defined( TARGET_RT_BYTE_ORDER ) ) + #if ( TARGET_RT_LITTLE_ENDIAN ) + #define TARGET_RT_BYTE_ORDER TARGET_RT_BYTE_ORDER_LITTLE_ENDIAN + #else + #define TARGET_RT_BYTE_ORDER TARGET_RT_BYTE_ORDER_BIG_ENDIAN + #endif +#endif + +#if 0 +#pragma mark == Constants == +#endif + +//=========================================================================================================================== +// Constants +//=========================================================================================================================== + +#if ( !TARGET_OS_MAC ) + #define CR '\r' +#endif + +#define LF '\n' +#define CRSTR "\r" +#define LFSTR "\n" +#define CRLF "\r\n" +#define CRCR "\r\r" + +#if 0 +#pragma mark == Compatibility == +#endif + +//=========================================================================================================================== +// Compatibility +//=========================================================================================================================== + +// Macros to allow the same code to work on Windows and other sockets API-compatible platforms. + +#if ( TARGET_OS_WIN32 ) + #define close_compat( X ) closesocket( X ) + #define errno_compat() (int) GetLastError() + #define set_errno_compat( X ) SetLastError( X ) + #define EWOULDBLOCK_compat WSAEWOULDBLOCK + #define ETIMEDOUT_compat WSAETIMEDOUT + #define ENOTCONN_compat WSAENOTCONN + #define IsValidSocket( X ) ( ( X ) != INVALID_SOCKET ) + #define kInvalidSocketRef INVALID_SOCKET + #if ( TARGET_LANGUAGE_C_LIKE ) +typedef SOCKET SocketRef; + #endif +#else + #define close_compat( X ) close( X ) + #define errno_compat() errno + #define set_errno_compat( X ) do { errno = ( X ); } while( 0 ) + #define EWOULDBLOCK_compat EWOULDBLOCK + #define ETIMEDOUT_compat ETIMEDOUT + #define ENOTCONN_compat ENOTCONN + #define IsValidSocket( X ) ( ( X ) >= 0 ) + #define kInvalidSocketRef -1 + #if ( TARGET_LANGUAGE_C_LIKE ) +typedef int SocketRef; + #endif +#endif + +// socklen_t is not defined on the following platforms so emulate it if not defined: +// +// - Pre-Panther Mac OS X. Panther defines SO_NOADDRERR so trigger off that. +// - Windows SDK prior to 2003. 2003+ SDK's define EAI_AGAIN so trigger off that. +// - VxWorks + +#if ( TARGET_LANGUAGE_C_LIKE ) + #if ( ( TARGET_OS_MAC && !defined( SO_NOADDRERR ) ) || ( TARGET_OS_WIN32 && !defined( EAI_AGAIN ) ) || TARGET_OS_VXWORKS ) +typedef int socklen_t; + #endif +#endif + +// ssize_t is not defined on the following platforms so emulate it if not defined: +// +// - Mac OS X when not building with BSD headers +// - Windows + +#if ( TARGET_LANGUAGE_C_LIKE ) + #if ( !defined(_SSIZE_T) && ( TARGET_OS_WIN32 || !defined( _BSD_SSIZE_T_DEFINED_ ) ) && !TARGET_OS_FREEBSD && !TARGET_OS_LINUX && !TARGET_OS_VXWORKS && !TARGET_OS_MAC) +typedef int ssize_t; + #endif +#endif + +// sockaddr_storage is not supported on non-IPv6 machines so alias it to an IPv4-compatible structure. + +#if ( TARGET_LANGUAGE_C_LIKE ) + #if ( !defined( AF_INET6 ) ) + #define sockaddr_storage sockaddr_in + #define ss_family sin_family + #endif +#endif + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @defined SOCKADDR_IS_IP_LOOPBACK + + @abstract Determines if a sockaddr is an IPv4 or IPv6 loopback address (if IPv6 is supported). + */ + +#if ( defined( AF_INET6 ) ) + #define SOCKADDR_IS_IP_LOOPBACK( SA ) \ + ( ( (const struct sockaddr *)( SA ) )->sa_family == AF_INET ) \ + ? ( ( (const struct sockaddr_in *)( SA ) )->sin_addr.s_addr == htonl( INADDR_LOOPBACK ) ) \ + : ( ( (const struct sockaddr *)( SA ) )->sa_family == AF_INET6 ) \ + ? IN6_IS_ADDR_LOOPBACK( &( (const struct sockaddr_in6 *)( SA ) )->sin6_addr ) \ + : 0 +#else + #define SOCKADDR_IS_IP_LOOPBACK( SA ) \ + ( ( (const struct sockaddr *)( SA ) )->sa_family == AF_INET ) \ + ? ( ( (const struct sockaddr_in *)( SA ) )->sin_addr.s_addr == htonl( INADDR_LOOPBACK ) ) \ + : 0 +#endif + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @defined SOCKADDR_IS_IP_LINK_LOCAL + + @abstract Determines if a sockaddr is an IPv4 or IPv6 link-local address (if IPv6 is supported). + */ + +#if ( defined( AF_INET6 ) ) + #define SOCKADDR_IS_IP_LINK_LOCAL( SA ) \ + ( ( ( (const struct sockaddr *)( SA ) )->sa_family == AF_INET ) \ + ? ( ( ( (uint8_t *)( &( (const struct sockaddr_in *)( SA ) )->sin_addr ) )[ 0 ] == 169 ) && \ + ( ( (uint8_t *)( &( (const struct sockaddr_in *)( SA ) )->sin_addr ) )[ 1 ] == 254 ) ) \ + : IN6_IS_ADDR_LOOPBACK( &( (const struct sockaddr_in6 *)( SA ) )->sin6_addr ) ) +#else + #define SOCKADDR_IS_IP_LINK_LOCAL( SA ) \ + ( ( ( (const struct sockaddr *)( SA ) )->sa_family == AF_INET ) \ + ? ( ( ( (uint8_t *)( &( (const struct sockaddr_in *)( SA ) )->sin_addr ) )[ 0 ] == 169 ) && \ + ( ( (uint8_t *)( &( (const struct sockaddr_in *)( SA ) )->sin_addr ) )[ 1 ] == 254 ) ) \ + : 0 ) +#endif + +// _beginthreadex and _endthreadex are not supported on Windows CE 2.1 or later (the C runtime issues with leaking +// resources have apparently been resolved and they seem to have just ripped out support for the API) so map it to +// CreateThread on Windows CE. + +#if ( TARGET_OS_WINDOWS_CE ) + #define _beginthreadex_compat( SECURITY_PTR, STACK_SIZE, START_ADDRESS, ARG_LIST, FLAGS, THREAD_ID_PTR ) \ + (uintptr_t) CreateThread( SECURITY_PTR, STACK_SIZE, (LPTHREAD_START_ROUTINE) START_ADDRESS, ARG_LIST, FLAGS, \ + (LPDWORD) THREAD_ID_PTR ) + + #define _endthreadex_compat( RESULT ) ExitThread( (DWORD) RESULT ) +#elif ( TARGET_OS_WIN32 ) + #define _beginthreadex_compat _beginthreadex + #define _endthreadex_compat _endthreadex +#endif + +// The C99 "inline" keyword is not supported by Microsoft compilers, but they do support __inline so map it when needed. + +#if ( defined( _MSC_VER ) ) + #define inline_compat __inline +#else + #define inline_compat inline +#endif + +// Calling conventions + +#if ( !defined( CALLBACK_COMPAT ) ) + #if ( TARGET_OS_WIN32 || TARGET_OS_WINDOWS_CE ) + #define CALLBACK_COMPAT CALLBACK + #else + #define CALLBACK_COMPAT + #endif +#endif + +#if 0 +#pragma mark == Macros == +#endif + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @defined kSizeCString + + @abstract A meta-value to pass to supported routines to indicate the size should be calculated with strlen. + */ + +#define kSizeCString ( (size_t) -1 ) + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @defined sizeof_array + + @abstract Determines the number of elements in an array. + */ + +#define sizeof_array( X ) ( sizeof( X ) / sizeof( X[ 0 ] ) ) + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @defined sizeof_element + + @abstract Determines the size of an array element. + */ + +#define sizeof_element( X ) sizeof( X[ 0 ] ) + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @defined sizeof_string + + @abstract Determines the size of a constant C string, excluding the null terminator. + */ + +#define sizeof_string( X ) ( sizeof( ( X ) ) - 1 ) + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @defined sizeof_field + + @abstract Determines the size of a field of a type. + */ + +#define sizeof_field( TYPE, FIELD ) sizeof( ( ( (TYPE *) 0 )->FIELD ) ) + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @function RoundUp + + @abstract Rounds X up to a multiple of Y. + */ + +#define RoundUp( X, Y ) ( ( X ) + ( ( Y ) -( ( X ) % ( Y ) ) ) ) + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @function IsAligned + + @abstract Returns non-zero if X is aligned to a Y byte boundary and 0 if not. Y must be a power of 2. + */ + +#define IsAligned( X, Y ) ( ( ( X ) &( ( Y ) -1 ) ) == 0 ) + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @function IsFieldAligned + + @abstract Returns non-zero if FIELD of type TYPE is aligned to a Y byte boundary and 0 if not. Y must be a power of 2. + */ + +#define IsFieldAligned( X, TYPE, FIELD, Y ) IsAligned( ( (uintptr_t)( X ) ) + offsetof( TYPE, FIELD ), ( Y ) ) + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @function AlignDown + + @abstract Aligns X down to a Y byte boundary. Y must be a power of 2. + */ + +#define AlignDown( X, Y ) ( ( X ) &~( ( Y ) -1 ) ) + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @function AlignUp + + @abstract Aligns X up to a Y byte boundary. Y must be a power of 2. + */ + +#define AlignUp( X, Y ) ( ( ( X ) + ( ( Y ) -1 ) ) & ~( ( Y ) -1 ) ) + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @function Min + + @abstract Returns the lesser of X and Y. + */ + +#if ( !defined( Min ) ) + #define Min( X, Y ) ( ( ( X ) < ( Y ) ) ? ( X ) : ( Y ) ) +#endif + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @function Max + + @abstract Returns the greater of X and Y. + */ + +#if ( !defined( Max ) ) + #define Max( X, Y ) ( ( ( X ) > ( Y ) ) ? ( X ) : ( Y ) ) +#endif + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @function InsertBits + + @abstract Inserts BITS (both 0 and 1 bits) into X, controlled by MASK and SHIFT, and returns the result. + + @discussion + + MASK is the bitmask of the bits in the final position. + SHIFT is the number of bits to shift left for 1 to reach the first bit position of MASK. + + For example, if you wanted to insert 0x3 into the leftmost 4 bits of a 32-bit value: + + InsertBits( 0, 0x3, 0xF0000000U, 28 ) == 0x30000000 + */ + +#define InsertBits( X, BITS, MASK, SHIFT ) ( ( ( X ) &~( MASK ) ) | ( ( ( BITS ) << ( SHIFT ) ) & ( MASK ) ) ) + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @function ExtractBits + + @abstract Extracts bits from X, controlled by MASK and SHIFT, and returns the result. + + @discussion + + MASK is the bitmask of the bits in the final position. + SHIFT is the number of bits to shift right to right justify MASK. + + For example, if you had a 32-bit value (e.g. 0x30000000) wanted the left-most 4 bits (e.g. 3 in this example): + + ExtractBits( 0x30000000U, 0xF0000000U, 28 ) == 0x3 + */ + +#define ExtractBits( X, MASK, SHIFT ) ( ( ( X ) >> ( SHIFT ) ) & ( ( MASK ) >> ( SHIFT ) ) ) + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @function Stringify + + @abstract Stringify's an expression. + + @discussion + + Stringify macros to process raw text passed via -D options to C string constants. The double-wrapping is necessary + because the C preprocessor doesn't perform its normal argument expansion pre-scan with stringified macros so the + -D macro needs to be expanded once via the wrapper macro then stringified so the raw text is stringified. Otherwise, + the replacement value would be used instead of the symbolic name (only for preprocessor symbols like #defines). + + For example: + + #define kMyConstant 1 + + printf( "%s", Stringify( kMyConstant ) ); // Prints "kMyConstant" + printf( "%s", StringifyExpansion( kMyConstant ) ); // Prints "1" + + Non-preprocessor symbols do not have this issue. For example: + + enum + { + kMyConstant = 1 + }; + + printf( "%s", Stringify( kMyConstant ) ); // Prints "kMyConstant" + printf( "%s", StringifyExpansion( kMyConstant ) ); // Prints "kMyConstant" + + See <http://gcc.gnu.org/onlinedocs/cpp/Argument-Prescan.html> for more info on C preprocessor pre-scanning. + */ + +#define Stringify( X ) # X +#define StringifyExpansion( X ) Stringify( X ) + +#if 0 +#pragma mark == Types == +#endif + +#if ( TARGET_LANGUAGE_C_LIKE ) +//=========================================================================================================================== +// Standard Types +//=========================================================================================================================== + +#if ( !defined( INT8_MIN ) ) + + #define INT8_MIN SCHAR_MIN + + #if ( defined( _MSC_VER ) ) + +// C99 stdint.h not supported in VC++/VS.NET yet. + +typedef INT8 int8_t; +typedef UINT8 uint8_t; +typedef INT16 int16_t; +typedef UINT16 uint16_t; +typedef INT32 int32_t; +typedef UINT32 uint32_t; +typedef __int64 int64_t; +typedef unsigned __int64 uint64_t; + + #elif ( TARGET_OS_VXWORKS && ( TORNADO_VERSION < 220 ) ) +typedef long long int64_t; +typedef unsigned long long uint64_t; + #endif + +typedef int8_t int_least8_t; +typedef int16_t int_least16_t; +typedef int32_t int_least32_t; +typedef int64_t int_least64_t; + +typedef uint8_t uint_least8_t; +typedef uint16_t uint_least16_t; +typedef uint32_t uint_least32_t; +typedef uint64_t uint_least64_t; + +typedef int8_t int_fast8_t; +typedef int16_t int_fast16_t; +typedef int32_t int_fast32_t; +typedef int64_t int_fast64_t; + +typedef uint8_t uint_fast8_t; +typedef uint16_t uint_fast16_t; +typedef uint32_t uint_fast32_t; +typedef uint64_t uint_fast64_t; + + #if ( !defined( _MSC_VER ) || TARGET_OS_WINDOWS_CE ) +typedef long int intptr_t; +typedef unsigned long int uintptr_t; + #endif + +#endif + +// Macros for minimum-width integer constants + +#if ( !defined( INT8_C ) ) + #define INT8_C( value ) value +#endif + +#if ( !defined( INT16_C ) ) + #define INT16_C( value ) value +#endif + +#if ( !defined( INT32_C ) ) + #define INT32_C( value ) value ## L +#endif + +#if ( !defined( INT64_C ) ) + #if ( defined( _MSC_VER ) ) + #define INT64_C( value ) value ## i64 + #else + #define INT64_C( value ) value ## LL + #endif +#endif + +#if ( !defined( UINT8_C ) ) + #define UINT8_C( value ) value ## U +#endif + +#if ( !defined( UINT16_C ) ) + #define UINT16_C( value ) value ## U +#endif + +#if ( !defined( UINT32_C ) ) + #define UINT32_C( value ) value ## UL +#endif + +#if ( !defined( UINT64_C ) ) + #if ( defined( _MSC_VER ) ) + #define UINT64_C( value ) value ## UI64 + #else + #define UINT64_C( value ) value ## ULL + #endif +#endif + +#if 0 +#pragma mark == bool == +#endif + +//=========================================================================================================================== +// Boolean Constants and Types +//=========================================================================================================================== + +// C++ defines bool, true, and false. Metrowerks allows this to be controlled by the "bool" option though. +// C99 defines __bool_true_false_are_defined when bool, true, and false are defined. +// MacTypes.h defines true and false (Mac builds only). +// +// Note: The Metrowerks has to be in its own block because Microsoft Visual Studio .NET does not completely +// short-circuit and gets confused by the option( bool ) portion of the conditional. + +#if ( defined( __MWERKS__ ) ) + +// Note: The following test is done on separate lines because CodeWarrior doesn't like it all on one line. + + #if ( !__bool_true_false_are_defined && ( !defined( __cplusplus ) || !__option( bool ) ) ) + #define COMMON_SERVICES_NEEDS_BOOL 1 + #else + #define COMMON_SERVICES_NEEDS_BOOL 0 + #endif + +// Workaround when building with CodeWarrior, but using the Apple stdbool.h header, which uses _Bool. + + #if ( __bool_true_false_are_defined && !defined( __cplusplus ) && !__option( c9x ) ) + #define _Bool int + #endif + +// Workaround when building with CodeWarrior for C++ with bool disabled and using the Apple stdbool.h header, +// which defines true and false to map to C++ true and false (which are not enabled). Serenity Now! + + #if ( __bool_true_false_are_defined && defined( __cplusplus ) && !__option( bool ) ) + #define true 1 + #define false 0 + #endif +#else + #define COMMON_SERVICES_NEEDS_BOOL ( !defined( __cplusplus ) && !__bool_true_false_are_defined ) +#endif + +#if ( COMMON_SERVICES_NEEDS_BOOL ) + +typedef int bool; + + #define bool bool + + #if ( !defined( __MACTYPES__ ) && !defined( true ) && !defined( false ) ) + #define true 1 + #define false 0 + #endif + + #define __bool_true_false_are_defined 1 +#endif + +// IOKit IOTypes.h typedef's bool if TYPE_BOOL is not defined so define it here to prevent redefinition by IOTypes.h. + +#if ( TARGET_API_MAC_OSX_KERNEL ) + #define TYPE_BOOL 1 +#endif + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @typedef CStr255 + + @abstract 255 character null-terminated (C-style) string. + */ + +#if ( TARGET_LANGUAGE_C_LIKE ) +typedef char CStr255[ 256 ]; +#endif + +#endif // TARGET_LANGUAGE_C_LIKE + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @defined TYPE_LONGLONG_NATIVE + + @abstract Defines whether long long (or its equivalent) is natively supported or requires special libraries. + */ + +#if ( !defined( TYPE_LONGLONG_NATIVE ) ) + #if ( !TARGET_OS_VXWORKS ) + #define TYPE_LONGLONG_NATIVE 1 + #else + #define TYPE_LONGLONG_NATIVE 0 + #endif +#endif + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @defined long_long_compat + + @abstract Compatibility type to map to the closest thing to long long and unsigned long long. + + @discussion + + Neither long long nor unsigned long long are supported by Microsoft compilers, but they do support proprietary + "__int64" and "unsigned __int64" equivalents so map to those types if the real long long is not supported. + */ + +#if ( TARGET_LANGUAGE_C_LIKE ) + #if ( TARGET_OS_WIN32 ) +typedef __int64 long_long_compat; +typedef unsigned __int64 unsigned_long_long_compat; + #else +typedef signed long long long_long_compat; +typedef unsigned long long unsigned_long_long_compat; + #endif +#endif + +#if 0 +#pragma mark == Errors == +#endif + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @enum OSStatus + + @abstract Status Code + + @constant kNoErr 0 No error occurred. + @constant kInProgressErr 1 Operation in progress. + @constant kUnknownErr -6700 Unknown error occurred. + @constant kOptionErr -6701 Option was not acceptable. + @constant kSelectorErr -6702 Selector passed in is invalid or unknown. + @constant kExecutionStateErr -6703 Call made in the wrong execution state (e.g. called at interrupt time). + @constant kPathErr -6704 Path is invalid, too long, or otherwise not usable. + @constant kParamErr -6705 Parameter is incorrect, missing, or not appropriate. + @constant kParamCountErr -6706 Incorrect or unsupported number of parameters. + @constant kCommandErr -6707 Command invalid or not supported. + @constant kIDErr -6708 Unknown, invalid, or inappropriate identifier. + @constant kStateErr -6709 Not in appropriate state to perform operation. + @constant kRangeErr -6710 Index is out of range or not valid. + @constant kRequestErr -6711 Request was improperly formed or not appropriate. + @constant kResponseErr -6712 Response was incorrect or out of sequence. + @constant kChecksumErr -6713 Checksum does not match the actual data. + @constant kNotHandledErr -6714 Operation was not handled (or not handled completely). + @constant kVersionErr -6715 Version is not incorrect or not compatibile. + @constant kSignatureErr -6716 Signature did not match what was expected. + @constant kFormatErr -6717 Unknown, invalid, or inappropriate file/data format. + @constant kNotInitializedErr -6718 Action request before needed services were initialized. + @constant kAlreadyInitializedErr -6719 Attempt made to initialize when already initialized. + @constant kNotInUseErr -6720 Object not in use (e.g. cannot abort if not already in use). + @constant kInUseErr -6721 Object is in use (e.g. cannot reuse active param blocks). + @constant kTimeoutErr -6722 Timeout occurred. + @constant kCanceledErr -6723 Operation canceled (successful cancel). + @constant kAlreadyCanceledErr -6724 Operation has already been canceled. + @constant kCannotCancelErr -6725 Operation could not be canceled (maybe already done or invalid). + @constant kDeletedErr -6726 Object has already been deleted. + @constant kNotFoundErr -6727 Something was not found. + @constant kNoMemoryErr -6728 Not enough memory was available to perform the operation. + @constant kNoResourcesErr -6729 Resources unavailable to perform the operation. + @constant kDuplicateErr -6730 Duplicate found or something is a duplicate. + @constant kImmutableErr -6731 Entity is not changeable. + @constant kUnsupportedDataErr -6732 Data is unknown or not supported. + @constant kIntegrityErr -6733 Data is corrupt. + @constant kIncompatibleErr -6734 Data is not compatible or it is in an incompatible format. + @constant kUnsupportedErr -6735 Feature or option is not supported. + @constant kUnexpectedErr -6736 Error occurred that was not expected. + @constant kValueErr -6737 Value is not appropriate. + @constant kNotReadableErr -6738 Could not read or reading is not allowed. + @constant kNotWritableErr -6739 Could not write or writing is not allowed. + @constant kBadReferenceErr -6740 An invalid or inappropriate reference was specified. + @constant kFlagErr -6741 An invalid, inappropriate, or unsupported flag was specified. + @constant kMalformedErr -6742 Something was not formed correctly. + @constant kSizeErr -6743 Size was too big, too small, or not appropriate. + @constant kNameErr -6744 Name was not correct, allowed, or appropriate. + @constant kNotReadyErr -6745 Device or service is not ready. + @constant kReadErr -6746 Could not read. + @constant kWriteErr -6747 Could not write. + @constant kMismatchErr -6748 Something does not match. + @constant kDateErr -6749 Date is invalid or out-of-range. + @constant kUnderrunErr -6750 Less data than expected. + @constant kOverrunErr -6751 More data than expected. + @constant kEndingErr -6752 Connection, session, or something is ending. + @constant kConnectionErr -6753 Connection failed or could not be established. + @constant kAuthenticationErr -6754 Authentication failed or is not supported. + @constant kOpenErr -6755 Could not open file, pipe, device, etc. + @constant kTypeErr -6756 Incorrect or incompatible type (e.g. file, data, etc.). + @constant kSkipErr -6757 Items should be or was skipped. + @constant kNoAckErr -6758 No acknowledge. + @constant kCollisionErr -6759 Collision occurred (e.g. two on bus at same time). + @constant kBackoffErr -6760 Backoff in progress and operation intentionally failed. + @constant kNoAddressAckErr -6761 No acknowledge of address. + @constant kBusyErr -6762 Cannot perform because something is busy. + @constant kNoSpaceErr -6763 Not enough space to perform operation. + */ + +#if ( TARGET_LANGUAGE_C_LIKE ) + #if ( !TARGET_OS_MAC && !TARGET_API_MAC_OSX_KERNEL ) +typedef int32_t OSStatus; + #endif +#endif + +#define kNoErr 0 +#define kInProgressErr 1 + +// Generic error codes are in the range -6700 to -6779. + +#define kGenericErrorBase -6700 // Starting error code for all generic errors. + +#define kUnknownErr -6700 +#define kOptionErr -6701 +#define kSelectorErr -6702 +#define kExecutionStateErr -6703 +#define kPathErr -6704 +#define kParamErr -6705 +#define kParamCountErr -6706 +#define kCommandErr -6707 +#define kIDErr -6708 +#define kStateErr -6709 +#define kRangeErr -6710 +#define kRequestErr -6711 +#define kResponseErr -6712 +#define kChecksumErr -6713 +#define kNotHandledErr -6714 +#define kVersionErr -6715 +#define kSignatureErr -6716 +#define kFormatErr -6717 +#define kNotInitializedErr -6718 +#define kAlreadyInitializedErr -6719 +#define kNotInUseErr -6720 +#define kInUseErr -6721 +#define kTimeoutErr -6722 +#define kCanceledErr -6723 +#define kAlreadyCanceledErr -6724 +#define kCannotCancelErr -6725 +#define kDeletedErr -6726 +#define kNotFoundErr -6727 +#define kNoMemoryErr -6728 +#define kNoResourcesErr -6729 +#define kDuplicateErr -6730 +#define kImmutableErr -6731 +#define kUnsupportedDataErr -6732 +#define kIntegrityErr -6733 +#define kIncompatibleErr -6734 +#define kUnsupportedErr -6735 +#define kUnexpectedErr -6736 +#define kValueErr -6737 +#define kNotReadableErr -6738 +#define kNotWritableErr -6739 +#define kBadReferenceErr -6740 +#define kFlagErr -6741 +#define kMalformedErr -6742 +#define kSizeErr -6743 +#define kNameErr -6744 +#define kNotReadyErr -6745 +#define kReadErr -6746 +#define kWriteErr -6747 +#define kMismatchErr -6748 +#define kDateErr -6749 +#define kUnderrunErr -6750 +#define kOverrunErr -6751 +#define kEndingErr -6752 +#define kConnectionErr -6753 +#define kAuthenticationErr -6754 +#define kOpenErr -6755 +#define kTypeErr -6756 +#define kSkipErr -6757 +#define kNoAckErr -6758 +#define kCollisionErr -6759 +#define kBackoffErr -6760 +#define kNoAddressAckErr -6761 +#define kBusyErr -6762 +#define kNoSpaceErr -6763 + +#define kGenericErrorEnd -6779 // Last generic error code (inclusive) + +#if 0 +#pragma mark == Mac Compatibility == +#endif + +//=========================================================================================================================== +// Mac Compatibility +//=========================================================================================================================== + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @enum Duration + + @abstract Type used to specify a duration of time. + + @constant kDurationImmediate Indicates no delay/wait time. + @constant kDurationMicrosecond Microsecond units. + @constant kDurationMillisecond Millisecond units. + @constant kDurationSecond Second units. + @constant kDurationMinute Minute units. + @constant kDurationHour Hour units. + @constant kDurationDay Day units. + @constant kDurationForever Infinite period of time (no timeout). + + @discussion + + Duration values are intended to be multiplied by the specific interval to achieve an actual duration. For example, + to wait for 5 seconds you would use "5 * kDurationSecond". + */ + +#if ( TARGET_LANGUAGE_C_LIKE ) + #if ( !TARGET_OS_MAC ) +typedef int32_t Duration; + #endif +#endif + +#define kDurationImmediate 0L +#define kDurationMicrosecond -1L +#define kDurationMillisecond 1L +#define kDurationSecond ( 1000L * kDurationMillisecond ) +#define kDurationMinute ( 60L * kDurationSecond ) +#define kDurationHour ( 60L * kDurationMinute ) +#define kDurationDay ( 24L * kDurationHour ) +#define kDurationForever 0x7FFFFFFFL + +// Seconds <-> Minutes <-> Hours <-> Days <-> Weeks <-> Months <-> Years conversions + +#define kNanosecondsPerMicrosecond 1000 +#define kNanosecondsPerMillisecond 1000000 +#define kNanosecondsPerSecond 1000000000 +#define kMicrosecondsPerSecond 1000000 +#define kMicrosecondsPerMillisecond 1000 +#define kMillisecondsPerSecond 1000 +#define kSecondsPerMinute 60 +#define kSecondsPerHour ( 60 * 60 ) // 3600 +#define kSecondsPerDay ( 60 * 60 * 24 ) // 86400 +#define kSecondsPerWeek ( 60 * 60 * 24 * 7 ) // 604800 +#define kMinutesPerHour 60 +#define kMinutesPerDay ( 60 * 24 ) // 1440 +#define kHoursPerDay 24 +#define kDaysPerWeek 7 +#define kWeeksPerYear 52 +#define kMonthsPerYear 12 + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @defined VersionStages + + @abstract NumVersion-style version stages. + */ + +#define kVersionStageDevelopment 0x20 +#define kVersionStageAlpha 0x40 +#define kVersionStageBeta 0x60 +#define kVersionStageFinal 0x80 + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @function NumVersionBuild + + @abstract Builds a 32-bit Mac-style NumVersion value (e.g. NumVersionBuild( 1, 2, 3, kVersionStageBeta, 4 ) -> 1.2.3b4). + */ + +#define NumVersionBuild( MAJOR, MINOR, BUGFIX, STAGE, REV ) \ + ( ( ( ( MAJOR ) & 0xFF ) << 24 ) | \ + ( ( ( MINOR ) & 0x0F ) << 20 ) | \ + ( ( ( BUGFIX ) & 0x0F ) << 16 ) | \ + ( ( ( STAGE ) & 0xFF ) << 8 ) | \ + ( ( ( REV ) & 0xFF ) ) ) + +#define NumVersionExtractMajor( VERSION ) ( (uint8_t)( ( ( VERSION ) >> 24 ) & 0xFF ) ) +#define NumVersionExtractMinorAndBugFix( VERSION ) ( (uint8_t)( ( ( VERSION ) >> 16 ) & 0xFF ) ) +#define NumVersionExtractMinor( VERSION ) ( (uint8_t)( ( ( VERSION ) >> 20 ) & 0x0F ) ) +#define NumVersionExtractBugFix( VERSION ) ( (uint8_t)( ( ( VERSION ) >> 16 ) & 0x0F ) ) +#define NumVersionExtractStage( VERSION ) ( (uint8_t)( ( ( VERSION ) >> 8 ) & 0xFF ) ) +#define NumVersionExtractRevision( VERSION ) ( (uint8_t)( ( VERSION ) & 0xFF ) ) + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @function NumVersionCompare + + @abstract Compares two NumVersion values and returns the following values: + + left < right -> -1 + left > right -> 1 + left = right -> 0 + */ + +#if ( TARGET_LANGUAGE_C_LIKE ) +int NumVersionCompare( uint32_t inLeft, uint32_t inRight ); +#endif + +#if 0 +#pragma mark == Binary Constants == +#endif + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @defined binary_4 + + @abstract Macro to generate an 4-bit constant using binary notation (e.g. binary_4( 1010 ) == 0xA). + */ + +#define binary_4( a ) binary_4_hex_wrap( hex_digit4( a ) ) +#define binary_4_hex_wrap( a ) binary_4_hex( a ) +#define binary_4_hex( a ) ( 0x ## a ) + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @defined binary_8 + + @abstract Macro to generate an 8-bit constant using binary notation (e.g. binary_8( 01111011 ) == 0x7B). + */ + +#define binary_8( a ) binary_8_hex_wrap( hex_digit8( a ) ) +#define binary_8_hex_wrap( a ) binary_8_hex( a ) +#define binary_8_hex( a ) ( 0x ## a ) + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @defined binary_16 + + @abstract Macro to generate an 16-bit constant using binary notation (e.g. binary_16( 01111011, 01111011 ) == 0x7B7B). + */ + +#define binary_16( a, b ) binary_16_hex_wrap( hex_digit8( a ), hex_digit8( b ) ) +#define binary_16_hex_wrap( a, b ) binary_16_hex( a, b ) +#define binary_16_hex( a, b ) ( 0x ## a ## b ) + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @defined binary_32 + + @abstract Macro to generate an 32-bit constant using binary notation + (e.g. binary_32( 01111011, 01111011, 01111011, 01111011 ) == 0x7B7B7B7B). + */ + +#define binary_32( a, b, c, d ) binary_32_hex_wrap( hex_digit8( a ), hex_digit8( b ), hex_digit8( c ), hex_digit8( d ) ) +#define binary_32_hex_wrap( a, b, c, d ) binary_32_hex( a, b, c, d ) +#define binary_32_hex( a, b, c, d ) ( 0x ## a ## b ## c ## d ) + +// Binary Constant Helpers + +#define hex_digit8( a ) HEX_DIGIT_ ## a +#define hex_digit4( a ) HEX_DIGIT_ ## 0000 ## a + +#define HEX_DIGIT_00000000 00 +#define HEX_DIGIT_00000001 01 +#define HEX_DIGIT_00000010 02 +#define HEX_DIGIT_00000011 03 +#define HEX_DIGIT_00000100 04 +#define HEX_DIGIT_00000101 05 +#define HEX_DIGIT_00000110 06 +#define HEX_DIGIT_00000111 07 +#define HEX_DIGIT_00001000 08 +#define HEX_DIGIT_00001001 09 +#define HEX_DIGIT_00001010 0A +#define HEX_DIGIT_00001011 0B +#define HEX_DIGIT_00001100 0C +#define HEX_DIGIT_00001101 0D +#define HEX_DIGIT_00001110 0E +#define HEX_DIGIT_00001111 0F +#define HEX_DIGIT_00010000 10 +#define HEX_DIGIT_00010001 11 +#define HEX_DIGIT_00010010 12 +#define HEX_DIGIT_00010011 13 +#define HEX_DIGIT_00010100 14 +#define HEX_DIGIT_00010101 15 +#define HEX_DIGIT_00010110 16 +#define HEX_DIGIT_00010111 17 +#define HEX_DIGIT_00011000 18 +#define HEX_DIGIT_00011001 19 +#define HEX_DIGIT_00011010 1A +#define HEX_DIGIT_00011011 1B +#define HEX_DIGIT_00011100 1C +#define HEX_DIGIT_00011101 1D +#define HEX_DIGIT_00011110 1E +#define HEX_DIGIT_00011111 1F +#define HEX_DIGIT_00100000 20 +#define HEX_DIGIT_00100001 21 +#define HEX_DIGIT_00100010 22 +#define HEX_DIGIT_00100011 23 +#define HEX_DIGIT_00100100 24 +#define HEX_DIGIT_00100101 25 +#define HEX_DIGIT_00100110 26 +#define HEX_DIGIT_00100111 27 +#define HEX_DIGIT_00101000 28 +#define HEX_DIGIT_00101001 29 +#define HEX_DIGIT_00101010 2A +#define HEX_DIGIT_00101011 2B +#define HEX_DIGIT_00101100 2C +#define HEX_DIGIT_00101101 2D +#define HEX_DIGIT_00101110 2E +#define HEX_DIGIT_00101111 2F +#define HEX_DIGIT_00110000 30 +#define HEX_DIGIT_00110001 31 +#define HEX_DIGIT_00110010 32 +#define HEX_DIGIT_00110011 33 +#define HEX_DIGIT_00110100 34 +#define HEX_DIGIT_00110101 35 +#define HEX_DIGIT_00110110 36 +#define HEX_DIGIT_00110111 37 +#define HEX_DIGIT_00111000 38 +#define HEX_DIGIT_00111001 39 +#define HEX_DIGIT_00111010 3A +#define HEX_DIGIT_00111011 3B +#define HEX_DIGIT_00111100 3C +#define HEX_DIGIT_00111101 3D +#define HEX_DIGIT_00111110 3E +#define HEX_DIGIT_00111111 3F +#define HEX_DIGIT_01000000 40 +#define HEX_DIGIT_01000001 41 +#define HEX_DIGIT_01000010 42 +#define HEX_DIGIT_01000011 43 +#define HEX_DIGIT_01000100 44 +#define HEX_DIGIT_01000101 45 +#define HEX_DIGIT_01000110 46 +#define HEX_DIGIT_01000111 47 +#define HEX_DIGIT_01001000 48 +#define HEX_DIGIT_01001001 49 +#define HEX_DIGIT_01001010 4A +#define HEX_DIGIT_01001011 4B +#define HEX_DIGIT_01001100 4C +#define HEX_DIGIT_01001101 4D +#define HEX_DIGIT_01001110 4E +#define HEX_DIGIT_01001111 4F +#define HEX_DIGIT_01010000 50 +#define HEX_DIGIT_01010001 51 +#define HEX_DIGIT_01010010 52 +#define HEX_DIGIT_01010011 53 +#define HEX_DIGIT_01010100 54 +#define HEX_DIGIT_01010101 55 +#define HEX_DIGIT_01010110 56 +#define HEX_DIGIT_01010111 57 +#define HEX_DIGIT_01011000 58 +#define HEX_DIGIT_01011001 59 +#define HEX_DIGIT_01011010 5A +#define HEX_DIGIT_01011011 5B +#define HEX_DIGIT_01011100 5C +#define HEX_DIGIT_01011101 5D +#define HEX_DIGIT_01011110 5E +#define HEX_DIGIT_01011111 5F +#define HEX_DIGIT_01100000 60 +#define HEX_DIGIT_01100001 61 +#define HEX_DIGIT_01100010 62 +#define HEX_DIGIT_01100011 63 +#define HEX_DIGIT_01100100 64 +#define HEX_DIGIT_01100101 65 +#define HEX_DIGIT_01100110 66 +#define HEX_DIGIT_01100111 67 +#define HEX_DIGIT_01101000 68 +#define HEX_DIGIT_01101001 69 +#define HEX_DIGIT_01101010 6A +#define HEX_DIGIT_01101011 6B +#define HEX_DIGIT_01101100 6C +#define HEX_DIGIT_01101101 6D +#define HEX_DIGIT_01101110 6E +#define HEX_DIGIT_01101111 6F +#define HEX_DIGIT_01110000 70 +#define HEX_DIGIT_01110001 71 +#define HEX_DIGIT_01110010 72 +#define HEX_DIGIT_01110011 73 +#define HEX_DIGIT_01110100 74 +#define HEX_DIGIT_01110101 75 +#define HEX_DIGIT_01110110 76 +#define HEX_DIGIT_01110111 77 +#define HEX_DIGIT_01111000 78 +#define HEX_DIGIT_01111001 79 +#define HEX_DIGIT_01111010 7A +#define HEX_DIGIT_01111011 7B +#define HEX_DIGIT_01111100 7C +#define HEX_DIGIT_01111101 7D +#define HEX_DIGIT_01111110 7E +#define HEX_DIGIT_01111111 7F +#define HEX_DIGIT_10000000 80 +#define HEX_DIGIT_10000001 81 +#define HEX_DIGIT_10000010 82 +#define HEX_DIGIT_10000011 83 +#define HEX_DIGIT_10000100 84 +#define HEX_DIGIT_10000101 85 +#define HEX_DIGIT_10000110 86 +#define HEX_DIGIT_10000111 87 +#define HEX_DIGIT_10001000 88 +#define HEX_DIGIT_10001001 89 +#define HEX_DIGIT_10001010 8A +#define HEX_DIGIT_10001011 8B +#define HEX_DIGIT_10001100 8C +#define HEX_DIGIT_10001101 8D +#define HEX_DIGIT_10001110 8E +#define HEX_DIGIT_10001111 8F +#define HEX_DIGIT_10010000 90 +#define HEX_DIGIT_10010001 91 +#define HEX_DIGIT_10010010 92 +#define HEX_DIGIT_10010011 93 +#define HEX_DIGIT_10010100 94 +#define HEX_DIGIT_10010101 95 +#define HEX_DIGIT_10010110 96 +#define HEX_DIGIT_10010111 97 +#define HEX_DIGIT_10011000 98 +#define HEX_DIGIT_10011001 99 +#define HEX_DIGIT_10011010 9A +#define HEX_DIGIT_10011011 9B +#define HEX_DIGIT_10011100 9C +#define HEX_DIGIT_10011101 9D +#define HEX_DIGIT_10011110 9E +#define HEX_DIGIT_10011111 9F +#define HEX_DIGIT_10100000 A0 +#define HEX_DIGIT_10100001 A1 +#define HEX_DIGIT_10100010 A2 +#define HEX_DIGIT_10100011 A3 +#define HEX_DIGIT_10100100 A4 +#define HEX_DIGIT_10100101 A5 +#define HEX_DIGIT_10100110 A6 +#define HEX_DIGIT_10100111 A7 +#define HEX_DIGIT_10101000 A8 +#define HEX_DIGIT_10101001 A9 +#define HEX_DIGIT_10101010 AA +#define HEX_DIGIT_10101011 AB +#define HEX_DIGIT_10101100 AC +#define HEX_DIGIT_10101101 AD +#define HEX_DIGIT_10101110 AE +#define HEX_DIGIT_10101111 AF +#define HEX_DIGIT_10110000 B0 +#define HEX_DIGIT_10110001 B1 +#define HEX_DIGIT_10110010 B2 +#define HEX_DIGIT_10110011 B3 +#define HEX_DIGIT_10110100 B4 +#define HEX_DIGIT_10110101 B5 +#define HEX_DIGIT_10110110 B6 +#define HEX_DIGIT_10110111 B7 +#define HEX_DIGIT_10111000 B8 +#define HEX_DIGIT_10111001 B9 +#define HEX_DIGIT_10111010 BA +#define HEX_DIGIT_10111011 BB +#define HEX_DIGIT_10111100 BC +#define HEX_DIGIT_10111101 BD +#define HEX_DIGIT_10111110 BE +#define HEX_DIGIT_10111111 BF +#define HEX_DIGIT_11000000 C0 +#define HEX_DIGIT_11000001 C1 +#define HEX_DIGIT_11000010 C2 +#define HEX_DIGIT_11000011 C3 +#define HEX_DIGIT_11000100 C4 +#define HEX_DIGIT_11000101 C5 +#define HEX_DIGIT_11000110 C6 +#define HEX_DIGIT_11000111 C7 +#define HEX_DIGIT_11001000 C8 +#define HEX_DIGIT_11001001 C9 +#define HEX_DIGIT_11001010 CA +#define HEX_DIGIT_11001011 CB +#define HEX_DIGIT_11001100 CC +#define HEX_DIGIT_11001101 CD +#define HEX_DIGIT_11001110 CE +#define HEX_DIGIT_11001111 CF +#define HEX_DIGIT_11010000 D0 +#define HEX_DIGIT_11010001 D1 +#define HEX_DIGIT_11010010 D2 +#define HEX_DIGIT_11010011 D3 +#define HEX_DIGIT_11010100 D4 +#define HEX_DIGIT_11010101 D5 +#define HEX_DIGIT_11010110 D6 +#define HEX_DIGIT_11010111 D7 +#define HEX_DIGIT_11011000 D8 +#define HEX_DIGIT_11011001 D9 +#define HEX_DIGIT_11011010 DA +#define HEX_DIGIT_11011011 DB +#define HEX_DIGIT_11011100 DC +#define HEX_DIGIT_11011101 DD +#define HEX_DIGIT_11011110 DE +#define HEX_DIGIT_11011111 DF +#define HEX_DIGIT_11100000 E0 +#define HEX_DIGIT_11100001 E1 +#define HEX_DIGIT_11100010 E2 +#define HEX_DIGIT_11100011 E3 +#define HEX_DIGIT_11100100 E4 +#define HEX_DIGIT_11100101 E5 +#define HEX_DIGIT_11100110 E6 +#define HEX_DIGIT_11100111 E7 +#define HEX_DIGIT_11101000 E8 +#define HEX_DIGIT_11101001 E9 +#define HEX_DIGIT_11101010 EA +#define HEX_DIGIT_11101011 EB +#define HEX_DIGIT_11101100 EC +#define HEX_DIGIT_11101101 ED +#define HEX_DIGIT_11101110 EE +#define HEX_DIGIT_11101111 EF +#define HEX_DIGIT_11110000 F0 +#define HEX_DIGIT_11110001 F1 +#define HEX_DIGIT_11110010 F2 +#define HEX_DIGIT_11110011 F3 +#define HEX_DIGIT_11110100 F4 +#define HEX_DIGIT_11110101 F5 +#define HEX_DIGIT_11110110 F6 +#define HEX_DIGIT_11110111 F7 +#define HEX_DIGIT_11111000 F8 +#define HEX_DIGIT_11111001 F9 +#define HEX_DIGIT_11111010 FA +#define HEX_DIGIT_11111011 FB +#define HEX_DIGIT_11111100 FC +#define HEX_DIGIT_11111101 FD +#define HEX_DIGIT_11111110 FE +#define HEX_DIGIT_11111111 FF + +#if 0 +#pragma mark == Debugging == +#endif + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @function CommonServicesTest + + @abstract Unit test. + */ + +#if ( DEBUG ) + #if ( TARGET_LANGUAGE_C_LIKE ) +OSStatus CommonServicesTest( void ); + #endif +#endif + +#ifdef __cplusplus +} +#endif + +#endif // __COMMON_SERVICES__ diff --git a/mDNSResponder/mDNSShared/DebugServices.c b/mDNSResponder/mDNSShared/DebugServices.c new file mode 100644 index 00000000..98f876a4 --- /dev/null +++ b/mDNSResponder/mDNSShared/DebugServices.c @@ -0,0 +1,3075 @@ +/* -*- Mode: C; tab-width: 4 -*- + * + * Copyright (c) 1997-2004 Apple Computer, Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + + To Do: + + - Use StackWalk on Windows to optionally print stack frames. + */ + +#if 0 +#pragma mark == Includes == +#endif + +//=========================================================================================================================== +// Includes +//=========================================================================================================================== + +#if ( !KERNEL ) + #include <ctype.h> + #include <stdio.h> + #include <string.h> +#endif + +#include "CommonServices.h" + +#include "DebugServices.h" + +#if ( DEBUG ) + +#if ( TARGET_OS_VXWORKS ) + #include "intLib.h" +#endif + +#if ( TARGET_OS_WIN32 ) + #include <time.h> + + #if ( !TARGET_OS_WINDOWS_CE ) + #include <fcntl.h> + #include <io.h> + #endif +#endif + +#if ( DEBUG_IDEBUG_ENABLED && TARGET_API_MAC_OSX_KERNEL ) + #include <IOKit/IOLib.h> +#endif + +// If MDNS_DEBUGMSGS is defined (even if defined 0), it is aware of mDNS and it is probably safe to include mDNSEmbeddedAPI.h. + +#if ( defined( MDNS_DEBUGMSGS ) ) + #include "mDNSEmbeddedAPI.h" +#endif + +#if 0 +#pragma mark == Macros == +#endif + +//=========================================================================================================================== +// Macros +//=========================================================================================================================== + +#define DebugIsPrint( C ) ( ( ( C ) >= 0x20 ) && ( ( C ) <= 0x7E ) ) + +#if 0 +#pragma mark == Prototypes == +#endif + +//=========================================================================================================================== +// Prototypes +//=========================================================================================================================== + +static OSStatus DebugPrint( DebugLevel inLevel, char *inData, size_t inSize ); + +// fprintf + +#if ( DEBUG_FPRINTF_ENABLED ) +static OSStatus DebugFPrintFInit( DebugOutputTypeFlags inFlags, const char *inFilename ); +static void DebugFPrintFPrint( char *inData, size_t inSize ); +#endif + +// iDebug (Mac OS X user and kernel) + +#if ( DEBUG_IDEBUG_ENABLED ) +static OSStatus DebugiDebugInit( void ); +static void DebugiDebugPrint( char *inData, size_t inSize ); +#endif + +// kprintf (Mac OS X Kernel) + +#if ( DEBUG_KPRINTF_ENABLED ) +static void DebugKPrintFPrint( char *inData, size_t inSize ); +#endif + +// Mac OS X IOLog (Mac OS X Kernel) + +#if ( DEBUG_MAC_OS_X_IOLOG_ENABLED ) +static void DebugMacOSXIOLogPrint( char *inData, size_t inSize ); +#endif + +// Mac OS X Log + +#if ( TARGET_OS_MAC ) +static OSStatus DebugMacOSXLogInit( void ); +static void DebugMacOSXLogPrint( char *inData, size_t inSize ); +#endif + +// Windows Debugger + +#if ( TARGET_OS_WIN32 ) +static void DebugWindowsDebuggerPrint( char *inData, size_t inSize ); +#endif + +// Windows Event Log + +#if ( TARGET_OS_WIN32 && !TARGET_OS_WINDOWS_CE ) +static OSStatus DebugWindowsEventLogInit( const char *inName, HMODULE inModule ); +static void DebugWindowsEventLogPrint( DebugLevel inLevel, char *inData, size_t inSize ); +#endif + +// DebugLib support + +#if ( DEBUG_CORE_SERVICE_ASSERTS_ENABLED ) +static pascal void +DebugAssertOutputHandler( + OSType inComponentSignature, + UInt32 inOptions, + const char * inAssertionString, + const char * inExceptionString, + const char * inErrorString, + const char * inFileName, + long inLineNumber, + void * inValue, + ConstStr255Param inOutputMsg ); +#endif + +// Utilities + +static char * DebugNumVersionToString( uint32_t inVersion, char *inString ); + +#if ( TARGET_OS_WIN32 && !TARGET_OS_WINDOWS_CE ) +static void DebugWinEnableConsole( void ); +#endif + +#if ( TARGET_OS_WIN32 ) +static TCHAR * +DebugWinCharToTCharString( + const char * inCharString, + size_t inCharCount, + TCHAR * outTCharString, + size_t inTCharCountMax, + size_t * outTCharCount ); +#endif + +#if 0 +#pragma mark == Globals == +#endif + +//=========================================================================================================================== +// Private Globals +//=========================================================================================================================== + +#if ( TARGET_OS_VXWORKS ) +// TCP States for inetstatShow. + +extern char ** pTcpstates; // defined in tcpLib.c + +const char * kDebugTCPStates[] = +{ + "(0) TCPS_CLOSED", + "(1) TCPS_LISTEN", + "(2) TCPS_SYN_SENT", + "(3) TCPS_SYN_RECEIVED", + "(4) TCPS_ESTABLISHED", + "(5) TCPS_CLOSE_WAIT", + "(6) TCPS_FIN_WAIT_1", + "(7) TCPS_CLOSING", + "(8) TCPS_LAST_ACK", + "(9) TCPS_FIN_WAIT_2", + "(10) TCPS_TIME_WAIT", +}; +#endif + +// General + +static bool gDebugInitialized = false; +static DebugOutputType gDebugOutputType = kDebugOutputTypeNone; +static DebugLevel gDebugPrintLevelMin = kDebugLevelInfo; +static DebugLevel gDebugPrintLevelMax = kDebugLevelMax; +static DebugLevel gDebugBreakLevel = kDebugLevelAssert; +#if ( DEBUG_CORE_SERVICE_ASSERTS_ENABLED ) +static DebugAssertOutputHandlerUPP gDebugAssertOutputHandlerUPP = NULL; +#endif + +// Custom + +static DebugOutputFunctionPtr gDebugCustomOutputFunction = NULL; +static void * gDebugCustomOutputContext = NULL; + +// fprintf + +#if ( DEBUG_FPRINTF_ENABLED ) +static FILE * gDebugFPrintFFile = NULL; +#endif + +// MacOSXLog + +#if ( TARGET_OS_MAC ) +typedef int ( *DebugMacOSXLogFunctionPtr )( const char *inFormat, ... ); + +static DebugMacOSXLogFunctionPtr gDebugMacOSXLogFunction = NULL; +#endif + +// WindowsEventLog + + +#if ( TARGET_OS_WIN32 && !TARGET_OS_WINDOWS_CE ) +static HANDLE gDebugWindowsEventLogEventSource = NULL; +#endif + +#if 0 +#pragma mark - +#pragma mark == General == +#endif + +//=========================================================================================================================== +// DebugInitialize +//=========================================================================================================================== + +DEBUG_EXPORT OSStatus DebugInitialize( DebugOutputType inType, ... ) +{ + OSStatus err; + DebugOutputType type; + va_list args; + + va_start( args, inType ); + +#if ( TARGET_OS_VXWORKS ) + // Set up the TCP state strings if they are not already set up by VxWorks (normally not set up for some reason). + + if( !pTcpstates ) + { + pTcpstates = (char **) kDebugTCPStates; + } +#endif + + // Set up DebugLib stuff (if building with Debugging.h). + +#if ( DEBUG_CORE_SERVICE_ASSERTS_ENABLED ) + if( !gDebugAssertOutputHandlerUPP ) + { + gDebugAssertOutputHandlerUPP = NewDebugAssertOutputHandlerUPP( DebugAssertOutputHandler ); + check( gDebugAssertOutputHandlerUPP ); + if( gDebugAssertOutputHandlerUPP ) + { + InstallDebugAssertOutputHandler( gDebugAssertOutputHandlerUPP ); + } + } +#endif + + // Pre-process meta-output kind to pick an appropriate output kind for the platform. + + type = inType; + if( type == kDebugOutputTypeMetaConsole ) + { + #if ( TARGET_OS_MAC ) + type = kDebugOutputTypeMacOSXLog; + #elif ( TARGET_OS_WIN32 && !TARGET_OS_WINDOWS_CE ) + #if ( DEBUG_FPRINTF_ENABLED ) + type = kDebugOutputTypeFPrintF; + #else + type = kDebugOutputTypeWindowsDebugger; + #endif + #elif ( TARGET_API_MAC_OSX_KERNEL ) + #if ( DEBUG_MAC_OS_X_IOLOG_ENABLED ) + type = kDebugOutputTypeMacOSXIOLog; + #elif ( DEBUG_IDEBUG_ENABLED ) + type = kDebugOutputTypeiDebug; + #elif ( DEBUG_KPRINTF_ENABLED ) + type = kDebugOutputTypeKPrintF; + #endif + #elif ( TARGET_OS_VXWORKS ) + #if ( DEBUG_FPRINTF_ENABLED ) + type = kDebugOutputTypeFPrintF; + #else + #error target is VxWorks, but fprintf output is disabled + #endif + #else + #if ( DEBUG_FPRINTF_ENABLED ) + type = kDebugOutputTypeFPrintF; + #endif + #endif + } + + // Process output kind. + + gDebugOutputType = type; + switch( type ) + { + case kDebugOutputTypeNone: + err = kNoErr; + break; + + case kDebugOutputTypeCustom: + gDebugCustomOutputFunction = va_arg( args, DebugOutputFunctionPtr ); + gDebugCustomOutputContext = va_arg( args, void * ); + err = kNoErr; + break; + +#if ( DEBUG_FPRINTF_ENABLED ) + case kDebugOutputTypeFPrintF: + if( inType == kDebugOutputTypeMetaConsole ) + { + err = DebugFPrintFInit( kDebugOutputTypeFlagsStdErr, NULL ); + } + else + { + DebugOutputTypeFlags flags; + const char * filename; + + flags = (DebugOutputTypeFlags) va_arg( args, unsigned int ); + if( ( flags & kDebugOutputTypeFlagsTypeMask ) == kDebugOutputTypeFlagsFile ) + { + filename = va_arg( args, const char * ); + } + else + { + filename = NULL; + } + err = DebugFPrintFInit( flags, filename ); + } + break; +#endif + +#if ( DEBUG_IDEBUG_ENABLED ) + case kDebugOutputTypeiDebug: + err = DebugiDebugInit(); + break; +#endif + +#if ( DEBUG_KPRINTF_ENABLED ) + case kDebugOutputTypeKPrintF: + err = kNoErr; + break; +#endif + +#if ( DEBUG_MAC_OS_X_IOLOG_ENABLED ) + case kDebugOutputTypeMacOSXIOLog: + err = kNoErr; + break; +#endif + +#if ( TARGET_OS_MAC ) + case kDebugOutputTypeMacOSXLog: + err = DebugMacOSXLogInit(); + break; +#endif + +#if ( TARGET_OS_WIN32 ) + case kDebugOutputTypeWindowsDebugger: + err = kNoErr; + break; +#endif + +#if ( TARGET_OS_WIN32 && !TARGET_OS_WINDOWS_CE ) + case kDebugOutputTypeWindowsEventLog: + { + const char * name; + HMODULE module; + + name = va_arg( args, const char * ); + module = va_arg( args, HMODULE ); + err = DebugWindowsEventLogInit( name, module ); + } + break; +#endif + + default: + err = kParamErr; + goto exit; + } + gDebugInitialized = true; + +exit: + va_end( args ); + return( err ); +} + +//=========================================================================================================================== +// DebugFinalize +//=========================================================================================================================== + +DEBUG_EXPORT void DebugFinalize( void ) +{ +#if ( DEBUG_CORE_SERVICE_ASSERTS_ENABLED ) + check( gDebugAssertOutputHandlerUPP ); + if( gDebugAssertOutputHandlerUPP ) + { + InstallDebugAssertOutputHandler( NULL ); + DisposeDebugAssertOutputHandlerUPP( gDebugAssertOutputHandlerUPP ); + gDebugAssertOutputHandlerUPP = NULL; + } +#endif +} + +//=========================================================================================================================== +// DebugGetProperty +//=========================================================================================================================== + +DEBUG_EXPORT OSStatus DebugGetProperty( DebugPropertyTag inTag, ... ) +{ + OSStatus err; + va_list args; + DebugLevel * level; + + va_start( args, inTag ); + switch( inTag ) + { + case kDebugPropertyTagPrintLevelMin: + level = va_arg( args, DebugLevel * ); + *level = gDebugPrintLevelMin; + err = kNoErr; + break; + + case kDebugPropertyTagPrintLevelMax: + level = va_arg( args, DebugLevel * ); + *level = gDebugPrintLevelMax; + err = kNoErr; + break; + + case kDebugPropertyTagBreakLevel: + level = va_arg( args, DebugLevel * ); + *level = gDebugBreakLevel; + err = kNoErr; + break; + + default: + err = kUnsupportedErr; + break; + } + va_end( args ); + return( err ); +} + +//=========================================================================================================================== +// DebugSetProperty +//=========================================================================================================================== + +DEBUG_EXPORT OSStatus DebugSetProperty( DebugPropertyTag inTag, ... ) +{ + OSStatus err; + va_list args; + DebugLevel level; + + va_start( args, inTag ); + switch( inTag ) + { + case kDebugPropertyTagPrintLevelMin: + level = va_arg( args, DebugLevel ); + gDebugPrintLevelMin = level; + err = kNoErr; + break; + + case kDebugPropertyTagPrintLevelMax: + level = va_arg( args, DebugLevel ); + gDebugPrintLevelMax = level; + err = kNoErr; + break; + + case kDebugPropertyTagBreakLevel: + level = va_arg( args, DebugLevel ); + gDebugBreakLevel = level; + err = kNoErr; + break; + + default: + err = kUnsupportedErr; + break; + } + va_end( args ); + return( err ); +} + +#if 0 +#pragma mark - +#pragma mark == Output == +#endif + +//=========================================================================================================================== +// DebugPrintF +//=========================================================================================================================== + +DEBUG_EXPORT size_t DebugPrintF( DebugLevel inLevel, const char *inFormat, ... ) +{ + va_list args; + size_t n; + + // Skip if the level is not in the enabled range.. + + if( ( inLevel < gDebugPrintLevelMin ) || ( inLevel > gDebugPrintLevelMax ) ) + { + n = 0; + goto exit; + } + + va_start( args, inFormat ); + n = DebugPrintFVAList( inLevel, inFormat, args ); + va_end( args ); + +exit: + return( n ); +} + +//=========================================================================================================================== +// DebugPrintFVAList +//=========================================================================================================================== + +DEBUG_EXPORT size_t DebugPrintFVAList( DebugLevel inLevel, const char *inFormat, va_list inArgs ) +{ + size_t n; + char buffer[ 512 ]; + + // Skip if the level is not in the enabled range.. + + if( ( inLevel < gDebugPrintLevelMin ) || ( inLevel > gDebugPrintLevelMax ) ) + { + n = 0; + goto exit; + } + + n = DebugSNPrintFVAList( buffer, sizeof( buffer ), inFormat, inArgs ); + DebugPrint( inLevel, buffer, (size_t) n ); + +exit: + return( n ); +} + +//=========================================================================================================================== +// DebugPrint +//=========================================================================================================================== + +static OSStatus DebugPrint( DebugLevel inLevel, char *inData, size_t inSize ) +{ + OSStatus err; + + // Skip if the level is not in the enabled range.. + + if( ( inLevel < gDebugPrintLevelMin ) || ( inLevel > gDebugPrintLevelMax ) ) + { + err = kRangeErr; + goto exit; + } + + // Printing is not safe at interrupt time so check for this and warn with an interrupt safe mechanism (if available). + + if( DebugTaskLevel() & kDebugInterruptLevelMask ) + { + #if ( TARGET_OS_VXWORKS ) + logMsg( "\ncannot print at interrupt time\n\n", 1, 2, 3, 4, 5, 6 ); + #endif + + err = kExecutionStateErr; + goto exit; + } + + // Initialize the debugging library if it hasn't already been initialized (allows for zero-config usage). + + if( !gDebugInitialized ) + { + debug_initialize( kDebugOutputTypeMetaConsole ); + } + + // Print based on the current output type. + + switch( gDebugOutputType ) + { + case kDebugOutputTypeNone: + break; + + case kDebugOutputTypeCustom: + if( gDebugCustomOutputFunction ) + { + gDebugCustomOutputFunction( inData, inSize, gDebugCustomOutputContext ); + } + break; + +#if ( DEBUG_FPRINTF_ENABLED ) + case kDebugOutputTypeFPrintF: + DebugFPrintFPrint( inData, inSize ); + break; +#endif + +#if ( DEBUG_IDEBUG_ENABLED ) + case kDebugOutputTypeiDebug: + DebugiDebugPrint( inData, inSize ); + break; +#endif + +#if ( DEBUG_KPRINTF_ENABLED ) + case kDebugOutputTypeKPrintF: + DebugKPrintFPrint( inData, inSize ); + break; +#endif + +#if ( DEBUG_MAC_OS_X_IOLOG_ENABLED ) + case kDebugOutputTypeMacOSXIOLog: + DebugMacOSXIOLogPrint( inData, inSize ); + break; +#endif + +#if ( TARGET_OS_MAC ) + case kDebugOutputTypeMacOSXLog: + DebugMacOSXLogPrint( inData, inSize ); + break; +#endif + +#if ( TARGET_OS_WIN32 ) + case kDebugOutputTypeWindowsDebugger: + DebugWindowsDebuggerPrint( inData, inSize ); + break; +#endif + +#if ( TARGET_OS_WIN32 && !TARGET_OS_WINDOWS_CE ) + case kDebugOutputTypeWindowsEventLog: + DebugWindowsEventLogPrint( inLevel, inData, inSize ); + break; +#endif + + default: + break; + } + err = kNoErr; + +exit: + return( err ); +} + +//=========================================================================================================================== +// DebugPrintAssert +// +// Warning: This routine relies on several of the strings being string constants that will exist forever because the +// underlying logMsg API that does the printing is asynchronous so it cannot use temporary/stack-based +// pointer variables (e.g. local strings). The debug macros that invoke this function only use constant +// constant strings, but if this function is invoked directly from other places, it must use constant strings. +//=========================================================================================================================== + +DEBUG_EXPORT void +DebugPrintAssert( + int_least32_t inErrorCode, + const char * inAssertString, + const char * inMessage, + const char * inFilename, + int_least32_t inLineNumber, + const char * inFunction ) +{ + // Skip if the level is not in the enabled range.. + + if( ( kDebugLevelAssert < gDebugPrintLevelMin ) || ( kDebugLevelAssert > gDebugPrintLevelMax ) ) + { + return; + } + + if( inErrorCode != 0 ) + { + DebugPrintF( + kDebugLevelAssert, + "\n" + "[ASSERT] error: %ld (%m)\n" + "[ASSERT] where: \"%s\", line %ld, \"%s\"\n" + "\n", + inErrorCode, inErrorCode, + inFilename ? inFilename : "", + inLineNumber, + inFunction ? inFunction : "" ); + } + else + { + DebugPrintF( + kDebugLevelAssert, + "\n" + "[ASSERT] assert: \"%s\" %s\n" + "[ASSERT] where: \"%s\", line %ld, \"%s\"\n" + "\n", + inAssertString ? inAssertString : "", + inMessage ? inMessage : "", + inFilename ? inFilename : "", + inLineNumber, + inFunction ? inFunction : "" ); + } + + // Break into the debugger if enabled. + + #if ( TARGET_OS_WIN32 ) + if( gDebugBreakLevel <= kDebugLevelAssert ) + { + if( IsDebuggerPresent() ) + { + DebugBreak(); + } + } + #endif +} + +#if 0 +#pragma mark - +#endif + +#if ( DEBUG_FPRINTF_ENABLED ) +//=========================================================================================================================== +// DebugFPrintFInit +//=========================================================================================================================== + +static OSStatus DebugFPrintFInit( DebugOutputTypeFlags inFlags, const char *inFilename ) +{ + OSStatus err; + DebugOutputTypeFlags typeFlags; + + typeFlags = inFlags & kDebugOutputTypeFlagsTypeMask; + if( typeFlags == kDebugOutputTypeFlagsStdOut ) + { + #if ( TARGET_OS_WIN32 ) + DebugWinEnableConsole(); + #endif + + gDebugFPrintFFile = stdout; + } + else if( typeFlags == kDebugOutputTypeFlagsStdErr ) + { + #if ( TARGET_OS_WIN32 ) + DebugWinEnableConsole(); + #endif + + gDebugFPrintFFile = stdout; + } + else if( typeFlags == kDebugOutputTypeFlagsFile ) + { + require_action_quiet( inFilename && ( *inFilename != '\0' ), exit, err = kOpenErr ); + + gDebugFPrintFFile = fopen( inFilename, "a" ); + require_action_quiet( gDebugFPrintFFile, exit, err = kOpenErr ); + } + else + { + err = kParamErr; + goto exit; + } + err = kNoErr; + +exit: + return( err ); +} + +//=========================================================================================================================== +// DebugFPrintFPrint +//=========================================================================================================================== + +static void DebugFPrintFPrint( char *inData, size_t inSize ) +{ + char * p; + char * q; + + // Convert \r to \n. fprintf will interpret \n and convert to whatever is appropriate for the platform. + + p = inData; + q = p + inSize; + while( p < q ) + { + if( *p == '\r' ) + { + *p = '\n'; + } + ++p; + } + + // Write the data and flush. + + if( gDebugFPrintFFile ) + { + fprintf( gDebugFPrintFFile, "%.*s", (int) inSize, inData ); + fflush( gDebugFPrintFFile ); + } +} +#endif // DEBUG_FPRINTF_ENABLED + +#if ( DEBUG_IDEBUG_ENABLED ) +//=========================================================================================================================== +// DebugiDebugInit +//=========================================================================================================================== + +static OSStatus DebugiDebugInit( void ) +{ + OSStatus err; + + #if ( TARGET_API_MAC_OSX_KERNEL ) + + extern uint32_t * _giDebugReserved1; + + // Emulate the iDebugSetOutputType macro in iDebugServices.h. + // Note: This is not thread safe, but neither is iDebugServices.h nor iDebugKext. + + if( !_giDebugReserved1 ) + { + _giDebugReserved1 = (uint32_t *) IOMalloc( sizeof( uint32_t ) ); + require_action_quiet( _giDebugReserved1, exit, err = kNoMemoryErr ); + } + *_giDebugReserved1 = 0x00010000U; + err = kNoErr; +exit: + #else + + __private_extern__ void iDebugSetOutputTypeInternal( uint32_t inType ); + + iDebugSetOutputTypeInternal( 0x00010000U ); + err = kNoErr; + + #endif + + return( err ); +} + +//=========================================================================================================================== +// DebugiDebugPrint +//=========================================================================================================================== + +static void DebugiDebugPrint( char *inData, size_t inSize ) +{ + #if ( TARGET_API_MAC_OSX_KERNEL ) + + // Locally declared here so we do not need to include iDebugKext.h. + // Note: IOKit uses a global namespace for all code and only a partial link occurs at build time. When the + // KEXT is loaded, the runtime linker will link in this extern'd symbol (assuming iDebug is present). + // _giDebugLogInternal is actually part of IOKit proper so this should link even if iDebug is not present. + + typedef void ( *iDebugLogFunctionPtr )( uint32_t inLevel, uint32_t inTag, const char *inFormat, ... ); + + extern iDebugLogFunctionPtr _giDebugLogInternal; + + if( _giDebugLogInternal ) + { + _giDebugLogInternal( 0, 0, "%.*s", (int) inSize, inData ); + } + + #else + + __private_extern__ void iDebugLogInternal( uint32_t inLevel, uint32_t inTag, const char *inFormat, ... ); + + iDebugLogInternal( 0, 0, "%.*s", (int) inSize, inData ); + + #endif +} +#endif + +#if ( DEBUG_KPRINTF_ENABLED ) +//=========================================================================================================================== +// DebugKPrintFPrint +//=========================================================================================================================== + +static void DebugKPrintFPrint( char *inData, size_t inSize ) +{ + extern void kprintf( const char *inFormat, ... ); + + kprintf( "%.*s", (int) inSize, inData ); +} +#endif + +#if ( DEBUG_MAC_OS_X_IOLOG_ENABLED ) +//=========================================================================================================================== +// DebugMacOSXIOLogPrint +//=========================================================================================================================== + +static void DebugMacOSXIOLogPrint( char *inData, size_t inSize ) +{ + extern void IOLog( const char *inFormat, ... ); + + IOLog( "%.*s", (int) inSize, inData ); +} +#endif + +#if ( TARGET_OS_MAC ) +//=========================================================================================================================== +// DebugMacOSXLogInit +//=========================================================================================================================== + +static OSStatus DebugMacOSXLogInit( void ) +{ + OSStatus err; + CFStringRef path; + CFURLRef url; + CFBundleRef bundle; + CFStringRef functionName; + void * functionPtr; + + bundle = NULL; + + // Create a bundle reference for System.framework. + + path = CFSTR( "/System/Library/Frameworks/System.framework" ); + url = CFURLCreateWithFileSystemPath( NULL, path, kCFURLPOSIXPathStyle, true ); + require_action_quiet( url, exit, err = memFullErr ); + + bundle = CFBundleCreate( NULL, url ); + CFRelease( url ); + require_action_quiet( bundle, exit, err = memFullErr ); + + // Get a ptr to the system's "printf" function from System.framework. + + functionName = CFSTR( "printf" ); + functionPtr = CFBundleGetFunctionPointerForName( bundle, functionName ); + require_action_quiet( functionPtr, exit, err = memFullErr ); + + // Success! Note: The bundle cannot be released because it would invalidate the function ptr. + + gDebugMacOSXLogFunction = (DebugMacOSXLogFunctionPtr) functionPtr; + bundle = NULL; + err = noErr; + +exit: + if( bundle ) + { + CFRelease( bundle ); + } + return( err ); +} + +//=========================================================================================================================== +// DebugMacOSXLogPrint +//=========================================================================================================================== + +static void DebugMacOSXLogPrint( char *inData, size_t inSize ) +{ + if( gDebugMacOSXLogFunction ) + { + gDebugMacOSXLogFunction( "%.*s", (int) inSize, inData ); + } +} +#endif + +#if ( TARGET_OS_WIN32 ) +//=========================================================================================================================== +// DebugWindowsDebuggerPrint +//=========================================================================================================================== + +void DebugWindowsDebuggerPrint( char *inData, size_t inSize ) +{ + TCHAR buffer[ 512 ]; + const char * src; + const char * end; + TCHAR * dst; + char c; + + // Copy locally and null terminate the string. This also converts from char to TCHAR in case we are + // building with UNICODE enabled since the input is always char. Also convert \r to \n in the process. + + src = inData; + if( inSize >= sizeof_array( buffer ) ) + { + inSize = sizeof_array( buffer ) - 1; + } + end = src + inSize; + dst = buffer; + while( src < end ) + { + c = *src++; + if( c == '\r' ) + { + c = '\n'; + } + *dst++ = (TCHAR) c; + } + *dst = 0; + + // Print out the string to the debugger. + + OutputDebugString( buffer ); +} +#endif + +#if ( TARGET_OS_WIN32 && !TARGET_OS_WINDOWS_CE ) +//=========================================================================================================================== +// DebugWindowsEventLogInit +//=========================================================================================================================== + +static OSStatus DebugWindowsEventLogInit( const char *inName, HMODULE inModule ) +{ + OSStatus err; + HKEY key; + TCHAR name[ 128 ]; + const char * src; + TCHAR path[ MAX_PATH ]; + size_t size; + DWORD typesSupported; + DWORD n; + + key = NULL; + + // Use a default name if needed then convert the name to TCHARs so it works on ANSI or Unicode builds. + + if( !inName || ( *inName == '\0' ) ) + { + inName = "DefaultApp"; + } + DebugWinCharToTCharString( inName, kSizeCString, name, sizeof( name ), NULL ); + + // Build the path string using the fixed registry path and app name. + + src = "SYSTEM\\CurrentControlSet\\Services\\EventLog\\Application\\"; + DebugWinCharToTCharString( src, kSizeCString, path, sizeof_array( path ), &size ); + DebugWinCharToTCharString( inName, kSizeCString, path + size, sizeof_array( path ) - size, NULL ); + + // Add/Open the source name as a sub-key under the Application key in the EventLog registry key. + + err = RegCreateKeyEx( HKEY_LOCAL_MACHINE, path, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &key, NULL ); + require_noerr_quiet( err, exit ); + + // Set the path in the EventMessageFile subkey. Add 1 to the TCHAR count to include the null terminator. + + n = GetModuleFileName( inModule, path, sizeof_array( path ) ); + err = translate_errno( n > 0, (OSStatus) GetLastError(), kParamErr ); + require_noerr_quiet( err, exit ); + n += 1; + n *= sizeof( TCHAR ); + + err = RegSetValueEx( key, TEXT( "EventMessageFile" ), 0, REG_EXPAND_SZ, (const LPBYTE) path, n ); + require_noerr_quiet( err, exit ); + + // Set the supported event types in the TypesSupported subkey. + + typesSupported = EVENTLOG_SUCCESS | EVENTLOG_ERROR_TYPE | EVENTLOG_WARNING_TYPE | EVENTLOG_INFORMATION_TYPE | + EVENTLOG_AUDIT_SUCCESS | EVENTLOG_AUDIT_FAILURE; + err = RegSetValueEx( key, TEXT( "TypesSupported" ), 0, REG_DWORD, (const LPBYTE) &typesSupported, sizeof( DWORD ) ); + require_noerr_quiet( err, exit ); + + // Set up the event source. + + gDebugWindowsEventLogEventSource = RegisterEventSource( NULL, name ); + err = translate_errno( gDebugWindowsEventLogEventSource, (OSStatus) GetLastError(), kParamErr ); + require_noerr_quiet( err, exit ); + +exit: + if( key ) + { + RegCloseKey( key ); + } + return( err ); +} + +//=========================================================================================================================== +// DebugWindowsEventLogPrint +//=========================================================================================================================== + +static void DebugWindowsEventLogPrint( DebugLevel inLevel, char *inData, size_t inSize ) +{ + WORD type; + TCHAR buffer[ 512 ]; + const char * src; + const char * end; + TCHAR * dst; + char c; + const TCHAR * array[ 1 ]; + + // Map the debug level to a Windows EventLog type. + + if( inLevel <= kDebugLevelNotice ) + { + type = EVENTLOG_INFORMATION_TYPE; + } + else if( inLevel <= kDebugLevelWarning ) + { + type = EVENTLOG_WARNING_TYPE; + } + else + { + type = EVENTLOG_ERROR_TYPE; + } + + // Copy locally and null terminate the string. This also converts from char to TCHAR in case we are + // building with UNICODE enabled since the input is always char. Also convert \r to \n in the process. + + src = inData; + if( inSize >= sizeof_array( buffer ) ) + { + inSize = sizeof_array( buffer ) - 1; + } + end = src + inSize; + dst = buffer; + while( src < end ) + { + c = *src++; + if( c == '\r' ) + { + c = '\n'; + } + *dst++ = (TCHAR) c; + } + *dst = 0; + + // Add the the string to the event log. + + array[ 0 ] = buffer; + if( gDebugWindowsEventLogEventSource ) + { + ReportEvent( gDebugWindowsEventLogEventSource, type, 0, 0x20000001L, NULL, 1, 0, array, NULL ); + } +} +#endif // TARGET_OS_WIN32 && !TARGET_OS_WINDOWS_CE + +#if ( DEBUG_CORE_SERVICE_ASSERTS_ENABLED ) +//=========================================================================================================================== +// DebugAssertOutputHandler +//=========================================================================================================================== + +static pascal void +DebugAssertOutputHandler( + OSType inComponentSignature, + UInt32 inOptions, + const char * inAssertString, + const char * inExceptionString, + const char * inErrorString, + const char * inFileName, + long inLineNumber, + void * inValue, + ConstStr255Param inOutputMsg ) +{ + DEBUG_UNUSED( inComponentSignature ); + DEBUG_UNUSED( inOptions ); + DEBUG_UNUSED( inExceptionString ); + DEBUG_UNUSED( inValue ); + DEBUG_UNUSED( inOutputMsg ); + + DebugPrintAssert( 0, inAssertString, inErrorString, inFileName, (int_least32_t) inLineNumber, "" ); +} +#endif + +#if 0 +#pragma mark - +#pragma mark == Utilities == +#endif + +//=========================================================================================================================== +// DebugSNPrintF +// +// Stolen from mDNS.c's mDNS_snprintf/mDNS_vsnprintf with the following changes: +// +// Changed names to avoid name collisions with the mDNS versions. +// Changed types to standard C types since mDNSEmbeddedAPI.h may not be available. +// Conditionalized mDNS stuff so it can be used with or with mDNSEmbeddedAPI.h. +// Added 64-bit support for %d (%lld), %i (%lli), %u (%llu), %o (%llo), %x (%llx), and %b (%llb). +// Added %@ - Cocoa/CoreFoundation object. Param is the object. Strings are used directly. Others use CFCopyDescription. +// Added %.8a - FIbre Channel address. Arg=ptr to address. +// Added %##a - IPv4 (if AF_INET defined) or IPv6 (if AF_INET6 defined) sockaddr. Arg=ptr to sockaddr. +// Added %b - Binary representation of integer (e.g. 01101011). Modifiers and arg=the same as %d, %x, etc. +// Added %C - Mac-style FourCharCode (e.g. 'APPL'). Arg=32-bit value to print as a Mac-style FourCharCode. +// Added %H - Hex Dump (e.g. "\x6b\xa7" -> "6B A7"). 1st arg=ptr, 2nd arg=size, 3rd arg=max size. +// Added %#H - Hex Dump & ASCII (e.g. "\x41\x62" -> "6B A7 'Ab'"). 1st arg=ptr, 2nd arg=size, 3rd arg=max size. +// Added %m - Error Message (e.g. 0 -> "kNoErr"). Modifiers and error code args are the same as %d, %x, etc. +// Added %S - UTF-16 string. Host order if no BOM. Precision is UTF-16 char count. BOM counts in any precision. Arg=ptr. +// Added %#S - Big Endian UTF-16 string (unless BOM overrides). Otherwise the same as %S. +// Added %##S - Little Endian UTF-16 string (unless BOM overrides). Otherwise the same as %S. +// Added %U - Universally Unique Identifier (UUID) (e.g. 6ba7b810-9dad-11d1-80b4-00c04fd430c8). Arg=ptr to 16-byte UUID. +//=========================================================================================================================== + +DEBUG_EXPORT size_t DebugSNPrintF(char *sbuffer, size_t buflen, const char *fmt, ...) +{ + size_t length; + + va_list ptr; + va_start(ptr,fmt); + length = DebugSNPrintFVAList(sbuffer, buflen, fmt, ptr); + va_end(ptr); + + return(length); +} + +//=========================================================================================================================== +// DebugSNPrintFVAList - va_list version of DebugSNPrintF. See DebugSNPrintF for more info. +//=========================================================================================================================== + +DEBUG_EXPORT size_t DebugSNPrintFVAList(char *sbuffer, size_t buflen, const char *fmt, va_list arg) +{ + static const struct DebugSNPrintF_format + { + unsigned leftJustify : 1; + unsigned forceSign : 1; + unsigned zeroPad : 1; + unsigned havePrecision : 1; + unsigned hSize : 1; + char lSize; + char altForm; + char sign; // +, - or space + unsigned int fieldWidth; + unsigned int precision; + } DebugSNPrintF_format_default = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + + size_t nwritten = 0; + int c; + if (buflen == 0) return(0); + buflen--; // Pre-reserve one space in the buffer for the terminating nul + if (buflen == 0) goto exit; + + for (c = *fmt; c != 0; c = *++fmt) + { + if (c != '%') + { + *sbuffer++ = (char)c; + if (++nwritten >= buflen) goto exit; + } + else + { + size_t i=0, j; + // The mDNS Vsprintf Argument Conversion Buffer is used as a temporary holding area for + // generating decimal numbers, hexdecimal numbers, IP addresses, domain name strings, etc. + // The size needs to be enough for a 256-byte domain name plus some error text. + #define mDNS_VACB_Size 300 + char mDNS_VACB[mDNS_VACB_Size]; + #define mDNS_VACB_Lim (&mDNS_VACB[mDNS_VACB_Size]) + #define mDNS_VACB_Remain(s) ((size_t)(mDNS_VACB_Lim - s)) + char *s = mDNS_VACB_Lim; + const char *digits = "0123456789ABCDEF"; + struct DebugSNPrintF_format F = DebugSNPrintF_format_default; + + for(;;) // decode flags + { + c = *++fmt; + if (c == '-') F.leftJustify = 1; + else if (c == '+') F.forceSign = 1; + else if (c == ' ') F.sign = ' '; + else if (c == '#') F.altForm++; + else if (c == '0') F.zeroPad = 1; + else break; + } + + if (c == '*') // decode field width + { + int f = va_arg(arg, int); + if (f < 0) { f = -f; F.leftJustify = 1; } + F.fieldWidth = (unsigned int)f; + c = *++fmt; + } + else + { + for (; c >= '0' && c <= '9'; c = *++fmt) + F.fieldWidth = (10 * F.fieldWidth) + (c - '0'); + } + + if (c == '.') // decode precision + { + if ((c = *++fmt) == '*') + { F.precision = va_arg(arg, unsigned int); c = *++fmt; } + else for (; c >= '0' && c <= '9'; c = *++fmt) + F.precision = (10 * F.precision) + (c - '0'); + F.havePrecision = 1; + } + + if (F.leftJustify) F.zeroPad = 0; + +conv: + switch (c) // perform appropriate conversion + { + #if TYPE_LONGLONG_NATIVE + unsigned_long_long_compat n; + unsigned_long_long_compat base; + #else + unsigned long n; + unsigned long base; + #endif + case 'h': F.hSize = 1; c = *++fmt; goto conv; + case 'l': // fall through + case 'L': F.lSize++; c = *++fmt; goto conv; + case 'd': + case 'i': base = 10; + goto canBeSigned; + case 'u': base = 10; + goto notSigned; + case 'o': base = 8; + goto notSigned; + case 'b': base = 2; + goto notSigned; + case 'p': n = va_arg(arg, uintptr_t); + F.havePrecision = 1; + F.precision = (sizeof(uintptr_t) == 4) ? 8 : 16; + F.sign = 0; + base = 16; + c = 'x'; + goto number; + case 'x': digits = "0123456789abcdef"; + case 'X': base = 16; + goto notSigned; +canBeSigned: + #if TYPE_LONGLONG_NATIVE + if (F.lSize == 1) n = (unsigned_long_long_compat)va_arg(arg, long); + else if (F.lSize == 2) n = (unsigned_long_long_compat)va_arg(arg, long_long_compat); + else n = (unsigned_long_long_compat)va_arg(arg, int); + #else + if (F.lSize == 1) n = (unsigned long)va_arg(arg, long); + else if (F.lSize == 2) goto exit; + else n = (unsigned long)va_arg(arg, int); + #endif + if (F.hSize) n = (short) n; + #if TYPE_LONGLONG_NATIVE + if ((long_long_compat) n < 0) { n = (unsigned_long_long_compat)-(long_long_compat)n; F.sign = '-'; } + #else + if ((long) n < 0) { n = (unsigned long)-(long)n; F.sign = '-'; } + #endif + else if (F.forceSign) F.sign = '+'; + goto number; + +notSigned: if (F.lSize == 1) n = va_arg(arg, unsigned long); + else if (F.lSize == 2) + { + #if TYPE_LONGLONG_NATIVE + n = va_arg(arg, unsigned_long_long_compat); + #else + goto exit; + #endif + } + else n = va_arg(arg, unsigned int); + if (F.hSize) n = (unsigned short) n; + F.sign = 0; + goto number; + +number: if (!F.havePrecision) + { + if (F.zeroPad) + { + F.precision = F.fieldWidth; + if (F.altForm) F.precision -= 2; + if (F.sign) --F.precision; + } + if (F.precision < 1) F.precision = 1; + } + if (F.precision > mDNS_VACB_Size - 1) + F.precision = mDNS_VACB_Size - 1; + for (i = 0; n; n /= base, i++) *--s = (char)(digits[n % base]); + for (; i < F.precision; i++) *--s = '0'; + if (F.altForm) { *--s = (char)c; *--s = '0'; i += 2; } + if (F.sign) { *--s = F.sign; i++; } + break; + + case 'a': { + unsigned char *a = va_arg(arg, unsigned char *); + char pre[4] = ""; + char post[32] = ""; + if (!a) { static char emsg[] = "<<NULL>>"; s = emsg; i = sizeof(emsg)-1; } + else + { + s = mDNS_VACB; // Adjust s to point to the start of the buffer, not the end + if (F.altForm == 1) + { + #if (defined(MDNS_DEBUGMSGS)) + mDNSAddr *ip = (mDNSAddr*)a; + switch (ip->type) + { + case mDNSAddrType_IPv4: F.precision = 4; a = (unsigned char *)&ip->ip.v4; break; + case mDNSAddrType_IPv6: F.precision = 16; a = (unsigned char *)&ip->ip.v6; break; + default: F.precision = 0; break; + } + #else + F.precision = 0; // mDNSEmbeddedAPI.h not included so no mDNSAddr support + #endif + } + else if (F.altForm == 2) + { + #ifdef AF_INET + const struct sockaddr *sa; + unsigned char *port; + sa = (const struct sockaddr*)a; + switch (sa->sa_family) + { + case AF_INET: F.precision = 4; a = (unsigned char*)&((const struct sockaddr_in *)a)->sin_addr; + port = (unsigned char*)&((const struct sockaddr_in *)sa)->sin_port; + DebugSNPrintF(post, sizeof(post), ":%d", (port[0] << 8) | port[1]); break; + #ifdef AF_INET6 + case AF_INET6: F.precision = 16; a = (unsigned char*)&((const struct sockaddr_in6 *)a)->sin6_addr; + pre[0] = '['; pre[1] = '\0'; + port = (unsigned char*)&((const struct sockaddr_in6 *)sa)->sin6_port; + DebugSNPrintF(post, sizeof(post), "%%%d]:%d", + (int)((const struct sockaddr_in6 *)sa)->sin6_scope_id, + (port[0] << 8) | port[1]); break; + #endif + default: F.precision = 0; break; + } + #else + F.precision = 0; // socket interfaces not included so no sockaddr support + #endif + } + switch (F.precision) + { + case 4: i = DebugSNPrintF(mDNS_VACB, sizeof(mDNS_VACB), "%d.%d.%d.%d%s", + a[0], a[1], a[2], a[3], post); break; + case 6: i = DebugSNPrintF(mDNS_VACB, sizeof(mDNS_VACB), "%02X:%02X:%02X:%02X:%02X:%02X", + a[0], a[1], a[2], a[3], a[4], a[5]); break; + case 8: i = DebugSNPrintF(mDNS_VACB, sizeof(mDNS_VACB), "%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X", + a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7]); break; + case 16: i = DebugSNPrintF(mDNS_VACB, sizeof(mDNS_VACB), + "%s%02X%02X:%02X%02X:%02X%02X:%02X%02X:%02X%02X:%02X%02X:%02X%02X:%02X%02X%s", + pre, a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], + a[9], a[10], a[11], a[12], a[13], a[14], a[15], post); break; + default: i = DebugSNPrintF(mDNS_VACB, sizeof(mDNS_VACB), "%s", "<< ERROR: Must specify address size " + "(i.e. %.4a=IPv4, %.6a=Ethernet, %.8a=Fibre Channel %.16a=IPv6) >>"); break; + } + } + } + break; + + case 'U': { + unsigned char *a = va_arg(arg, unsigned char *); + if (!a) { static char emsg[] = "<<NULL>>"; s = emsg; i = sizeof(emsg)-1; } + else + { + s = mDNS_VACB; // Adjust s to point to the start of the buffer, not the end + i = DebugSNPrintF(mDNS_VACB, sizeof(mDNS_VACB), "%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x", + *((uint32_t*) &a[0]), *((uint16_t*) &a[4]), *((uint16_t*) &a[6]), + a[8], a[9], a[10], a[11], a[12], a[13], a[14], a[15]); break; + } + } + break; + + case 'c': *--s = (char)va_arg(arg, int); i = 1; break; + + case 'C': if (F.lSize) n = va_arg(arg, unsigned long); + else n = va_arg(arg, unsigned int); + if (F.hSize) n = (unsigned short) n; + c = (int)( n & 0xFF); *--s = (char)(DebugIsPrint(c) ? c : '^'); + c = (int)((n >> 8) & 0xFF); *--s = (char)(DebugIsPrint(c) ? c : '^'); + c = (int)((n >> 16) & 0xFF); *--s = (char)(DebugIsPrint(c) ? c : '^'); + c = (int)((n >> 24) & 0xFF); *--s = (char)(DebugIsPrint(c) ? c : '^'); + i = 4; + break; + + case 's': s = va_arg(arg, char *); + if (!s) { static char emsg[] = "<<NULL>>"; s = emsg; i = sizeof(emsg)-1; } + else switch (F.altForm) + { + case 0: i=0; + if (F.havePrecision) // C string + { + while((i < F.precision) && s[i]) i++; + // Make sure we don't truncate in the middle of a UTF-8 character. + // If the last character is part of a multi-byte UTF-8 character, back up to the start of it. + j=0; + while((i > 0) && ((c = s[i-1]) & 0x80)) { j++; i--; if((c & 0xC0) != 0x80) break;} + // If the actual count of UTF-8 characters matches the encoded UTF-8 count, add it back. + if((j > 1) && (j <= 6)) + { + int test = (0xFF << (8-j)) & 0xFF; + int mask = test | (1 << ((8-j)-1)); + if((c & mask) == test) i += j; + } + } + else + while(s[i]) i++; + break; + case 1: i = (unsigned char) *s++; break; // Pascal string + case 2: { // DNS label-sequence name + unsigned char *a = (unsigned char *)s; + s = mDNS_VACB; // Adjust s to point to the start of the buffer, not the end + if (*a == 0) *s++ = '.'; // Special case for root DNS name + while (*a) + { + if (*a > 63) { s += DebugSNPrintF(s, mDNS_VACB_Remain(s), "<<INVALID LABEL LENGTH %u>>", *a); break; } + if (s + *a >= &mDNS_VACB[254]) { s += DebugSNPrintF(s, mDNS_VACB_Remain(s), "<<NAME TOO LONG>>"); break; } + s += DebugSNPrintF(s, mDNS_VACB_Remain(s), "%#s.", a); + a += 1 + *a; + } + i = (size_t)(s - mDNS_VACB); + s = mDNS_VACB; // Reset s back to the start of the buffer + break; + } + } + if (F.havePrecision && i > F.precision) // Make sure we don't truncate in the middle of a UTF-8 character + { i = F.precision; while (i>0 && (s[i] & 0xC0) == 0x80) i--;} + break; + + case 'S': { // UTF-16 string + unsigned char *a = va_arg(arg, unsigned char *); + uint16_t *u = (uint16_t*)a; + if (!u) { static char emsg[] = "<<NULL>>"; s = emsg; i = sizeof(emsg)-1; } + if ((!F.havePrecision || F.precision)) + { + if ((a[0] == 0xFE) && (a[1] == 0xFF)) { F.altForm = 1; u += 1; a += 2; F.precision--; } // Big Endian + else if ((a[0] == 0xFF) && (a[1] == 0xFE)) { F.altForm = 2; u += 1; a += 2; F.precision--; } // Little Endian + } + s = mDNS_VACB; // Adjust s to point to the start of the buffer, not the end + switch (F.altForm) + { + case 0: while ((!F.havePrecision || (i < F.precision)) && u[i] && mDNS_VACB_Remain(s)) // Host Endian + { c = u[i]; *s++ = (char)(DebugIsPrint(c) ? c : '^'); i++; } + break; + case 1: while ((!F.havePrecision || (i < F.precision)) && u[i] && mDNS_VACB_Remain(s)) // Big Endian + { c = ((a[0] << 8) | a[1]) & 0xFF; *s++ = (char)(DebugIsPrint(c) ? c : '^'); i++; a += 2; } + break; + case 2: while ((!F.havePrecision || (i < F.precision)) && u[i] && mDNS_VACB_Remain(s)) // Little Endian + { c = ((a[1] << 8) | a[0]) & 0xFF; *s++ = (char)(DebugIsPrint(c) ? c : '^'); i++; a += 2; } + break; + } + } + s = mDNS_VACB; // Reset s back to the start of the buffer + break; + + #if TARGET_OS_MAC + case '@': { // Cocoa/CoreFoundation object + CFTypeRef cfObj; + CFStringRef cfStr; + cfObj = (CFTypeRef) va_arg(arg, void *); + cfStr = (CFGetTypeID(cfObj) == CFStringGetTypeID()) ? (CFStringRef)CFRetain(cfObj) : CFCopyDescription(cfObj); + s = mDNS_VACB; // Adjust s to point to the start of the buffer, not the end + if (cfStr) + { + CFRange range; + CFIndex m; + range = CFRangeMake(0, CFStringGetLength(cfStr)); + m = 0; + CFStringGetBytes(cfStr, range, kCFStringEncodingUTF8, '^', false, (UInt8*)mDNS_VACB, (CFIndex)sizeof(mDNS_VACB), &m); + CFRelease(cfStr); + i = (size_t) m; + } + else + { + i = DebugSNPrintF(mDNS_VACB, sizeof(mDNS_VACB), "%s", "ERROR: <invalid CF object>" ); + } + } + if (F.havePrecision && i > F.precision) // Make sure we don't truncate in the middle of a UTF-8 character + { i = F.precision; while (i>0 && (s[i] & 0xC0) == 0x80) i--;} + break; + #endif + + case 'm': { // Error Message + long err; + if (F.lSize) err = va_arg(arg, long); + else err = va_arg(arg, int); + if (F.hSize) err = (short)err; + DebugGetErrorString(err, mDNS_VACB, sizeof(mDNS_VACB)); + s = mDNS_VACB; // Adjust s to point to the start of the buffer, not the end + for(i=0; s[i]; i++) {} + } + break; + + case 'H': { // Hex Dump + void *a = va_arg(arg, void *); + size_t size = (size_t)va_arg(arg, int); + size_t max = (size_t)va_arg(arg, int); + DebugFlags flags = + kDebugFlagsNoAddress | kDebugFlagsNoOffset | kDebugFlagsNoNewLine | + kDebugFlags8BitSeparator | kDebugFlagsNo32BitSeparator | + kDebugFlagsNo16ByteHexPad | kDebugFlagsNoByteCount; + if (F.altForm == 0) flags |= kDebugFlagsNoASCII; + size = (max < size) ? max : size; + s = mDNS_VACB; // Adjust s to point to the start of the buffer, not the end + i = DebugHexDump(kDebugLevelMax, 0, NULL, 0, 0, NULL, 0, a, a, size, flags, mDNS_VACB, sizeof(mDNS_VACB)); + } + break; + + case 'v': { // Version + uint32_t version; + version = va_arg(arg, unsigned int); + DebugNumVersionToString(version, mDNS_VACB); + s = mDNS_VACB; // Adjust s to point to the start of the buffer, not the end + for(i=0; s[i]; i++) {} + } + break; + + case 'n': s = va_arg(arg, char *); + if (F.hSize) *(short *) s = (short)nwritten; + else if (F.lSize) *(long *) s = (long)nwritten; + else *(int *) s = (int)nwritten; + continue; + + default: s = mDNS_VACB; + i = DebugSNPrintF(mDNS_VACB, sizeof(mDNS_VACB), "<<UNKNOWN FORMAT CONVERSION CODE %%%c>>", c); + + case '%': *sbuffer++ = (char)c; + if (++nwritten >= buflen) goto exit; + break; + } + + if (i < F.fieldWidth && !F.leftJustify) // Pad on the left + do { + *sbuffer++ = ' '; + if (++nwritten >= buflen) goto exit; + } while (i < --F.fieldWidth); + + if (i > buflen - nwritten) // Make sure we don't truncate in the middle of a UTF-8 character + { i = buflen - nwritten; while (i>0 && (s[i] & 0xC0) == 0x80) i--;} + for (j=0; j<i; j++) *sbuffer++ = *s++; // Write the converted result + nwritten += i; + if (nwritten >= buflen) goto exit; + + for (; i < F.fieldWidth; i++) // Pad on the right + { + *sbuffer++ = ' '; + if (++nwritten >= buflen) goto exit; + } + } + } +exit: + *sbuffer++ = 0; + return(nwritten); +} + +//=========================================================================================================================== +// DebugGetErrorString +//=========================================================================================================================== + +DEBUG_EXPORT const char * DebugGetErrorString( int_least32_t inErrorCode, char *inBuffer, size_t inBufferSize ) +{ + const char * s; + char * dst; + char * end; +#if ( TARGET_OS_WIN32 && !TARGET_OS_WINDOWS_CE ) + char buffer[ 256 ]; +#endif + + switch( inErrorCode ) + { + #define CaseErrorString( X, STR ) case X: s = STR; break + #define CaseErrorStringify( X ) case X: s = # X; break + #define CaseErrorStringifyHardCode( VALUE, X ) case VALUE: s = # X; break + + // General Errors + + CaseErrorString( 0, "no error" ); + CaseErrorString( 1, "in-progress/waiting" ); + CaseErrorString( -1, "catch-all unknown error" ); + + // ACP Errors + + CaseErrorStringifyHardCode( -2, kACPBadRequestErr ); + CaseErrorStringifyHardCode( -3, kACPNoMemoryErr ); + CaseErrorStringifyHardCode( -4, kACPBadParamErr ); + CaseErrorStringifyHardCode( -5, kACPNotFoundErr ); + CaseErrorStringifyHardCode( -6, kACPBadChecksumErr ); + CaseErrorStringifyHardCode( -7, kACPCommandNotHandledErr ); + CaseErrorStringifyHardCode( -8, kACPNetworkErr ); + CaseErrorStringifyHardCode( -9, kACPDuplicateCommandHandlerErr ); + CaseErrorStringifyHardCode( -10, kACPUnknownPropertyErr ); + CaseErrorStringifyHardCode( -11, kACPImmutablePropertyErr ); + CaseErrorStringifyHardCode( -12, kACPBadPropertyValueErr ); + CaseErrorStringifyHardCode( -13, kACPNoResourcesErr ); + CaseErrorStringifyHardCode( -14, kACPBadOptionErr ); + CaseErrorStringifyHardCode( -15, kACPBadSizeErr ); + CaseErrorStringifyHardCode( -16, kACPBadPasswordErr ); + CaseErrorStringifyHardCode( -17, kACPNotInitializedErr ); + CaseErrorStringifyHardCode( -18, kACPNonReadablePropertyErr ); + CaseErrorStringifyHardCode( -19, kACPBadVersionErr ); + CaseErrorStringifyHardCode( -20, kACPBadSignatureErr ); + CaseErrorStringifyHardCode( -21, kACPBadIndexErr ); + CaseErrorStringifyHardCode( -22, kACPUnsupportedErr ); + CaseErrorStringifyHardCode( -23, kACPInUseErr ); + CaseErrorStringifyHardCode( -24, kACPParamCountErr ); + CaseErrorStringifyHardCode( -25, kACPIDErr ); + CaseErrorStringifyHardCode( -26, kACPFormatErr ); + CaseErrorStringifyHardCode( -27, kACPUnknownUserErr ); + CaseErrorStringifyHardCode( -28, kACPAccessDeniedErr ); + CaseErrorStringifyHardCode( -29, kACPIncorrectFWErr ); + + // Common Services Errors + + CaseErrorStringify( kUnknownErr ); + CaseErrorStringify( kOptionErr ); + CaseErrorStringify( kSelectorErr ); + CaseErrorStringify( kExecutionStateErr ); + CaseErrorStringify( kPathErr ); + CaseErrorStringify( kParamErr ); + CaseErrorStringify( kParamCountErr ); + CaseErrorStringify( kCommandErr ); + CaseErrorStringify( kIDErr ); + CaseErrorStringify( kStateErr ); + CaseErrorStringify( kRangeErr ); + CaseErrorStringify( kRequestErr ); + CaseErrorStringify( kResponseErr ); + CaseErrorStringify( kChecksumErr ); + CaseErrorStringify( kNotHandledErr ); + CaseErrorStringify( kVersionErr ); + CaseErrorStringify( kSignatureErr ); + CaseErrorStringify( kFormatErr ); + CaseErrorStringify( kNotInitializedErr ); + CaseErrorStringify( kAlreadyInitializedErr ); + CaseErrorStringify( kNotInUseErr ); + CaseErrorStringify( kInUseErr ); + CaseErrorStringify( kTimeoutErr ); + CaseErrorStringify( kCanceledErr ); + CaseErrorStringify( kAlreadyCanceledErr ); + CaseErrorStringify( kCannotCancelErr ); + CaseErrorStringify( kDeletedErr ); + CaseErrorStringify( kNotFoundErr ); + CaseErrorStringify( kNoMemoryErr ); + CaseErrorStringify( kNoResourcesErr ); + CaseErrorStringify( kDuplicateErr ); + CaseErrorStringify( kImmutableErr ); + CaseErrorStringify( kUnsupportedDataErr ); + CaseErrorStringify( kIntegrityErr ); + CaseErrorStringify( kIncompatibleErr ); + CaseErrorStringify( kUnsupportedErr ); + CaseErrorStringify( kUnexpectedErr ); + CaseErrorStringify( kValueErr ); + CaseErrorStringify( kNotReadableErr ); + CaseErrorStringify( kNotWritableErr ); + CaseErrorStringify( kBadReferenceErr ); + CaseErrorStringify( kFlagErr ); + CaseErrorStringify( kMalformedErr ); + CaseErrorStringify( kSizeErr ); + CaseErrorStringify( kNameErr ); + CaseErrorStringify( kNotReadyErr ); + CaseErrorStringify( kReadErr ); + CaseErrorStringify( kWriteErr ); + CaseErrorStringify( kMismatchErr ); + CaseErrorStringify( kDateErr ); + CaseErrorStringify( kUnderrunErr ); + CaseErrorStringify( kOverrunErr ); + CaseErrorStringify( kEndingErr ); + CaseErrorStringify( kConnectionErr ); + CaseErrorStringify( kAuthenticationErr ); + CaseErrorStringify( kOpenErr ); + CaseErrorStringify( kTypeErr ); + CaseErrorStringify( kSkipErr ); + CaseErrorStringify( kNoAckErr ); + CaseErrorStringify( kCollisionErr ); + CaseErrorStringify( kBackoffErr ); + CaseErrorStringify( kNoAddressAckErr ); + CaseErrorStringify( kBusyErr ); + CaseErrorStringify( kNoSpaceErr ); + + // mDNS/DNS-SD Errors + + CaseErrorStringifyHardCode( -65537, mStatus_UnknownErr ); + CaseErrorStringifyHardCode( -65538, mStatus_NoSuchNameErr ); + CaseErrorStringifyHardCode( -65539, mStatus_NoMemoryErr ); + CaseErrorStringifyHardCode( -65540, mStatus_BadParamErr ); + CaseErrorStringifyHardCode( -65541, mStatus_BadReferenceErr ); + CaseErrorStringifyHardCode( -65542, mStatus_BadStateErr ); + CaseErrorStringifyHardCode( -65543, mStatus_BadFlagsErr ); + CaseErrorStringifyHardCode( -65544, mStatus_UnsupportedErr ); + CaseErrorStringifyHardCode( -65545, mStatus_NotInitializedErr ); + CaseErrorStringifyHardCode( -65546, mStatus_NoCache ); + CaseErrorStringifyHardCode( -65547, mStatus_AlreadyRegistered ); + CaseErrorStringifyHardCode( -65548, mStatus_NameConflict ); + CaseErrorStringifyHardCode( -65549, mStatus_Invalid ); + CaseErrorStringifyHardCode( -65550, mStatus_GrowCache ); + CaseErrorStringifyHardCode( -65551, mStatus_BadInterfaceErr ); + CaseErrorStringifyHardCode( -65552, mStatus_Incompatible ); + CaseErrorStringifyHardCode( -65791, mStatus_ConfigChanged ); + CaseErrorStringifyHardCode( -65792, mStatus_MemFree ); + + // RSP Errors + + CaseErrorStringifyHardCode( -400000, kRSPUnknownErr ); + CaseErrorStringifyHardCode( -400050, kRSPParamErr ); + CaseErrorStringifyHardCode( -400108, kRSPNoMemoryErr ); + CaseErrorStringifyHardCode( -405246, kRSPRangeErr ); + CaseErrorStringifyHardCode( -409057, kRSPSizeErr ); + CaseErrorStringifyHardCode( -400200, kRSPHardwareErr ); + CaseErrorStringifyHardCode( -401712, kRSPTimeoutErr ); + CaseErrorStringifyHardCode( -402053, kRSPUnsupportedErr ); + CaseErrorStringifyHardCode( -402419, kRSPIDErr ); + CaseErrorStringifyHardCode( -403165, kRSPFlagErr ); + CaseErrorString( -200000, "kRSPControllerStatusBase - 0x50" ); + CaseErrorString( -200080, "kRSPCommandSucceededErr - 0x50" ); + CaseErrorString( -200001, "kRSPCommandFailedErr - 0x01" ); + CaseErrorString( -200051, "kRSPChecksumErr - 0x33" ); + CaseErrorString( -200132, "kRSPCommandTimeoutErr - 0x84" ); + CaseErrorString( -200034, "kRSPPasswordRequiredErr - 0x22 OBSOLETE" ); + CaseErrorString( -200128, "kRSPCanceledErr - 0x02 Async" ); + + // XML Errors + + CaseErrorStringifyHardCode( -100043, kXMLNotFoundErr ); + CaseErrorStringifyHardCode( -100050, kXMLParamErr ); + CaseErrorStringifyHardCode( -100108, kXMLNoMemoryErr ); + CaseErrorStringifyHardCode( -100206, kXMLFormatErr ); + CaseErrorStringifyHardCode( -100586, kXMLNoRootElementErr ); + CaseErrorStringifyHardCode( -101703, kXMLWrongDataTypeErr ); + CaseErrorStringifyHardCode( -101726, kXMLKeyErr ); + CaseErrorStringifyHardCode( -102053, kXMLUnsupportedErr ); + CaseErrorStringifyHardCode( -102063, kXMLMissingElementErr ); + CaseErrorStringifyHardCode( -103026, kXMLParseErr ); + CaseErrorStringifyHardCode( -103159, kXMLBadDataErr ); + CaseErrorStringifyHardCode( -103170, kXMLBadNameErr ); + CaseErrorStringifyHardCode( -105246, kXMLRangeErr ); + CaseErrorStringifyHardCode( -105251, kXMLUnknownElementErr ); + CaseErrorStringifyHardCode( -108739, kXMLMalformedInputErr ); + CaseErrorStringifyHardCode( -109057, kXMLBadSizeErr ); + CaseErrorStringifyHardCode( -101730, kXMLMissingChildElementErr ); + CaseErrorStringifyHardCode( -102107, kXMLMissingParentElementErr ); + CaseErrorStringifyHardCode( -130587, kXMLNonRootElementErr ); + CaseErrorStringifyHardCode( -102015, kXMLDateErr ); + + #if ( __MACH__ ) + + // Mach Errors + + CaseErrorStringifyHardCode( 0x00002000, MACH_MSG_IPC_SPACE ); + CaseErrorStringifyHardCode( 0x00001000, MACH_MSG_VM_SPACE ); + CaseErrorStringifyHardCode( 0x00000800, MACH_MSG_IPC_KERNEL ); + CaseErrorStringifyHardCode( 0x00000400, MACH_MSG_VM_KERNEL ); + CaseErrorStringifyHardCode( 0x10000001, MACH_SEND_IN_PROGRESS ); + CaseErrorStringifyHardCode( 0x10000002, MACH_SEND_INVALID_DATA ); + CaseErrorStringifyHardCode( 0x10000003, MACH_SEND_INVALID_DEST ); + CaseErrorStringifyHardCode( 0x10000004, MACH_SEND_TIMED_OUT ); + CaseErrorStringifyHardCode( 0x10000007, MACH_SEND_INTERRUPTED ); + CaseErrorStringifyHardCode( 0x10000008, MACH_SEND_MSG_TOO_SMALL ); + CaseErrorStringifyHardCode( 0x10000009, MACH_SEND_INVALID_REPLY ); + CaseErrorStringifyHardCode( 0x1000000A, MACH_SEND_INVALID_RIGHT ); + CaseErrorStringifyHardCode( 0x1000000B, MACH_SEND_INVALID_NOTIFY ); + CaseErrorStringifyHardCode( 0x1000000C, MACH_SEND_INVALID_MEMORY ); + CaseErrorStringifyHardCode( 0x1000000D, MACH_SEND_NO_BUFFER ); + CaseErrorStringifyHardCode( 0x1000000E, MACH_SEND_TOO_LARGE ); + CaseErrorStringifyHardCode( 0x1000000F, MACH_SEND_INVALID_TYPE ); + CaseErrorStringifyHardCode( 0x10000010, MACH_SEND_INVALID_HEADER ); + CaseErrorStringifyHardCode( 0x10000011, MACH_SEND_INVALID_TRAILER ); + CaseErrorStringifyHardCode( 0x10000015, MACH_SEND_INVALID_RT_OOL_SIZE ); + CaseErrorStringifyHardCode( 0x10004001, MACH_RCV_IN_PROGRESS ); + CaseErrorStringifyHardCode( 0x10004002, MACH_RCV_INVALID_NAME ); + CaseErrorStringifyHardCode( 0x10004003, MACH_RCV_TIMED_OUT ); + CaseErrorStringifyHardCode( 0x10004004, MACH_RCV_TOO_LARGE ); + CaseErrorStringifyHardCode( 0x10004005, MACH_RCV_INTERRUPTED ); + CaseErrorStringifyHardCode( 0x10004006, MACH_RCV_PORT_CHANGED ); + CaseErrorStringifyHardCode( 0x10004007, MACH_RCV_INVALID_NOTIFY ); + CaseErrorStringifyHardCode( 0x10004008, MACH_RCV_INVALID_DATA ); + CaseErrorStringifyHardCode( 0x10004009, MACH_RCV_PORT_DIED ); + CaseErrorStringifyHardCode( 0x1000400A, MACH_RCV_IN_SET ); + CaseErrorStringifyHardCode( 0x1000400B, MACH_RCV_HEADER_ERROR ); + CaseErrorStringifyHardCode( 0x1000400C, MACH_RCV_BODY_ERROR ); + CaseErrorStringifyHardCode( 0x1000400D, MACH_RCV_INVALID_TYPE ); + CaseErrorStringifyHardCode( 0x1000400E, MACH_RCV_SCATTER_SMALL ); + CaseErrorStringifyHardCode( 0x1000400F, MACH_RCV_INVALID_TRAILER ); + CaseErrorStringifyHardCode( 0x10004011, MACH_RCV_IN_PROGRESS_TIMED ); + + // Mach OSReturn Errors + + CaseErrorStringifyHardCode( 0xDC000001, kOSReturnError ); + CaseErrorStringifyHardCode( 0xDC004001, kOSMetaClassInternal ); + CaseErrorStringifyHardCode( 0xDC004002, kOSMetaClassHasInstances ); + CaseErrorStringifyHardCode( 0xDC004003, kOSMetaClassNoInit ); + CaseErrorStringifyHardCode( 0xDC004004, kOSMetaClassNoTempData ); + CaseErrorStringifyHardCode( 0xDC004005, kOSMetaClassNoDicts ); + CaseErrorStringifyHardCode( 0xDC004006, kOSMetaClassNoKModSet ); + CaseErrorStringifyHardCode( 0xDC004007, kOSMetaClassNoInsKModSet ); + CaseErrorStringifyHardCode( 0xDC004008, kOSMetaClassNoSuper ); + CaseErrorStringifyHardCode( 0xDC004009, kOSMetaClassInstNoSuper ); + CaseErrorStringifyHardCode( 0xDC00400A, kOSMetaClassDuplicateClass ); + + // IOKit Errors + + CaseErrorStringifyHardCode( 0xE00002BC, kIOReturnError ); + CaseErrorStringifyHardCode( 0xE00002BD, kIOReturnNoMemory ); + CaseErrorStringifyHardCode( 0xE00002BE, kIOReturnNoResources ); + CaseErrorStringifyHardCode( 0xE00002BF, kIOReturnIPCError ); + CaseErrorStringifyHardCode( 0xE00002C0, kIOReturnNoDevice ); + CaseErrorStringifyHardCode( 0xE00002C1, kIOReturnNotPrivileged ); + CaseErrorStringifyHardCode( 0xE00002C2, kIOReturnBadArgument ); + CaseErrorStringifyHardCode( 0xE00002C3, kIOReturnLockedRead ); + CaseErrorStringifyHardCode( 0xE00002C4, kIOReturnLockedWrite ); + CaseErrorStringifyHardCode( 0xE00002C5, kIOReturnExclusiveAccess ); + CaseErrorStringifyHardCode( 0xE00002C6, kIOReturnBadMessageID ); + CaseErrorStringifyHardCode( 0xE00002C7, kIOReturnUnsupported ); + CaseErrorStringifyHardCode( 0xE00002C8, kIOReturnVMError ); + CaseErrorStringifyHardCode( 0xE00002C9, kIOReturnInternalError ); + CaseErrorStringifyHardCode( 0xE00002CA, kIOReturnIOError ); + CaseErrorStringifyHardCode( 0xE00002CC, kIOReturnCannotLock ); + CaseErrorStringifyHardCode( 0xE00002CD, kIOReturnNotOpen ); + CaseErrorStringifyHardCode( 0xE00002CE, kIOReturnNotReadable ); + CaseErrorStringifyHardCode( 0xE00002CF, kIOReturnNotWritable ); + CaseErrorStringifyHardCode( 0xE00002D0, kIOReturnNotAligned ); + CaseErrorStringifyHardCode( 0xE00002D1, kIOReturnBadMedia ); + CaseErrorStringifyHardCode( 0xE00002D2, kIOReturnStillOpen ); + CaseErrorStringifyHardCode( 0xE00002D3, kIOReturnRLDError ); + CaseErrorStringifyHardCode( 0xE00002D4, kIOReturnDMAError ); + CaseErrorStringifyHardCode( 0xE00002D5, kIOReturnBusy ); + CaseErrorStringifyHardCode( 0xE00002D6, kIOReturnTimeout ); + CaseErrorStringifyHardCode( 0xE00002D7, kIOReturnOffline ); + CaseErrorStringifyHardCode( 0xE00002D8, kIOReturnNotReady ); + CaseErrorStringifyHardCode( 0xE00002D9, kIOReturnNotAttached ); + CaseErrorStringifyHardCode( 0xE00002DA, kIOReturnNoChannels ); + CaseErrorStringifyHardCode( 0xE00002DB, kIOReturnNoSpace ); + CaseErrorStringifyHardCode( 0xE00002DD, kIOReturnPortExists ); + CaseErrorStringifyHardCode( 0xE00002DE, kIOReturnCannotWire ); + CaseErrorStringifyHardCode( 0xE00002DF, kIOReturnNoInterrupt ); + CaseErrorStringifyHardCode( 0xE00002E0, kIOReturnNoFrames ); + CaseErrorStringifyHardCode( 0xE00002E1, kIOReturnMessageTooLarge ); + CaseErrorStringifyHardCode( 0xE00002E2, kIOReturnNotPermitted ); + CaseErrorStringifyHardCode( 0xE00002E3, kIOReturnNoPower ); + CaseErrorStringifyHardCode( 0xE00002E4, kIOReturnNoMedia ); + CaseErrorStringifyHardCode( 0xE00002E5, kIOReturnUnformattedMedia ); + CaseErrorStringifyHardCode( 0xE00002E6, kIOReturnUnsupportedMode ); + CaseErrorStringifyHardCode( 0xE00002E7, kIOReturnUnderrun ); + CaseErrorStringifyHardCode( 0xE00002E8, kIOReturnOverrun ); + CaseErrorStringifyHardCode( 0xE00002E9, kIOReturnDeviceError ); + CaseErrorStringifyHardCode( 0xE00002EA, kIOReturnNoCompletion ); + CaseErrorStringifyHardCode( 0xE00002EB, kIOReturnAborted ); + CaseErrorStringifyHardCode( 0xE00002EC, kIOReturnNoBandwidth ); + CaseErrorStringifyHardCode( 0xE00002ED, kIOReturnNotResponding ); + CaseErrorStringifyHardCode( 0xE00002EE, kIOReturnIsoTooOld ); + CaseErrorStringifyHardCode( 0xE00002EF, kIOReturnIsoTooNew ); + CaseErrorStringifyHardCode( 0xE00002F0, kIOReturnNotFound ); + CaseErrorStringifyHardCode( 0xE0000001, kIOReturnInvalid ); + + // IOKit FireWire Errors + + CaseErrorStringifyHardCode( 0xE0008010, kIOFireWireResponseBase ); + CaseErrorStringifyHardCode( 0xE0008020, kIOFireWireBusReset ); + CaseErrorStringifyHardCode( 0xE0008001, kIOConfigNoEntry ); + CaseErrorStringifyHardCode( 0xE0008002, kIOFireWirePending ); + CaseErrorStringifyHardCode( 0xE0008003, kIOFireWireLastDCLToken ); + CaseErrorStringifyHardCode( 0xE0008004, kIOFireWireConfigROMInvalid ); + CaseErrorStringifyHardCode( 0xE0008005, kIOFireWireAlreadyRegistered ); + CaseErrorStringifyHardCode( 0xE0008006, kIOFireWireMultipleTalkers ); + CaseErrorStringifyHardCode( 0xE0008007, kIOFireWireChannelActive ); + CaseErrorStringifyHardCode( 0xE0008008, kIOFireWireNoListenerOrTalker ); + CaseErrorStringifyHardCode( 0xE0008009, kIOFireWireNoChannels ); + CaseErrorStringifyHardCode( 0xE000800A, kIOFireWireChannelNotAvailable ); + CaseErrorStringifyHardCode( 0xE000800B, kIOFireWireSeparateBus ); + CaseErrorStringifyHardCode( 0xE000800C, kIOFireWireBadSelfIDs ); + CaseErrorStringifyHardCode( 0xE000800D, kIOFireWireLowCableVoltage ); + CaseErrorStringifyHardCode( 0xE000800E, kIOFireWireInsufficientPower ); + CaseErrorStringifyHardCode( 0xE000800F, kIOFireWireOutOfTLabels ); + CaseErrorStringifyHardCode( 0xE0008101, kIOFireWireBogusDCLProgram ); + CaseErrorStringifyHardCode( 0xE0008102, kIOFireWireTalkingAndListening ); + CaseErrorStringifyHardCode( 0xE0008103, kIOFireWireHardwareSlept ); + CaseErrorStringifyHardCode( 0xE00087D0, kIOFWMessageServiceIsRequestingClose ); + CaseErrorStringifyHardCode( 0xE00087D1, kIOFWMessagePowerStateChanged ); + CaseErrorStringifyHardCode( 0xE00087D2, kIOFWMessageTopologyChanged ); + + // IOKit USB Errors + + CaseErrorStringifyHardCode( 0xE0004061, kIOUSBUnknownPipeErr ); + CaseErrorStringifyHardCode( 0xE0004060, kIOUSBTooManyPipesErr ); + CaseErrorStringifyHardCode( 0xE000405F, kIOUSBNoAsyncPortErr ); + CaseErrorStringifyHardCode( 0xE000405E, kIOUSBNotEnoughPipesErr ); + CaseErrorStringifyHardCode( 0xE000405D, kIOUSBNotEnoughPowerErr ); + CaseErrorStringifyHardCode( 0xE0004057, kIOUSBEndpointNotFound ); + CaseErrorStringifyHardCode( 0xE0004056, kIOUSBConfigNotFound ); + CaseErrorStringifyHardCode( 0xE0004051, kIOUSBTransactionTimeout ); + CaseErrorStringifyHardCode( 0xE0004050, kIOUSBTransactionReturned ); + CaseErrorStringifyHardCode( 0xE000404F, kIOUSBPipeStalled ); + CaseErrorStringifyHardCode( 0xE000404E, kIOUSBInterfaceNotFound ); + CaseErrorStringifyHardCode( 0xE000404D, kIOUSBLowLatencyBufferNotPreviouslyAllocated ); + CaseErrorStringifyHardCode( 0xE000404C, kIOUSBLowLatencyFrameListNotPreviouslyAllocated ); + CaseErrorStringifyHardCode( 0xE000404B, kIOUSBHighSpeedSplitError ); + CaseErrorStringifyHardCode( 0xE0004010, kIOUSBLinkErr ); + CaseErrorStringifyHardCode( 0xE000400F, kIOUSBNotSent2Err ); + CaseErrorStringifyHardCode( 0xE000400E, kIOUSBNotSent1Err ); + CaseErrorStringifyHardCode( 0xE000400D, kIOUSBBufferUnderrunErr ); + CaseErrorStringifyHardCode( 0xE000400C, kIOUSBBufferOverrunErr ); + CaseErrorStringifyHardCode( 0xE000400B, kIOUSBReserved2Err ); + CaseErrorStringifyHardCode( 0xE000400A, kIOUSBReserved1Err ); + CaseErrorStringifyHardCode( 0xE0004007, kIOUSBWrongPIDErr ); + CaseErrorStringifyHardCode( 0xE0004006, kIOUSBPIDCheckErr ); + CaseErrorStringifyHardCode( 0xE0004003, kIOUSBDataToggleErr ); + CaseErrorStringifyHardCode( 0xE0004002, kIOUSBBitstufErr ); + CaseErrorStringifyHardCode( 0xE0004001, kIOUSBCRCErr ); + + #endif // __MACH__ + + // Other Errors + + default: + s = NULL; + #if ( TARGET_OS_WIN32 && !TARGET_OS_WINDOWS_CE ) + if( inBuffer && ( inBufferSize > 0 ) ) + { + DWORD n; + + n = FormatMessageA( FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, (DWORD) inErrorCode, + MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), buffer, sizeof( buffer ), NULL ); + if( n > 0 ) + { + // Remove any trailing CR's or LF's since some messages have them. + + while( ( n > 0 ) && isspace( ( (unsigned char *) buffer )[ n - 1 ] ) ) + { + buffer[ --n ] = '\0'; + } + s = buffer; + } + } + #endif + + if( !s ) + { + #if ( !TARGET_API_MAC_OSX_KERNEL && !TARGET_OS_WINDOWS_CE ) + s = strerror( inErrorCode ); + #endif + if( !s ) + { + s = "<unknown error code>"; + } + } + break; + } + + // Copy the string to the output buffer. If no buffer is supplied or it is empty, return an empty string. + + if( inBuffer && ( inBufferSize > 0 ) ) + { + dst = inBuffer; + end = dst + ( inBufferSize - 1 ); + while( ( ( end - dst ) > 0 ) && ( *s != '\0' ) ) + { + *dst++ = *s++; + } + *dst = '\0'; + s = inBuffer; + } + return( s ); +} + +//=========================================================================================================================== +// DebugHexDump +//=========================================================================================================================== + +DEBUG_EXPORT size_t +DebugHexDump( + DebugLevel inLevel, + int inIndent, + const char * inLabel, + size_t inLabelSize, + int inLabelMinWidth, + const char * inType, + size_t inTypeSize, + const void * inDataStart, + const void * inData, + size_t inDataSize, + DebugFlags inFlags, + char * outBuffer, + size_t inBufferSize ) +{ + static const char kHexChars[] = "0123456789ABCDEF"; + const uint8_t * start; + const uint8_t * src; + char * dst; + char * end; + size_t n; + int offset; + int width; + const char * newline; + char separator[ 8 ]; + char * s; + + DEBUG_UNUSED( inType ); + DEBUG_UNUSED( inTypeSize ); + + // Set up the function-wide variables. + + if( inLabelSize == kSizeCString ) + { + inLabelSize = strlen( inLabel ); + } + start = (const uint8_t *) inData; + src = start; + dst = outBuffer; + end = dst + inBufferSize; + offset = (int)( (intptr_t) inData - (intptr_t) inDataStart ); + width = ( (int) inLabelSize > inLabelMinWidth ) ? (int) inLabelSize : inLabelMinWidth; + newline = ( inFlags & kDebugFlagsNoNewLine ) ? "" : "\n"; + + // Set up the separator string. This is used to insert spaces on subsequent "lines" when not using newlines. + + s = separator; + if( inFlags & kDebugFlagsNoNewLine ) + { + if( inFlags & kDebugFlags8BitSeparator ) + { + *s++ = ' '; + } + if( inFlags & kDebugFlags16BitSeparator ) + { + *s++ = ' '; + } + if( !( inFlags & kDebugFlagsNo32BitSeparator ) ) + { + *s++ = ' '; + } + check( ( (size_t)( s - separator ) ) < sizeof( separator ) ); + } + *s = '\0'; + + for( ;; ) + { + char prefixString[ 32 ]; + char hexString[ 64 ]; + char asciiString[ 32 ]; + char byteCountString[ 32 ]; + int c; + size_t chunkSize; + size_t i; + + // If this is a label-only item (i.e. no data), print the label (accounting for prefix string spacing) and exit. + + if( inDataSize == 0 ) + { + if( inLabel && ( inLabelSize > 0 ) ) + { + width = 0; + if( !( inFlags & kDebugFlagsNoAddress ) ) + { + width += 8; // "00000000" + if( !( inFlags & kDebugFlagsNoOffset ) ) + { + width += 1; // "+" + } + } + if( inFlags & kDebugFlags32BitOffset ) + { + width += 8; // "00000000" + } + else if( !( inFlags & kDebugFlagsNoOffset ) ) + { + width += 4; // "0000" + } + + if( outBuffer ) + { + dst += DebugSNPrintF( dst, (size_t)( end - dst ), "%*s" "%-*.*s" "%.*s" "%s", + width, "", + ( width > 0 ) ? ": " : "", + width, (int) inLabelSize, inLabel, + newline ); + } + else + { + dst += DebugPrintF( inLevel, "%*s" "%-*.*s" "%.*s" "%s", + width, "", + ( width > 0 ) ? ": " : "", + width, (int) inLabelSize, inLabel, + newline ); + } + } + break; + } + + // Build the prefix string. It will be in one of the following formats: + // + // 1) "00000000+0000[0000]" (address and offset) + // 2) "00000000" (address only) + // 3) "0000[0000]" (offset only) + // 4) "" (no address or offset) + // + // Note: If we're printing multiple "lines", but not printing newlines, a space is used to separate. + + s = prefixString; + if( !( inFlags & kDebugFlagsNoAddress ) ) + { + *s++ = kHexChars[ ( ( (uintptr_t) src ) >> 28 ) & 0xF ]; + *s++ = kHexChars[ ( ( (uintptr_t) src ) >> 24 ) & 0xF ]; + *s++ = kHexChars[ ( ( (uintptr_t) src ) >> 20 ) & 0xF ]; + *s++ = kHexChars[ ( ( (uintptr_t) src ) >> 16 ) & 0xF ]; + *s++ = kHexChars[ ( ( (uintptr_t) src ) >> 12 ) & 0xF ]; + *s++ = kHexChars[ ( ( (uintptr_t) src ) >> 8 ) & 0xF ]; + *s++ = kHexChars[ ( ( (uintptr_t) src ) >> 4 ) & 0xF ]; + *s++ = kHexChars[ ( (uintptr_t) src ) & 0xF ]; + + if( !( inFlags & kDebugFlagsNoOffset ) ) + { + *s++ = '+'; + } + } + if( !( inFlags & kDebugFlagsNoOffset ) ) + { + if( inFlags & kDebugFlags32BitOffset ) + { + *s++ = kHexChars[ ( offset >> 28 ) & 0xF ]; + *s++ = kHexChars[ ( offset >> 24 ) & 0xF ]; + *s++ = kHexChars[ ( offset >> 20 ) & 0xF ]; + *s++ = kHexChars[ ( offset >> 16 ) & 0xF ]; + } + *s++ = kHexChars[ ( offset >> 12 ) & 0xF ]; + *s++ = kHexChars[ ( offset >> 8 ) & 0xF ]; + *s++ = kHexChars[ ( offset >> 4 ) & 0xF ]; + *s++ = kHexChars[ offset & 0xF ]; + } + if( s != prefixString ) + { + *s++ = ':'; + *s++ = ' '; + } + check( ( (size_t)( s - prefixString ) ) < sizeof( prefixString ) ); + *s = '\0'; + + // Build a hex string with a optional spaces after every 1, 2, and/or 4 bytes to make it easier to read. + // Optionally pads the hex string with space to fill the full 16 byte range (so it lines up). + + s = hexString; + chunkSize = ( inDataSize < 16 ) ? inDataSize : 16; + n = ( inFlags & kDebugFlagsNo16ByteHexPad ) ? chunkSize : 16; + for( i = 0; i < n; ++i ) + { + if( ( inFlags & kDebugFlags8BitSeparator ) && ( i > 0 ) ) + { + *s++ = ' '; + } + if( ( inFlags & kDebugFlags16BitSeparator ) && ( i > 0 ) && ( ( i % 2 ) == 0 ) ) + { + *s++ = ' '; + } + if( !( inFlags & kDebugFlagsNo32BitSeparator ) && ( i > 0 ) && ( ( i % 4 ) == 0 ) ) + { + *s++ = ' '; + } + if( i < chunkSize ) + { + *s++ = kHexChars[ src[ i ] >> 4 ]; + *s++ = kHexChars[ src[ i ] & 0xF ]; + } + else + { + *s++ = ' '; + *s++ = ' '; + } + } + check( ( (size_t)( s - hexString ) ) < sizeof( hexString ) ); + *s = '\0'; + + // Build a string with the ASCII version of the data (replaces non-printable characters with '^'). + // Optionally pads the string with '`' to fill the full 16 byte range (so it lines up). + + s = asciiString; + if( !( inFlags & kDebugFlagsNoASCII ) ) + { + *s++ = ' '; + *s++ = '|'; + for( i = 0; i < n; ++i ) + { + if( i < chunkSize ) + { + c = src[ i ]; + if( !DebugIsPrint( c ) ) + { + c = '^'; + } + } + else + { + c = '`'; + } + *s++ = (char) c; + } + *s++ = '|'; + check( ( (size_t)( s - asciiString ) ) < sizeof( asciiString ) ); + } + *s = '\0'; + + // Build a string indicating how bytes are in the hex dump. Only printed on the first line. + + s = byteCountString; + if( !( inFlags & kDebugFlagsNoByteCount ) ) + { + if( src == start ) + { + s += DebugSNPrintF( s, sizeof( byteCountString ), " (%d bytes)", (int) inDataSize ); + } + } + check( ( (size_t)( s - byteCountString ) ) < sizeof( byteCountString ) ); + *s = '\0'; + + // Build the entire line from all the pieces we've previously built. + + if( outBuffer ) + { + if( src == start ) + { + dst += DebugSNPrintF( dst, (size_t)( end - dst ), + "%*s" // Indention + "%s" // Separator (only if needed) + "%s" // Prefix + "%-*.*s" // Label + "%s" // Separator + "%s" // Hex + "%s" // ASCII + "%s" // Byte Count + "%s", // Newline + inIndent, "", + ( src != start ) ? separator : "", + prefixString, + width, (int) inLabelSize, inLabel ? inLabel : "", + ( width > 0 ) ? " " : "", + hexString, + asciiString, + byteCountString, + newline ); + } + else + { + dst += DebugSNPrintF( dst, (size_t)( end - dst ), + "%*s" // Indention + "%s" // Separator (only if needed) + "%s" // Prefix + "%*s" // Label Spacing + "%s" // Separator + "%s" // Hex + "%s" // ASCII + "%s" // Byte Count + "%s", // Newline + inIndent, "", + ( src != start ) ? separator : "", + prefixString, + width, "", + ( width > 0 ) ? " " : "", + hexString, + asciiString, + byteCountString, + newline ); + } + } + else + { + if( src == start ) + { + dst += DebugPrintF( inLevel, + "%*s" // Indention + "%s" // Separator (only if needed) + "%s" // Prefix + "%-*.*s" // Label + "%s" // Separator + "%s" // Hex + "%s" // ASCII + "%s" // Byte Count + "%s", // Newline + inIndent, "", + ( src != start ) ? separator : "", + prefixString, + width, (int) inLabelSize, inLabel, + ( width > 0 ) ? " " : "", + hexString, + asciiString, + byteCountString, + newline ); + } + else + { + dst += DebugPrintF( inLevel, + "%*s" // Indention + "%s" // Separator (only if needed) + "%s" // Prefix + "%*s" // Label Spacing + "%s" // Separator + "%s" // Hex + "%s" // ASCII + "%s" // Byte Count + "%s", // Newline + inIndent, "", + ( src != start ) ? separator : "", + prefixString, + width, "", + ( width > 0 ) ? " " : "", + hexString, + asciiString, + byteCountString, + newline ); + } + } + + // Move to the next chunk. Exit if there is no more data. + + offset += (int) chunkSize; + src += chunkSize; + inDataSize -= chunkSize; + if( inDataSize == 0 ) + { + break; + } + } + + // Note: The "dst - outBuffer" size calculation works even if "outBuffer" is NULL because it's all relative. + + return( (size_t)( dst - outBuffer ) ); +} + +//=========================================================================================================================== +// DebugNumVersionToString +//=========================================================================================================================== + +static char * DebugNumVersionToString( uint32_t inVersion, char *inString ) +{ + char * s; + uint8_t majorRev; + uint8_t minor; + uint8_t bugFix; + uint8_t stage; + uint8_t revision; + + check( inString ); + + majorRev = (uint8_t)( ( inVersion >> 24 ) & 0xFF ); + minor = (uint8_t)( ( inVersion >> 20 ) & 0x0F ); + bugFix = (uint8_t)( ( inVersion >> 16 ) & 0x0F ); + stage = (uint8_t)( ( inVersion >> 8 ) & 0xFF ); + revision = (uint8_t)( inVersion & 0xFF ); + + // Convert the major, minor, and bugfix numbers. + + s = inString; + s += sprintf( s, "%u", majorRev ); + s += sprintf( s, ".%u", minor ); + if( bugFix != 0 ) + { + s += sprintf( s, ".%u", bugFix ); + } + + // Convert the version stage and non-release revision number. + + switch( stage ) + { + case kVersionStageDevelopment: + s += sprintf( s, "d%u", revision ); + break; + + case kVersionStageAlpha: + s += sprintf( s, "a%u", revision ); + break; + + case kVersionStageBeta: + s += sprintf( s, "b%u", revision ); + break; + + case kVersionStageFinal: + + // A non-release revision of zero is a special case indicating the software is GM (at the golden master + // stage) and therefore, the non-release revision should not be added to the string. + + if( revision != 0 ) + { + s += sprintf( s, "f%u", revision ); + } + break; + + default: + dlog( kDebugLevelError, "invalid NumVersion stage (0x%02X)\n", stage ); + break; + } + return( inString ); +} + +//=========================================================================================================================== +// DebugTaskLevel +//=========================================================================================================================== + +DEBUG_EXPORT uint32_t DebugTaskLevel( void ) +{ + uint32_t level; + + level = 0; + +#if ( TARGET_OS_VXWORKS ) + if( intContext() ) + { + level |= ( ( 1 << kDebugInterruptLevelShift ) & kDebugInterruptLevelMask ); + } +#endif + + return( level ); +} + +#if ( TARGET_OS_WIN32 && !TARGET_OS_WINDOWS_CE ) +//=========================================================================================================================== +// DebugWinEnableConsole +//=========================================================================================================================== + +#pragma warning( disable:4311 ) + +static void DebugWinEnableConsole( void ) +{ + static bool sConsoleEnabled = false; + BOOL result; + int fileHandle; + FILE * file; + int err; + + if( sConsoleEnabled ) + { + goto exit; + } + + // Create console window. + + result = AllocConsole(); + require_quiet( result, exit ); + + // Redirect stdin to the console stdin. + + fileHandle = _open_osfhandle( (long) GetStdHandle( STD_INPUT_HANDLE ), _O_TEXT ); + + #if ( defined( __MWERKS__ ) ) + file = __handle_reopen( (unsigned long) fileHandle, "r", stdin ); + require_quiet( file, exit ); + #else + file = _fdopen( fileHandle, "r" ); + require_quiet( file, exit ); + + *stdin = *file; + #endif + + err = setvbuf( stdin, NULL, _IONBF, 0 ); + require_noerr_quiet( err, exit ); + + // Redirect stdout to the console stdout. + + fileHandle = _open_osfhandle( (long) GetStdHandle( STD_OUTPUT_HANDLE ), _O_TEXT ); + + #if ( defined( __MWERKS__ ) ) + file = __handle_reopen( (unsigned long) fileHandle, "w", stdout ); + require_quiet( file, exit ); + #else + file = _fdopen( fileHandle, "w" ); + require_quiet( file, exit ); + + *stdout = *file; + #endif + + err = setvbuf( stdout, NULL, _IONBF, 0 ); + require_noerr_quiet( err, exit ); + + // Redirect stderr to the console stdout. + + fileHandle = _open_osfhandle( (long) GetStdHandle( STD_OUTPUT_HANDLE ), _O_TEXT ); + + #if ( defined( __MWERKS__ ) ) + file = __handle_reopen( (unsigned long) fileHandle, "w", stderr ); + require_quiet( file, exit ); + #else + file = _fdopen( fileHandle, "w" ); + require_quiet( file, exit ); + + *stderr = *file; + #endif + + err = setvbuf( stderr, NULL, _IONBF, 0 ); + require_noerr_quiet( err, exit ); + + sConsoleEnabled = true; + +exit: + return; +} + +#pragma warning( default:4311 ) + +#endif // TARGET_OS_WIN32 && !TARGET_OS_WINDOWS_CE + +#if ( TARGET_OS_WIN32 ) +//=========================================================================================================================== +// DebugWinCharToTCharString +//=========================================================================================================================== + +static TCHAR * +DebugWinCharToTCharString( + const char * inCharString, + size_t inCharCount, + TCHAR * outTCharString, + size_t inTCharCountMax, + size_t * outTCharCount ) +{ + const char * src; + TCHAR * dst; + TCHAR * end; + + if( inCharCount == kSizeCString ) + { + inCharCount = strlen( inCharString ); + } + src = inCharString; + dst = outTCharString; + if( inTCharCountMax > 0 ) + { + inTCharCountMax -= 1; + if( inTCharCountMax > inCharCount ) + { + inTCharCountMax = inCharCount; + } + + end = dst + inTCharCountMax; + while( dst < end ) + { + *dst++ = (TCHAR) *src++; + } + *dst = 0; + } + if( outTCharCount ) + { + *outTCharCount = (size_t)( dst - outTCharString ); + } + return( outTCharString ); +} +#endif + +#if 0 +#pragma mark - +#pragma mark == Debugging == +#endif + +//=========================================================================================================================== +// DebugServicesTest +//=========================================================================================================================== + +DEBUG_EXPORT OSStatus DebugServicesTest( void ) +{ + OSStatus err; + char s[ 512 ]; + uint8_t * p; + uint8_t data[] = + { + 0x11, 0x22, 0x33, 0x44, + 0x55, 0x66, + 0x77, 0x88, 0x99, 0xAA, + 0xBB, 0xCC, 0xDD, + 0xEE, + 0xFF, + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, + 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80, 0x90, 0xA0, + 0x11, 0x21, 0x31, 0x41, 0x51, 0x61, 0x71, 0x81, 0x91, 0xA1 + }; + + debug_initialize( kDebugOutputTypeMetaConsole ); + + // check's + + check( 0 && "SHOULD SEE: check" ); + check( 1 && "SHOULD *NOT* SEE: check (valid)" ); + check_string( 0, "SHOULD SEE: check_string" ); + check_string( 1, "SHOULD *NOT* SEE: check_string (valid)" ); + check_noerr( -123 ); + check_noerr( 10038 ); + check_noerr( 22 ); + check_noerr( 0 ); + check_noerr_string( -6712, "SHOULD SEE: check_noerr_string" ); + check_noerr_string( 0, "SHOULD *NOT* SEE: check_noerr_string (valid)" ); + check_translated_errno( 0 >= 0 && "SHOULD *NOT* SEE", -384, -999 ); + check_translated_errno( -1 >= 0 && "SHOULD SEE", -384, -999 ); + check_translated_errno( -1 >= 0 && "SHOULD SEE", 0, -999 ); + check_ptr_overlap( "SHOULD *NOT* SEE" ? 10 : 0, 10, 22, 10 ); + check_ptr_overlap( "SHOULD SEE" ? 10 : 0, 10, 5, 10 ); + check_ptr_overlap( "SHOULD SEE" ? 10 : 0, 10, 12, 6 ); + check_ptr_overlap( "SHOULD SEE" ? 12 : 0, 6, 10, 10 ); + check_ptr_overlap( "SHOULD SEE" ? 12 : 0, 10, 10, 10 ); + check_ptr_overlap( "SHOULD *NOT* SEE" ? 22 : 0, 10, 10, 10 ); + check_ptr_overlap( "SHOULD *NOT* SEE" ? 10 : 0, 10, 20, 10 ); + check_ptr_overlap( "SHOULD *NOT* SEE" ? 20 : 0, 10, 10, 10 ); + + // require's + + require( 0 && "SHOULD SEE", require1 ); + { err = kResponseErr; goto exit; } +require1: + require( 1 && "SHOULD *NOT* SEE", require2 ); + goto require2Good; +require2: + { err = kResponseErr; goto exit; } +require2Good: + require_string( 0 && "SHOULD SEE", require3, "SHOULD SEE: require_string" ); + { err = kResponseErr; goto exit; } +require3: + require_string( 1 && "SHOULD *NOT* SEE", require4, "SHOULD *NOT* SEE: require_string (valid)" ); + goto require4Good; +require4: + { err = kResponseErr; goto exit; } +require4Good: + require_quiet( 0 && "SHOULD SEE", require5 ); + { err = kResponseErr; goto exit; } +require5: + require_quiet( 1 && "SHOULD *NOT* SEE", require6 ); + goto require6Good; +require6: + { err = kResponseErr; goto exit; } +require6Good: + require_noerr( -1, require7 ); + { err = kResponseErr; goto exit; } +require7: + require_noerr( 0, require8 ); + goto require8Good; +require8: + { err = kResponseErr; goto exit; } +require8Good: + require_noerr_string( -2, require9, "SHOULD SEE: require_noerr_string"); + { err = kResponseErr; goto exit; } +require9: + require_noerr_string( 0, require10, "SHOULD *NOT* SEE: require_noerr_string (valid)" ); + goto require10Good; +require10: + { err = kResponseErr; goto exit; } +require10Good: + require_noerr_action_string( -3, require11, dlog( kDebugLevelMax, "action 1 (expected)\n" ), "require_noerr_action_string" ); + { err = kResponseErr; goto exit; } +require11: + require_noerr_action_string( 0, require12, dlog( kDebugLevelMax, "action 2\n" ), "require_noerr_action_string (valid)" ); + goto require12Good; +require12: + { err = kResponseErr; goto exit; } +require12Good: + require_noerr_quiet( -4, require13 ); + { err = kResponseErr; goto exit; } +require13: + require_noerr_quiet( 0, require14 ); + goto require14Good; +require14: + { err = kResponseErr; goto exit; } +require14Good: + require_noerr_action( -5, require15, dlog( kDebugLevelMax, "SHOULD SEE: action 3 (expected)\n" ) ); + { err = kResponseErr; goto exit; } +require15: + require_noerr_action( 0, require16, dlog( kDebugLevelMax, "SHOULD *NOT* SEE: action 4\n" ) ); + goto require16Good; +require16: + { err = kResponseErr; goto exit; } +require16Good: + require_noerr_action_quiet( -4, require17, dlog( kDebugLevelMax, "SHOULD SEE: action 5 (expected)\n" ) ); + { err = kResponseErr; goto exit; } +require17: + require_noerr_action_quiet( 0, require18, dlog( kDebugLevelMax, "SHOULD *NOT* SEE: action 6\n" ) ); + goto require18Good; +require18: + { err = kResponseErr; goto exit; } +require18Good: + require_action( 0 && "SHOULD SEE", require19, dlog( kDebugLevelMax, "SHOULD SEE: action 7 (expected)\n" ) ); + { err = kResponseErr; goto exit; } +require19: + require_action( 1 && "SHOULD *NOT* SEE", require20, dlog( kDebugLevelMax, "SHOULD *NOT* SEE: action 8\n" ) ); + goto require20Good; +require20: + { err = kResponseErr; goto exit; } +require20Good: + require_action_quiet( 0, require21, dlog( kDebugLevelMax, "SHOULD SEE: action 9 (expected)\n" ) ); + { err = kResponseErr; goto exit; } +require21: + require_action_quiet( 1, require22, dlog( kDebugLevelMax, "SHOULD *NOT* SEE: action 10\n" ) ); + goto require22Good; +require22: + { err = kResponseErr; goto exit; } +require22Good: + require_action_string( 0, require23, dlog( kDebugLevelMax, "SHOULD SEE: action 11 (expected)\n" ), "SHOULD SEE: require_action_string" ); + { err = kResponseErr; goto exit; } +require23: + require_action_string( 1, require24, dlog( kDebugLevelMax, "SHOULD *NOT* SEE: action 12\n" ), "SHOULD *NOT* SEE: require_action_string" ); + goto require24Good; +require24: + { err = kResponseErr; goto exit; } +require24Good: + +#if ( defined( __MWERKS__ ) ) + #if ( defined( __cplusplus ) && __option( exceptions ) ) + #define COMPILER_HAS_EXCEPTIONS 1 + #else + #define COMPILER_HAS_EXCEPTIONS 0 + #endif +#else + #if ( defined( __cplusplus ) ) + #define COMPILER_HAS_EXCEPTIONS 1 + #else + #define COMPILER_HAS_EXCEPTIONS 0 + #endif +#endif + +#if ( COMPILER_HAS_EXCEPTIONS ) + try + { + require_throw( 1 && "SHOULD *NOT* SEE" ); + require_throw( 0 && "SHOULD SEE" ); + } + catch(... ) + { + goto require26Good; + } + { err = kResponseErr; goto exit; } +require26Good: +#endif + + // translate_errno + + err = translate_errno( 1 != -1, -123, -567 ); + require( ( err == 0 ) && "SHOULD *NOT* SEE", exit ); + + err = translate_errno( -1 != -1, -123, -567 ); + require( ( err == -123 ) && "SHOULD *NOT* SEE", exit ); + + err = translate_errno( -1 != -1, 0, -567 ); + require( ( err == -567 ) && "SHOULD *NOT* SEE", exit ); + + // debug_string + + debug_string( "debug_string" ); + + // DebugSNPrintF + + DebugSNPrintF( s, sizeof( s ), "%d", 1234 ); + require_action( strcmp( s, "1234" ) == 0, exit, err = -1 ); + + DebugSNPrintF( s, sizeof( s ), "%X", 0x2345 ); + require_action( strcmp( s, "2345" ) == 0, exit, err = -1 ); + + DebugSNPrintF( s, sizeof( s ), "%#s", "\05test" ); + require_action( strcmp( s, "test" ) == 0, exit, err = -1 ); + + DebugSNPrintF( s, sizeof( s ), "%##s", "\03www\05apple\03com" ); + require_action( strcmp( s, "www.apple.com." ) == 0, exit, err = -1 ); + + DebugSNPrintF( s, sizeof( s ), "%ld", (long) INT32_C( 2147483647 ) ); + require_action( strcmp( s, "2147483647" ) == 0, exit, err = -1 ); + + DebugSNPrintF( s, sizeof( s ), "%lu", (unsigned long) UINT32_C( 4294967295 ) ); + require_action( strcmp( s, "4294967295" ) == 0, exit, err = -1 ); + + #if ( TYPE_LONGLONG_NATIVE ) + DebugSNPrintF( s, sizeof( s ), "%lld", (long_long_compat) INT64_C( 9223372036854775807 ) ); + require_action( strcmp( s, "9223372036854775807" ) == 0, exit, err = -1 ); + + DebugSNPrintF( s, sizeof( s ), "%lld", (long_long_compat) INT64_C( -9223372036854775807 ) ); + require_action( strcmp( s, "-9223372036854775807" ) == 0, exit, err = -1 ); + + DebugSNPrintF( s, sizeof( s ), "%llu", (unsigned_long_long_compat) UINT64_C( 18446744073709551615 ) ); + require_action( strcmp( s, "18446744073709551615" ) == 0, exit, err = -1 ); + #endif + + DebugSNPrintF( s, sizeof( s ), "%lb", (unsigned long) binary_32( 01111011, 01111011, 01111011, 01111011 ) ); + require_action( strcmp( s, "1111011011110110111101101111011" ) == 0, exit, err = -1 ); + + DebugSNPrintF( s, sizeof( s ), "%C", 0x41624364 ); // 'AbCd' + require_action( strcmp( s, "AbCd" ) == 0, exit, err = -1 ); + + #if ( defined( MDNS_DEBUGMSGS ) ) + { + mDNSAddr maddr; + + memset( &maddr, 0, sizeof( maddr ) ); + maddr.type = mDNSAddrType_IPv4; + maddr.ip.v4.b[ 0 ] = 127; + maddr.ip.v4.b[ 1 ] = 0; + maddr.ip.v4.b[ 2 ] = 0; + maddr.ip.v4.b[ 3 ] = 1; + DebugSNPrintF( s, sizeof( s ), "%#a", &maddr ); + require_action( strcmp( s, "127.0.0.1" ) == 0, exit, err = -1 ); + + memset( &maddr, 0, sizeof( maddr ) ); + maddr.type = mDNSAddrType_IPv6; + maddr.ip.v6.b[ 0 ] = 0xFE; + maddr.ip.v6.b[ 1 ] = 0x80; + maddr.ip.v6.b[ 15 ] = 0x01; + DebugSNPrintF( s, sizeof( s ), "%#a", &maddr ); + require_action( strcmp( s, "FE80:0000:0000:0000:0000:0000:0000:0001" ) == 0, exit, err = -1 ); + } + #endif + + #if ( AF_INET ) + { + struct sockaddr_in sa4; + + memset( &sa4, 0, sizeof( sa4 ) ); + sa4.sin_family = AF_INET; + p = (uint8_t *) &sa4.sin_port; + p[ 0 ] = (uint8_t)( ( 80 >> 8 ) & 0xFF ); + p[ 1 ] = (uint8_t)( 80 & 0xFF ); + p = (uint8_t *) &sa4.sin_addr.s_addr; + p[ 0 ] = (uint8_t)( ( INADDR_LOOPBACK >> 24 ) & 0xFF ); + p[ 1 ] = (uint8_t)( ( INADDR_LOOPBACK >> 16 ) & 0xFF ); + p[ 2 ] = (uint8_t)( ( INADDR_LOOPBACK >> 8 ) & 0xFF ); + p[ 3 ] = (uint8_t)( INADDR_LOOPBACK & 0xFF ); + DebugSNPrintF( s, sizeof( s ), "%##a", &sa4 ); + require_action( strcmp( s, "127.0.0.1:80" ) == 0, exit, err = -1 ); + } + #endif + + #if ( AF_INET6 ) + { + struct sockaddr_in6 sa6; + + memset( &sa6, 0, sizeof( sa6 ) ); + sa6.sin6_family = AF_INET6; + p = (uint8_t *) &sa6.sin6_port; + p[ 0 ] = (uint8_t)( ( 80 >> 8 ) & 0xFF ); + p[ 1 ] = (uint8_t)( 80 & 0xFF ); + sa6.sin6_addr.s6_addr[ 0 ] = 0xFE; + sa6.sin6_addr.s6_addr[ 1 ] = 0x80; + sa6.sin6_addr.s6_addr[ 15 ] = 0x01; + sa6.sin6_scope_id = 2; + DebugSNPrintF( s, sizeof( s ), "%##a", &sa6 ); + require_action( strcmp( s, "[FE80:0000:0000:0000:0000:0000:0000:0001%2]:80" ) == 0, exit, err = -1 ); + } + #endif + + // Unicode + + DebugSNPrintF(s, sizeof(s), "%.*s", 4, "tes" ); + require_action( strcmp( s, "tes" ) == 0, exit, err = kResponseErr ); + + DebugSNPrintF(s, sizeof(s), "%.*s", 4, "test" ); + require_action( strcmp( s, "test" ) == 0, exit, err = kResponseErr ); + + DebugSNPrintF(s, sizeof(s), "%.*s", 4, "testing" ); + require_action( strcmp( s, "test" ) == 0, exit, err = kResponseErr ); + + DebugSNPrintF(s, sizeof(s), "%.*s", 4, "te\xC3\xA9" ); + require_action( strcmp( s, "te\xC3\xA9" ) == 0, exit, err = kResponseErr ); + + DebugSNPrintF(s, sizeof(s), "%.*s", 4, "te\xC3\xA9ing" ); + require_action( strcmp( s, "te\xC3\xA9" ) == 0, exit, err = kResponseErr ); + + DebugSNPrintF(s, sizeof(s), "%.*s", 4, "tes\xC3\xA9ing" ); + require_action( strcmp( s, "tes" ) == 0, exit, err = kResponseErr ); + + DebugSNPrintF(s, sizeof(s), "%.*s", 4, "t\xed\x9f\xbf" ); + require_action( strcmp( s, "t\xed\x9f\xbf" ) == 0, exit, err = kResponseErr ); + + DebugSNPrintF(s, sizeof(s), "%.*s", 4, "t\xed\x9f\xbfing" ); + require_action( strcmp( s, "t\xed\x9f\xbf" ) == 0, exit, err = kResponseErr ); + + DebugSNPrintF(s, sizeof(s), "%.*s", 4, "te\xed\x9f\xbf" ); + require_action( strcmp( s, "te" ) == 0, exit, err = kResponseErr ); + + DebugSNPrintF(s, sizeof(s), "%.*s", 4, "te\xed\x9f\xbfing" ); + require_action( strcmp( s, "te" ) == 0, exit, err = kResponseErr ); + + DebugSNPrintF(s, sizeof(s), "%.*s", 7, "te\xC3\xA9\xed\x9f\xbfing" ); + require_action( strcmp( s, "te\xC3\xA9\xed\x9f\xbf" ) == 0, exit, err = kResponseErr ); + + DebugSNPrintF(s, sizeof(s), "%.*s", 6, "te\xC3\xA9\xed\x9f\xbfing" ); + require_action( strcmp( s, "te\xC3\xA9" ) == 0, exit, err = kResponseErr ); + + DebugSNPrintF(s, sizeof(s), "%.*s", 5, "te\xC3\xA9\xed\x9f\xbfing" ); + require_action( strcmp( s, "te\xC3\xA9" ) == 0, exit, err = kResponseErr ); + + #if ( TARGET_RT_BIG_ENDIAN ) + DebugSNPrintF( s, sizeof( s ), "%S", "\x00" "a" "\x00" "b" "\x00" "c" "\x00" "d" "\x00" "\x00" ); + require_action( strcmp( s, "abcd" ) == 0, exit, err = -1 ); + #else + DebugSNPrintF( s, sizeof( s ), "%S", "a" "\x00" "b" "\x00" "c" "\x00" "d" "\x00" "\x00" "\x00" ); + require_action( strcmp( s, "abcd" ) == 0, exit, err = -1 ); + #endif + + DebugSNPrintF( s, sizeof( s ), "%S", + "\xFE\xFF" "\x00" "a" "\x00" "b" "\x00" "c" "\x00" "d" "\x00" "\x00" ); // Big Endian BOM + require_action( strcmp( s, "abcd" ) == 0, exit, err = -1 ); + + DebugSNPrintF( s, sizeof( s ), "%S", + "\xFF\xFE" "a" "\x00" "b" "\x00" "c" "\x00" "d" "\x00" "\x00" "\x00" ); // Little Endian BOM + require_action( strcmp( s, "abcd" ) == 0, exit, err = -1 ); + + DebugSNPrintF( s, sizeof( s ), "%#S", "\x00" "a" "\x00" "b" "\x00" "c" "\x00" "d" "\x00" "\x00" ); // Big Endian + require_action( strcmp( s, "abcd" ) == 0, exit, err = -1 ); + + DebugSNPrintF( s, sizeof( s ), "%##S", "a" "\x00" "b" "\x00" "c" "\x00" "d" "\x00" "\x00" "\x00" ); // Little Endian + require_action( strcmp( s, "abcd" ) == 0, exit, err = -1 ); + + DebugSNPrintF( s, sizeof( s ), "%.*S", + 4, "\xFE\xFF" "\x00" "a" "\x00" "b" "\x00" "c" "\x00" "d" ); // Big Endian BOM + require_action( strcmp( s, "abc" ) == 0, exit, err = -1 ); + + DebugSNPrintF( s, sizeof( s ), "%.*S", + 4, "\xFF\xFE" "a" "\x00" "b" "\x00" "c" "\x00" "d" "\x00" ); // Little Endian BOM + require_action( strcmp( s, "abc" ) == 0, exit, err = -1 ); + + #if ( TARGET_RT_BIG_ENDIAN ) + DebugSNPrintF( s, sizeof( s ), "%.*S", 3, "\x00" "a" "\x00" "b" "\x00" "c" "\x00" "d" ); + require_action( strcmp( s, "abc" ) == 0, exit, err = -1 ); + #else + DebugSNPrintF( s, sizeof( s ), "%.*S", 3, "a" "\x00" "b" "\x00" "c" "\x00" "d" "\x00" ); + require_action( strcmp( s, "abc" ) == 0, exit, err = -1 ); + #endif + + DebugSNPrintF( s, sizeof( s ), "%#.*S", 3, "\x00" "a" "\x00" "b" "\x00" "c" "\x00" "d" ); // Big Endian + require_action( strcmp( s, "abc" ) == 0, exit, err = -1 ); + + DebugSNPrintF( s, sizeof( s ), "%##.*S", 3, "a" "\x00" "b" "\x00" "c" "\x00" "d" "\x00" ); // Little Endian + require_action( strcmp( s, "abc" ) == 0, exit, err = -1 ); + + // Misc + + DebugSNPrintF( s, sizeof( s ), "%U", "\x10\xb8\xa7\x6b" "\xad\x9d" "\xd1\x11" "\x80\xb4" "\x00\xc0\x4f\xd4\x30\xc8" ); + require_action( strcmp( s, "6ba7b810-9dad-11d1-80b4-00c04fd430c8" ) == 0, exit, err = -1 ); + + DebugSNPrintF( s, sizeof( s ), "%m", 0 ); + require_action( strcmp( s, "no error" ) == 0, exit, err = -1 ); + + DebugSNPrintF( s, sizeof( s ), "%lm", (long) 0 ); + require_action( strcmp( s, "no error" ) == 0, exit, err = -1 ); + + DebugSNPrintF( s, sizeof( s ), "\"%H\"", "\x6b\xa7\xb8\x10\x9d\xad\x11\xd1\x80\xb4\x00\xc0\x4f\xd4\x30\xc8", 16, 16 ); + DebugPrintF( kDebugLevelMax, "%s\n\n", s ); + + DebugSNPrintF( s, sizeof( s ), "\"%H\"", + "\x6b\xa7\xb8\x10\x9d\xad\x11\xd1\x80\xb4\x00\xc0\x4f\xd4\x30\xc8" + "\x6b\xa7\xb8\x10\x9d\xad\x11\xd1\x80\xb4\x00\xc0\x4f\xd4\x30\xc8", + 32, 32 ); + DebugPrintF( kDebugLevelMax, "%s\n\n", s ); + + DebugSNPrintF( s, sizeof( s ), "\"%H\"", "\x6b\xa7", 2, 2 ); + DebugPrintF( kDebugLevelMax, "%s\n\n", s ); + + // Hex Dumps + + s[ 0 ] = '\0'; + DebugHexDump( kDebugLevelMax, 0, "My Label", kSizeCString, 0, NULL, 0, data, data, sizeof( data ), + kDebugFlagsNone, s, sizeof( s ) ); + DebugPrintF( kDebugLevelMax, "%s\n", s ); + + s[ 0 ] = '\0'; + DebugHexDump( kDebugLevelMax, 0, NULL, 0, 0, NULL, 0, data, data, sizeof( data ), + kDebugFlagsNoAddress | kDebugFlagsNoOffset, s, sizeof( s ) ); + DebugPrintF( kDebugLevelMax, "%s\n", s ); + + s[ 0 ] = '\0'; + DebugHexDump( kDebugLevelMax, 0, "My Label", kSizeCString, 0, NULL, 0, data, data, sizeof( data ), + kDebugFlagsNoAddress | kDebugFlagsNoOffset, s, sizeof( s ) ); + DebugPrintF( kDebugLevelMax, "%s\n", s ); + + s[ 0 ] = '\0'; + DebugHexDump( kDebugLevelMax, 0, "My Label", kSizeCString, 0, NULL, 0, data, data, sizeof( data ), + kDebugFlagsNoAddress, s, sizeof( s ) ); + DebugPrintF( kDebugLevelMax, "%s\n", s ); + + s[ 0 ] = '\0'; + DebugHexDump( kDebugLevelMax, 0, NULL, 0, 0, NULL, 0, data, data, sizeof( data ), + kDebugFlagsNoOffset, s, sizeof( s ) ); + DebugPrintF( kDebugLevelMax, "%s\n", s ); + + s[ 0 ] = '\0'; + DebugHexDump( kDebugLevelMax, 0, NULL, 0, 0, NULL, 0, data, data, sizeof( data ), + kDebugFlagsNoAddress, s, sizeof( s ) ); + DebugPrintF( kDebugLevelMax, "%s\n", s ); + + s[ 0 ] = '\0'; + DebugHexDump( kDebugLevelMax, 0, NULL, 0, 0, NULL, 0, data, data, sizeof( data ), + kDebugFlagsNoOffset, s, sizeof( s ) ); + DebugPrintF( kDebugLevelMax, "%s\n", s ); + + s[ 0 ] = '\0'; + DebugHexDump( kDebugLevelMax, 0, NULL, 0, 0, NULL, 0, data, data, sizeof( data ), + kDebugFlagsNoByteCount, s, sizeof( s ) ); + DebugPrintF( kDebugLevelMax, "%s\n", s ); + + s[ 0 ] = '\0'; + DebugHexDump( kDebugLevelMax, 0, NULL, 0, 0, NULL, 0, "\x41\x62\x43\x64", "\x41\x62\x43\x64", 4, // 'AbCd' + kDebugFlagsNoAddress | kDebugFlagsNoOffset | kDebugFlagsNoNewLine | + kDebugFlagsNo32BitSeparator | kDebugFlagsNo16ByteHexPad | kDebugFlagsNoByteCount, + s, sizeof( s ) ); + DebugPrintF( kDebugLevelMax, "%s\n", s ); + + s[ 0 ] = '\0'; + DebugHexDump( kDebugLevelMax, 0, NULL, 0, 0, NULL, 0, data, data, sizeof( data ), + kDebugFlagsNoAddress | kDebugFlagsNoOffset | kDebugFlagsNoASCII | kDebugFlagsNoNewLine | + kDebugFlags16BitSeparator | kDebugFlagsNo32BitSeparator | + kDebugFlagsNo16ByteHexPad | kDebugFlagsNoByteCount, s, sizeof( s ) ); + DebugPrintF( kDebugLevelMax, "%s\n", s ); + + s[ 0 ] = '\0'; + DebugHexDump( kDebugLevelMax, 8, NULL, 0, 0, NULL, 0, data, data, sizeof( data ), kDebugFlagsNone, s, sizeof( s ) ); + DebugPrintF( kDebugLevelMax, "%s\n", s ); + + // dlog's + + dlog( kDebugLevelNotice, "dlog\n" ); + dlog( kDebugLevelNotice, "dlog integer: %d\n", 123 ); + dlog( kDebugLevelNotice, "dlog string: \"%s\"\n", "test string" ); + dlogmem( kDebugLevelNotice, data, sizeof( data ) ); + + // Done + + DebugPrintF( kDebugLevelMax, "\n\nALL TESTS DONE\n\n" ); + err = kNoErr; + +exit: + if( err ) + { + DebugPrintF( kDebugLevelMax, "\n\n### TEST FAILED ###\n\n" ); + } + return( err ); +} + +#endif // DEBUG diff --git a/mDNSResponder/mDNSShared/DebugServices.h b/mDNSResponder/mDNSShared/DebugServices.h new file mode 100644 index 00000000..108f7f5f --- /dev/null +++ b/mDNSResponder/mDNSShared/DebugServices.h @@ -0,0 +1,1607 @@ +/* -*- Mode: C; tab-width: 4 -*- + * + * Copyright (c) 1997-2004 Apple Computer, Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @header DebugServices + + Debugging Library + */ + +#ifndef __DEBUG_SERVICES__ +#define __DEBUG_SERVICES__ + +#include <stdarg.h> + +#include "CommonServices.h" + +#if ( TARGET_OS_VXWORKS ) + #include "logLib.h" +#endif + +#if 0 +#pragma mark == Settings == +#endif + +//=========================================================================================================================== +// Settings +//=========================================================================================================================== + +// General + +#if ( !defined( DEBUG ) ) + #define DEBUG 0 +#endif + +#if ( defined( NDEBUG ) && DEBUG ) + #error NDEBUG defined and DEBUG is also enabled...they need to be in-sync +#endif + +// AssertMacros.h/Debugging.h overrides. + +#if ( !defined( DEBUG_OVERRIDE_APPLE_MACROS ) ) + #define DEBUG_OVERRIDE_APPLE_MACROS 1 +#endif + +// Routine name. Uses ISO __func__ where possible. Otherwise, uses the best thing that is available (if anything). + +#if ( defined( __MWERKS__ ) || ( __GNUC__ > 2 ) || ( ( __GNUC__ == 2 ) && ( __GNUC_MINOR__ >= 9 ) ) ) + #define __ROUTINE__ __func__ +#elif ( defined( __GNUC__ ) ) + #define __ROUTINE__ __PRETTY_FUNCTION__ +#elif ( defined( _MSC_VER ) && !defined( _WIN32_WCE ) ) + #define __ROUTINE__ __FUNCTION__ +#else + #define __ROUTINE__ "" +#endif + +// Variable argument macro support. Use ANSI C99 __VA_ARGS__ where possible. Otherwise, use the next best thing. + +#if ( defined( __GNUC__ ) ) + #if ( ( __GNUC__ > 3 ) || ( ( __GNUC__ == 3 ) && ( __GNUC_MINOR__ >= 3) ) ) + #define DEBUG_C99_VA_ARGS 1 + #define DEBUG_GNU_VA_ARGS 0 + #else + #define DEBUG_C99_VA_ARGS 0 + #define DEBUG_GNU_VA_ARGS 1 + #endif +#elif ( defined( __MWERKS__ ) ) + #define DEBUG_C99_VA_ARGS 1 + #define DEBUG_GNU_VA_ARGS 0 +#else + #define DEBUG_C99_VA_ARGS 0 + #define DEBUG_GNU_VA_ARGS 0 +#endif + +#if 0 +#pragma mark == Output == +#endif + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @defined DEBUG_FPRINTF_ENABLED + + @abstract Enables ANSI C fprintf output. + */ + +#if ( !defined( DEBUG_FPRINTF_ENABLED ) ) + #if ( !TARGET_API_MAC_OSX_KERNEL && !TARGET_OS_WINDOWS_CE ) + #define DEBUG_FPRINTF_ENABLED 1 + #else + #define DEBUG_FPRINTF_ENABLED 0 + #endif +#else + #if ( TARGET_API_MAC_OSX_KERNEL || TARGET_OS_WINDOWS_CE ) + #error fprintf enabled, but not supported on Mac OS X kernel or Windows CE + #endif +#endif + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @defined DEBUG_MAC_OS_X_IOLOG_ENABLED + + @abstract Enables IOLog (Mac OS X Kernel) output. + */ + +#if ( !defined( DEBUG_MAC_OS_X_IOLOG_ENABLED ) ) + #define DEBUG_MAC_OS_X_IOLOG_ENABLED TARGET_API_MAC_OSX_KERNEL +#endif + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @defined DEBUG_KPRINTF_ENABLED + + @abstract Enables kprintf (Mac OS X Kernel) output. + */ + +#if ( !defined( DEBUG_KPRINTF_ENABLED ) ) + #define DEBUG_KPRINTF_ENABLED TARGET_API_MAC_OSX_KERNEL +#endif + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @defined DEBUG_IDEBUG_ENABLED + + @abstract Enables iDebug (Mac OS X user and Kernel) output. + + @discussion + + For Mac OS X kernel development, iDebug is enabled by default because we can dynamically check for the presence + of iDebug via some exported IOKit symbols. Mac OS X app usage doesn't allow dynamic detection because it relies + on statically linking to the iDebugServices.cp file so for Mac OS X app usage, you have to manually enable iDebug. + */ + +#if ( !defined( DEBUG_IDEBUG_ENABLED ) ) + #define DEBUG_IDEBUG_ENABLED TARGET_API_MAC_OSX_KERNEL +#endif + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @defined DEBUG_CORE_SERVICE_ASSERTS_ENABLED + + @abstract Controls whether Core Services assert handling is enabled. Enabling requires CoreServices framework. + */ + +#if ( !defined( DEBUG_CORE_SERVICE_ASSERTS_ENABLED ) ) + #if ( defined( __DEBUGGING__ ) ) + #define DEBUG_CORE_SERVICE_ASSERTS_ENABLED 1 + #else + #define DEBUG_CORE_SERVICE_ASSERTS_ENABLED 0 + #endif +#endif + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @typedef DebugOutputType + + @abstract Type of debug output (i.e. where the output goes). + */ + +typedef uint32_t DebugOutputType; + +#define kDebugOutputTypeNone 0x6E6F6E65U // 'none' - no params +#define kDebugOutputTypeCustom 0x63757374U // 'cust' - 1st param = function ptr, 2nd param = context +#define kDebugOutputTypeFPrintF 0x66707269U // 'fpri' - 1st param = DebugOutputTypeFlags [, 2nd param = filename] +#define kDebugOutputTypeiDebug 0x69646267U // 'idbg' - no params +#define kDebugOutputTypeKPrintF 0x6B707266U // 'kprf' - no params +#define kDebugOutputTypeMacOSXIOLog 0x696C6F67U // 'ilog' - no params +#define kDebugOutputTypeMacOSXLog 0x786C6F67U // 'xlog' - no params +#define kDebugOutputTypeWindowsDebugger 0x77696E64U // 'wind' - no params +#define kDebugOutputTypeWindowsEventLog 0x7765766CU // 'wevl' - 1st param = C-string name, 2nd param = HMODULE or NULL. + +// Console meta output kind - Any kind of Console output (in horizontal order of preference): +// +// Mac OS X = ANSI printf (viewable in Console.app) +// Mac OS X Kernel = IOLog (/var/log/system.log) or kprintf (serial). +// Windows = ANSI printf (Console window) or OutputDebugString (debugger). +// Other = ANSI printf (viewer varies). + +#define kDebugOutputTypeMetaConsole 0x434F4E53U // 'CONS' - no params + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @typedef DebugOutputTypeFlags + + @abstract Flags controlling how the output type is configured. + + @constant kDebugOutputTypeFlagsTypeMask Bit mask for the output type (e.g. stdout, stderr, file, etc.). + @constant kDebugOutputTypeFlagsStdOut fprintf should go to stdout. + @constant kDebugOutputTypeFlagsStdErr fprintf should go to stderr. + @constant kDebugOutputTypeFlagsFile fprintf should go to a specific file (filename passed as va_arg). + */ + +typedef unsigned int DebugOutputTypeFlags; + +#define kDebugOutputTypeFlagsTypeMask 0xF +#define kDebugOutputTypeFlagsStdOut 1 +#define kDebugOutputTypeFlagsStdErr 2 +#define kDebugOutputTypeFlagsFile 10 + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @typedef DebugOutputFunctionPtr + + @abstract Function ptr for a custom callback to print debug output. + */ + +typedef void ( *DebugOutputFunctionPtr )( char *inData, size_t inSize, void *inContext ); + +//=========================================================================================================================== +// Constants +//=========================================================================================================================== + +#if 0 +#pragma mark == Flags == +#endif + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @typedef DebugFlags + + @abstract Flags controlling how output is printed. + */ + +typedef uint32_t DebugFlags; + +#define kDebugFlagsNone 0 +#define kDebugFlagsNoAddress ( 1 << 0 ) +#define kDebugFlagsNoOffset ( 1 << 1 ) +#define kDebugFlags32BitOffset ( 1 << 2 ) +#define kDebugFlagsNoASCII ( 1 << 3 ) +#define kDebugFlagsNoNewLine ( 1 << 4 ) +#define kDebugFlags8BitSeparator ( 1 << 5 ) +#define kDebugFlags16BitSeparator ( 1 << 6 ) +#define kDebugFlagsNo32BitSeparator ( 1 << 7 ) +#define kDebugFlagsNo16ByteHexPad ( 1 << 8 ) +#define kDebugFlagsNoByteCount ( 1 << 9 ) + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @enum DebugTaskLevelFlags + + @abstract Flags indicating the task level. + */ + +enum +{ + kDebugInterruptLevelShift = 0, + kDebugInterruptLevelMask = 0x00000007, + kDebugInVBLTaskMask = 0x00000010, + kDebugInDeferredTaskMask = 0x00000020, + kDebugInSecondaryInterruptHandlerMask = 0x00000040, + kDebugPageFaultFatalMask = 0x00000100, // There should be a "kPageFaultFatalMask" in Debugging.h. + kDebugMPTaskLevelMask = 0x00000200, // There should be a "kMPTaskLevelMask" in Debugging.h. + kDebugInterruptDepthShift = 16, + kDebugInterruptDepthMask = 0x00FF0000 +}; + +#define DebugExtractTaskLevelInterruptLevel( LEVEL ) \ + ( ( ( LEVEL ) &kDebugInterruptLevelMask ) >> kDebugInterruptLevelShift ) + +#define DebugExtractTaskLevelInterruptDepth( LEVEL ) \ + ( ( ( LEVEL ) &kDebugInterruptDepthMask ) >> kDebugInterruptDepthShift ) + +#if 0 +#pragma mark == Levels == +#endif + +//=========================================================================================================================== +// Constants & Types - Levels +//=========================================================================================================================== + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @typedef DebugLevel + + @abstract Level used to control debug logging. + */ + +typedef int32_t DebugLevel; + +// Levels + +#define kDebugLevelMask 0x0000FFFF +#define kDebugLevelChatty 100 +#define kDebugLevelVerbose 500 +#define kDebugLevelTrace 800 +#define kDebugLevelInfo 1000 +#define kDebugLevelNotice 3000 +#define kDebugLevelWarning 5000 +#define kDebugLevelAssert 6000 +#define kDebugLevelRequire 7000 +#define kDebugLevelError 8000 +#define kDebugLevelCritical 9000 +#define kDebugLevelAlert 10000 +#define kDebugLevelEmergency 11000 +#define kDebugLevelTragic 12000 +#define kDebugLevelMax 0x0000FFFF + +// Level Flags + +#define kDebugLevelFlagMask 0xFFFF0000 +#define kDebugLevelFlagStackTrace 0x00010000 +#define kDebugLevelFlagDebugBreak 0x00020000 + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @typedef LogLevel + + @abstract Level used to control which events are logged. + */ + +typedef int32_t LogLevel; + +#define kLogLevelUninitialized -1L +#define kLogLevelAll 0L +#define kLogLevelChatty 100L +#define kLogLevelVerbose 500L +#define kLogLevelTrace 800L +#define kLogLevelInfo 1000L +#define kLogLevelNotice 3000L +#define kLogLevelWarning 4000L +#define kLogLevelAssert 6000L +#define kLogLevelRequire 7000L +#define kLogLevelError 8000L +#define kLogLevelCritical 9000L +#define kLogLevelAlert 10000L +#define kLogLevelEmergency 11000L +#define kLogLevelTragic 12000L +#define kLogLevelOff 0x0000FFFEL + +#if 0 +#pragma mark == Properties == +#endif + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @typedef DebugPropertyTag + + @abstract Tag for properties. + */ + +typedef uint32_t DebugPropertyTag; + +#define kDebugPropertyTagPrintLevelMin 0x6D696E70U // 'minp' Get: 1st param = DebugLevel * + // Set: 1st param = DebugLevel + +#define kDebugPropertyTagPrintLevel kDebugPropertyTagPrintLevelMin + +#define kDebugPropertyTagPrintLevelMax 0x706D786CU // 'maxp' Get: 1st param = DebugLevel * + // Set: 1st param = DebugLevel + +#define kDebugPropertyTagBreakLevel 0x62726B6CU // 'brkl' Get: 1st param = DebugLevel * + // Set: 1st param = DebugLevel +#if 0 +#pragma mark == General macros == +#endif + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @defined DEBUG_UNUSED + + @abstract Macro to mark a paramter as unused to avoid unused parameter warnings. + + @discussion + + There is no universally supported pragma/attribute for indicating a variable is unused. DEBUG_UNUSED lets us + indicate a variable is unused in a manner that is supported by most compilers. + */ + +#define DEBUG_UNUSED( X ) (void)( X ) + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @defined DEBUG_USE_ONLY + + @abstract Macro to mark a variable as used only when debugging is enabled. + + @discussion + + Variables are sometimes needed only for debugging. When debugging is turned off, these debug-only variables generate + compiler warnings about unused variables. To eliminate these warnings, use these macros to indicate variables that + are only used for debugging. + */ + +#if ( DEBUG ) + #define DEBUG_USE_ONLY( X ) +#else + #define DEBUG_USE_ONLY( X ) (void)( X ) +#endif + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @defined DEBUG_LOCAL + + @abstract Macros to make variables and functions static when debugging is off, but extern when debugging is on. + + @discussion + + Rather than using "static" directly, using this macros allows you to access these variables external while + debugging without being penalized for production builds. + */ + +#if ( DEBUG ) + #define DEBUG_LOCAL +#else + #define DEBUG_LOCAL static +#endif + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @defined DEBUG_STATIC + + @abstract Macros to make variables and functions static when debugging is off, but extern when debugging is on. + + @discussion + + Rather than using "static" directly, using this macros allows you to access these variables external while + debugging without being penalized for production builds. + */ + +#if ( DEBUG ) + #define DEBUG_STATIC +#else + #define DEBUG_STATIC static +#endif + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @defined DEBUG_EXPORT + + @abstract Macros to export variables. + + @discussion + + "__private_extern__" is a hack for IOKit to allow symbols to be exported from compilation units, but + // not exported outside a driver (IOKit uses a lame global namespace for symbols). This still does not + // solve the problem of multiple drivers in the same dependency chain since they share symbols. + */ + +#if ( TARGET_API_MAC_OSX_KERNEL ) + #define DEBUG_EXPORT __private_extern__ +#else + #define DEBUG_EXPORT extern +#endif + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @defined debug_add + + @abstract Macro to add (or subtract if negative) a value when debugging is on. Does nothing if debugging is off. + */ + +#if ( DEBUG ) + #define debug_add( A, B ) ( A ) += ( B ) +#else + #define debug_add( A, B ) +#endif + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @defined debug_perform + + @abstract Macro to perform something in debug-only builds. + */ + +#if ( DEBUG ) + #define debug_perform( X ) do { X; } while( 0 ) +#else + #define debug_perform( X ) +#endif + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @function translate_errno + + @abstract Returns 0 if the test success. If the test fails, returns errno if non-zero and othewise the alternate error. + */ + +#define translate_errno( TEST, ERRNO, ALTERNATE_ERROR ) ( ( TEST ) ? 0 : ( ERRNO ) ? ( ERRNO ) : ( ALTERNATE_ERROR ) ) + +#if 0 +#pragma mark == Compile Time macros == +#endif + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @defined check_compile_time + + @abstract Performs a compile-time check of something such as the size of an int. + + @discussion + + This declares an array with a size that is determined by a compile-time expression. If the expression evaluates + to 0, the array has a size of -1, which is illegal and generates a compile-time error. + + For example: + + check_compile_time( sizeof( int ) == 4 ); + + Note: This only works with compile-time expressions. + Note: This only works in places where extern declarations are allowed (e.g. global scope). + + References: + + <http://www.jaggersoft.com/pubs/CVu11_3.html> + <http://www.jaggersoft.com/pubs/CVu11_5.html> + + Note: The following macros differ from the macros on the www.jaggersoft.com web site because those versions do not + work with GCC due to GCC allow a zero-length array. Using a -1 condition turned out to be more portable. + */ + +#define check_compile_time( X ) extern int debug_compile_time_name[ ( X ) ? 1 : -1 ] + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @defined check_compile_time_code + + @abstract Perform a compile-time check, suitable for placement in code, of something such as the size of an int. + + @discussion + + This creates a switch statement with an existing case for 0 and an additional case using the result of a + compile-time expression. A switch statement cannot have two case labels with the same constant so if the + compile-time expression evaluates to 0, it is illegal and generates a compile-time error. If the compile-time + expression does not evaluate to 0, the resulting value is used as the case label and it compiles without error. + + For example: + + check_compile_time_code( sizeof( int ) == 4 ); + + Note: This only works with compile-time expressions. + Note: This does not work in a global scope so it must be inside a function. + + References: + + <http://www.jaggersoft.com/pubs/CVu11_3.html> + <http://www.jaggersoft.com/pubs/CVu11_5.html> + */ + +#define check_compile_time_code( X ) switch( 0 ) { case 0: case X:; } + +#if 0 +#pragma mark == check macros == +#endif + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @defined check + + @abstract Check that an expression is true (non-zero). + + @discussion + + If expression evalulates to false, this prints debugging information (actual expression string, file, line number, + function name, etc.) using the default debugging output method. + + Code inside check() statements is not compiled into production builds. + */ + +#if ( DEBUG_OVERRIDE_APPLE_MACROS ) + #undef check +#endif +#if ( !defined( check ) ) + #if ( DEBUG ) + #define check( X ) \ + do \ + { \ + if( !( X ) ) \ + { \ + debug_print_assert( 0, # X, NULL, __FILE__, __LINE__, __ROUTINE__ ); \ + } \ + } while( 0 ) + #else + #define check( X ) + #endif +#endif + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @defined check_string + + @abstract Check that an expression is true (non-zero) with an explanation. + + @discussion + + If expression evalulates to false, this prints debugging information (actual expression string, file, line number, + function name, etc.) and a custom explanation string using the default debugging output method. + + Code inside check_string() statements is not compiled into production builds. + */ + +#if ( DEBUG_OVERRIDE_APPLE_MACROS ) + #undef check_string +#endif +#if ( !defined( check_string ) ) + #if ( DEBUG ) + #define check_string( X, STR ) \ + do \ + { \ + if( !( X ) ) \ + { \ + debug_print_assert( 0, # X, STR, __FILE__, __LINE__, __ROUTINE__ ); \ + } \ + \ + } while( 0 ) + #else + #define check_string( X, STR ) + #endif +#endif + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @defined check_noerr + + @abstract Check that an error code is noErr (0). + + @discussion + + If the error code is non-0, this prints debugging information (actual expression string, file, line number, + function name, etc.) using the default debugging output method. + + Code inside check_noerr() statements is not compiled into production builds. + */ + +#if ( DEBUG_OVERRIDE_APPLE_MACROS ) + #undef check_noerr +#endif +#if ( !defined( check_noerr ) ) + #if ( DEBUG ) + #define check_noerr( ERR ) \ + do \ + { \ + int_least32_t localErr; \ + \ + localErr = (int_least32_t)( ERR ); \ + if( localErr != 0 ) \ + { \ + debug_print_assert( localErr, NULL, NULL, __FILE__, __LINE__, __ROUTINE__ ); \ + } \ + \ + } while( 0 ) + #else + #define check_noerr( ERR ) + #endif +#endif + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @defined check_noerr_string + + @abstract Check that an error code is noErr (0) with an explanation. + + @discussion + + If the error code is non-0, this prints debugging information (actual expression string, file, line number, + function name, etc.) and a custom explanation string using the default debugging output method. + + Code inside check_noerr_string() statements is not compiled into production builds. + */ + +#if ( DEBUG_OVERRIDE_APPLE_MACROS ) + #undef check_noerr_string +#endif +#if ( !defined( check_noerr_string ) ) + #if ( DEBUG ) + #define check_noerr_string( ERR, STR ) \ + do \ + { \ + int_least32_t localErr; \ + \ + localErr = (int_least32_t)( ERR ); \ + if( localErr != 0 ) \ + { \ + debug_print_assert( localErr, NULL, STR, __FILE__, __LINE__, __ROUTINE__ ); \ + } \ + \ + } while( 0 ) + #else + #define check_noerr_string( ERR, STR ) + #endif +#endif + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @defined check_translated_errno + + @abstract Check a condition and prints errno (if non-zero) to the log. + + @discussion + + Code inside check_translated_errno() statements is not compiled into production builds. + */ + +#if ( !defined( check_translated_errno ) ) + #if ( DEBUG ) + #define check_translated_errno( TEST, ERRNO, ALTERNATE_ERROR ) \ + do \ + { \ + if( !( TEST ) ) \ + { \ + int_least32_t localErr; \ + \ + localErr = (int_least32_t)( ERRNO ); \ + localErr = ( localErr != 0 ) ? localErr : (int_least32_t)( ALTERNATE_ERROR ); \ + debug_print_assert( localErr, # TEST, NULL, __FILE__, __LINE__, __ROUTINE__ ); \ + } \ + \ + } while( 0 ) + #else + #define check_translated_errno( TEST, ERRNO, ALTERNATE_ERROR ) + #endif +#endif + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @defined check_ptr_overlap + + @abstract Checks that two ptrs do not overlap. + */ + +#define check_ptr_overlap( P1, P1_SIZE, P2, P2_SIZE ) \ + do \ + { \ + check( !( ( (uintptr_t)( P1 ) >= (uintptr_t)( P2 ) ) && \ + ( (uintptr_t)( P1 ) < ( ( (uintptr_t)( P2 ) ) + ( P2_SIZE ) ) ) ) ); \ + check( !( ( (uintptr_t)( P2 ) >= (uintptr_t)( P1 ) ) && \ + ( (uintptr_t)( P2 ) < ( ( (uintptr_t)( P1 ) ) + ( P1_SIZE ) ) ) ) ); \ + \ + } while( 0 ) + +#if 0 +#pragma mark == require macros == +#endif + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @defined require + + @abstract Requires that an expression evaluate to true. + + @discussion + + If expression evalulates to false, this prints debugging information (actual expression string, file, line number, + function name, etc.) using the default debugging output method then jumps to a label. + */ + +#if ( DEBUG_OVERRIDE_APPLE_MACROS ) + #undef require +#endif +#if ( !defined( require ) ) + #define require( X, LABEL ) \ + do \ + { \ + if( !( X ) ) \ + { \ + debug_print_assert( 0, # X, NULL, __FILE__, __LINE__, __ROUTINE__ ); \ + goto LABEL; \ + } \ + \ + } while( 0 ) +#endif + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @defined require_string + + @abstract Requires that an expression evaluate to true with an explanation. + + @discussion + + If expression evalulates to false, this prints debugging information (actual expression string, file, line number, + function name, etc.) and a custom explanation string using the default debugging output method then jumps to a label. + */ + +#if ( DEBUG_OVERRIDE_APPLE_MACROS ) + #undef require_string +#endif +#if ( !defined( require_string ) ) + #define require_string( X, LABEL, STR ) \ + do \ + { \ + if( !( X ) ) \ + { \ + debug_print_assert( 0, # X, STR, __FILE__, __LINE__, __ROUTINE__ ); \ + goto LABEL; \ + } \ + \ + } while( 0 ) +#endif + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @defined require_quiet + + @abstract Requires that an expression evaluate to true. + + @discussion + + If expression evalulates to false, this jumps to a label. No debugging information is printed. + */ + +#if ( DEBUG_OVERRIDE_APPLE_MACROS ) + #undef require_quiet +#endif +#if ( !defined( require_quiet ) ) + #define require_quiet( X, LABEL ) \ + do \ + { \ + if( !( X ) ) \ + { \ + goto LABEL; \ + } \ + \ + } while( 0 ) +#endif + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @defined require_noerr + + @abstract Require that an error code is noErr (0). + + @discussion + + If the error code is non-0, this prints debugging information (actual expression string, file, line number, + function name, etc.) using the default debugging output method then jumps to a label. + */ + +#if ( DEBUG_OVERRIDE_APPLE_MACROS ) + #undef require_noerr +#endif +#if ( !defined( require_noerr ) ) + #define require_noerr( ERR, LABEL ) \ + do \ + { \ + int_least32_t localErr; \ + \ + localErr = (int_least32_t)( ERR ); \ + if( localErr != 0 ) \ + { \ + debug_print_assert( localErr, NULL, NULL, __FILE__, __LINE__, __ROUTINE__ ); \ + goto LABEL; \ + } \ + \ + } while( 0 ) +#endif + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @defined require_noerr_string + + @abstract Require that an error code is noErr (0). + + @discussion + + If the error code is non-0, this prints debugging information (actual expression string, file, line number, + function name, etc.), and a custom explanation string using the default debugging output method using the + default debugging output method then jumps to a label. + */ + +#if ( DEBUG_OVERRIDE_APPLE_MACROS ) + #undef require_noerr_string +#endif +#if ( !defined( require_noerr_string ) ) + #define require_noerr_string( ERR, LABEL, STR ) \ + do \ + { \ + int_least32_t localErr; \ + \ + localErr = (int_least32_t)( ERR ); \ + if( localErr != 0 ) \ + { \ + debug_print_assert( localErr, NULL, STR, __FILE__, __LINE__, __ROUTINE__ ); \ + goto LABEL; \ + } \ + \ + } while( 0 ) +#endif + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @defined require_noerr_action_string + + @abstract Require that an error code is noErr (0). + + @discussion + + If the error code is non-0, this prints debugging information (actual expression string, file, line number, + function name, etc.), and a custom explanation string using the default debugging output method using the + default debugging output method then executes an action and jumps to a label. + */ + +#if ( DEBUG_OVERRIDE_APPLE_MACROS ) + #undef require_noerr_action_string +#endif +#if ( !defined( require_noerr_action_string ) ) + #define require_noerr_action_string( ERR, LABEL, ACTION, STR ) \ + do \ + { \ + int_least32_t localErr; \ + \ + localErr = (int_least32_t)( ERR ); \ + if( localErr != 0 ) \ + { \ + debug_print_assert( localErr, NULL, STR, __FILE__, __LINE__, __ROUTINE__ ); \ + { ACTION; } \ + goto LABEL; \ + } \ + \ + } while( 0 ) +#endif + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @defined require_noerr_quiet + + @abstract Require that an error code is noErr (0). + + @discussion + + If the error code is non-0, this jumps to a label. No debugging information is printed. + */ + +#if ( DEBUG_OVERRIDE_APPLE_MACROS ) + #undef require_noerr_quiet +#endif +#if ( !defined( require_noerr_quiet ) ) + #define require_noerr_quiet( ERR, LABEL ) \ + do \ + { \ + if( ( ERR ) != 0 ) \ + { \ + goto LABEL; \ + } \ + \ + } while( 0 ) +#endif + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @defined require_noerr_action + + @abstract Require that an error code is noErr (0) with an action to execute otherwise. + + @discussion + + If the error code is non-0, this prints debugging information (actual expression string, file, line number, + function name, etc.) using the default debugging output method then executes an action and jumps to a label. + */ + +#if ( DEBUG_OVERRIDE_APPLE_MACROS ) + #undef require_noerr_action +#endif +#if ( !defined( require_noerr_action ) ) + #define require_noerr_action( ERR, LABEL, ACTION ) \ + do \ + { \ + int_least32_t localErr; \ + \ + localErr = (int_least32_t)( ERR ); \ + if( localErr != 0 ) \ + { \ + debug_print_assert( localErr, NULL, NULL, __FILE__, __LINE__, __ROUTINE__ ); \ + { ACTION; } \ + goto LABEL; \ + } \ + \ + } while( 0 ) +#endif + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @defined require_noerr_action_quiet + + @abstract Require that an error code is noErr (0) with an action to execute otherwise. + + @discussion + + If the error code is non-0, this executes an action and jumps to a label. No debugging information is printed. + */ + +#if ( DEBUG_OVERRIDE_APPLE_MACROS ) + #undef require_noerr_action_quiet +#endif +#if ( !defined( require_noerr_action_quiet ) ) + #define require_noerr_action_quiet( ERR, LABEL, ACTION ) \ + do \ + { \ + if( ( ERR ) != 0 ) \ + { \ + { ACTION; } \ + goto LABEL; \ + } \ + \ + } while( 0 ) +#endif + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @defined require_action + + @abstract Requires that an expression evaluate to true with an action to execute otherwise. + + @discussion + + If expression evalulates to false, this prints debugging information (actual expression string, file, line number, + function name, etc.) using the default debugging output method then executes an action and jumps to a label. + */ + +#if ( DEBUG_OVERRIDE_APPLE_MACROS ) + #undef require_action +#endif +#if ( !defined( require_action ) ) + #define require_action( X, LABEL, ACTION ) \ + do \ + { \ + if( !( X ) ) \ + { \ + debug_print_assert( 0, # X, NULL, __FILE__, __LINE__, __ROUTINE__ ); \ + { ACTION; } \ + goto LABEL; \ + } \ + \ + } while( 0 ) +#endif + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @defined require_action_quiet + + @abstract Requires that an expression evaluate to true with an action to execute otherwise. + + @discussion + + If expression evalulates to false, this executes an action and jumps to a label. No debugging information is printed. + */ + +#if ( DEBUG_OVERRIDE_APPLE_MACROS ) + #undef require_action_quiet +#endif +#if ( !defined( require_action_quiet ) ) + #define require_action_quiet( X, LABEL, ACTION ) \ + do \ + { \ + if( !( X ) ) \ + { \ + { ACTION; } \ + goto LABEL; \ + } \ + \ + } while( 0 ) +#endif + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @defined require_action_string + + @abstract Requires that an expression evaluate to true with an explanation and action to execute otherwise. + + @discussion + + If expression evalulates to false, this prints debugging information (actual expression string, file, line number, + function name, etc.) and a custom explanation string using the default debugging output method then executes an + action and jumps to a label. + */ + +#if ( DEBUG_OVERRIDE_APPLE_MACROS ) + #undef require_action_string +#endif +#if ( !defined( require_action_string ) ) + #define require_action_string( X, LABEL, ACTION, STR ) \ + do \ + { \ + if( !( X ) ) \ + { \ + debug_print_assert( 0, # X, STR, __FILE__, __LINE__, __ROUTINE__ ); \ + { ACTION; } \ + goto LABEL; \ + } \ + \ + } while( 0 ) + +#endif + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @defined require_throw + + @abstract Requires that an expression evaluates to true or an exception is thrown. + + @discussion + + If the expression evaluates to false, this prints debugging information (actual expression string, file, + line number, function name, etc.) using the default debugging output method then throws an exception. + */ + +#if ( defined( __cplusplus ) ) + #define require_throw( X ) \ + do \ + { \ + if( !( X ) ) \ + { \ + debug_print_assert( 0, # X, NULL, __FILE__, __LINE__, __ROUTINE__ ); \ + throw kUnknownErr; \ + } \ + \ + } while( 0 ) +#endif + +#if 0 +#pragma mark == Design-By-Contract macros == +#endif + +//=========================================================================================================================== +// Design-By-Contract macros +//=========================================================================================================================== + +#define ensure( X ) check( X ) +#define ensure_string( X, STR ) check_string( X, STR ) +#define ensure_noerr( ERR ) check_noerr( ERR ) +#define ensure_noerr_string( ERR, STR ) check_noerr_string( ERR, STR ) +#define ensure_translated_errno( TEST, ERRNO, ALTERNATE_ERROR ) check_translated_errno( TEST, ERRNO, ALTERNATE_ERROR ) + +// Note: Design-By-Contract "require" macros are already defined elsewhere. + +#if 0 +#pragma mark == Expect macros == +#endif + +//=========================================================================================================================== +// Expect macros +//=========================================================================================================================== + +// Expect macros allow code to include runtime checking of things that should not happen in shipping code (e.g. internal +// programmer errors, such as a NULL parameter where it is not allowed). Once the code has been verified to work correctly +// without asserting, the DEBUG_EXPECT_VERIFIED conditional can be set to eliminate the error checking entirely. It can +// also be useful to measure the cost of error checking code by profiling with it enable and with it disabled. + +#if ( DEBUG_EXPECT_VERIFIED ) + #define require_expect + #define require_string_expect + #define require_quiet_expect + #define require_noerr_expect + #define require_noerr_string_expect + #define require_noerr_action_string_expect + #define require_noerr_quiet_expect + #define require_noerr_action_expect + #define require_noerr_action_quiet_expect + #define require_action_expect + #define require_action_quiet_expect + #define require_action_string_expect +#else + #define require_expect require + #define require_string_expect require_string + #define require_quiet_expect require_quiet + #define require_noerr_expect require_noerr + #define require_noerr_string_expect require_noerr_string + #define require_noerr_action_string_expect require_noerr_action_string + #define require_noerr_quiet_expect require_noerr_quiet + #define require_noerr_action_expect require_noerr_action + #define require_noerr_action_quiet_expect require_noerr_action_quiet + #define require_action_expect require_action + #define require_action_quiet_expect require_action_quiet + #define require_action_string_expect require_action_string +#endif + +#if 0 +#pragma mark == Output macros == +#endif + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @defined debug_string + + @abstract Prints a debugging C string. + */ + +#if ( DEBUG_OVERRIDE_APPLE_MACROS ) + #undef debug_string +#endif +#if ( !defined( debug_string ) ) + #if ( DEBUG ) + #define debug_string( STR ) \ + do \ + { \ + debug_print_assert( 0, NULL, STR, __FILE__, __LINE__, __ROUTINE__ ); \ + \ + } while( 0 ) + #else + #define debug_string( STR ) + #endif +#endif + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @defined debug_print_assert + + @abstract Prints an assertion. + */ + +#if ( DEBUG ) + #define debug_print_assert( ERROR_CODE, ASSERT_STRING, MESSAGE, FILENAME, LINE_NUMBER, FUNCTION ) \ + DebugPrintAssert( ERROR_CODE, ASSERT_STRING, MESSAGE, FILENAME, LINE_NUMBER, FUNCTION ) +#else + #define debug_print_assert( ERROR_CODE, ASSERT_STRING, MESSAGE, FILENAME, LINE_NUMBER, FUNCTION ) +#endif + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @defined dlog + + @abstract Prints a debug-only message. + */ + +#if ( DEBUG ) + #if ( DEBUG_C99_VA_ARGS ) + #define dlog(... ) DebugPrintF( __VA_ARGS__ ) + #elif ( DEBUG_GNU_VA_ARGS ) + #define dlog( ARGS... ) DebugPrintF( ## ARGS ) + #else + #define dlog DebugPrintF + #endif +#else + #if ( DEBUG_C99_VA_ARGS ) + #define dlog(... ) + #elif ( DEBUG_GNU_VA_ARGS ) + #define dlog( ARGS... ) + #else + #define dlog while( 0 ) + #endif +#endif + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @defined dlogv + + @abstract Prints a debug-only message. + */ + +#if ( DEBUG ) + #define dlogv( LEVEL, FORMAT, LIST ) DebugPrintFVAList( ( LEVEL ), ( FORMAT ), ( LIST ) ) +#else + #define dlogv( LEVEL, FORMAT, LIST ) +#endif + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @defined dlogmem + + @abstract Prints a debug-only dump of memory. + */ + +#if ( DEBUG ) + #define dlogmem( LEVEL, PTR, SIZE ) \ + DebugHexDump( ( LEVEL ), 0, NULL, 0, 0, NULL, 0, ( PTR ), ( PTR ), ( SIZE ), kDebugFlagsNone, NULL, 0 ) +#else + #define dlogmem( LEVEL, PTR, SIZE ) +#endif + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @defined DebugNSLog + + @abstract Debug-only macro for the Cocoa NSLog function. + */ + +#if ( DEBUG ) + #if ( DEBUG_C99_VA_ARGS ) + #define DebugNSLog(... ) NSLog( __VA_ARGS__ ) + #elif ( DEBUG_GNU_VA_ARGS ) + #define DebugNSLog( ARGS... ) NSLog( ## ARGS ) + #else + #define DebugNSLog NSLog + #endif +#else + #if ( DEBUG_C99_VA_ARGS ) + #define DebugNSLog(... ) + #elif ( DEBUG_GNU_VA_ARGS ) + #define DebugNSLog( ARGS... ) + #else + #define DebugNSLog while( 0 ) + #endif +#endif + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @defined DebugLogMsg + + @abstract Debug-only macro for the VxWorks logMsg function. + */ + +#if ( TARGET_OS_VXWORKS ) + #if ( DEBUG ) + #define DebugLogMsg( LEVEL, FORMAT, P1, P2, P3, P4, P5, P6 ) \ + do \ + { \ + if( ( inLevel >= gDebugPrintLevelMin ) || ( inLevel <= gDebugPrintLevelMax ) ) \ + { \ + logMsg( ( FORMAT ), ( P1 ), ( P2 ), ( P3 ), ( P4 ), ( P5 ), ( P6 ) ); \ + } \ + \ + } while( 0 ) + #else + #define DebugLogMsg( LEVEL, FORMAT, P1, P2, P3, P4, P5, P6 ) + #endif +#else + #define DebugLogMsg dlog +#endif + +#if 0 +#pragma mark == Routines - General == +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @function DebugInitialize + + @abstract Initializes the debugging library for a specific kind of output. + + @param inType + @param varArg Variable number parameters, controlled by the "inType" parameter. + */ + +#if ( DEBUG ) +DEBUG_EXPORT OSStatus DebugInitialize( DebugOutputType inType, ... ); +#endif + +#if ( DEBUG ) + #if ( DEBUG_C99_VA_ARGS ) + #define debug_initialize(... ) DebugInitialize( __VA_ARGS__ ) + #elif ( DEBUG_GNU_VA_ARGS ) + #define debug_initialize( ARGS... ) DebugInitialize( ## ARGS ) + #else + #define debug_initialize DebugInitialize + #endif +#else + #if ( DEBUG_C99_VA_ARGS ) + #define debug_initialize(... ) + #elif ( DEBUG_GNU_VA_ARGS ) + #define debug_initialize( ARGS... ) + #else + #define debug_initialize while( 0 ) + #endif +#endif + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @function DebugFinalize + + @abstract Releases any resources used by the debugging library + */ + +#if ( DEBUG ) +DEBUG_EXPORT void DebugFinalize( void ); +#endif + +#if ( DEBUG ) + #define debug_terminate() DebugFinalize() +#else + #define debug_terminate() +#endif + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @function DebugGetProperty + + @abstract Gets the specified property from the debugging library. + */ + +#if ( DEBUG ) +DEBUG_EXPORT OSStatus DebugGetProperty( DebugPropertyTag inTag, ... ); +#endif + +#if ( DEBUG ) + #if ( DEBUG_C99_VA_ARGS ) + #define debug_get_property(... ) DebugGetProperty( __VA_ARGS__ ) + #elif ( DEBUG_GNU_VA_ARGS ) + #define debug_get_property( ARGS... ) DebugGetProperty( ## ARGS ) + #else + #define debug_get_property DebugGetProperty + #endif +#else + #if ( DEBUG_C99_VA_ARGS ) + #define debug_get_property(... ) + #elif ( DEBUG_GNU_VA_ARGS ) + #define debug_get_property( ARGS... ) + #else + #define debug_get_property while( 0 ) + #endif +#endif + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @function DebugSetProperty + + @abstract Sets the specified property from the debugging library. + */ + +#if ( DEBUG ) +DEBUG_EXPORT OSStatus DebugSetProperty( DebugPropertyTag inTag, ... ); +#endif + +#if ( DEBUG ) + #if ( DEBUG_C99_VA_ARGS ) + #define debug_set_property(... ) DebugSetProperty( __VA_ARGS__ ) + #elif ( DEBUG_GNU_VA_ARGS ) + #define debug_set_property( ARGS... ) DebugSetProperty( ## ARGS ) + #else + #define debug_set_property DebugSetProperty + #endif +#else + #if ( DEBUG_C99_VA_ARGS ) + #define debug_set_property(... ) + #elif ( DEBUG_GNU_VA_ARGS ) + #define debug_set_property( ARGS... ) + #else + #define debug_set_property while( 0 ) + #endif +#endif + +#if 0 +#pragma mark == Routines - Debugging Output == +#endif + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @function DebugPrintF + + @abstract Prints a debug message with printf-style formatting. + + @param inLevel Error that generated this assert or noErr. + + @param inFormatString + C string containing assertion text. + + @param VAR_ARG + Variable number of arguments depending on the format string. + + @result Number of bytes printed or -1 on error. + */ + +#if ( DEBUG ) +DEBUG_EXPORT size_t DebugPrintF( DebugLevel inLevel, const char *inFormat, ... ); +#endif + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @function DebugPrintFVAList + + @abstract va_list version of DebugPrintF. See DebugPrintF for more info. + */ + +#if ( DEBUG ) +DEBUG_EXPORT size_t DebugPrintFVAList( DebugLevel inLevel, const char *inFormat, va_list inArgs ); +#endif + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @function DebugPrintAssert + + @abstract Prints a message describing the reason the (e.g. an assert failed), an optional error message, + an optional source filename, an optional source line number. + + @param inErrorCode Error that generated this assert or noErr. + @param inAssertString C string containing assertion text. + @param inMessage C string containing a message about the assert. + @param inFileName C string containing path of file where the error occurred. + @param inLineNumber Line number in source file where the error occurred. + @param inFunction C string containing name of function where assert occurred. + + @discussion + + Example output: + + [ASSERT] assert: "dataPtr != NULL" allocate memory for object failed + [ASSERT] where: "MyFile.c", line 123, ("MyFunction") + + OR + + [ASSERT] error: -6728 (kNoMemoryErr) + [ASSERT] where: "MyFile.c", line 123, ("MyFunction") + */ + +#if ( DEBUG ) +DEBUG_EXPORT void +DebugPrintAssert( + int_least32_t inErrorCode, + const char * inAssertString, + const char * inMessage, + const char * inFilename, + int_least32_t inLineNumber, + const char * inFunction ); +#endif + +#if 0 +#pragma mark == Routines - Utilities == +#endif + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @function DebugSNPrintF + + @abstract Debugging versions of standard C snprintf with extra features. + + @param sbuffer Buffer to receive result. Null terminated unless the buffer size is 0. + @param buflen Size of the buffer including space for the null terminator. + @param fmt printf-style format string. + @param VAR_ARG Variable number of arguments depending on the format string. + + @result Number of characters written (minus the null terminator). + + @discussion + + Extra features over the standard C snprintf: + <pre> + 64-bit support for %d (%lld), %i (%lli), %u (%llu), %o (%llo), %x (%llx), and %b (%llb). + %@ - Cocoa/CoreFoundation object. Param is the object. Strings are used directly. Others use CFCopyDescription. + %a - Network Address: %.4a=IPv4, %.6a=Ethernet, %.8a Fibre Channel, %.16a=IPv6. Arg=ptr to network address. + %#a - IPv4 or IPv6 mDNSAddr. Arg=ptr to mDNSAddr. + %##a - IPv4 (if AF_INET defined) or IPv6 (if AF_INET6 defined) sockaddr. Arg=ptr to sockaddr. + %b - Binary representation of integer (e.g. 01101011). Modifiers and arg=the same as %d, %x, etc. + %C - Mac-style FourCharCode (e.g. 'APPL'). Arg=32-bit value to print as a Mac-style FourCharCode. + %H - Hex Dump (e.g. "\x6b\xa7" -> "6B A7"). 1st arg=ptr, 2nd arg=size, 3rd arg=max size. + %#H - Hex Dump & ASCII (e.g. "\x41\x62" -> "6B A7 'Ab'"). 1st arg=ptr, 2nd arg=size, 3rd arg=max size. + %m - Error Message (e.g. 0 -> "kNoErr"). Modifiers and error code arg=the same as %d, %x, etc. + %#s - Pascal-style length-prefixed string. Arg=ptr to string. + %##s - DNS label-sequence name. Arg=ptr to name. + %S - UTF-16 string, 0x0000 terminated. Host order if no BOM. Precision is UTF-16 count. Precision includes BOM. + %#S - Big Endian UTF-16 string (unless BOM overrides). Otherwise, the same as %S. + %##S - Little Endian UTF-16 string (unless BOM overrides). Otherwise, the same as %S. + %U - Universally Unique Identifier (UUID) (e.g. 6ba7b810-9dad-11d1-80b4-00c04fd430c8). Arg=ptr to 16-byte UUID. + </pre> + */ + +#if ( DEBUG ) +DEBUG_EXPORT size_t DebugSNPrintF(char *sbuffer, size_t buflen, const char *fmt, ...); +#endif + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @function DebugSNPrintFVAList + + @abstract va_list version of DebugSNPrintF. See DebugSNPrintF for more info. + */ + +#if ( DEBUG ) +DEBUG_EXPORT size_t DebugSNPrintFVAList(char *sbuffer, size_t buflen, const char *fmt, va_list arg); +#endif + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @function DebugGetErrorString + + @abstract Gets an error string from an error code. + + @param inStatus Error code to get the string for. + @param inBuffer Optional buffer to copy the string to for non-static strings. May be null. + @param inBufferSize Size of optional buffer. May be 0. + + @result C string containing error string for the error code. Guaranteed to be a valid, static string. If a + buffer is supplied, the return value will always be a pointer to the supplied buffer, which will + contain the best available description of the error code. If a buffer is not supplied, the return + value will be the best available description of the error code that can be represented as a static + string. This allows code that cannot use a temporary buffer to hold the result to still get a useful + error string in most cases, but also allows code that can use a temporary buffer to get the best + available description. + */ + +#if ( DEBUG ) +DEBUG_EXPORT const char * DebugGetErrorString( int_least32_t inErrorCode, char *inBuffer, size_t inBufferSize ); +#endif + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @function DebugHexDump + + @abstract Hex dumps data to a string or to the output device. + */ + +#if ( DEBUG ) +DEBUG_EXPORT size_t +DebugHexDump( + DebugLevel inLevel, + int inIndent, + const char * inLabel, + size_t inLabelSize, + int inLabelMinWidth, + const char * inType, + size_t inTypeSize, + const void * inDataStart, + const void * inData, + size_t inDataSize, + DebugFlags inFlags, + char * outBuffer, + size_t inBufferSize ); +#endif + +#if ( DEBUG ) + #define dloghex( LEVEL, INDENT, LABEL, LABEL_SIZE, LABEL_MIN_SIZE, TYPE, TYPE_SIZE, DATA_START, DATA, DATA_SIZE, FLAGS, BUFFER, BUFFER_SIZE ) \ + DebugHexDump( ( LEVEL ), (INDENT), ( LABEL ), ( LABEL_SIZE ), ( LABEL_MIN_SIZE ), ( TYPE ), ( TYPE_SIZE ), \ + ( DATA_START ), ( DATA ), ( DATA_SIZE ), ( FLAGS ), ( BUFFER ), ( BUFFER_SIZE ) ) +#else + #define dloghex( LEVEL, INDENT, LABEL, LABEL_SIZE, LABEL_MIN_SIZE, TYPE, TYPE_SIZE, DATA_START, DATA, DATA_SIZE, FLAGS, BUFFER, BUFFER_SIZE ) +#endif + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @function DebugTaskLevel + + @abstract Returns the current task level. + + @result Current task level + + @discussion + + Bit masks to isolate portions of the result (note that some masks may also need bit shifts to right justify): + <pre> + kDebugInterruptLevelMask - Indicates the current interrupt level (> 0 means interrupt time). + kDebugInVBLTaskMask - Indicates if a VBL task is currently being executed. + kDebugInDeferredTaskMask - Indicates if a Deferred Task is currently being executed. + kDebugInSecondaryInterruptHandlerMask - Indicates if a Secondary Interrupt Handler is currently being executed. + kDebugPageFaultFatalMask - Indicates if it is unsafe to cause a page fault (worse than interrupt time). + kDebugMPTaskLevelMask - Indicates if being called from an MP task. + kDebugInterruptDepthMask - 0 means task level, 1 means in interrupt, > 1 means in nested interrupt. + </pre> + + Helpers: + <pre> + DebugExtractTaskLevelInterruptDepth() - Macro to extract interrupt depth from task level value. + </pre> + */ + +#if ( DEBUG ) +DEBUG_EXPORT uint32_t DebugTaskLevel( void ); +#endif + +//--------------------------------------------------------------------------------------------------------------------------- +/*! @function DebugServicesTest + + @abstract Unit test. + */ + +#if ( DEBUG ) +DEBUG_EXPORT OSStatus DebugServicesTest( void ); +#endif + +#ifdef __cplusplus +} +#endif + +#endif // __DEBUG_SERVICES__ diff --git a/mDNSResponder/mDNSShared/GenLinkedList.c b/mDNSResponder/mDNSShared/GenLinkedList.c new file mode 100644 index 00000000..45dbb7cb --- /dev/null +++ b/mDNSResponder/mDNSShared/GenLinkedList.c @@ -0,0 +1,319 @@ +/* -*- Mode: C; tab-width: 4 -*- + * + * Copyright (c) 2003 Apple Computer, Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + + File: GenLinkedList.c + + Contains: implementation of generic linked lists. + + Version: 1.0 + Tabs: 4 spaces + */ + +#include "GenLinkedList.h" + + +// Return the link pointer contained within element e at offset o. +#define GETLINK( e, o) ( *(void**)((char*) (e) + (o)) ) + +// Assign the link pointer l to element e at offset o. +#define ASSIGNLINK( e, l, o) ( *((void**)((char*) (e) + (o))) = (l)) + + +// GenLinkedList ///////////////////////////////////////////////////////////// + +void InitLinkedList( GenLinkedList *pList, size_t linkOffset) +/* Initialize the block of memory pointed to by pList as a linked list. */ +{ + pList->Head = NULL; + pList->Tail = NULL; + pList->LinkOffset = linkOffset; +} + + +void AddToTail( GenLinkedList *pList, void *elem) +/* Add a linked list element to the tail of the list. */ +{ + if ( pList->Tail) { + ASSIGNLINK( pList->Tail, elem, pList->LinkOffset); + } else + pList->Head = elem; + ASSIGNLINK( elem, NULL, pList->LinkOffset); + + pList->Tail = elem; +} + + +void AddToHead( GenLinkedList *pList, void *elem) +/* Add a linked list element to the head of the list. */ +{ + ASSIGNLINK( elem, pList->Head, pList->LinkOffset); + if ( pList->Tail == NULL) + pList->Tail = elem; + + pList->Head = elem; +} + + +int RemoveFromList( GenLinkedList *pList, void *elem) +/* Remove a linked list element from the list. Return 0 if it was not found. */ +/* If the element is removed, its link will be set to NULL. */ +{ + void *iElem, *lastElem; + + for ( iElem = pList->Head, lastElem = NULL; iElem; iElem = GETLINK( iElem, pList->LinkOffset)) { + if ( iElem == elem) { + if ( lastElem) { // somewhere past the head + ASSIGNLINK( lastElem, GETLINK( elem, pList->LinkOffset), pList->LinkOffset); + } else { // at the head + pList->Head = GETLINK( elem, pList->LinkOffset); + } + if ( pList->Tail == elem) + pList->Tail = lastElem ? lastElem : NULL; + ASSIGNLINK( elem, NULL, pList->LinkOffset); // maybe catch a stale reference bug. + return 1; + } + lastElem = iElem; + } + + return 0; +} + + +int ReplaceElem( GenLinkedList *pList, void *elemInList, void *newElem) +/* Replace an element in the list with a new element, in the same position. */ +{ + void *iElem, *lastElem; + + if ( elemInList == NULL || newElem == NULL) + return 0; + + for ( iElem = pList->Head, lastElem = NULL; iElem; iElem = GETLINK( iElem, pList->LinkOffset)) + { + if ( iElem == elemInList) + { + ASSIGNLINK( newElem, GETLINK( elemInList, pList->LinkOffset), pList->LinkOffset); + if ( lastElem) // somewhere past the head + { + ASSIGNLINK( lastElem, newElem, pList->LinkOffset); + } + else // at the head + { + pList->Head = newElem; + } + if ( pList->Tail == elemInList) + pList->Tail = newElem; + return 1; + } + lastElem = iElem; + } + + return 0; +} + + +// GenDoubleLinkedList ///////////////////////////////////////////////////////// + +void InitDoubleLinkedList( GenDoubleLinkedList *pList, size_t fwdLinkOffset, + size_t backLinkOffset) +/* Initialize the block of memory pointed to by pList as a double linked list. */ +{ + pList->Head = NULL; + pList->Tail = NULL; + pList->FwdLinkOffset = fwdLinkOffset; + pList->BackLinkOffset = backLinkOffset; +} + + +void DLLAddToHead( GenDoubleLinkedList *pList, void *elem) +/* Add a linked list element to the head of the list. */ +{ + void *pNext; + + pNext = pList->Head; + + // fix up the forward links + ASSIGNLINK( elem, pList->Head, pList->FwdLinkOffset); + pList->Head = elem; + + // fix up the backward links + if ( pNext) { + ASSIGNLINK( pNext, elem, pList->BackLinkOffset); + } else + pList->Tail = elem; + ASSIGNLINK( elem, NULL, pList->BackLinkOffset); +} + + +void DLLRemoveFromList( GenDoubleLinkedList *pList, void *elem) +/* Remove a linked list element from the list. */ +/* When the element is removed, its link will be set to NULL. */ +{ + void *pNext, *pPrev; + + pNext = GETLINK( elem, pList->FwdLinkOffset); + pPrev = GETLINK( elem, pList->BackLinkOffset); + + // fix up the forward links + if ( pPrev) + ASSIGNLINK( pPrev, pNext, pList->FwdLinkOffset); + else + pList->Head = pNext; + + // fix up the backward links + if ( pNext) + ASSIGNLINK( pNext, pPrev, pList->BackLinkOffset); + else + pList->Tail = pPrev; + + ASSIGNLINK( elem, NULL, pList->FwdLinkOffset); + ASSIGNLINK( elem, NULL, pList->BackLinkOffset); +} + + +// GenLinkedOffsetList ///////////////////////////////////////////////////// + +// Extract the Next offset from element +#define GETOFFSET( e, o) ( *(size_t*)((char*) (e) + (o)) ) + +static void AssignOffsetLink( void *elem, void *link, size_t linkOffset); + + +static void AssignOffsetLink( void *elem, void *link, size_t linkOffset) +// Assign link to elem as an offset from elem. Assign 0 to elem if link is NULL. +{ + GETOFFSET( elem, linkOffset) = link ? (size_t) link - (size_t) elem : 0; +} + + +void *GetHeadPtr( GenLinkedOffsetList *pList) +/* Return a pointer to the head element of a list, or NULL if none. */ +{ + return pList->Head ? ( (char*) (pList) + pList->Head) : NULL; +} + + +void *GetTailPtr( GenLinkedOffsetList *pList) +/* Return a pointer to the tail element of a list, or NULL if none. */ +{ + return pList->Tail ? ( (char*) (pList) + pList->Tail) : NULL; +} + + +void *GetOffsetLink( GenLinkedOffsetList *pList, void *elem) +/* Return the link pointer contained within element e for pList, or NULL if it is 0. */ +{ + size_t nextOffset; + + nextOffset = GETOFFSET( elem, pList->LinkOffset); + + return nextOffset ? (char*) elem + nextOffset : NULL; +} + + +void InitLinkedOffsetList( GenLinkedOffsetList *pList, size_t linkOffset) +/* Initialize the block of memory pointed to by pList as a linked list. */ +{ + pList->Head = 0; + pList->Tail = 0; + pList->LinkOffset = linkOffset; +} + + +void OffsetAddToTail( GenLinkedOffsetList *pList, void *elem) +/* Add a linked list element to the tail of the list. */ +{ + if ( pList->Tail) { + AssignOffsetLink( GetTailPtr( pList), elem, pList->LinkOffset); + } else + pList->Head = (size_t) elem - (size_t) pList; + AssignOffsetLink( elem, NULL, pList->LinkOffset); + + pList->Tail = (size_t) elem - (size_t) pList; +} + + +void OffsetAddToHead( GenLinkedOffsetList *pList, void *elem) +/* Add a linked list element to the head of the list. */ +{ + AssignOffsetLink( elem, GetHeadPtr( pList), pList->LinkOffset); + if ( pList->Tail == 0) + pList->Tail = (size_t) elem - (size_t) pList; + + pList->Head = (size_t) elem - (size_t) pList; +} + + +int OffsetRemoveFromList( GenLinkedOffsetList *pList, void *elem) +/* Remove a linked list element from the list. Return 0 if it was not found. */ +/* If the element is removed, its link will be set to NULL. */ +{ + void *iElem, *lastElem; + + for ( iElem = GetHeadPtr( pList), lastElem = NULL; iElem; + iElem = GetOffsetLink( pList, iElem)) + { + if ( iElem == elem) { + if ( lastElem) { // somewhere past the head + AssignOffsetLink( lastElem, GetOffsetLink( pList, elem), pList->LinkOffset); + } else { // at the head + iElem = GetOffsetLink( pList, elem); + pList->Head = iElem ? (size_t) iElem - (size_t) pList : 0; + } + if ( GetTailPtr( pList) == elem) + pList->Tail = lastElem ? (size_t) lastElem - (size_t) pList : 0; + AssignOffsetLink( elem, NULL, pList->LinkOffset); // maybe catch a stale reference bug. + return 1; + } + lastElem = iElem; + } + + return 0; +} + + +int OffsetReplaceElem( GenLinkedOffsetList *pList, void *elemInList, void *newElem) +/* Replace an element in the list with a new element, in the same position. */ +{ + void *iElem, *lastElem; + + if ( elemInList == NULL || newElem == NULL) + return 0; + + for ( iElem = GetHeadPtr( pList), lastElem = NULL; iElem; + iElem = GetOffsetLink( pList, iElem)) + { + if ( iElem == elemInList) + { + AssignOffsetLink( newElem, GetOffsetLink( pList, elemInList), pList->LinkOffset); + if ( lastElem) // somewhere past the head + { + AssignOffsetLink( lastElem, newElem, pList->LinkOffset); + } + else // at the head + { + pList->Head = (size_t) newElem - (size_t) pList; + } + if ( GetTailPtr( pList) == elemInList) + pList->Tail = (size_t) newElem - (size_t) pList; + return 1; + } + lastElem = iElem; + } + + return 0; +} + + diff --git a/mDNSResponder/mDNSShared/GenLinkedList.h b/mDNSResponder/mDNSShared/GenLinkedList.h new file mode 100644 index 00000000..2d0ada6d --- /dev/null +++ b/mDNSResponder/mDNSShared/GenLinkedList.h @@ -0,0 +1,90 @@ +/* -*- Mode: C; tab-width: 4 -*- + * + * Copyright (c) 2003 Apple Computer, Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __GenLinkedList__ +#define __GenLinkedList__ + + +#include <stddef.h> + + +struct GenLinkedList +{ + void *Head, + *Tail; + size_t LinkOffset; +}; +typedef struct GenLinkedList GenLinkedList; + + +void InitLinkedList( GenLinkedList *pList, size_t linkOffset); + +void AddToHead( GenLinkedList *pList, void *elem); +void AddToTail( GenLinkedList *pList, void *elem); + +int RemoveFromList( GenLinkedList *pList, void *elem); + +int ReplaceElem( GenLinkedList *pList, void *elemInList, void *newElem); + + + +struct GenDoubleLinkedList +{ + void *Head, + *Tail; + size_t FwdLinkOffset, + BackLinkOffset; +}; +typedef struct GenDoubleLinkedList GenDoubleLinkedList; + + +void InitDoubleLinkedList( GenDoubleLinkedList *pList, size_t fwdLinkOffset, + size_t backLinkOffset); + +void DLLAddToHead( GenDoubleLinkedList *pList, void *elem); + +void DLLRemoveFromList( GenDoubleLinkedList *pList, void *elem); + + + +/* A GenLinkedOffsetList is like a GenLinkedList that stores the *Next field as a signed */ +/* offset from the address of the beginning of the element, rather than as a pointer. */ + +struct GenLinkedOffsetList +{ + size_t Head, + Tail; + size_t LinkOffset; +}; +typedef struct GenLinkedOffsetList GenLinkedOffsetList; + + +void InitLinkedOffsetList( GenLinkedOffsetList *pList, size_t linkOffset); + +void *GetHeadPtr( GenLinkedOffsetList *pList); +void *GetTailPtr( GenLinkedOffsetList *pList); +void *GetOffsetLink( GenLinkedOffsetList *pList, void *elem); + +void OffsetAddToHead( GenLinkedOffsetList *pList, void *elem); +void OffsetAddToTail( GenLinkedOffsetList *pList, void *elem); + +int OffsetRemoveFromList( GenLinkedOffsetList *pList, void *elem); + +int OffsetReplaceElem( GenLinkedOffsetList *pList, void *elemInList, void *newElem); + + +#endif // __GenLinkedList__ diff --git a/mDNSResponder/mDNSShared/Java/BaseListener.java b/mDNSResponder/mDNSShared/Java/BaseListener.java new file mode 100644 index 00000000..b99d341c --- /dev/null +++ b/mDNSResponder/mDNSShared/Java/BaseListener.java @@ -0,0 +1,36 @@ +/* -*- Mode: Java; tab-width: 4 -*- + * + * Copyright (c) 2004 Apple Computer, Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package com.apple.dnssd; + + +/** A base class for DNSSD listeners. */ + +public interface BaseListener +{ + /** Called to report DNSSD operation failures.<P> + + @param service + The service that encountered the failure. + <P> + @param errorCode + Indicates the failure that occurred. See {@link DNSSDException} for error codes. + */ + void operationFailed( DNSSDService service, int errorCode); +} + diff --git a/mDNSResponder/mDNSShared/Java/BrowseListener.java b/mDNSResponder/mDNSShared/Java/BrowseListener.java new file mode 100644 index 00000000..b92b9dc5 --- /dev/null +++ b/mDNSResponder/mDNSShared/Java/BrowseListener.java @@ -0,0 +1,73 @@ +/* -*- Mode: Java; tab-width: 4 -*- + * + * Copyright (c) 2004 Apple Computer, Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package com.apple.dnssd; + + +/** A listener that receives results from {@link DNSSD#browse}. */ + +public interface BrowseListener extends BaseListener +{ + /** Called to report discovered services.<P> + + @param browser + The active browse service. + <P> + @param flags + Possible values are DNSSD.MORE_COMING. + <P> + @param ifIndex + The interface on which the service is advertised. This index should be passed + to {@link DNSSD#resolve} when resolving the service. + <P> + @param serviceName + The service name discovered. + <P> + @param regType + The registration type, as passed in to DNSSD.browse(). + <P> + @param domain + The domain in which the service was discovered. + */ + void serviceFound( DNSSDService browser, int flags, int ifIndex, + String serviceName, String regType, String domain); + + /** Called to report services which have been deregistered.<P> + + @param browser + The active browse service. + <P> + @param flags + Possible values are DNSSD.MORE_COMING. + <P> + @param ifIndex + The interface on which the service is advertised. + <P> + @param serviceName + The service name which has deregistered. + <P> + @param regType + The registration type, as passed in to DNSSD.browse(). + <P> + @param domain + The domain in which the service was discovered. + */ + void serviceLost( DNSSDService browser, int flags, int ifIndex, + String serviceName, String regType, String domain); +} + diff --git a/mDNSResponder/mDNSShared/Java/DNSRecord.java b/mDNSResponder/mDNSShared/Java/DNSRecord.java new file mode 100644 index 00000000..a853d091 --- /dev/null +++ b/mDNSResponder/mDNSShared/Java/DNSRecord.java @@ -0,0 +1,52 @@ +/* -*- Mode: Java; tab-width: 4 -*- + * + * Copyright (c) 2004 Apple Computer, Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package com.apple.dnssd; + + +/** + Reference to a record returned by {@link DNSSDRegistration#addRecord}.<P> + + Note: client is responsible for serializing access to these objects if + they are shared between concurrent threads. +*/ + +public interface DNSRecord +{ + /** Update a registered resource record.<P> + The record must either be the primary txt record of a service registered via DNSSD.register(), + or a record added to a registered service via addRecord().<P> + + @param flags + Currently unused, reserved for future use. + <P> + @param rData + The new rdata to be contained in the updated resource record. + <P> + @param ttl + The time to live of the updated resource record, in seconds. + */ + void update( int flags, byte[] rData, int ttl) + throws DNSSDException; + + /** Remove a registered resource record.<P> + */ + void remove() + throws DNSSDException; +} + diff --git a/mDNSResponder/mDNSShared/Java/DNSSD.java b/mDNSResponder/mDNSShared/Java/DNSSD.java new file mode 100644 index 00000000..f749a88e --- /dev/null +++ b/mDNSResponder/mDNSShared/Java/DNSSD.java @@ -0,0 +1,860 @@ +/* -*- Mode: Java; tab-width: 4 -*- + * + * Copyright (c) 2004 Apple Computer, Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + + This file declares and implements DNSSD, the central Java factory class + for doing DNS Service Discovery. It includes the mostly-abstract public + interface, as well as the Apple* implementation subclasses. + */ + + +package com.apple.dnssd; + + +/** + DNSSD provides access to DNS Service Discovery features of ZeroConf networking.<P> + + It is a factory class that is used to invoke registration and discovery-related + operations. Most operations are non-blocking; clients are called back through an interface + with the result of an operation. Callbacks are made from a separate worker thread.<P> + + For example, in this program<P> + <PRE><CODE> + class MyClient implements BrowseListener { + void lookForWebServers() { + myBrowser = DNSSD.browse("_http._tcp", this); + } + + public void serviceFound(DNSSDService browser, int flags, int ifIndex, + String serviceName, String regType, String domain) {} + ... + }</CODE></PRE> + <CODE>MyClient.serviceFound()</CODE> would be called for every HTTP server discovered in the + default browse domain(s). +*/ + +abstract public class DNSSD +{ + /** Flag indicates to a {@link BrowseListener} that another result is + queued. Applications should not update their UI to display browse + results if the MORE_COMING flag is set; they will be called at least once + more with the flag clear. + */ + public static final int MORE_COMING = ( 1 << 0 ); + + /** If flag is set in a {@link DomainListener} callback, indicates that the result is the default domain. */ + public static final int DEFAULT = ( 1 << 2 ); + + /** If flag is set, a name conflict will trigger an exception when registering non-shared records.<P> + A name must be explicitly specified when registering a service if this bit is set + (i.e. the default name may not not be used). + */ + public static final int NO_AUTO_RENAME = ( 1 << 3 ); + + /** If flag is set, allow multiple records with this name on the network (e.g. PTR records) + when registering individual records on a {@link DNSSDRegistration}. + */ + public static final int SHARED = ( 1 << 4 ); + + /** If flag is set, records with this name must be unique on the network (e.g. SRV records). */ + public static final int UNIQUE = ( 1 << 5 ); + + /** Set flag when calling enumerateDomains() to restrict results to domains recommended for browsing. */ + public static final int BROWSE_DOMAINS = ( 1 << 6 ); + /** Set flag when calling enumerateDomains() to restrict results to domains recommended for registration. */ + public static final int REGISTRATION_DOMAINS = ( 1 << 7 ); + + /** Maximum length, in bytes, of a domain name represented as an escaped C-String. */ + public static final int MAX_DOMAIN_NAME = 1009; + + /** Pass for ifIndex to specify all available interfaces. */ + public static final int ALL_INTERFACES = 0; + + /** Pass for ifIndex to specify the localhost interface. */ + public static final int LOCALHOST_ONLY = -1; + + /** Browse for instances of a service.<P> + + Note: browsing consumes network bandwidth. Call {@link DNSSDService#stop} when you have finished browsing.<P> + + @param flags + Currently ignored, reserved for future use. + <P> + @param ifIndex + If non-zero, specifies the interface on which to browse for services + (the index for a given interface is determined via the if_nametoindex() + family of calls.) Most applications will pass 0 to browse on all available + interfaces. Pass -1 to only browse for services provided on the local host. + <P> + @param regType + The registration type being browsed for followed by the protocol, separated by a + dot (e.g. "_ftp._tcp"). The transport protocol must be "_tcp" or "_udp". + <P> + @param domain + If non-null, specifies the domain on which to browse for services. + Most applications will not specify a domain, instead browsing on the + default domain(s). + <P> + @param listener + This object will get called when instances of the service are discovered (or disappear). + <P> + @return A {@link DNSSDService} that represents the active browse operation. + + @throws SecurityException If a security manager is present and denies <tt>RuntimePermission("getDNSSDInstance")</tt>. + @see RuntimePermission + */ + public static DNSSDService browse( int flags, int ifIndex, String regType, String domain, BrowseListener listener) + throws DNSSDException + { return getInstance()._makeBrowser( flags, ifIndex, regType, domain, listener); } + + /** Browse for instances of a service. Use default flags, ifIndex and domain.<P> + + @param regType + The registration type being browsed for followed by the protocol, separated by a + dot (e.g. "_ftp._tcp"). The transport protocol must be "_tcp" or "_udp". + <P> + @param listener + This object will get called when instances of the service are discovered (or disappear). + <P> + @return A {@link DNSSDService} that represents the active browse operation. + + @throws SecurityException If a security manager is present and denies <tt>RuntimePermission("getDNSSDInstance")</tt>. + @see RuntimePermission + */ + public static DNSSDService browse( String regType, BrowseListener listener) + throws DNSSDException + { return browse( 0, 0, regType, "", listener); } + + /** Resolve a service name discovered via browse() to a target host name, port number, and txt record.<P> + + Note: Applications should NOT use resolve() solely for txt record monitoring - use + queryRecord() instead, as it is more efficient for this task.<P> + + Note: When the desired results have been returned, the client MUST terminate the resolve by + calling {@link DNSSDService#stop}.<P> + + Note: resolve() behaves correctly for typical services that have a single SRV record and + a single TXT record (the TXT record may be empty.) To resolve non-standard services with + multiple SRV or TXT records, use queryRecord().<P> + + @param flags + Currently ignored, reserved for future use. + <P> + @param ifIndex + The interface on which to resolve the service. The client should + pass the interface on which the serviceName was discovered (i.e. + the ifIndex passed to the serviceFound() callback) + or 0 to resolve the named service on all available interfaces. + <P> + @param serviceName + The servicename to be resolved. + <P> + @param regType + The registration type being resolved followed by the protocol, separated by a + dot (e.g. "_ftp._tcp"). The transport protocol must be "_tcp" or "_udp". + <P> + @param domain + The domain on which the service is registered, i.e. the domain passed + to the serviceFound() callback. + <P> + @param listener + This object will get called when the service is resolved. + <P> + @return A {@link DNSSDService} that represents the active resolve operation. + + @throws SecurityException If a security manager is present and denies <tt>RuntimePermission("getDNSSDInstance")</tt>. + @see RuntimePermission + */ + public static DNSSDService resolve( int flags, int ifIndex, String serviceName, String regType, + String domain, ResolveListener listener) + throws DNSSDException + { return getInstance()._resolve( flags, ifIndex, serviceName, regType, domain, listener); } + + /** Register a service, to be discovered via browse() and resolve() calls.<P> + @param flags + Possible values are: NO_AUTO_RENAME. + <P> + @param ifIndex + If non-zero, specifies the interface on which to register the service + (the index for a given interface is determined via the if_nametoindex() + family of calls.) Most applications will pass 0 to register on all + available interfaces. Pass -1 to register a service only on the local + machine (service will not be visible to remote hosts). + <P> + @param serviceName + If non-null, specifies the service name to be registered. + Applications need not specify a name, in which case the + computer name is used (this name is communicated to the client via + the serviceRegistered() callback). + <P> + @param regType + The registration type being registered followed by the protocol, separated by a + dot (e.g. "_ftp._tcp"). The transport protocol must be "_tcp" or "_udp". + <P> + @param domain + If non-null, specifies the domain on which to advertise the service. + Most applications will not specify a domain, instead automatically + registering in the default domain(s). + <P> + @param host + If non-null, specifies the SRV target host name. Most applications + will not specify a host, instead automatically using the machine's + default host name(s). Note that specifying a non-null host does NOT + create an address record for that host - the application is responsible + for ensuring that the appropriate address record exists, or creating it + via {@link DNSSDRegistration#addRecord}. + <P> + @param port + The port on which the service accepts connections. Pass 0 for a + "placeholder" service (i.e. a service that will not be discovered by + browsing, but will cause a name conflict if another client tries to + register that same name.) Most clients will not use placeholder services. + <P> + @param txtRecord + The txt record rdata. May be null. Note that a non-null txtRecord + MUST be a properly formatted DNS TXT record, i.e. <length byte> <data> + <length byte> <data> ... + <P> + @param listener + This object will get called when the service is registered. + <P> + @return A {@link DNSSDRegistration} that controls the active registration. + + @throws SecurityException If a security manager is present and denies <tt>RuntimePermission("getDNSSDInstance")</tt>. + @see RuntimePermission + */ + public static DNSSDRegistration register( int flags, int ifIndex, String serviceName, String regType, + String domain, String host, int port, TXTRecord txtRecord, RegisterListener listener) + throws DNSSDException + { return getInstance()._register( flags, ifIndex, serviceName, regType, domain, host, port, txtRecord, listener); } + + /** Register a service, to be discovered via browse() and resolve() calls. Use default flags, ifIndex, domain, host and txtRecord.<P> + @param serviceName + If non-null, specifies the service name to be registered. + Applications need not specify a name, in which case the + computer name is used (this name is communicated to the client via + the serviceRegistered() callback). + <P> + @param regType + The registration type being registered followed by the protocol, separated by a + dot (e.g. "_ftp._tcp"). The transport protocol must be "_tcp" or "_udp". + <P> + @param port + The port on which the service accepts connections. Pass 0 for a + "placeholder" service (i.e. a service that will not be discovered by + browsing, but will cause a name conflict if another client tries to + register that same name.) Most clients will not use placeholder services. + <P> + @param listener + This object will get called when the service is registered. + <P> + @return A {@link DNSSDRegistration} that controls the active registration. + + @throws SecurityException If a security manager is present and denies <tt>RuntimePermission("getDNSSDInstance")</tt>. + @see RuntimePermission + */ + public static DNSSDRegistration register( String serviceName, String regType, int port, RegisterListener listener) + throws DNSSDException + { return register( 0, 0, serviceName, regType, null, null, port, null, listener); } + + /** Create a {@link DNSSDRecordRegistrar} allowing efficient registration of + multiple individual records.<P> + <P> + @return A {@link DNSSDRecordRegistrar} that can be used to register records. + + @throws SecurityException If a security manager is present and denies <tt>RuntimePermission("getDNSSDInstance")</tt>. + @see RuntimePermission + */ + public static DNSSDRecordRegistrar createRecordRegistrar( RegisterRecordListener listener) + throws DNSSDException + { return getInstance()._createRecordRegistrar( listener); } + + /** Query for an arbitrary DNS record.<P> + @param flags + Possible values are: MORE_COMING. + <P> + @param ifIndex + If non-zero, specifies the interface on which to issue the query + (the index for a given interface is determined via the if_nametoindex() + family of calls.) Passing 0 causes the name to be queried for on all + interfaces. Passing -1 causes the name to be queried for only on the + local host. + <P> + @param serviceName + The full domain name of the resource record to be queried for. + <P> + @param rrtype + The numerical type of the resource record to be queried for (e.g. PTR, SRV, etc) + as defined in nameser.h. + <P> + @param rrclass + The class of the resource record, as defined in nameser.h + (usually 1 for the Internet class). + <P> + @param listener + This object will get called when the query completes. + <P> + @return A {@link DNSSDService} that controls the active query. + + @throws SecurityException If a security manager is present and denies <tt>RuntimePermission("getDNSSDInstance")</tt>. + @see RuntimePermission + */ + public static DNSSDService queryRecord( int flags, int ifIndex, String serviceName, int rrtype, + int rrclass, QueryListener listener) + throws DNSSDException + { return getInstance()._queryRecord( flags, ifIndex, serviceName, rrtype, rrclass, listener); } + + /** Asynchronously enumerate domains available for browsing and registration.<P> + + Currently, the only domain returned is "local.", but other domains will be returned in future.<P> + + The enumeration MUST be cancelled by calling {@link DNSSDService#stop} when no more domains + are to be found.<P> + @param flags + Possible values are: BROWSE_DOMAINS, REGISTRATION_DOMAINS. + <P> + @param ifIndex + If non-zero, specifies the interface on which to look for domains. + (the index for a given interface is determined via the if_nametoindex() + family of calls.) Most applications will pass 0 to enumerate domains on + all interfaces. + <P> + @param listener + This object will get called when domains are found. + <P> + @return A {@link DNSSDService} that controls the active enumeration. + + @throws SecurityException If a security manager is present and denies <tt>RuntimePermission("getDNSSDInstance")</tt>. + @see RuntimePermission + */ + public static DNSSDService enumerateDomains( int flags, int ifIndex, DomainListener listener) + throws DNSSDException + { return getInstance()._enumerateDomains( flags, ifIndex, listener); } + + /** Concatenate a three-part domain name (as provided to the listeners) into a + properly-escaped full domain name. Note that strings passed to listeners are + ALREADY ESCAPED where necessary.<P> + @param serviceName + The service name - any dots or slashes must NOT be escaped. + May be null (to construct a PTR record name, e.g. "_ftp._tcp.apple.com"). + <P> + @param regType + The registration type followed by the protocol, separated by a dot (e.g. "_ftp._tcp"). + <P> + @param domain + The domain name, e.g. "apple.com". Any literal dots or backslashes must be escaped. + <P> + @return The full domain name. + + @throws SecurityException If a security manager is present and denies <tt>RuntimePermission("getDNSSDInstance")</tt>. + @see RuntimePermission + */ + public static String constructFullName( String serviceName, String regType, String domain) + throws DNSSDException + { return getInstance()._constructFullName( serviceName, regType, domain); } + + /** Instruct the daemon to verify the validity of a resource record that appears to + be out of date. (e.g. because tcp connection to a service's target failed.) <P> + + Causes the record to be flushed from the daemon's cache (as well as all other + daemons' caches on the network) if the record is determined to be invalid.<P> + @param flags + Currently unused, reserved for future use. + <P> + @param ifIndex + If non-zero, specifies the interface on which to reconfirm the record + (the index for a given interface is determined via the if_nametoindex() + family of calls.) Passing 0 causes the name to be reconfirmed on all + interfaces. Passing -1 causes the name to be reconfirmed only on the + local host. + <P> + @param fullName + The resource record's full domain name. + <P> + @param rrtype + The resource record's type (e.g. PTR, SRV, etc) as defined in nameser.h. + <P> + @param rrclass + The class of the resource record, as defined in nameser.h (usually 1). + <P> + @param rdata + The raw rdata of the resource record. + + @throws SecurityException If a security manager is present and denies <tt>RuntimePermission("getDNSSDInstance")</tt>. + @see RuntimePermission + */ + public static void reconfirmRecord( int flags, int ifIndex, String fullName, int rrtype, + int rrclass, byte[] rdata) + { getInstance()._reconfirmRecord( flags, ifIndex, fullName, rrtype, rrclass, rdata); } + + /** Return the canonical name of a particular interface index.<P> + @param ifIndex + A valid interface index. Must not be ALL_INTERFACES. + <P> + @return The name of the interface, which should match java.net.NetworkInterface.getName(). + + @throws SecurityException If a security manager is present and denies <tt>RuntimePermission("getDNSSDInstance")</tt>. + @see RuntimePermission + */ + public static String getNameForIfIndex( int ifIndex) + { return getInstance()._getNameForIfIndex( ifIndex); } + + /** Return the index of a named interface.<P> + @param ifName + A valid interface name. An example is java.net.NetworkInterface.getName(). + <P> + @return The interface index. + + @throws SecurityException If a security manager is present and denies <tt>RuntimePermission("getDNSSDInstance")</tt>. + @see RuntimePermission + */ + public static int getIfIndexForName( String ifName) + { return getInstance()._getIfIndexForName( ifName); } + + protected DNSSD() {} // prevent direct instantiation + + /** Return the single instance of DNSSD. */ + static protected final DNSSD getInstance() + { + SecurityManager sm = System.getSecurityManager(); + if (sm != null) + sm.checkPermission( new RuntimePermission( "getDNSSDInstance")); + return fInstance; + } + + abstract protected DNSSDService _makeBrowser( int flags, int ifIndex, String regType, String domain, BrowseListener listener) + throws DNSSDException; + + abstract protected DNSSDService _resolve( int flags, int ifIndex, String serviceName, String regType, + String domain, ResolveListener listener) + throws DNSSDException; + + abstract protected DNSSDRegistration _register( int flags, int ifIndex, String serviceName, String regType, + String domain, String host, int port, TXTRecord txtRecord, RegisterListener listener) + throws DNSSDException; + + abstract protected DNSSDRecordRegistrar _createRecordRegistrar( RegisterRecordListener listener) + throws DNSSDException; + + abstract protected DNSSDService _queryRecord( int flags, int ifIndex, String serviceName, int rrtype, + int rrclass, QueryListener listener) + throws DNSSDException; + + abstract protected DNSSDService _enumerateDomains( int flags, int ifIndex, DomainListener listener) + throws DNSSDException; + + abstract protected String _constructFullName( String serviceName, String regType, String domain) + throws DNSSDException; + + abstract protected void _reconfirmRecord( int flags, int ifIndex, String fullName, int rrtype, + int rrclass, byte[] rdata); + + abstract protected String _getNameForIfIndex( int ifIndex); + + abstract protected int _getIfIndexForName( String ifName); + + protected static DNSSD fInstance; + + static + { + try + { + String name = System.getProperty( "com.apple.dnssd.DNSSD" ); + if (name == null) + name = "com.apple.dnssd.AppleDNSSD"; // Fall back to Apple-provided class. + fInstance = (DNSSD) Class.forName(name).newInstance(); + } + catch( Exception e ) + { + throw new InternalError( "cannot instantiate DNSSD" + e ); + } + } +} + + +// Concrete implementation of DNSSDException +class AppleDNSSDException extends DNSSDException +{ + public AppleDNSSDException( int errorCode) { fErrorCode = errorCode; } + + public int getErrorCode() { return fErrorCode; } + + public String getMessage() + { + final String kMessages[] = { // should probably be put into a resource or something + "UNKNOWN", + "NO_SUCH_NAME", + "NO_MEMORY", + "BAD_PARAM", + "BAD_REFERENCE", + "BAD_STATE", + "BAD_FLAGS", + "UNSUPPORTED", + "NOT_INITIALIZED", + "NO_CACHE", + "ALREADY_REGISTERED", + "NAME_CONFLICT", + "INVALID", + "FIREWALL", + "INCOMPATIBLE", + "BAD_INTERFACE_INDEX", + "REFUSED", + "NOSUCHRECORD", + "NOAUTH", + "NOSUCHKEY", + "NATTRAVERSAL", + "DOUBLENAT", + "BADTIME", + "BADSIG", + "BADKEY", + "TRANSIENT", + "SERVICENOTRUNNING", + "NATPORTMAPPINGUNSUPPORTED", + "NATPORTMAPPINGDISABLED" + }; + + if (fErrorCode <= UNKNOWN && fErrorCode > ( UNKNOWN - kMessages.length)) + { + return "DNS-SD Error " + String.valueOf( fErrorCode) + ": " + kMessages[ UNKNOWN - fErrorCode]; + } + else + return super.getMessage() + "(" + String.valueOf( fErrorCode) + ")"; + } + + protected int fErrorCode; +} + +// The concrete, default implementation. +class AppleDNSSD extends DNSSD +{ + static + { + System.loadLibrary( "jdns_sd"); + + int libInitResult = InitLibrary( 2); // Current version number (must be sync'd with jnilib version) + + if (libInitResult != DNSSDException.NO_ERROR) + throw new InternalError( "cannot instantiate DNSSD: " + new AppleDNSSDException( libInitResult).getMessage()); + } + + static public boolean hasAutoCallbacks; // Set by InitLibrary() to value of AUTO_CALLBACKS + + protected DNSSDService _makeBrowser( int flags, int ifIndex, String regType, String domain, BrowseListener client) + throws DNSSDException + { + return new AppleBrowser( flags, ifIndex, regType, domain, client); + } + + protected DNSSDService _resolve( int flags, int ifIndex, String serviceName, String regType, + String domain, ResolveListener client) + throws DNSSDException + { + return new AppleResolver( flags, ifIndex, serviceName, regType, domain, client); + } + + protected DNSSDRegistration _register( int flags, int ifIndex, String serviceName, String regType, + String domain, String host, int port, TXTRecord txtRecord, RegisterListener client) + throws DNSSDException + { + return new AppleRegistration( flags, ifIndex, serviceName, regType, domain, host, port, + ( txtRecord != null) ? txtRecord.getRawBytes() : null, client); + } + + protected DNSSDRecordRegistrar _createRecordRegistrar( RegisterRecordListener listener) + throws DNSSDException + { + return new AppleRecordRegistrar( listener); + } + + protected DNSSDService _queryRecord( int flags, int ifIndex, String serviceName, int rrtype, + int rrclass, QueryListener client) + throws DNSSDException + { + return new AppleQuery( flags, ifIndex, serviceName, rrtype, rrclass, client); + } + + protected DNSSDService _enumerateDomains( int flags, int ifIndex, DomainListener listener) + throws DNSSDException + { + return new AppleDomainEnum( flags, ifIndex, listener); + } + + protected String _constructFullName( String serviceName, String regType, String domain) + throws DNSSDException + { + String[] responseHolder = new String[1]; // lame maneuver to get around Java's lack of reference parameters + + int rc = ConstructName( serviceName, regType, domain, responseHolder); + if (rc != 0) + throw new AppleDNSSDException( rc); + + return responseHolder[0]; + } + + protected void _reconfirmRecord( int flags, int ifIndex, String fullName, int rrtype, + int rrclass, byte[] rdata) + { + ReconfirmRecord( flags, ifIndex, fullName, rrtype, rrclass, rdata); + } + + protected String _getNameForIfIndex( int ifIndex) + { + return GetNameForIfIndex( ifIndex); + } + + protected int _getIfIndexForName( String ifName) + { + return GetIfIndexForName( ifName); + } + + + protected native int ConstructName( String serviceName, String regType, String domain, String[] pOut); + + protected native void ReconfirmRecord( int flags, int ifIndex, String fullName, int rrtype, + int rrclass, byte[] rdata); + + protected native String GetNameForIfIndex( int ifIndex); + + protected native int GetIfIndexForName( String ifName); + + protected static native int InitLibrary( int callerVersion); +} + +class AppleService implements DNSSDService, Runnable +{ + public AppleService(BaseListener listener) { fNativeContext = 0; fListener = listener; } + + public void stop() { this.HaltOperation(); } + + /* Block until data arrives, or one second passes. Returns 1 if data present, 0 otherwise. */ + protected native int BlockForData(); + + /* Call ProcessResults when data appears on socket descriptor. */ + protected native int ProcessResults(); + + protected synchronized native void HaltOperation(); + + protected void ThrowOnErr( int rc) throws DNSSDException + { + if (rc != 0) + throw new AppleDNSSDException( rc); + } + + protected long /* warning */ fNativeContext; // Private storage for native side + + public void run() + { + while ( true ) + { + // Note: We want to allow our DNS-SD operation to be stopped from other threads, so we have to + // block waiting for data *outside* the synchronized section. Because we're doing this unsynchronized + // we have to write some careful code. Suppose our DNS-SD operation is stopped from some other thread, + // and then immediately afterwards that thread (or some third, unrelated thread) starts a new DNS-SD + // operation. The Unix kernel always allocates the lowest available file descriptor to a new socket, + // so the same file descriptor is highly likely to be reused for the new operation, and if our old + // stale ServiceThread accidentally consumes bytes off that new socket we'll get really messed up. + // To guard against that, before calling ProcessResults we check to ensure that our + // fNativeContext has not been deleted, which is a telltale sign that our operation was stopped. + // After calling ProcessResults we check again, because it's extremely common for callback + // functions to stop their own operation and start others. For example, a resolveListener callback + // may well stop the resolve and then start a QueryRecord call to monitor the TXT record. + // + // The remaining risk is that between our checking fNativeContext and calling ProcessResults(), + // some other thread could stop the operation and start a new one using same file descriptor, and + // we wouldn't know. To prevent this, the AppleService object's HaltOperation() routine is declared + // synchronized and we perform our checks synchronized on the AppleService object, which ensures + // that HaltOperation() can't execute while we're doing it. Because Java locks are re-entrant this + // locking DOESN'T prevent the callback routine from stopping its own operation, but DOES prevent + // any other thread from stopping it until after the callback has completed and returned to us here. + + int result = BlockForData(); + synchronized (this) + { + if (fNativeContext == 0) break; // Some other thread stopped our DNSSD operation; time to terminate this thread + if (result == 0) continue; // If BlockForData() said there was no data, go back and block again + result = ProcessResults(); + if (fNativeContext == 0) break; // Event listener stopped its own DNSSD operation; terminate this thread + if (result != 0) { fListener.operationFailed(this, result); break; } // If error, notify listener + } + } + } + + protected BaseListener fListener; +} + + +class AppleBrowser extends AppleService +{ + public AppleBrowser( int flags, int ifIndex, String regType, String domain, BrowseListener client) + throws DNSSDException + { + super(client); + this.ThrowOnErr( this.CreateBrowser( flags, ifIndex, regType, domain)); + if (!AppleDNSSD.hasAutoCallbacks) + new Thread(this).start(); + } + + // Sets fNativeContext. Returns non-zero on error. + protected native int CreateBrowser( int flags, int ifIndex, String regType, String domain); +} + +class AppleResolver extends AppleService +{ + public AppleResolver( int flags, int ifIndex, String serviceName, String regType, + String domain, ResolveListener client) + throws DNSSDException + { + super(client); + this.ThrowOnErr( this.CreateResolver( flags, ifIndex, serviceName, regType, domain)); + if (!AppleDNSSD.hasAutoCallbacks) + new Thread(this).start(); + } + + // Sets fNativeContext. Returns non-zero on error. + protected native int CreateResolver( int flags, int ifIndex, String serviceName, String regType, + String domain); +} + +// An AppleDNSRecord is a simple wrapper around a dns_sd DNSRecord. +class AppleDNSRecord implements DNSRecord +{ + public AppleDNSRecord( AppleService owner) + { + fOwner = owner; + fRecord = 0; // record always starts out empty + } + + public void update( int flags, byte[] rData, int ttl) + throws DNSSDException + { + this.ThrowOnErr( this.Update( flags, rData, ttl)); + } + + public void remove() + throws DNSSDException + { + this.ThrowOnErr( this.Remove()); + } + + protected long fRecord; // Really a DNSRecord; sizeof(long) == sizeof(void*) ? + protected AppleService fOwner; + + protected void ThrowOnErr( int rc) throws DNSSDException + { + if (rc != 0) + throw new AppleDNSSDException( rc); + } + + protected native int Update( int flags, byte[] rData, int ttl); + + protected native int Remove(); +} + +class AppleRegistration extends AppleService implements DNSSDRegistration +{ + public AppleRegistration( int flags, int ifIndex, String serviceName, String regType, String domain, + String host, int port, byte[] txtRecord, RegisterListener client) + throws DNSSDException + { + super(client); + this.ThrowOnErr( this.BeginRegister( ifIndex, flags, serviceName, regType, domain, host, port, txtRecord)); + if (!AppleDNSSD.hasAutoCallbacks) + new Thread(this).start(); + } + + public DNSRecord addRecord( int flags, int rrType, byte[] rData, int ttl) + throws DNSSDException + { + AppleDNSRecord newRecord = new AppleDNSRecord( this); + + this.ThrowOnErr( this.AddRecord( flags, rrType, rData, ttl, newRecord)); + return newRecord; + } + + public DNSRecord getTXTRecord() + throws DNSSDException + { + return new AppleDNSRecord( this); // A record with ref 0 is understood to be primary TXT record + } + + // Sets fNativeContext. Returns non-zero on error. + protected native int BeginRegister( int ifIndex, int flags, String serviceName, String regType, + String domain, String host, int port, byte[] txtRecord); + + // Sets fNativeContext. Returns non-zero on error. + protected native int AddRecord( int flags, int rrType, byte[] rData, int ttl, AppleDNSRecord destObj); +} + +class AppleRecordRegistrar extends AppleService implements DNSSDRecordRegistrar +{ + public AppleRecordRegistrar( RegisterRecordListener listener) + throws DNSSDException + { + super(listener); + this.ThrowOnErr( this.CreateConnection()); + if (!AppleDNSSD.hasAutoCallbacks) + new Thread(this).start(); + } + + public DNSRecord registerRecord( int flags, int ifIndex, String fullname, int rrtype, + int rrclass, byte[] rdata, int ttl) + throws DNSSDException + { + AppleDNSRecord newRecord = new AppleDNSRecord( this); + + this.ThrowOnErr( this.RegisterRecord( flags, ifIndex, fullname, rrtype, rrclass, rdata, ttl, newRecord)); + return newRecord; + } + + // Sets fNativeContext. Returns non-zero on error. + protected native int CreateConnection(); + + // Sets fNativeContext. Returns non-zero on error. + protected native int RegisterRecord( int flags, int ifIndex, String fullname, int rrtype, + int rrclass, byte[] rdata, int ttl, AppleDNSRecord destObj); +} + +class AppleQuery extends AppleService +{ + public AppleQuery( int flags, int ifIndex, String serviceName, int rrtype, + int rrclass, QueryListener client) + throws DNSSDException + { + super(client); + this.ThrowOnErr( this.CreateQuery( flags, ifIndex, serviceName, rrtype, rrclass)); + if (!AppleDNSSD.hasAutoCallbacks) + new Thread(this).start(); + } + + // Sets fNativeContext. Returns non-zero on error. + protected native int CreateQuery( int flags, int ifIndex, String serviceName, int rrtype, int rrclass); +} + +class AppleDomainEnum extends AppleService +{ + public AppleDomainEnum( int flags, int ifIndex, DomainListener client) + throws DNSSDException + { + super(client); + this.ThrowOnErr( this.BeginEnum( flags, ifIndex)); + if (!AppleDNSSD.hasAutoCallbacks) + new Thread(this).start(); + } + + // Sets fNativeContext. Returns non-zero on error. + protected native int BeginEnum( int flags, int ifIndex); +} + + diff --git a/mDNSResponder/mDNSShared/Java/DNSSDException.java b/mDNSResponder/mDNSShared/Java/DNSSDException.java new file mode 100644 index 00000000..99549b5d --- /dev/null +++ b/mDNSResponder/mDNSShared/Java/DNSSDException.java @@ -0,0 +1,64 @@ +/* -*- Mode: Java; tab-width: 4 -*- + * + * Copyright (c) 2004 Apple Computer, Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.apple.dnssd; + + +/** + Used to report various DNS-SD-related error conditions. +*/ + +abstract public class DNSSDException extends Exception +{ + public static final int NO_ERROR = 0; + public static final int UNKNOWN = -65537; + public static final int NO_SUCH_NAME = -65538; + public static final int NO_MEMORY = -65539; + public static final int BAD_PARAM = -65540; + public static final int BAD_REFERENCE = -65541; + public static final int BAD_STATE = -65542; + public static final int BAD_FLAGS = -65543; + public static final int UNSUPPORTED = -65544; + public static final int NOT_INITIALIZED = -65545; + public static final int NO_CACHE = -65546; + public static final int ALREADY_REGISTERED = -65547; + public static final int NAME_CONFLICT = -65548; + public static final int INVALID = -65549; + public static final int FIREWALL = -65550; + public static final int INCOMPATIBLE = -65551; + public static final int BAD_INTERFACE_INDEX = -65552; + public static final int REFUSED = -65553; + public static final int NOSUCHRECORD = -65554; + public static final int NOAUTH = -65555; + public static final int NOSUCHKEY = -65556; + public static final int NATTRAVERSAL = -65557; + public static final int DOUBLENAT = -65558; + public static final int BADTIME = -65559; + public static final int BADSIG = -65560; + public static final int BADKEY = -65561; + public static final int TRANSIENT = -65562; + public static final int SERVICENOTRUNNING = -65563; + public static final int NATPORTMAPPINGUNSUPPORTED = -65564; + public static final int NATPORTMAPPINGDISABLED = -65565; + + // Note: When adding new error values here, remember also + // to update the corresponding kMessages array in AppleDNSSDException (DNSSD.java) + + /** Returns the sub-code that identifies the particular error. */ + abstract public int getErrorCode(); +} + diff --git a/mDNSResponder/mDNSShared/Java/DNSSDRecordRegistrar.java b/mDNSResponder/mDNSShared/Java/DNSSDRecordRegistrar.java new file mode 100644 index 00000000..3baac677 --- /dev/null +++ b/mDNSResponder/mDNSShared/Java/DNSSDRecordRegistrar.java @@ -0,0 +1,64 @@ +/* -*- Mode: Java; tab-width: 4 -*- + * + * Copyright (c) 2006 Apple Computer, Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package com.apple.dnssd; + + +/** An object for registering records, created by {@link DNSSD#createRecordRegistrar}. */ + +public interface DNSSDRecordRegistrar extends DNSSDService +{ + /** Register an independent {@link DNSRecord}.<P> + @param flags + Possible values are SHARED or UNIQUE (see flag type definitions for details). + <P> + @param ifIndex + If non-zero, specifies the interface on which to register the record + (the index for a given interface is determined via the if_nametoindex() + family of calls.) Passing 0 causes the record to be registered on all interfaces. + <P> + @param fullname + The full domain name of the resource record. + <P> + @param rrtype + The numerical type of the resource record to be queried for (e.g. PTR, SRV, etc) + as defined in nameser.h. + <P> + @param rrclass + The class of the resource record, as defined in nameser.h + (usually 1 for the Internet class). + <P> + @param rData + The new rdata as it is to appear in the DNS record. + <P> + @param ttl + The time to live of the resource record, in seconds. Pass 0 to use a default value. + <P> + @param listener + This object will get called when the service is registered. + <P> + @return A {@link DNSSDService} that can be used to abort the record registration. + + @throws SecurityException If a security manager is present and denies <tt>RuntimePermission("getDNSSDInstance")</tt>. + @see RuntimePermission + */ + public DNSRecord registerRecord( int flags, int ifIndex, String fullname, int rrtype, + int rrclass, byte[] rdata, int ttl) + throws DNSSDException; +} + diff --git a/mDNSResponder/mDNSShared/Java/DNSSDRegistration.java b/mDNSResponder/mDNSShared/Java/DNSSDRegistration.java new file mode 100644 index 00000000..720df0b8 --- /dev/null +++ b/mDNSResponder/mDNSShared/Java/DNSSDRegistration.java @@ -0,0 +1,60 @@ +/* -*- Mode: Java; tab-width: 4 -*- + * + * Copyright (c) 2004 Apple Computer, Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package com.apple.dnssd; + + +/** A tracking object for a registration created by {@link DNSSD#register}. */ + +public interface DNSSDRegistration extends DNSSDService +{ + /** Get a reference to the primary TXT record of a registered service.<P> + The record can be updated by sending it an update() message.<P> + + <P> + @return A {@link DNSRecord}. + If {@link DNSSDRegistration#stop} is called, the DNSRecord is also + invalidated and may not be used further. + */ + DNSRecord getTXTRecord() + throws DNSSDException; + + /** Add a record to a registered service.<P> + The name of the record will be the same as the registered service's name.<P> + The record can be updated or deregistered by sending it an update() or remove() message.<P> + + @param flags + Currently unused, reserved for future use. + <P> + @param rrType + The type of the record (e.g. TXT, SRV, etc), as defined in nameser.h. + <P> + @param rData + The raw rdata to be contained in the added resource record. + <P> + @param ttl + The time to live of the resource record, in seconds. + <P> + @return A {@link DNSRecord} that may be passed to updateRecord() or removeRecord(). + If {@link DNSSDRegistration#stop} is called, the DNSRecord is also + invalidated and may not be used further. + */ + DNSRecord addRecord( int flags, int rrType, byte[] rData, int ttl) + throws DNSSDException; +} + diff --git a/mDNSResponder/mDNSShared/Java/DNSSDService.java b/mDNSResponder/mDNSShared/Java/DNSSDService.java new file mode 100644 index 00000000..10f74021 --- /dev/null +++ b/mDNSResponder/mDNSShared/Java/DNSSDService.java @@ -0,0 +1,37 @@ +/* -*- Mode: Java; tab-width: 4 -*- + * + * Copyright (c) 2004 Apple Computer, Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package com.apple.dnssd; + +/** A tracking object for a service created by {@link DNSSD}. */ + +public interface DNSSDService +{ + /** + Halt the active operation and free resources associated with the DNSSDService.<P> + + Any services or records registered with this DNSSDService will be deregistered. Any + Browse, Resolve, or Query operations associated with this reference will be terminated.<P> + + Note: if the service was initialized with DNSSD.register(), and an extra resource record was + added to the service via {@link DNSSDRegistration#addRecord}, the DNSRecord so created + is invalidated when this method is called - the DNSRecord may not be used afterward. + */ + void stop(); +} + diff --git a/mDNSResponder/mDNSShared/Java/DomainListener.java b/mDNSResponder/mDNSShared/Java/DomainListener.java new file mode 100644 index 00000000..852f6430 --- /dev/null +++ b/mDNSResponder/mDNSShared/Java/DomainListener.java @@ -0,0 +1,60 @@ +/* -*- Mode: Java; tab-width: 4 -*- + * + * Copyright (c) 2004 Apple Computer, Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package com.apple.dnssd; + + +/** + A listener that receives results from {@link DNSSD#enumerateDomains}. +*/ + +public interface DomainListener extends BaseListener +{ + /** Called to report discovered domains.<P> + + @param domainEnum + The active domain enumerator. + @param flags + Possible values are: DNSSD.MORE_COMING, DNSSD.DEFAULT + <P> + @param ifIndex + Specifies the interface on which the domain exists. (The index for a given + interface is determined via the if_nametoindex() family of calls.) + <P> + @param domain + The name of the domain. + */ + void domainFound( DNSSDService domainEnum, int flags, int ifIndex, String domain); + + /** Called to report that a domain has disappeared.<P> + + @param domainEnum + The active domain enumerator. + @param flags + Possible values are: DNSSD.MORE_COMING, DNSSD.DEFAULT + <P> + @param ifIndex + Specifies the interface on which the domain exists. (The index for a given + interface is determined via the if_nametoindex() family of calls.) + <P> + @param domain + The name of the domain. + */ + void domainLost( DNSSDService domainEnum, int flags, int ifIndex, String domain); +} + diff --git a/mDNSResponder/mDNSShared/Java/JNISupport.c b/mDNSResponder/mDNSShared/Java/JNISupport.c new file mode 100644 index 00000000..22b40930 --- /dev/null +++ b/mDNSResponder/mDNSShared/Java/JNISupport.c @@ -0,0 +1,1072 @@ +/* -*- Mode: C; tab-width: 4 -*- + * + * Copyright (c) 2004 Apple Computer, Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + + This file contains the platform support for DNSSD and related Java classes. + It is used to shim through to the underlying <dns_sd.h> API. + */ + +// AUTO_CALLBACKS should be set to 1 if the underlying mDNS implementation fires response +// callbacks automatically (as in the early Windows prototypes). +// AUTO_CALLBACKS should be set to 0 if the client must call DNSServiceProcessResult() to +// invoke response callbacks (as is true on Mac OS X, Posix, Windows, etc.). +// (Invoking callbacks automatically on a different thread sounds attractive, but while +// the client gains by not needing to add an event source to its main event loop, it loses +// by being forced to deal with concurrency and locking, which can be a bigger burden.) +#ifndef AUTO_CALLBACKS +#define AUTO_CALLBACKS 0 +#endif + +#if !AUTO_CALLBACKS +#ifdef _WIN32 +#include <winsock2.h> +#else //_WIN32 +#include <sys/types.h> +#include <sys/select.h> +#endif // _WIN32 +#endif // AUTO_CALLBACKS + +#include <dns_sd.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#ifdef _WIN32 +#include <winsock2.h> +#include <iphlpapi.h> +static char * win32_if_indextoname( DWORD ifIndex, char * nameBuff); +static DWORD win32_if_nametoindex( const char * nameStr ); +#define if_indextoname win32_if_indextoname +#define if_nametoindex win32_if_nametoindex +#define IF_NAMESIZE MAX_ADAPTER_NAME_LENGTH +#else // _WIN32 +#include <sys/socket.h> +#include <net/if.h> +#endif // _WIN32 + +// When compiling with "-Wshadow" set, including jni.h produces the following error: +// /System/Library/Frameworks/JavaVM.framework/Versions/A/Headers/jni.h:609: warning: declaration of 'index' shadows a global declaration +// To work around this, we use the preprocessor to map the identifier 'index', which appears harmlessly in function prototype declarations, +// to something 'jni_index', which doesn't conflict +#define index jni_index +#include "DNSSD.java.h" +#undef index + +//#include <syslog.h> + +// convenience definition +#ifdef __GNUC__ +#define _UNUSED __attribute__ ((unused)) +#else +#define _UNUSED +#endif + +enum { + kInterfaceVersionOne = 1, + kInterfaceVersionCurrent // Must match version in .jar file +}; + +typedef struct OpContext OpContext; + +struct OpContext +{ + DNSServiceRef ServiceRef; + JNIEnv *Env; + jobject JavaObj; + jobject ClientObj; + jmethodID Callback; + jmethodID Callback2; +}; + +// For AUTO_CALLBACKS, we must attach the callback thread to the Java VM prior to upcall. +#if AUTO_CALLBACKS +JavaVM *gJavaVM = NULL; +#endif + + +JNIEXPORT jint JNICALL Java_com_apple_dnssd_AppleDNSSD_InitLibrary( JNIEnv *pEnv, jclass cls, + jint callerVersion) +{ + /* Ensure that caller & interface versions match. */ + if ( callerVersion != kInterfaceVersionCurrent) + return kDNSServiceErr_Incompatible; + +#if AUTO_CALLBACKS + { + jsize numVMs; + + if ( 0 != JNI_GetCreatedJavaVMs( &gJavaVM, 1, &numVMs)) + return kDNSServiceErr_BadState; + } +#endif + + // Set AppleDNSSD.hasAutoCallbacks + { +#if AUTO_CALLBACKS + jboolean hasAutoC = JNI_TRUE; +#else + jboolean hasAutoC = JNI_FALSE; +#endif + jfieldID hasAutoCField = (*pEnv)->GetStaticFieldID( pEnv, cls, "hasAutoCallbacks", "Z"); + (*pEnv)->SetStaticBooleanField( pEnv, cls, hasAutoCField, hasAutoC); + } + + return kDNSServiceErr_NoError; +} + + +static const char* SafeGetUTFChars( JNIEnv *pEnv, jstring str) +// Wrapper for JNI GetStringUTFChars() that returns NULL for null str. +{ + return str != NULL ? (*pEnv)->GetStringUTFChars( pEnv, str, 0) : NULL; +} + +static void SafeReleaseUTFChars( JNIEnv *pEnv, jstring str, const char *buff) +// Wrapper for JNI GetStringUTFChars() that handles null str. +{ + if ( str != NULL) + (*pEnv)->ReleaseStringUTFChars( pEnv, str, buff); +} + + +#if AUTO_CALLBACKS +static void SetupCallbackState( JNIEnv **ppEnv) +{ + (*gJavaVM)->AttachCurrentThread( gJavaVM, (void**) ppEnv, NULL); +} + +static void TeardownCallbackState( void ) +{ + (*gJavaVM)->DetachCurrentThread( gJavaVM); +} + +#else // AUTO_CALLBACKS + +static void SetupCallbackState( JNIEnv **ppEnv _UNUSED) +{ + // No setup necessary if ProcessResults() has been called +} + +static void TeardownCallbackState( void ) +{ + // No teardown necessary if ProcessResults() has been called +} +#endif // AUTO_CALLBACKS + + +static OpContext *NewContext( JNIEnv *pEnv, jobject owner, + const char *callbackName, const char *callbackSig) +// Create and initialize a new OpContext. +{ + OpContext *pContext = (OpContext*) malloc( sizeof *pContext); + + if ( pContext != NULL) + { + jfieldID clientField = (*pEnv)->GetFieldID( pEnv, (*pEnv)->GetObjectClass( pEnv, owner), + "fListener", "Lcom/apple/dnssd/BaseListener;"); + + pContext->JavaObj = (*pEnv)->NewWeakGlobalRef( pEnv, owner); // must convert local ref to global to cache; + pContext->ClientObj = (*pEnv)->GetObjectField( pEnv, owner, clientField); + pContext->ClientObj = (*pEnv)->NewWeakGlobalRef( pEnv, pContext->ClientObj); // must convert local ref to global to cache + pContext->Callback = (*pEnv)->GetMethodID( pEnv, + (*pEnv)->GetObjectClass( pEnv, pContext->ClientObj), + callbackName, callbackSig); + pContext->Callback2 = NULL; // not always used + } + + return pContext; +} + + +static void ReportError( JNIEnv *pEnv, jobject target, jobject service, DNSServiceErrorType err) +// Invoke operationFailed() method on target with err. +{ + jclass cls = (*pEnv)->GetObjectClass( pEnv, target); + jmethodID opFailed = (*pEnv)->GetMethodID( pEnv, cls, "operationFailed", + "(Lcom/apple/dnssd/DNSSDService;I)V"); + + (*pEnv)->CallVoidMethod( pEnv, target, opFailed, service, err); +} + +JNIEXPORT void JNICALL Java_com_apple_dnssd_AppleService_HaltOperation( JNIEnv *pEnv, jobject pThis) +/* Deallocate the dns_sd service browser and set the Java object's fNativeContext field to 0. */ +{ + jclass cls = (*pEnv)->GetObjectClass( pEnv, pThis); + jfieldID contextField = (*pEnv)->GetFieldID( pEnv, cls, "fNativeContext", "J"); + + if ( contextField != 0) + { + OpContext *pContext = (OpContext*) (long) (*pEnv)->GetLongField(pEnv, pThis, contextField); + if ( pContext != NULL) + { + // MUST clear fNativeContext first, BEFORE calling DNSServiceRefDeallocate() + (*pEnv)->SetLongField(pEnv, pThis, contextField, 0); + if ( pContext->ServiceRef != NULL) + DNSServiceRefDeallocate( pContext->ServiceRef); + + (*pEnv)->DeleteWeakGlobalRef( pEnv, pContext->JavaObj); + (*pEnv)->DeleteWeakGlobalRef( pEnv, pContext->ClientObj); + free( pContext); + } + } +} + + +JNIEXPORT jint JNICALL Java_com_apple_dnssd_AppleService_BlockForData( JNIEnv *pEnv, jobject pThis) +/* Block until data arrives, or one second passes. Returns 1 if data present, 0 otherwise. */ +{ +// BlockForData() not supported with AUTO_CALLBACKS +#if !AUTO_CALLBACKS + jclass cls = (*pEnv)->GetObjectClass( pEnv, pThis); + jfieldID contextField = (*pEnv)->GetFieldID( pEnv, cls, "fNativeContext", "J"); + + if ( contextField != 0) + { + OpContext *pContext = (OpContext*) (long) (*pEnv)->GetLongField(pEnv, pThis, contextField); + if ( pContext != NULL) + { + fd_set readFDs; + int sd = DNSServiceRefSockFD( pContext->ServiceRef); + struct timeval timeout = { 1, 0 }; + FD_ZERO( &readFDs); + FD_SET( sd, &readFDs); + + // Q: Why do we poll here? + // A: Because there's no other thread-safe way to do it. + // Mac OS X terminates a select() call if you close one of the sockets it's listening on, but Linux does not, + // and arguably Linux is correct (See <http://www.ussg.iu.edu/hypermail/linux/kernel/0405.1/0418.html>) + // The problem is that the Mac OS X behaviour assumes that it's okay for one thread to close a socket while + // some other thread is monitoring that socket in select(), but the difficulty is that there's no general way + // to make that thread-safe, because there's no atomic way to enter select() and release a lock simultaneously. + // If we try to do this without holding any lock, then right as we jump to the select() routine, + // some other thread could stop our operation (thereby closing the socket), + // and then that thread (or even some third, unrelated thread) + // could do some other DNS-SD operation (or some other operation that opens a new file descriptor) + // and then we'd blindly resume our fall into the select() call, now blocking on a file descriptor + // that may coincidentally have the same numerical value, but is semantically unrelated + // to the true file descriptor we thought we were blocking on. + // We can't stop this race condition from happening, but at least if we wake up once a second we can detect + // when fNativeContext has gone to zero, and thereby discover that we were blocking on the wrong fd. + + if (select( sd + 1, &readFDs, (fd_set*) NULL, (fd_set*) NULL, &timeout) == 1) return(1); + } + } +#endif // !AUTO_CALLBACKS + return(0); +} + + +JNIEXPORT jint JNICALL Java_com_apple_dnssd_AppleService_ProcessResults( JNIEnv *pEnv, jobject pThis) +/* Call through to DNSServiceProcessResult() while data remains on socket. */ +{ +#if !AUTO_CALLBACKS // ProcessResults() not supported with AUTO_CALLBACKS + + jclass cls = (*pEnv)->GetObjectClass( pEnv, pThis); + jfieldID contextField = (*pEnv)->GetFieldID( pEnv, cls, "fNativeContext", "J"); + OpContext *pContext = (OpContext*) (long) (*pEnv)->GetLongField(pEnv, pThis, contextField); + DNSServiceErrorType err = kDNSServiceErr_BadState; + + if ( pContext != NULL) + { + int sd = DNSServiceRefSockFD( pContext->ServiceRef); + fd_set readFDs; + struct timeval zeroTimeout = { 0, 0 }; + + pContext->Env = pEnv; + + FD_ZERO( &readFDs); + FD_SET( sd, &readFDs); + + err = kDNSServiceErr_NoError; + if (0 < select(sd + 1, &readFDs, (fd_set*) NULL, (fd_set*) NULL, &zeroTimeout)) + { + err = DNSServiceProcessResult(pContext->ServiceRef); + // Use caution here! + // We cannot touch any data structures associated with this operation! + // The DNSServiceProcessResult() routine should have invoked our callback, + // and our callback could have terminated the operation with op.stop(); + // and that means HaltOperation() will have been called, which frees pContext. + // Basically, from here we just have to get out without touching any stale + // data structures that could blow up on us! Particularly, any attempt + // to loop here reading more results from the file descriptor is unsafe. + } + } + return err; +#endif // AUTO_CALLBACKS +} + + +static void DNSSD_API ServiceBrowseReply( DNSServiceRef sdRef _UNUSED, DNSServiceFlags flags, uint32_t interfaceIndex, + DNSServiceErrorType errorCode, const char *serviceName, const char *regtype, + const char *replyDomain, void *context) +{ + OpContext *pContext = (OpContext*) context; + + SetupCallbackState( &pContext->Env); + + if ( pContext->ClientObj != NULL && pContext->Callback != NULL) + { + if ( errorCode == kDNSServiceErr_NoError) + { + (*pContext->Env)->CallVoidMethod( pContext->Env, pContext->ClientObj, + ( flags & kDNSServiceFlagsAdd) != 0 ? pContext->Callback : pContext->Callback2, + pContext->JavaObj, flags, interfaceIndex, + (*pContext->Env)->NewStringUTF( pContext->Env, serviceName), + (*pContext->Env)->NewStringUTF( pContext->Env, regtype), + (*pContext->Env)->NewStringUTF( pContext->Env, replyDomain)); + } + else + ReportError( pContext->Env, pContext->ClientObj, pContext->JavaObj, errorCode); + } + + TeardownCallbackState(); +} + +JNIEXPORT jint JNICALL Java_com_apple_dnssd_AppleBrowser_CreateBrowser( JNIEnv *pEnv, jobject pThis, + jint flags, jint ifIndex, jstring regType, jstring domain) +{ + jclass cls = (*pEnv)->GetObjectClass( pEnv, pThis); + jfieldID contextField = (*pEnv)->GetFieldID( pEnv, cls, "fNativeContext", "J"); + OpContext *pContext = NULL; + DNSServiceErrorType err = kDNSServiceErr_NoError; + + if ( contextField != 0) + pContext = NewContext( pEnv, pThis, "serviceFound", + "(Lcom/apple/dnssd/DNSSDService;IILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)V"); + else + err = kDNSServiceErr_BadParam; + + if ( pContext != NULL) + { + const char *regStr = SafeGetUTFChars( pEnv, regType); + const char *domainStr = SafeGetUTFChars( pEnv, domain); + + pContext->Callback2 = (*pEnv)->GetMethodID( pEnv, + (*pEnv)->GetObjectClass( pEnv, pContext->ClientObj), + "serviceLost", "(Lcom/apple/dnssd/DNSSDService;IILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)V"); + + err = DNSServiceBrowse( &pContext->ServiceRef, flags, ifIndex, regStr, domainStr, ServiceBrowseReply, pContext); + if ( err == kDNSServiceErr_NoError) + { + (*pEnv)->SetLongField(pEnv, pThis, contextField, (long) pContext); + } + + SafeReleaseUTFChars( pEnv, regType, regStr); + SafeReleaseUTFChars( pEnv, domain, domainStr); + } + else + err = kDNSServiceErr_NoMemory; + + return err; +} + + +static void DNSSD_API ServiceResolveReply( DNSServiceRef sdRef _UNUSED, DNSServiceFlags flags, uint32_t interfaceIndex, + DNSServiceErrorType errorCode, const char *fullname, const char *hosttarget, + uint16_t port, uint16_t txtLen, const unsigned char *txtRecord, void *context) +{ + OpContext *pContext = (OpContext*) context; + jclass txtCls; + jmethodID txtCtor; + jbyteArray txtBytes; + jobject txtObj; + jbyte *pBytes; + + SetupCallbackState( &pContext->Env); + + txtCls = (*pContext->Env)->FindClass( pContext->Env, "com/apple/dnssd/TXTRecord"); + txtCtor = (*pContext->Env)->GetMethodID( pContext->Env, txtCls, "<init>", "([B)V"); + + if ( pContext->ClientObj != NULL && pContext->Callback != NULL && txtCtor != NULL && + NULL != ( txtBytes = (*pContext->Env)->NewByteArray( pContext->Env, txtLen))) + { + if ( errorCode == kDNSServiceErr_NoError) + { + // Since Java ints are defined to be big-endian, we canonicalize 'port' from a 16-bit + // pattern into a number here. + port = ( ((unsigned char*) &port)[0] << 8) | ((unsigned char*) &port)[1]; + + // Initialize txtBytes with contents of txtRecord + pBytes = (*pContext->Env)->GetByteArrayElements( pContext->Env, txtBytes, NULL); + memcpy( pBytes, txtRecord, txtLen); + (*pContext->Env)->ReleaseByteArrayElements( pContext->Env, txtBytes, pBytes, JNI_COMMIT); + + // Construct txtObj with txtBytes + txtObj = (*pContext->Env)->NewObject( pContext->Env, txtCls, txtCtor, txtBytes); + (*pContext->Env)->DeleteLocalRef( pContext->Env, txtBytes); + + (*pContext->Env)->CallVoidMethod( pContext->Env, pContext->ClientObj, pContext->Callback, + pContext->JavaObj, flags, interfaceIndex, + (*pContext->Env)->NewStringUTF( pContext->Env, fullname), + (*pContext->Env)->NewStringUTF( pContext->Env, hosttarget), + port, txtObj); + } + else + ReportError( pContext->Env, pContext->ClientObj, pContext->JavaObj, errorCode); + } + + TeardownCallbackState(); +} + +JNIEXPORT jint JNICALL Java_com_apple_dnssd_AppleResolver_CreateResolver( JNIEnv *pEnv, jobject pThis, + jint flags, jint ifIndex, jstring serviceName, jstring regType, jstring domain) +{ + jclass cls = (*pEnv)->GetObjectClass( pEnv, pThis); + jfieldID contextField = (*pEnv)->GetFieldID( pEnv, cls, "fNativeContext", "J"); + OpContext *pContext = NULL; + DNSServiceErrorType err = kDNSServiceErr_NoError; + + if ( contextField != 0) + pContext = NewContext( pEnv, pThis, "serviceResolved", + "(Lcom/apple/dnssd/DNSSDService;IILjava/lang/String;Ljava/lang/String;ILcom/apple/dnssd/TXTRecord;)V"); + else + err = kDNSServiceErr_BadParam; + + if ( pContext != NULL) + { + const char *servStr = SafeGetUTFChars( pEnv, serviceName); + const char *regStr = SafeGetUTFChars( pEnv, regType); + const char *domainStr = SafeGetUTFChars( pEnv, domain); + + err = DNSServiceResolve( &pContext->ServiceRef, flags, ifIndex, + servStr, regStr, domainStr, ServiceResolveReply, pContext); + if ( err == kDNSServiceErr_NoError) + { + (*pEnv)->SetLongField(pEnv, pThis, contextField, (long) pContext); + } + + SafeReleaseUTFChars( pEnv, serviceName, servStr); + SafeReleaseUTFChars( pEnv, regType, regStr); + SafeReleaseUTFChars( pEnv, domain, domainStr); + } + else + err = kDNSServiceErr_NoMemory; + + return err; +} + + +static void DNSSD_API ServiceRegisterReply( DNSServiceRef sdRef _UNUSED, DNSServiceFlags flags, + DNSServiceErrorType errorCode, const char *serviceName, + const char *regType, const char *domain, void *context) +{ + OpContext *pContext = (OpContext*) context; + + SetupCallbackState( &pContext->Env); + + if ( pContext->ClientObj != NULL && pContext->Callback != NULL) + { + if ( errorCode == kDNSServiceErr_NoError) + { + (*pContext->Env)->CallVoidMethod( pContext->Env, pContext->ClientObj, pContext->Callback, + pContext->JavaObj, flags, + (*pContext->Env)->NewStringUTF( pContext->Env, serviceName), + (*pContext->Env)->NewStringUTF( pContext->Env, regType), + (*pContext->Env)->NewStringUTF( pContext->Env, domain)); + } + else + ReportError( pContext->Env, pContext->ClientObj, pContext->JavaObj, errorCode); + } + TeardownCallbackState(); +} + +JNIEXPORT jint JNICALL Java_com_apple_dnssd_AppleRegistration_BeginRegister( JNIEnv *pEnv, jobject pThis, + jint ifIndex, jint flags, jstring serviceName, jstring regType, + jstring domain, jstring host, jint port, jbyteArray txtRecord) +{ + //syslog(LOG_ERR, "BR"); + jclass cls = (*pEnv)->GetObjectClass( pEnv, pThis); + jfieldID contextField = (*pEnv)->GetFieldID( pEnv, cls, "fNativeContext", "J"); + OpContext *pContext = NULL; + DNSServiceErrorType err = kDNSServiceErr_NoError; + jbyte *pBytes; + jsize numBytes; + + //syslog(LOG_ERR, "BR: contextField %d", contextField); + + if ( contextField != 0) + pContext = NewContext( pEnv, pThis, "serviceRegistered", + "(Lcom/apple/dnssd/DNSSDRegistration;ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)V"); + else + err = kDNSServiceErr_BadParam; + + if ( pContext != NULL) + { + const char *servStr = SafeGetUTFChars( pEnv, serviceName); + const char *regStr = SafeGetUTFChars( pEnv, regType); + const char *domainStr = SafeGetUTFChars( pEnv, domain); + const char *hostStr = SafeGetUTFChars( pEnv, host); + + //syslog(LOG_ERR, "BR: regStr %s", regStr); + + // Since Java ints are defined to be big-endian, we de-canonicalize 'port' from a + // big-endian number into a 16-bit pattern here. + uint16_t portBits = port; + portBits = ( ((unsigned char*) &portBits)[0] << 8) | ((unsigned char*) &portBits)[1]; + + pBytes = txtRecord ? (*pEnv)->GetByteArrayElements( pEnv, txtRecord, NULL) : NULL; + numBytes = txtRecord ? (*pEnv)->GetArrayLength( pEnv, txtRecord) : 0; + + err = DNSServiceRegister( &pContext->ServiceRef, flags, ifIndex, servStr, regStr, + domainStr, hostStr, portBits, + numBytes, pBytes, ServiceRegisterReply, pContext); + if ( err == kDNSServiceErr_NoError) + { + (*pEnv)->SetLongField(pEnv, pThis, contextField, (long) pContext); + } + + if ( pBytes != NULL) + (*pEnv)->ReleaseByteArrayElements( pEnv, txtRecord, pBytes, 0); + + SafeReleaseUTFChars( pEnv, serviceName, servStr); + SafeReleaseUTFChars( pEnv, regType, regStr); + SafeReleaseUTFChars( pEnv, domain, domainStr); + SafeReleaseUTFChars( pEnv, host, hostStr); + } + else + err = kDNSServiceErr_NoMemory; + + return err; +} + +JNIEXPORT jint JNICALL Java_com_apple_dnssd_AppleRegistration_AddRecord( JNIEnv *pEnv, jobject pThis, + jint flags, jint rrType, jbyteArray rData, jint ttl, jobject destObj) +{ + jclass cls = (*pEnv)->GetObjectClass( pEnv, pThis); + jfieldID contextField = (*pEnv)->GetFieldID( pEnv, cls, "fNativeContext", "J"); + jclass destCls = (*pEnv)->GetObjectClass( pEnv, destObj); + jfieldID recField = (*pEnv)->GetFieldID( pEnv, destCls, "fRecord", "J"); + OpContext *pContext = NULL; + DNSServiceErrorType err = kDNSServiceErr_NoError; + jbyte *pBytes; + jsize numBytes; + DNSRecordRef recRef; + + if ( contextField != 0) + pContext = (OpContext*) (long) (*pEnv)->GetLongField(pEnv, pThis, contextField); + if ( pContext == NULL || pContext->ServiceRef == NULL) + return kDNSServiceErr_BadParam; + + pBytes = (*pEnv)->GetByteArrayElements( pEnv, rData, NULL); + numBytes = (*pEnv)->GetArrayLength( pEnv, rData); + + err = DNSServiceAddRecord( pContext->ServiceRef, &recRef, flags, rrType, numBytes, pBytes, ttl); + if ( err == kDNSServiceErr_NoError) + { + (*pEnv)->SetLongField(pEnv, destObj, recField, (long) recRef); + } + + if ( pBytes != NULL) + (*pEnv)->ReleaseByteArrayElements( pEnv, rData, pBytes, 0); + + return err; +} + +JNIEXPORT jint JNICALL Java_com_apple_dnssd_AppleDNSRecord_Update( JNIEnv *pEnv, jobject pThis, + jint flags, jbyteArray rData, jint ttl) +{ + jclass cls = (*pEnv)->GetObjectClass( pEnv, pThis); + jfieldID ownerField = (*pEnv)->GetFieldID( pEnv, cls, "fOwner", "Lcom/apple/dnssd/AppleService;"); + jfieldID recField = (*pEnv)->GetFieldID( pEnv, cls, "fRecord", "J"); + OpContext *pContext = NULL; + DNSServiceErrorType err = kDNSServiceErr_NoError; + jbyte *pBytes; + jsize numBytes; + DNSRecordRef recRef = NULL; + + if ( ownerField != 0) + { + jobject ownerObj = (*pEnv)->GetObjectField( pEnv, pThis, ownerField); + jclass ownerClass = (*pEnv)->GetObjectClass( pEnv, ownerObj); + jfieldID contextField = (*pEnv)->GetFieldID( pEnv, ownerClass, "fNativeContext", "J"); + if ( contextField != 0) + pContext = (OpContext*) (long) (*pEnv)->GetLongField(pEnv, ownerObj, contextField); + } + if ( recField != 0) + recRef = (DNSRecordRef) (long) (*pEnv)->GetLongField(pEnv, pThis, recField); + if ( pContext == NULL || pContext->ServiceRef == NULL) + return kDNSServiceErr_BadParam; + + pBytes = (*pEnv)->GetByteArrayElements( pEnv, rData, NULL); + numBytes = (*pEnv)->GetArrayLength( pEnv, rData); + + err = DNSServiceUpdateRecord( pContext->ServiceRef, recRef, flags, numBytes, pBytes, ttl); + + if ( pBytes != NULL) + (*pEnv)->ReleaseByteArrayElements( pEnv, rData, pBytes, 0); + + return err; +} + +JNIEXPORT jint JNICALL Java_com_apple_dnssd_AppleDNSRecord_Remove( JNIEnv *pEnv, jobject pThis) +{ + jclass cls = (*pEnv)->GetObjectClass( pEnv, pThis); + jfieldID ownerField = (*pEnv)->GetFieldID( pEnv, cls, "fOwner", "Lcom/apple/dnssd/AppleService;"); + jfieldID recField = (*pEnv)->GetFieldID( pEnv, cls, "fRecord", "J"); + OpContext *pContext = NULL; + DNSServiceErrorType err = kDNSServiceErr_NoError; + DNSRecordRef recRef = NULL; + + if ( ownerField != 0) + { + jobject ownerObj = (*pEnv)->GetObjectField( pEnv, pThis, ownerField); + jclass ownerClass = (*pEnv)->GetObjectClass( pEnv, ownerObj); + jfieldID contextField = (*pEnv)->GetFieldID( pEnv, ownerClass, "fNativeContext", "J"); + if ( contextField != 0) + pContext = (OpContext*) (long) (*pEnv)->GetLongField(pEnv, ownerObj, contextField); + } + if ( recField != 0) + recRef = (DNSRecordRef) (long) (*pEnv)->GetLongField(pEnv, pThis, recField); + if ( pContext == NULL || pContext->ServiceRef == NULL) + return kDNSServiceErr_BadParam; + + err = DNSServiceRemoveRecord( pContext->ServiceRef, recRef, 0); + + return err; +} + + +JNIEXPORT jint JNICALL Java_com_apple_dnssd_AppleRecordRegistrar_CreateConnection( JNIEnv *pEnv, jobject pThis) +{ + jclass cls = (*pEnv)->GetObjectClass( pEnv, pThis); + jfieldID contextField = (*pEnv)->GetFieldID( pEnv, cls, "fNativeContext", "J"); + OpContext *pContext = NULL; + DNSServiceErrorType err = kDNSServiceErr_NoError; + + if ( contextField != 0) + pContext = NewContext( pEnv, pThis, "recordRegistered", "(Lcom/apple/dnssd/DNSRecord;I)V"); + else + err = kDNSServiceErr_BadParam; + + if ( pContext != NULL) + { + err = DNSServiceCreateConnection( &pContext->ServiceRef); + if ( err == kDNSServiceErr_NoError) + { + (*pEnv)->SetLongField(pEnv, pThis, contextField, (long) pContext); + } + } + else + err = kDNSServiceErr_NoMemory; + + return err; +} + +struct RecordRegistrationRef +{ + OpContext *Context; + jobject RecordObj; +}; +typedef struct RecordRegistrationRef RecordRegistrationRef; + +static void DNSSD_API RegisterRecordReply( DNSServiceRef sdRef _UNUSED, + DNSRecordRef recordRef _UNUSED, DNSServiceFlags flags, + DNSServiceErrorType errorCode, void *context) +{ + RecordRegistrationRef *regEnvelope = (RecordRegistrationRef*) context; + OpContext *pContext = regEnvelope->Context; + + SetupCallbackState( &pContext->Env); + + if ( pContext->ClientObj != NULL && pContext->Callback != NULL) + { + if ( errorCode == kDNSServiceErr_NoError) + { + (*pContext->Env)->CallVoidMethod( pContext->Env, pContext->ClientObj, pContext->Callback, + regEnvelope->RecordObj, flags); + } + else + ReportError( pContext->Env, pContext->ClientObj, pContext->JavaObj, errorCode); + } + + (*pContext->Env)->DeleteWeakGlobalRef( pContext->Env, regEnvelope->RecordObj); + free( regEnvelope); + + TeardownCallbackState(); +} + +JNIEXPORT jint JNICALL Java_com_apple_dnssd_AppleRecordRegistrar_RegisterRecord( JNIEnv *pEnv, jobject pThis, + jint flags, jint ifIndex, jstring fullname, jint rrType, jint rrClass, + jbyteArray rData, jint ttl, jobject destObj) +{ + jclass cls = (*pEnv)->GetObjectClass( pEnv, pThis); + jfieldID contextField = (*pEnv)->GetFieldID( pEnv, cls, "fNativeContext", "J"); + jclass destCls = (*pEnv)->GetObjectClass( pEnv, destObj); + jfieldID recField = (*pEnv)->GetFieldID( pEnv, destCls, "fRecord", "J"); + const char *nameStr = SafeGetUTFChars( pEnv, fullname); + OpContext *pContext = NULL; + DNSServiceErrorType err = kDNSServiceErr_NoError; + jbyte *pBytes; + jsize numBytes; + DNSRecordRef recRef; + RecordRegistrationRef *regEnvelope; + + if ( contextField != 0) + pContext = (OpContext*) (long) (*pEnv)->GetLongField(pEnv, pThis, contextField); + if ( pContext == NULL || pContext->ServiceRef == NULL || nameStr == NULL) + return kDNSServiceErr_BadParam; + + regEnvelope = calloc( 1, sizeof *regEnvelope); + if ( regEnvelope == NULL) + return kDNSServiceErr_NoMemory; + regEnvelope->Context = pContext; + regEnvelope->RecordObj = (*pEnv)->NewWeakGlobalRef( pEnv, destObj); // must convert local ref to global to cache + + pBytes = (*pEnv)->GetByteArrayElements( pEnv, rData, NULL); + numBytes = (*pEnv)->GetArrayLength( pEnv, rData); + + err = DNSServiceRegisterRecord( pContext->ServiceRef, &recRef, flags, ifIndex, + nameStr, rrType, rrClass, numBytes, pBytes, ttl, + RegisterRecordReply, regEnvelope); + + if ( err == kDNSServiceErr_NoError) + { + (*pEnv)->SetLongField(pEnv, destObj, recField, (long) recRef); + } + else + { + if ( regEnvelope->RecordObj != NULL) + (*pEnv)->DeleteWeakGlobalRef( pEnv, regEnvelope->RecordObj); + free( regEnvelope); + } + + if ( pBytes != NULL) + (*pEnv)->ReleaseByteArrayElements( pEnv, rData, pBytes, 0); + + SafeReleaseUTFChars( pEnv, fullname, nameStr); + + return err; +} + + +static void DNSSD_API ServiceQueryReply( DNSServiceRef sdRef _UNUSED, DNSServiceFlags flags, uint32_t interfaceIndex, + DNSServiceErrorType errorCode, const char *serviceName, + uint16_t rrtype, uint16_t rrclass, uint16_t rdlen, + const void *rdata, uint32_t ttl, void *context) +{ + OpContext *pContext = (OpContext*) context; + jbyteArray rDataObj; + jbyte *pBytes; + + SetupCallbackState( &pContext->Env); + + if ( pContext->ClientObj != NULL && pContext->Callback != NULL && + NULL != ( rDataObj = (*pContext->Env)->NewByteArray( pContext->Env, rdlen))) + { + if ( errorCode == kDNSServiceErr_NoError) + { + // Initialize rDataObj with contents of rdata + pBytes = (*pContext->Env)->GetByteArrayElements( pContext->Env, rDataObj, NULL); + memcpy( pBytes, rdata, rdlen); + (*pContext->Env)->ReleaseByteArrayElements( pContext->Env, rDataObj, pBytes, JNI_COMMIT); + + (*pContext->Env)->CallVoidMethod( pContext->Env, pContext->ClientObj, pContext->Callback, + pContext->JavaObj, flags, interfaceIndex, + (*pContext->Env)->NewStringUTF( pContext->Env, serviceName), + rrtype, rrclass, rDataObj, ttl); + } + else + ReportError( pContext->Env, pContext->ClientObj, pContext->JavaObj, errorCode); + } + TeardownCallbackState(); +} + +JNIEXPORT jint JNICALL Java_com_apple_dnssd_AppleQuery_CreateQuery( JNIEnv *pEnv, jobject pThis, + jint flags, jint ifIndex, jstring serviceName, jint rrtype, jint rrclass) +{ + jclass cls = (*pEnv)->GetObjectClass( pEnv, pThis); + jfieldID contextField = (*pEnv)->GetFieldID( pEnv, cls, "fNativeContext", "J"); + OpContext *pContext = NULL; + DNSServiceErrorType err = kDNSServiceErr_NoError; + + if ( contextField != 0) + pContext = NewContext( pEnv, pThis, "queryAnswered", + "(Lcom/apple/dnssd/DNSSDService;IILjava/lang/String;II[BI)V"); + else + err = kDNSServiceErr_BadParam; + + if ( pContext != NULL) + { + const char *servStr = SafeGetUTFChars( pEnv, serviceName); + + err = DNSServiceQueryRecord( &pContext->ServiceRef, flags, ifIndex, servStr, + rrtype, rrclass, ServiceQueryReply, pContext); + if ( err == kDNSServiceErr_NoError) + { + (*pEnv)->SetLongField(pEnv, pThis, contextField, (long) pContext); + } + + SafeReleaseUTFChars( pEnv, serviceName, servStr); + } + else + err = kDNSServiceErr_NoMemory; + + return err; +} + + +static void DNSSD_API DomainEnumReply( DNSServiceRef sdRef _UNUSED, DNSServiceFlags flags, uint32_t interfaceIndex, + DNSServiceErrorType errorCode, const char *replyDomain, void *context) +{ + OpContext *pContext = (OpContext*) context; + + SetupCallbackState( &pContext->Env); + + if ( pContext->ClientObj != NULL && pContext->Callback != NULL) + { + if ( errorCode == kDNSServiceErr_NoError) + { + (*pContext->Env)->CallVoidMethod( pContext->Env, pContext->ClientObj, + ( flags & kDNSServiceFlagsAdd) != 0 ? pContext->Callback : pContext->Callback2, + pContext->JavaObj, flags, interfaceIndex, + (*pContext->Env)->NewStringUTF( pContext->Env, replyDomain)); + } + else + ReportError( pContext->Env, pContext->ClientObj, pContext->JavaObj, errorCode); + } + TeardownCallbackState(); +} + +JNIEXPORT jint JNICALL Java_com_apple_dnssd_AppleDomainEnum_BeginEnum( JNIEnv *pEnv, jobject pThis, + jint flags, jint ifIndex) +{ + jclass cls = (*pEnv)->GetObjectClass( pEnv, pThis); + jfieldID contextField = (*pEnv)->GetFieldID( pEnv, cls, "fNativeContext", "J"); + OpContext *pContext = NULL; + DNSServiceErrorType err = kDNSServiceErr_NoError; + + if ( contextField != 0) + pContext = NewContext( pEnv, pThis, "domainFound", + "(Lcom/apple/dnssd/DNSSDService;IILjava/lang/String;)V"); + else + err = kDNSServiceErr_BadParam; + + if ( pContext != NULL) + { + pContext->Callback2 = (*pEnv)->GetMethodID( pEnv, + (*pEnv)->GetObjectClass( pEnv, pContext->ClientObj), + "domainLost", "(Lcom/apple/dnssd/DNSSDService;IILjava/lang/String;)V"); + + err = DNSServiceEnumerateDomains( &pContext->ServiceRef, flags, ifIndex, + DomainEnumReply, pContext); + if ( err == kDNSServiceErr_NoError) + { + (*pEnv)->SetLongField(pEnv, pThis, contextField, (long) pContext); + } + } + else + err = kDNSServiceErr_NoMemory; + + return err; +} + + +JNIEXPORT jint JNICALL Java_com_apple_dnssd_AppleDNSSD_ConstructName( JNIEnv *pEnv, jobject pThis _UNUSED, + jstring serviceName, jstring regtype, jstring domain, jobjectArray pOut) +{ + DNSServiceErrorType err = kDNSServiceErr_NoError; + const char *nameStr = SafeGetUTFChars( pEnv, serviceName); + const char *regStr = SafeGetUTFChars( pEnv, regtype); + const char *domStr = SafeGetUTFChars( pEnv, domain); + char buff[ kDNSServiceMaxDomainName + 1]; + + err = DNSServiceConstructFullName( buff, nameStr, regStr, domStr); + + if ( err == kDNSServiceErr_NoError) + { + // pOut is expected to be a String[1] array. + (*pEnv)->SetObjectArrayElement( pEnv, pOut, 0, (*pEnv)->NewStringUTF( pEnv, buff)); + } + + SafeReleaseUTFChars( pEnv, serviceName, nameStr); + SafeReleaseUTFChars( pEnv, regtype, regStr); + SafeReleaseUTFChars( pEnv, domain, domStr); + + return err; +} + +JNIEXPORT void JNICALL Java_com_apple_dnssd_AppleDNSSD_ReconfirmRecord( JNIEnv *pEnv, jobject pThis _UNUSED, + jint flags, jint ifIndex, jstring fullName, + jint rrtype, jint rrclass, jbyteArray rdata) +{ + jbyte *pBytes; + jsize numBytes; + const char *nameStr = SafeGetUTFChars( pEnv, fullName); + + pBytes = (*pEnv)->GetByteArrayElements( pEnv, rdata, NULL); + numBytes = (*pEnv)->GetArrayLength( pEnv, rdata); + + DNSServiceReconfirmRecord( flags, ifIndex, nameStr, rrtype, rrclass, numBytes, pBytes); + + if ( pBytes != NULL) + (*pEnv)->ReleaseByteArrayElements( pEnv, rdata, pBytes, 0); + + SafeReleaseUTFChars( pEnv, fullName, nameStr); +} + +#define LOCAL_ONLY_NAME "loo" +#define P2P_NAME "p2p" + +JNIEXPORT jstring JNICALL Java_com_apple_dnssd_AppleDNSSD_GetNameForIfIndex( JNIEnv *pEnv, jobject pThis _UNUSED, + jint ifIndex) +{ + char *p = LOCAL_ONLY_NAME, nameBuff[IF_NAMESIZE]; + + if (ifIndex == (jint) kDNSServiceInterfaceIndexP2P) + p = P2P_NAME; + else if (ifIndex != (jint) kDNSServiceInterfaceIndexLocalOnly) + p = if_indextoname( ifIndex, nameBuff ); + + return (*pEnv)->NewStringUTF( pEnv, p); +} + + +JNIEXPORT jint JNICALL Java_com_apple_dnssd_AppleDNSSD_GetIfIndexForName( JNIEnv *pEnv, jobject pThis _UNUSED, + jstring ifName) +{ + uint32_t ifIndex = kDNSServiceInterfaceIndexLocalOnly; + const char *nameStr = SafeGetUTFChars( pEnv, ifName); + + if (strcmp(nameStr, P2P_NAME) == 0) + ifIndex = kDNSServiceInterfaceIndexP2P; + else if (strcmp(nameStr, LOCAL_ONLY_NAME)) + ifIndex = if_nametoindex( nameStr); + + SafeReleaseUTFChars( pEnv, ifName, nameStr); + + return ifIndex; +} + + +#if defined(_WIN32) +static char* +win32_if_indextoname( DWORD ifIndex, char * nameBuff) +{ + PIP_ADAPTER_INFO pAdapterInfo = NULL; + PIP_ADAPTER_INFO pAdapter = NULL; + DWORD dwRetVal = 0; + char * ifName = NULL; + ULONG ulOutBufLen = 0; + + if (GetAdaptersInfo( NULL, &ulOutBufLen) != ERROR_BUFFER_OVERFLOW) + { + goto exit; + } + + pAdapterInfo = (IP_ADAPTER_INFO *) malloc(ulOutBufLen); + + if (pAdapterInfo == NULL) + { + goto exit; + } + + dwRetVal = GetAdaptersInfo( pAdapterInfo, &ulOutBufLen ); + + if (dwRetVal != NO_ERROR) + { + goto exit; + } + + pAdapter = pAdapterInfo; + while (pAdapter) + { + if (pAdapter->Index == ifIndex) + { + // It would be better if we passed in the length of nameBuff to this + // function, so we would have absolute certainty that no buffer + // overflows would occur. Buffer overflows *shouldn't* occur because + // nameBuff is of size MAX_ADAPTER_NAME_LENGTH. + strcpy( nameBuff, pAdapter->AdapterName ); + ifName = nameBuff; + break; + } + + pAdapter = pAdapter->Next; + } + +exit: + + if (pAdapterInfo != NULL) + { + free( pAdapterInfo ); + pAdapterInfo = NULL; + } + + return ifName; +} + + +static DWORD +win32_if_nametoindex( const char * nameStr ) +{ + PIP_ADAPTER_INFO pAdapterInfo = NULL; + PIP_ADAPTER_INFO pAdapter = NULL; + DWORD dwRetVal = 0; + DWORD ifIndex = 0; + ULONG ulOutBufLen = 0; + + if (GetAdaptersInfo( NULL, &ulOutBufLen) != ERROR_BUFFER_OVERFLOW) + { + goto exit; + } + + pAdapterInfo = (IP_ADAPTER_INFO *) malloc(ulOutBufLen); + + if (pAdapterInfo == NULL) + { + goto exit; + } + + dwRetVal = GetAdaptersInfo( pAdapterInfo, &ulOutBufLen ); + + if (dwRetVal != NO_ERROR) + { + goto exit; + } + + pAdapter = pAdapterInfo; + while (pAdapter) + { + if (strcmp(pAdapter->AdapterName, nameStr) == 0) + { + ifIndex = pAdapter->Index; + break; + } + + pAdapter = pAdapter->Next; + } + +exit: + + if (pAdapterInfo != NULL) + { + free( pAdapterInfo ); + pAdapterInfo = NULL; + } + + return ifIndex; +} +#endif + + +// Note: The C preprocessor stringify operator ('#') makes a string from its argument, without macro expansion +// e.g. If "version" is #define'd to be "4", then STRINGIFY_AWE(version) will return the string "version", not "4" +// To expand "version" to its value before making the string, use STRINGIFY(version) instead +#define STRINGIFY_ARGUMENT_WITHOUT_EXPANSION(s) # s +#define STRINGIFY(s) STRINGIFY_ARGUMENT_WITHOUT_EXPANSION(s) + +// NOT static -- otherwise the compiler may optimize it out +// The "@(#) " pattern is a special prefix the "what" command looks for +const char VersionString_SCCS[] = "@(#) libjdns_sd " STRINGIFY(mDNSResponderVersion) " (" __DATE__ " " __TIME__ ")"; diff --git a/mDNSResponder/mDNSShared/Java/QueryListener.java b/mDNSResponder/mDNSShared/Java/QueryListener.java new file mode 100644 index 00000000..0decb7fc --- /dev/null +++ b/mDNSResponder/mDNSShared/Java/QueryListener.java @@ -0,0 +1,59 @@ +/* -*- Mode: Java; tab-width: 4 -*- + * + * Copyright (c) 2004 Apple Computer, Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package com.apple.dnssd; + + +/** A listener that receives results from {@link DNSSD#queryRecord}. */ + +public interface QueryListener extends BaseListener +{ + /** Called when a record query has been completed. Inspect flags + parameter to determine nature of query event.<P> + + @param query + The active query object. + <P> + @param flags + If kDNSServiceFlagsAdd bit is set, this is a newly discovered answer; + otherwise this is a previously discovered answer which has expired. + Other possible values are DNSSD.MORE_COMING. + <P> + @param ifIndex + The interface on which the query was resolved. (The index for a given + interface is determined via the if_nametoindex() family of calls.) + <P> + @param fullName + The resource record's full domain name. + <P> + @param rrtype + The resource record's type (e.g. PTR, SRV, etc) as defined by RFC 1035 and its updates. + <P> + @param rrclass + The class of the resource record, as defined by RFC 1035 and its updates. + <P> + @param rdata + The raw rdata of the resource record. + <P> + @param ttl + The resource record's time to live, in seconds. + */ + void queryAnswered( DNSSDService query, int flags, int ifIndex, String fullName, + int rrtype, int rrclass, byte[] rdata, int ttl); +} + diff --git a/mDNSResponder/mDNSShared/Java/RegisterListener.java b/mDNSResponder/mDNSShared/Java/RegisterListener.java new file mode 100644 index 00000000..00fa1a63 --- /dev/null +++ b/mDNSResponder/mDNSShared/Java/RegisterListener.java @@ -0,0 +1,49 @@ +/* -*- Mode: Java; tab-width: 4 -*- + * + * Copyright (c) 2004 Apple Computer, Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package com.apple.dnssd; + + +/** A listener that receives results from {@link DNSSD#register}. */ + +public interface RegisterListener extends BaseListener +{ + /** Called when a registration has been completed.<P> + + @param registration + The active registration. + <P> + @param flags + Currently unused, reserved for future use. + <P> + @param serviceName + The service name registered (if the application did not specify a name in + DNSSD.register(), this indicates what name was automatically chosen). + <P> + @param regType + The type of service registered, as it was passed to DNSSD.register(). + <P> + @param domain + The domain on which the service was registered. If the application did not + specify a domain in DNSSD.register(), this is the default domain + on which the service was registered. + */ + void serviceRegistered( DNSSDRegistration registration, int flags, String serviceName, + String regType, String domain); +} + diff --git a/mDNSResponder/mDNSShared/Java/RegisterRecordListener.java b/mDNSResponder/mDNSShared/Java/RegisterRecordListener.java new file mode 100644 index 00000000..6fecf8d8 --- /dev/null +++ b/mDNSResponder/mDNSShared/Java/RegisterRecordListener.java @@ -0,0 +1,37 @@ +/* -*- Mode: Java; tab-width: 4 -*- + * + * Copyright (c) 2004 Apple Computer, Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package com.apple.dnssd; + + +/** A listener that receives results from {@link DNSSDRecordRegistrar#registerRecord}. */ + +public interface RegisterRecordListener extends BaseListener +{ + /** Called when a record registration succeeds.<P> + + @param record + A {@link DNSRecord}. + <P> + @param flags + Currently ignored, reserved for future use. + <P> + */ + void recordRegistered( DNSRecord record, int flags); +} + diff --git a/mDNSResponder/mDNSShared/Java/ResolveListener.java b/mDNSResponder/mDNSShared/Java/ResolveListener.java new file mode 100644 index 00000000..33dafa38 --- /dev/null +++ b/mDNSResponder/mDNSShared/Java/ResolveListener.java @@ -0,0 +1,56 @@ +/* -*- Mode: Java; tab-width: 4 -*- + * + * Copyright (c) 2004 Apple Computer, Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package com.apple.dnssd; + + +/** A listener that receives results from {@link DNSSD#resolve}. */ + +public interface ResolveListener extends BaseListener +{ + /** Called when a service has been resolved.<P> + + @param resolver + The active resolver object. + <P> + @param flags + Currently unused, reserved for future use. + <P> + @param fullName + The full service domain name, in the form <servicename>.<protocol>.<domain>. + (Any literal dots (".") are escaped with a backslash ("\."), and literal + backslashes are escaped with a second backslash ("\\"), e.g. a web server + named "Dr. Pepper" would have the fullname "Dr\.\032Pepper._http._tcp.local."). + This is the appropriate format to pass to standard system DNS APIs such as + res_query(), or to the special-purpose functions included in this API that + take fullname parameters. + <P> + @param hostName + The target hostname of the machine providing the service. This name can + be passed to functions like queryRecord() to look up the host's IP address. + <P> + @param port + The port number on which connections are accepted for this service. + <P> + @param txtRecord + The service's primary txt record. + */ + void serviceResolved( DNSSDService resolver, int flags, int ifIndex, String fullName, + String hostName, int port, TXTRecord txtRecord); +} + diff --git a/mDNSResponder/mDNSShared/Java/TXTRecord.java b/mDNSResponder/mDNSShared/Java/TXTRecord.java new file mode 100644 index 00000000..8d9df7a1 --- /dev/null +++ b/mDNSResponder/mDNSShared/Java/TXTRecord.java @@ -0,0 +1,290 @@ +/* -*- Mode: Java; tab-width: 4 -*- + * + * Copyright (c) 2004 Apple Computer, Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + + To do: + - implement remove() + - fix set() to replace existing values + */ + + +package com.apple.dnssd; + + +/** + Object used to construct and parse DNS-SD format TXT records. + For more info see <a href="http://files.dns-sd.org/draft-cheshire-dnsext-dns-sd.txt">DNS-Based Service Discovery</a>, section 6. +*/ + +public class TXTRecord +{ + /* + DNS-SD specifies that a TXT record corresponding to an SRV record consist of + a packed array of bytes, each preceded by a length byte. Each string + is an attribute-value pair. + + The TXTRecord object stores the entire TXT data as a single byte array, traversing it + as need be to implement its various methods. + */ + + static final protected byte kAttrSep = '='; + + protected byte[] fBytes; + + /** Constructs a new, empty TXT record. */ + public TXTRecord() + { fBytes = new byte[0]; } + + /** Constructs a new TXT record from a byte array in the standard format. */ + public TXTRecord( byte[] initBytes) + { fBytes = (byte[]) initBytes.clone(); } + + /** Set a key/value pair in the TXT record. Setting an existing key will replace its value.<P> + @param key + The key name. Must be ASCII, with no '=' characters. + <P> + @param value + Value to be encoded into bytes using the default platform character set. + */ + public void set( String key, String value) + { + byte[] valBytes = (value != null) ? value.getBytes() : null; + this.set( key, valBytes); + } + + /** Set a key/value pair in the TXT record. Setting an existing key will replace its value.<P> + @param key + The key name. Must be ASCII, with no '=' characters. + <P> + @param value + Binary representation of the value. + */ + public void set( String key, byte[] value) + { + byte[] keyBytes; + int valLen = (value != null) ? value.length : 0; + + try { + keyBytes = key.getBytes( "US-ASCII"); + } + catch ( java.io.UnsupportedEncodingException uee) { + throw new IllegalArgumentException(); + } + + for ( int i=0; i < keyBytes.length; i++) + if ( keyBytes[i] == '=') + throw new IllegalArgumentException(); + + if ( keyBytes.length + valLen >= 255) + throw new ArrayIndexOutOfBoundsException(); + + int prevLoc = this.remove( key); + if ( prevLoc == -1) + prevLoc = this.size(); + + this.insert( keyBytes, value, prevLoc); + } + + protected void insert( byte[] keyBytes, byte[] value, int index) + // Insert a key-value pair at index + { + byte[] oldBytes = fBytes; + int valLen = (value != null) ? value.length : 0; + int insertion = 0; + int newLen, avLen; + + // locate the insertion point + for ( int i=0; i < index && insertion < fBytes.length; i++) + insertion += (0xFF & (fBytes[ insertion] + 1)); + + avLen = keyBytes.length + valLen + (value != null ? 1 : 0); + newLen = avLen + oldBytes.length + 1; + + fBytes = new byte[ newLen]; + System.arraycopy( oldBytes, 0, fBytes, 0, insertion); + int secondHalfLen = oldBytes.length - insertion; + System.arraycopy( oldBytes, insertion, fBytes, newLen - secondHalfLen, secondHalfLen); + fBytes[ insertion] = ( byte) avLen; + System.arraycopy( keyBytes, 0, fBytes, insertion + 1, keyBytes.length); + if ( value != null) + { + fBytes[ insertion + 1 + keyBytes.length] = kAttrSep; + System.arraycopy( value, 0, fBytes, insertion + keyBytes.length + 2, valLen); + } + } + + /** Remove a key/value pair from the TXT record. Returns index it was at, or -1 if not found. */ + public int remove( String key) + { + int avStart = 0; + + for ( int i=0; avStart < fBytes.length; i++) + { + int avLen = fBytes[ avStart]; + if ( key.length() <= avLen && + ( key.length() == avLen || fBytes[ avStart + key.length() + 1] == kAttrSep)) + { + String s = new String( fBytes, avStart + 1, key.length()); + if ( 0 == key.compareToIgnoreCase( s)) + { + byte[] oldBytes = fBytes; + fBytes = new byte[ oldBytes.length - avLen - 1]; + System.arraycopy( oldBytes, 0, fBytes, 0, avStart); + System.arraycopy( oldBytes, avStart + avLen + 1, fBytes, avStart, oldBytes.length - avStart - avLen - 1); + return i; + } + } + avStart += (0xFF & (avLen + 1)); + } + return -1; + } + + /** Return the number of keys in the TXT record. */ + public int size() + { + int i, avStart; + + for ( i=0, avStart=0; avStart < fBytes.length; i++) + avStart += (0xFF & (fBytes[ avStart] + 1)); + return i; + } + + /** Return true if key is present in the TXT record, false if not. */ + public boolean contains( String key) + { + String s = null; + + for ( int i=0; null != ( s = this.getKey( i)); i++) + if ( 0 == key.compareToIgnoreCase( s)) + return true; + return false; + } + + /** Return a key in the TXT record by zero-based index. Returns null if index exceeds the total number of keys. */ + public String getKey( int index) + { + int avStart = 0; + + for ( int i=0; i < index && avStart < fBytes.length; i++) + avStart += fBytes[ avStart] + 1; + + if ( avStart < fBytes.length) + { + int avLen = fBytes[ avStart]; + int aLen = 0; + + for ( aLen=0; aLen < avLen; aLen++) + if ( fBytes[ avStart + aLen + 1] == kAttrSep) + break; + return new String( fBytes, avStart + 1, aLen); + } + return null; + } + + /** + Look up a key in the TXT record by zero-based index and return its value. <P> + Returns null if index exceeds the total number of keys. + Returns null if the key is present with no value. + */ + public byte[] getValue( int index) + { + int avStart = 0; + byte[] value = null; + + for ( int i=0; i < index && avStart < fBytes.length; i++) + avStart += fBytes[ avStart] + 1; + + if ( avStart < fBytes.length) + { + int avLen = fBytes[ avStart]; + int aLen = 0; + + for ( aLen=0; aLen < avLen; aLen++) + { + if ( fBytes[ avStart + aLen + 1] == kAttrSep) + { + value = new byte[ avLen - aLen - 1]; + System.arraycopy( fBytes, avStart + aLen + 2, value, 0, avLen - aLen - 1); + break; + } + } + } + return value; + } + + /** Converts the result of getValue() to a string in the platform default character set. */ + public String getValueAsString( int index) + { + byte[] value = this.getValue( index); + return value != null ? new String( value) : null; + } + + /** Get the value associated with a key. Will be null if the key is not defined. + Array will have length 0 if the key is defined with an = but no value.<P> + + @param forKey + The left-hand side of the key-value pair. + <P> + @return The binary representation of the value. + */ + public byte[] getValue( String forKey) + { + String s = null; + int i; + + for ( i=0; null != ( s = this.getKey( i)); i++) + if ( 0 == forKey.compareToIgnoreCase( s)) + return this.getValue( i); + return null; + } + + /** Converts the result of getValue() to a string in the platform default character set.<P> + + @param forKey + The left-hand side of the key-value pair. + <P> + @return The value represented in the default platform character set. + */ + public String getValueAsString( String forKey) + { + byte[] val = this.getValue( forKey); + return val != null ? new String( val) : null; + } + + /** Return the contents of the TXT record as raw bytes. */ + public byte[] getRawBytes() { return (byte[]) fBytes.clone(); } + + /** Return a string representation of the object. */ + public String toString() + { + String a, result = null; + + for ( int i=0; null != ( a = this.getKey( i)); i++) + { + String av = String.valueOf( i) + "={" + a; + String val = this.getValueAsString( i); + if ( val != null) + av += "=" + val + "}"; + else + av += "}"; + if ( result == null) + result = av; + else + result = result + ", " + av; + } + return result != null ? result : ""; + } +} + diff --git a/mDNSResponder/mDNSShared/PlatformCommon.c b/mDNSResponder/mDNSShared/PlatformCommon.c new file mode 100644 index 00000000..d86a755a --- /dev/null +++ b/mDNSResponder/mDNSShared/PlatformCommon.c @@ -0,0 +1,199 @@ +/* -*- Mode: C; tab-width: 4 -*- + * + * Copyright (c) 2004 Apple Computer, Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdio.h> // Needed for fopen() etc. +#include <unistd.h> // Needed for close() +#include <string.h> // Needed for strlen() etc. +#include <errno.h> // Needed for errno etc. +#include <sys/socket.h> // Needed for socket() etc. +#include <netinet/in.h> // Needed for sockaddr_in +#include <syslog.h> + +#include "mDNSEmbeddedAPI.h" // Defines the interface provided to the client layer above +#include "DNSCommon.h" +#include "PlatformCommon.h" + +#ifdef NOT_HAVE_SOCKLEN_T +typedef unsigned int socklen_t; +#endif + +// Bind a UDP socket to find the source address to a destination +mDNSexport void mDNSPlatformSourceAddrForDest(mDNSAddr *const src, const mDNSAddr *const dst) +{ + union { struct sockaddr s; struct sockaddr_in a4; struct sockaddr_in6 a6; } addr; + socklen_t len = sizeof(addr); + socklen_t inner_len = 0; + int sock = socket(AF_INET, SOCK_DGRAM, 0); + src->type = mDNSAddrType_None; + if (sock == -1) return; + if (dst->type == mDNSAddrType_IPv4) + { + inner_len = sizeof(addr.a4); + #ifndef NOT_HAVE_SA_LEN + addr.a4.sin_len = inner_len; + #endif + addr.a4.sin_family = AF_INET; + addr.a4.sin_port = 1; // Not important, any port will do + addr.a4.sin_addr.s_addr = dst->ip.v4.NotAnInteger; + } + else if (dst->type == mDNSAddrType_IPv6) + { + inner_len = sizeof(addr.a6); + #ifndef NOT_HAVE_SA_LEN + addr.a6.sin6_len = inner_len; + #endif + addr.a6.sin6_family = AF_INET6; + addr.a6.sin6_flowinfo = 0; + addr.a6.sin6_port = 1; // Not important, any port will do + addr.a6.sin6_addr = *(struct in6_addr*)&dst->ip.v6; + addr.a6.sin6_scope_id = 0; + } + else return; + + if ((connect(sock, &addr.s, inner_len)) < 0) + { LogMsg("mDNSPlatformSourceAddrForDest: connect %#a failed errno %d (%s)", dst, errno, strerror(errno)); goto exit; } + + if ((getsockname(sock, &addr.s, &len)) < 0) + { LogMsg("mDNSPlatformSourceAddrForDest: getsockname failed errno %d (%s)", errno, strerror(errno)); goto exit; } + + src->type = dst->type; + if (dst->type == mDNSAddrType_IPv4) src->ip.v4.NotAnInteger = addr.a4.sin_addr.s_addr; + else src->ip.v6 = *(mDNSv6Addr*)&addr.a6.sin6_addr; +exit: + close(sock); +} + +// dst must be at least MAX_ESCAPED_DOMAIN_NAME bytes, and option must be less than 32 bytes in length +mDNSlocal mDNSBool GetConfigOption(char *dst, const char *option, FILE *f) +{ + char buf[32+1+MAX_ESCAPED_DOMAIN_NAME]; // Option name, one space, option value + unsigned int len = strlen(option); + if (len + 1 + MAX_ESCAPED_DOMAIN_NAME > sizeof(buf)-1) { LogMsg("GetConfigOption: option %s too long", option); return mDNSfalse; } + fseek(f, 0, SEEK_SET); // set position to beginning of stream + while (fgets(buf, sizeof(buf), f)) // Read at most sizeof(buf)-1 bytes from file, and append '\0' C-string terminator + { + if (!strncmp(buf, option, len)) + { + strncpy(dst, buf + len + 1, MAX_ESCAPED_DOMAIN_NAME-1); + if (dst[MAX_ESCAPED_DOMAIN_NAME-1]) dst[MAX_ESCAPED_DOMAIN_NAME-1] = '\0'; + len = strlen(dst); + if (len && dst[len-1] == '\n') dst[len-1] = '\0'; // chop newline + return mDNStrue; + } + } + debugf("Option %s not set", option); + return mDNSfalse; +} + +mDNSexport void ReadDDNSSettingsFromConfFile(mDNS *const m, const char *const filename, domainname *const hostname, domainname *const domain, mDNSBool *DomainDiscoveryDisabled) +{ + char buf[MAX_ESCAPED_DOMAIN_NAME] = ""; + mStatus err; + FILE *f = fopen(filename, "r"); + + if (hostname) hostname->c[0] = 0; + if (domain) domain->c[0] = 0; + if (DomainDiscoveryDisabled) *DomainDiscoveryDisabled = mDNSfalse; + + if (f) + { + if (DomainDiscoveryDisabled && GetConfigOption(buf, "DomainDiscoveryDisabled", f) && !strcasecmp(buf, "true")) *DomainDiscoveryDisabled = mDNStrue; + if (hostname && GetConfigOption(buf, "hostname", f) && !MakeDomainNameFromDNSNameString(hostname, buf)) goto badf; + if (domain && GetConfigOption(buf, "zone", f) && !MakeDomainNameFromDNSNameString(domain, buf)) goto badf; + buf[0] = 0; + GetConfigOption(buf, "secret-64", f); // failure means no authentication + fclose(f); + f = NULL; + } + else + { + if (errno != ENOENT) LogMsg("ERROR: Config file exists, but cannot be opened."); + return; + } + + if (domain && domain->c[0] && buf[0]) + { + DomainAuthInfo *info = (DomainAuthInfo*)mDNSPlatformMemAllocate(sizeof(*info)); + // for now we assume keyname = service reg domain and we use same key for service and hostname registration + err = mDNS_SetSecretForDomain(m, info, domain, domain, buf, NULL, 0, mDNSfalse); + if (err) LogMsg("ERROR: mDNS_SetSecretForDomain returned %d for domain %##s", err, domain->c); + } + + return; + +badf: + LogMsg("ERROR: malformatted config file"); + if (f) fclose(f); +} + +#if MDNS_DEBUGMSGS +mDNSexport void mDNSPlatformWriteDebugMsg(const char *msg) +{ + fprintf(stderr,"%s\n", msg); + fflush(stderr); +} +#endif + +mDNSexport void mDNSPlatformWriteLogMsg(const char *ident, const char *buffer, mDNSLogLevel_t loglevel) +{ +#if APPLE_OSX_mDNSResponder && LogTimeStamps + extern mDNS mDNSStorage; + extern mDNSu32 mDNSPlatformClockDivisor; + mDNSs32 t = mDNSStorage.timenow ? mDNSStorage.timenow : mDNSPlatformClockDivisor ? mDNS_TimeNow_NoLock(&mDNSStorage) : 0; + int ms = ((t < 0) ? -t : t) % 1000; +#endif + + if (mDNS_DebugMode) // In debug mode we write to stderr + { +#if APPLE_OSX_mDNSResponder && LogTimeStamps + if (ident && ident[0] && mDNSPlatformClockDivisor) + fprintf(stderr,"%8d.%03d: %s\n", (int)(t/1000), ms, buffer); + else +#endif + fprintf(stderr,"%s\n", buffer); + fflush(stderr); + } + else // else, in production mode, we write to syslog + { + static int log_inited = 0; + + int syslog_level = LOG_ERR; + switch (loglevel) + { + case MDNS_LOG_MSG: syslog_level = LOG_ERR; break; + case MDNS_LOG_OPERATION: syslog_level = LOG_WARNING; break; + case MDNS_LOG_SPS: syslog_level = LOG_NOTICE; break; + case MDNS_LOG_INFO: syslog_level = LOG_INFO; break; + case MDNS_LOG_DEBUG: syslog_level = LOG_DEBUG; break; + default: + fprintf(stderr, "Unknown loglevel %d, assuming LOG_ERR\n", loglevel); + fflush(stderr); + } + + if (!log_inited) { openlog(ident, LOG_CONS, LOG_DAEMON); log_inited++; } + +#if APPLE_OSX_mDNSResponder && LogTimeStamps + if (ident && ident[0] && mDNSPlatformClockDivisor) + syslog(syslog_level, "%8d.%03d: %s", (int)(t/1000), ms, buffer); + else +#elif APPLE_OSX_mDNSResponder + mDNSPlatformLogToFile(syslog_level, buffer); +#else + syslog(syslog_level, "%s", buffer); +#endif + } +} diff --git a/mDNSResponder/mDNSShared/PlatformCommon.h b/mDNSResponder/mDNSShared/PlatformCommon.h new file mode 100644 index 00000000..2a068711 --- /dev/null +++ b/mDNSResponder/mDNSShared/PlatformCommon.h @@ -0,0 +1,18 @@ +/* -*- Mode: C; tab-width: 4 -*- + * + * Copyright (c) 2004 Apple Computer, Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +extern void ReadDDNSSettingsFromConfFile(mDNS *const m, const char *const filename, domainname *const hostname, domainname *const domain, mDNSBool *DomainDiscoveryDisabled); diff --git a/mDNSResponder/mDNSShared/dns-sd.1 b/mDNSResponder/mDNSShared/dns-sd.1 new file mode 100644 index 00000000..9d8323b9 --- /dev/null +++ b/mDNSResponder/mDNSShared/dns-sd.1 @@ -0,0 +1,266 @@ +.\" -*- tab-width: 4 -*- +.\" +.\" Copyright (c) 2004 Apple Computer, Inc. All Rights Reserved. +.\" +.\" Licensed under the Apache License, Version 2.0 (the "License"); +.\" you may not use this file except in compliance with the License. +.\" You may obtain a copy of the License at +.\" +.\" http://www.apache.org/licenses/LICENSE-2.0 +.\" +.\" Unless required by applicable law or agreed to in writing, software +.\" distributed under the License is distributed on an "AS IS" BASIS, +.\" WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.\" See the License for the specific language governing permissions and +.\" limitations under the License. +.\" +.Dd April 2004 \" Date +.Dt dns-sd 1 \" Document Title +.Os Darwin \" Operating System +.\" +.Sh NAME +.Nm dns-sd +.Nd Multicast DNS (mDNS) & DNS Service Discovery (DNS-SD) Test Tool \" For whatis +.\" +.Sh SYNOPSIS +.Nm Fl E +.Pp +.Nm Fl F +.Pp +.Nm Fl R Ar name type domain port Op Ar key=value ... +.Pp +.Nm Fl B Ar type domain +.Pp +.Nm Fl L Ar name type domain +.Pp +.Nm Fl P Ar name type domain port host IP Op Ar key=value ... +.Pp +.Nm Fl q Ar name rrtype rrclass +.Pp +.Nm Fl Z Ar type domain +.Pp +.Nm Fl G Ns \ v4/v6/v4v6 Ar name +.Pp +.Nm Fl V +.\" +.Sh DESCRIPTION +The +.Nm +command is a network diagnostic tool, much like +.Xr ping 8 +or +.Xr traceroute 8 . +However, unlike those tools, most of its functionality is not implemented in the +.Nm +executable itself, but in library code that is available to any application. +The library API that +.Nm +uses is documented in +.Pa /usr/include/dns_sd.h . +The +.Nm +command replaces the older +mDNS +command. +.Pp +The +.Nm +command is primarily intended for interactive use. +Because its command-line arguments and output format are subject to change, +invoking it from a shell script will generally be fragile. Additionally, +the asynchronous nature of DNS Service Discovery does +not lend itself easily to script-oriented programming. For example, +calls like "browse" never complete; the action of performing a "browse" +sets in motion machinery to notify the client whenever instances of +that service type appear or disappear from the network. These +notifications continue to be delivered indefinitely, for minutes, +hours, or even days, as services come and go, until the client +explicitly terminates the call. This style of asynchronous interaction +works best with applications that are either multi-threaded, or use a +main event-handling loop to receive keystrokes, network data, and other +asynchronous event notifications as they happen. +.br +If you wish to perform DNS Service Discovery operations from a +scripting language, then the best way to do this is not to execute the +.Nm +command and then attempt to decipher the textual output, but instead to +directly call the DNS-SD APIs using a binding for your chosen language. +.br +For example, if you are programming in Ruby, then you can +directly call DNS-SD APIs using the dnssd package documented at +.Pa <http://rubyforge.org/projects/dnssd/> . +.br +Similar bindings for other languages are also in development. +.Pp +.Bl -tag -width E +.It Nm Fl E +return a list of domains recommended for registering(advertising) services. +.Pp +.It Nm Fl F +return a list of domains recommended for browsing services. +.Pp +Normally, on your home network, the only domain you are likely to see is "local". +However if your network administrator has created Domain Enumeration records, +then you may also see other recommended domains for registering and browsing. +.Pp +.It Nm Fl R Ar name type domain port Op Ar key=value ... +register (advertise) a service in the specified +.Ar domain +with the given +.Ar name +and +.Ar type +as listening (on the current machine) on +.Ar port. +.Pp +.Ar name +can be arbitrary unicode text, containing any legal unicode characters +(including dots, spaces, slashes, colons, etc. without restriction), +up to 63 UTF-8 bytes long. +.Ar type +must be of the form "_app-proto._tcp" or "_app-proto._udp", where +"app-proto" is an application protocol name registered at +.Pa http://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xml . +.Pp +.Ar domain +is the domain in which to register the service. +In current implementations, only the local multicast domain "local" is +supported. In the future, registering will be supported in any arbitrary +domain that has a working DNS Update server [RFC 2136]. The +.Ar domain +"." is a synonym for "pick a sensible default" which today +means "local". +.Pp +.Ar port +is a number from 0 to 65535, and is the TCP or UDP port number upon +which the service is listening. +.Pp +Additional attributes of the service may optionally be described by +key/value pairs, which are stored in the advertised service's DNS TXT +record. Allowable keys and values are listed with the service +registration at +.Pa http://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xml . +.It Nm Fl B Ar type domain +browse for instances of service +.Ar type +in +.Ar domain . +.Pp +For valid +.Ar type Ns s +see +.Pa http://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xml . +as described above. Omitting the +.Ar domain +or using "." means "pick a sensible default." +.It Nm Fl L Ar name type domain +look up and display the information necessary to contact and use the +named service: the hostname of the machine where that service is +available, the port number on which the service is listening, and (if +present) TXT record attributes describing properties of the service. +.Pp +Note that in a typical application, browsing may only happen rarely, while lookup +(or "resolving") happens every time the service is used. For example, a +user browses the network to pick a default printer fairly rarely, but once +a default printer has been picked, that named service is resolved to its +current IP address and port number every time the user presses Cmd-P to +print. +.Pp +.It Nm Fl P Ar name type domain port host IP Op Ar key=value ... +create a proxy advertisement for a service running on(offered by) some other machine. +The two new options are Host, a name for the device and IP, the address of it. +.Pp +The service for which you create a proxy advertisement does not necessarily have to be on your local network. +You can set up a local proxy for a website on the Internet. +.Pp +.It Nm Fl q Ar name rrtype rrclass +look up any DNS name, resource record type, and resource record class, +not necessarily DNS-SD names and record types. +If rrtype is not specified, it queries for the IPv4 address of the name, +if rrclass is not specified, IN class is assumed. If the name is not a fully +qualified domain name, then search domains may be appended. +.Pp +.It Nm Fl Z Ar type domain +browse for service instances and display output in zone file format. +.Pp +.It Nm Fl G Ns \ v4/v6/v4v6 Ar name +look up the IP address information of the name. +If v4 is specified, the IPv4 address of the name is looked up, +if v6 is specified the IPv6 address is looked up. If v4v6 is specified both the IPv4 and IPv6 +address is looked up. If the name is not a fully qualified domain name, +then search domains may be appended. +.Pp +.It Nm Fl V +return the version of the currently running daemon/system service. +.El +.Sh EXAMPLES +.Pp +To advertise the existence of LPR printing service on port 515 on this +machine, such that it will be discovered by the Mac OS X printing software +and other DNS-SD compatible printing clients, use: +.Pp +.Dl Nm Fl R Ns \ \&"My Test\&" _printer._tcp. \&. 515 pdl=application/postscript +.Pp +For this registration to be useful, you need to actually have LPR service +available on port 515. Advertising a service that does not exist is not +very useful, and will be confusing and annoying to other people on the +network. +.Pp +Similarly, to advertise a web page being served by an HTTP +server on port 80 on this machine, such that it will show up in the +Bonjour list in Safari and other DNS-SD compatible Web clients, use: +.Pp +.Dl Nm Fl R Ns \ \&"My Test\&" _http._tcp \&. 80 path=/path-to-page.html +.Pp +To find the advertised web pages on the local network (the same list that +Safari shows), use: +.Pp +.Dl Nm Fl B Ns \ _http._tcp +.Pp +While that command is running, in another window, try the +.Nm Fl R +example given above to advertise a web page, and you should see the +"Add" event reported to the +.Nm Fl B +window. Now press Ctrl-C in the +.Nm Fl R +window and you should see the "Remove" event reported to the +.Nm Fl B +window. +.Pp +In the example below, the www.apple.com web page is advertised as a service called "apple", +running on a target host called apple.local, which resolves to 17.149.160.49. +.Pp +.Dl Nm Fl P Ns \ apple _http._tcp \&"\&"\& 80 apple.local 17.149.160.49 +.Pp +The Bonjour menu in the Safari web browser will now show "apple". +The same IP address can be reached by entering apple.local in the web browser. +In either case, the request will be resolved to the IP address and browser will show +contents associated with www.apple.com. +.Pp +If a client wants to be notified of changes in server state, it can +initiate a query for the service's particular record and leave it running. +For example, to monitor the status of an iChat user you can use: +.Pp +.Dl Nm Fl q Ns \ someone@ex1._presence._tcp.local txt +.Pp +Everytime status of that user(someone) changes, you will see a new TXT record result reported. +.Pp +You can also query for a unicast name like www.apple.com and monitor its status. +.Pp +.Dl Nm Fl q Ns \ www.apple.com +.Pp +.Sh FILES +.Pa /usr/bin/dns-sd \" Pathname +.\" +.Sh SEE ALSO +.Xr mDNSResponder 8 +.\" +.Sh BUGS +.Nm +bugs are tracked in Apple Radar component "mDNSResponder". +.\" +.Sh HISTORY +The +.Nm +command first appeared in Mac OS X 10.4 (Tiger). diff --git a/mDNSResponder/mDNSShared/dns_sd.h b/mDNSResponder/mDNSShared/dns_sd.h new file mode 100644 index 00000000..99373de6 --- /dev/null +++ b/mDNSResponder/mDNSShared/dns_sd.h @@ -0,0 +1,2657 @@ +/* -*- Mode: C; tab-width: 4 -*- + * + * Copyright (c) 2003-2013 Apple Computer, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + +/*! @header DNS Service Discovery + * + * @discussion This section describes the functions, callbacks, and data structures + * that make up the DNS Service Discovery API. + * + * The DNS Service Discovery API is part of Bonjour, Apple's implementation + * of zero-configuration networking (ZEROCONF). + * + * Bonjour allows you to register a network service, such as a + * printer or file server, so that it can be found by name or browsed + * for by service type and domain. Using Bonjour, applications can + * discover what services are available on the network, along with + * all the information -- such as name, IP address, and port -- + * necessary to access a particular service. + * + * In effect, Bonjour combines the functions of a local DNS server and + * AppleTalk. Bonjour allows applications to provide user-friendly printer + * and server browsing, among other things, over standard IP networks. + * This behavior is a result of combining protocols such as multicast and + * DNS to add new functionality to the network (such as multicast DNS). + * + * Bonjour gives applications easy access to services over local IP + * networks without requiring the service or the application to support + * an AppleTalk or a Netbeui stack, and without requiring a DNS server + * for the local network. + */ + + +/* _DNS_SD_H contains the mDNSResponder version number for this header file, formatted as follows: + * Major part of the build number * 10000 + + * minor part of the build number * 100 + * For example, Mac OS X 10.4.9 has mDNSResponder-108.4, which would be represented as + * version 1080400. This allows C code to do simple greater-than and less-than comparisons: + * e.g. an application that requires the DNSServiceGetProperty() call (new in mDNSResponder-126) can check: + * + * #if _DNS_SD_H+0 >= 1260000 + * ... some C code that calls DNSServiceGetProperty() ... + * #endif + * + * The version defined in this header file symbol allows for compile-time + * checking, so that C code building with earlier versions of the header file + * can avoid compile errors trying to use functions that aren't even defined + * in those earlier versions. Similar checks may also be performed at run-time: + * => weak linking -- to avoid link failures if run with an earlier + * version of the library that's missing some desired symbol, or + * => DNSServiceGetProperty(DaemonVersion) -- to verify whether the running daemon + * ("system service" on Windows) meets some required minimum functionality level. + */ + +#ifndef _DNS_SD_H +#define _DNS_SD_H 5440000 + +#ifdef __cplusplus +extern "C" { +#endif + +/* Set to 1 if libdispatch is supported + * Note: May also be set by project and/or Makefile + */ +#ifndef _DNS_SD_LIBDISPATCH +#define _DNS_SD_LIBDISPATCH 0 +#endif /* ndef _DNS_SD_LIBDISPATCH */ + +/* standard calling convention under Win32 is __stdcall */ +/* Note: When compiling Intel EFI (Extensible Firmware Interface) under MS Visual Studio, the */ +/* _WIN32 symbol is defined by the compiler even though it's NOT compiling code for Windows32 */ +#if defined(_WIN32) && !defined(EFI32) && !defined(EFI64) +#define DNSSD_API __stdcall +#else +#define DNSSD_API +#endif + +/* stdint.h does not exist on FreeBSD 4.x; its types are defined in sys/types.h instead */ +#if defined(__FreeBSD__) && (__FreeBSD__ < 5) +#include <sys/types.h> + +/* Likewise, on Sun, standard integer types are in sys/types.h */ +#elif defined(__sun__) +#include <sys/types.h> + +/* EFI does not have stdint.h, or anything else equivalent */ +#elif defined(EFI32) || defined(EFI64) || defined(EFIX64) +#include "Tiano.h" +#if !defined(_STDINT_H_) +typedef UINT8 uint8_t; +typedef INT8 int8_t; +typedef UINT16 uint16_t; +typedef INT16 int16_t; +typedef UINT32 uint32_t; +typedef INT32 int32_t; +#endif +/* Windows has its own differences */ +#elif defined(_WIN32) +#include <windows.h> +#define _UNUSED +#ifndef _MSL_STDINT_H +typedef UINT8 uint8_t; +typedef INT8 int8_t; +typedef UINT16 uint16_t; +typedef INT16 int16_t; +typedef UINT32 uint32_t; +typedef INT32 int32_t; +#endif + +/* All other Posix platforms use stdint.h */ +#else +#include <stdint.h> +#endif + +#if _DNS_SD_LIBDISPATCH +#include <dispatch/dispatch.h> +#endif + +/* DNSServiceRef, DNSRecordRef + * + * Opaque internal data types. + * Note: client is responsible for serializing access to these structures if + * they are shared between concurrent threads. + */ + +typedef struct _DNSServiceRef_t *DNSServiceRef; +typedef struct _DNSRecordRef_t *DNSRecordRef; + +struct sockaddr; + +/*! @enum General flags + * Most DNS-SD API functions and callbacks include a DNSServiceFlags parameter. + * As a general rule, any given bit in the 32-bit flags field has a specific fixed meaning, + * regardless of the function or callback being used. For any given function or callback, + * typically only a subset of the possible flags are meaningful, and all others should be zero. + * The discussion section for each API call describes which flags are valid for that call + * and callback. In some cases, for a particular call, it may be that no flags are currently + * defined, in which case the DNSServiceFlags parameter exists purely to allow future expansion. + * In all cases, developers should expect that in future releases, it is possible that new flag + * values will be defined, and write code with this in mind. For example, code that tests + * if (flags == kDNSServiceFlagsAdd) ... + * will fail if, in a future release, another bit in the 32-bit flags field is also set. + * The reliable way to test whether a particular bit is set is not with an equality test, + * but with a bitwise mask: + * if (flags & kDNSServiceFlagsAdd) ... + * With the exception of kDNSServiceFlagsValidate, each flag can be valid(be set) + * EITHER only as an input to one of the DNSService*() APIs OR only as an output + * (provide status) through any of the callbacks used. For example, kDNSServiceFlagsAdd + * can be set only as an output in the callback, whereas the kDNSServiceFlagsIncludeP2P + * can be set only as an input to the DNSService*() APIs. See comments on kDNSServiceFlagsValidate + * defined in enum below. + */ +enum +{ + kDNSServiceFlagsMoreComing = 0x1, + /* MoreComing indicates to a callback that at least one more result is + * queued and will be delivered following immediately after this one. + * When the MoreComing flag is set, applications should not immediately + * update their UI, because this can result in a great deal of ugly flickering + * on the screen, and can waste a great deal of CPU time repeatedly updating + * the screen with content that is then immediately erased, over and over. + * Applications should wait until MoreComing is not set, and then + * update their UI when no more changes are imminent. + * When MoreComing is not set, that doesn't mean there will be no more + * answers EVER, just that there are no more answers immediately + * available right now at this instant. If more answers become available + * in the future they will be delivered as usual. + */ + + kDNSServiceFlagsAdd = 0x2, + kDNSServiceFlagsDefault = 0x4, + /* Flags for domain enumeration and browse/query reply callbacks. + * "Default" applies only to enumeration and is only valid in + * conjunction with "Add". An enumeration callback with the "Add" + * flag NOT set indicates a "Remove", i.e. the domain is no longer + * valid. + */ + + kDNSServiceFlagsNoAutoRename = 0x8, + /* Flag for specifying renaming behavior on name conflict when registering + * non-shared records. By default, name conflicts are automatically handled + * by renaming the service. NoAutoRename overrides this behavior - with this + * flag set, name conflicts will result in a callback. The NoAutorename flag + * is only valid if a name is explicitly specified when registering a service + * (i.e. the default name is not used.) + */ + + kDNSServiceFlagsShared = 0x10, + kDNSServiceFlagsUnique = 0x20, + /* Flag for registering individual records on a connected + * DNSServiceRef. Shared indicates that there may be multiple records + * with this name on the network (e.g. PTR records). Unique indicates that the + * record's name is to be unique on the network (e.g. SRV records). + */ + + kDNSServiceFlagsBrowseDomains = 0x40, + kDNSServiceFlagsRegistrationDomains = 0x80, + /* Flags for specifying domain enumeration type in DNSServiceEnumerateDomains. + * BrowseDomains enumerates domains recommended for browsing, RegistrationDomains + * enumerates domains recommended for registration. + */ + + kDNSServiceFlagsLongLivedQuery = 0x100, + /* Flag for creating a long-lived unicast query for the DNSServiceQueryRecord call. */ + + kDNSServiceFlagsAllowRemoteQuery = 0x200, + /* Flag for creating a record for which we will answer remote queries + * (queries from hosts more than one hop away; hosts not directly connected to the local link). + */ + + kDNSServiceFlagsForceMulticast = 0x400, + /* Flag for signifying that a query or registration should be performed exclusively via multicast + * DNS, even for a name in a domain (e.g. foo.apple.com.) that would normally imply unicast DNS. + */ + + kDNSServiceFlagsForce = 0x800, // This flag is deprecated. + + kDNSServiceFlagsKnownUnique = 0x800, + /* + * Client guarantees that record names are unique, so we can skip sending out initial + * probe messages. Standard name conflict resolution is still done if a conflict is discovered. + * Currently only valid for a DNSServiceRegister call. + */ + + kDNSServiceFlagsReturnIntermediates = 0x1000, + /* Flag for returning intermediate results. + * For example, if a query results in an authoritative NXDomain (name does not exist) + * then that result is returned to the client. However the query is not implicitly + * cancelled -- it remains active and if the answer subsequently changes + * (e.g. because a VPN tunnel is subsequently established) then that positive + * result will still be returned to the client. + * Similarly, if a query results in a CNAME record, then in addition to following + * the CNAME referral, the intermediate CNAME result is also returned to the client. + * When this flag is not set, NXDomain errors are not returned, and CNAME records + * are followed silently without informing the client of the intermediate steps. + * (In earlier builds this flag was briefly calledkDNSServiceFlagsReturnCNAME) + */ + + kDNSServiceFlagsNonBrowsable = 0x2000, + /* A service registered with the NonBrowsable flag set can be resolved using + * DNSServiceResolve(), but will not be discoverable using DNSServiceBrowse(). + * This is for cases where the name is actually a GUID; it is found by other means; + * there is no end-user benefit to browsing to find a long list of opaque GUIDs. + * Using the NonBrowsable flag creates SRV+TXT without the cost of also advertising + * an associated PTR record. + */ + + kDNSServiceFlagsShareConnection = 0x4000, + /* For efficiency, clients that perform many concurrent operations may want to use a + * single Unix Domain Socket connection with the background daemon, instead of having a + * separate connection for each independent operation. To use this mode, clients first + * call DNSServiceCreateConnection(&MainRef) to initialize the main DNSServiceRef. + * For each subsequent operation that is to share that same connection, the client copies + * the MainRef, and then passes the address of that copy, setting the ShareConnection flag + * to tell the library that this DNSServiceRef is not a typical uninitialized DNSServiceRef; + * it's a copy of an existing DNSServiceRef whose connection information should be reused. + * + * For example: + * + * DNSServiceErrorType error; + * DNSServiceRef MainRef; + * error = DNSServiceCreateConnection(&MainRef); + * if (error) ... + * DNSServiceRef BrowseRef = MainRef; // Important: COPY the primary DNSServiceRef first... + * error = DNSServiceBrowse(&BrowseRef, kDNSServiceFlagsShareConnection, ...); // then use the copy + * if (error) ... + * ... + * DNSServiceRefDeallocate(BrowseRef); // Terminate the browse operation + * DNSServiceRefDeallocate(MainRef); // Terminate the shared connection + * + * Notes: + * + * 1. Collective kDNSServiceFlagsMoreComing flag + * When callbacks are invoked using a shared DNSServiceRef, the + * kDNSServiceFlagsMoreComing flag applies collectively to *all* active + * operations sharing the same parent DNSServiceRef. If the MoreComing flag is + * set it means that there are more results queued on this parent DNSServiceRef, + * but not necessarily more results for this particular callback function. + * The implication of this for client programmers is that when a callback + * is invoked with the MoreComing flag set, the code should update its + * internal data structures with the new result, and set a variable indicating + * that its UI needs to be updated. Then, later when a callback is eventually + * invoked with the MoreComing flag not set, the code should update *all* + * stale UI elements related to that shared parent DNSServiceRef that need + * updating, not just the UI elements related to the particular callback + * that happened to be the last one to be invoked. + * + * 2. Canceling operations and kDNSServiceFlagsMoreComing + * Whenever you cancel any operation for which you had deferred UI updates + * waiting because of a kDNSServiceFlagsMoreComing flag, you should perform + * those deferred UI updates. This is because, after cancelling the operation, + * you can no longer wait for a callback *without* MoreComing set, to tell + * you do perform your deferred UI updates (the operation has been canceled, + * so there will be no more callbacks). An implication of the collective + * kDNSServiceFlagsMoreComing flag for shared connections is that this + * guideline applies more broadly -- any time you cancel an operation on + * a shared connection, you should perform all deferred UI updates for all + * operations sharing that connection. This is because the MoreComing flag + * might have been referring to events coming for the operation you canceled, + * which will now not be coming because the operation has been canceled. + * + * 3. Only share DNSServiceRef's created with DNSServiceCreateConnection + * Calling DNSServiceCreateConnection(&ref) creates a special shareable DNSServiceRef. + * DNSServiceRef's created by other calls like DNSServiceBrowse() or DNSServiceResolve() + * cannot be shared by copying them and using kDNSServiceFlagsShareConnection. + * + * 4. Don't Double-Deallocate + * Calling DNSServiceRefDeallocate(ref) for a particular operation's DNSServiceRef terminates + * just that operation. Calling DNSServiceRefDeallocate(ref) for the main shared DNSServiceRef + * (the parent DNSServiceRef, originally created by DNSServiceCreateConnection(&ref)) + * automatically terminates the shared connection and all operations that were still using it. + * After doing this, DO NOT then attempt to deallocate any remaining subordinate DNSServiceRef's. + * The memory used by those subordinate DNSServiceRef's has already been freed, so any attempt + * to do a DNSServiceRefDeallocate (or any other operation) on them will result in accesses + * to freed memory, leading to crashes or other equally undesirable results. + * + * 5. Thread Safety + * The dns_sd.h API does not presuppose any particular threading model, and consequently + * does no locking of its own (which would require linking some specific threading library). + * If client code calls API routines on the same DNSServiceRef concurrently + * from multiple threads, it is the client's responsibility to use a mutext + * lock or take similar appropriate precautions to serialize those calls. + */ + + kDNSServiceFlagsSuppressUnusable = 0x8000, + /* + * This flag is meaningful only in DNSServiceQueryRecord which suppresses unusable queries on the + * wire. If "hostname" is a wide-area unicast DNS hostname (i.e. not a ".local." name) + * but this host has no routable IPv6 address, then the call will not try to look up IPv6 addresses + * for "hostname", since any addresses it found would be unlikely to be of any use anyway. Similarly, + * if this host has no routable IPv4 address, the call will not try to look up IPv4 addresses for + * "hostname". + */ + + kDNSServiceFlagsTimeout = 0x10000, + /* + * When kDNServiceFlagsTimeout is passed to DNSServiceQueryRecord or DNSServiceGetAddrInfo, the query is + * stopped after a certain number of seconds have elapsed. The time at which the query will be stopped + * is determined by the system and cannot be configured by the user. The query will be stopped irrespective + * of whether a response was given earlier or not. When the query is stopped, the callback will be called + * with an error code of kDNSServiceErr_Timeout and a NULL sockaddr will be returned for DNSServiceGetAddrInfo + * and zero length rdata will be returned for DNSServiceQueryRecord. + */ + + kDNSServiceFlagsIncludeP2P = 0x20000, + /* + * Include P2P interfaces when kDNSServiceInterfaceIndexAny is specified. + * By default, specifying kDNSServiceInterfaceIndexAny does not include P2P interfaces. + */ + + kDNSServiceFlagsWakeOnResolve = 0x40000, + /* + * This flag is meaningful only in DNSServiceResolve. When set, it tries to send a magic packet + * to wake up the client. + */ + + kDNSServiceFlagsBackgroundTrafficClass = 0x80000, + /* + * This flag is meaningful in DNSServiceBrowse, DNSServiceGetAddrInfo, DNSServiceQueryRecord, + * and DNSServiceResolve. When set, it uses the background traffic + * class for packets that service the request. + */ + + kDNSServiceFlagsIncludeAWDL = 0x100000, + /* + * Include AWDL interface when kDNSServiceInterfaceIndexAny is specified. + */ + + kDNSServiceFlagsValidate = 0x200000, + /* + * This flag is meaningful in DNSServiceGetAddrInfo and DNSServiceQueryRecord. This is the ONLY flag to be valid + * as an input to the APIs and also an output through the callbacks in the APIs. + * + * When this flag is passed to DNSServiceQueryRecord and DNSServiceGetAddrInfo to resolve unicast names, + * the response will be validated using DNSSEC. The validation results are delivered using the flags field in + * the callback and kDNSServiceFlagsValidate is marked in the flags to indicate that DNSSEC status is also available. + * When the callback is called to deliver the query results, the validation results may or may not be available. + * If it is not delivered along with the results, the validation status is delivered when the validation completes. + * + * When the validation results are delivered in the callback, it is indicated by marking the flags with + * kDNSServiceFlagsValidate and kDNSServiceFlagsAdd along with the DNSSEC status flags (described below) and a NULL + * sockaddr will be returned for DNSServiceGetAddrInfo and zero length rdata will be returned for DNSServiceQueryRecord. + * DNSSEC validation results are for the whole RRSet and not just individual records delivered in the callback. When + * kDNSServiceFlagsAdd is not set in the flags, applications should implicitly assume that the DNSSEC status of the + * RRSet that has been delivered up until that point is not valid anymore, till another callback is called with + * kDNSServiceFlagsAdd and kDNSServiceFlagsValidate. + * + * The following four flags indicate the status of the DNSSEC validation and marked in the flags field of the callback. + * When any of the four flags is set, kDNSServiceFlagsValidate will also be set. To check the validation status, the + * other applicable output flags should be masked. See kDNSServiceOutputFlags below. + */ + + kDNSServiceFlagsSecure = 0x200010, + /* + * The response has been validated by verifying all the signaures in the response and was able to + * build a successful authentication chain starting from a known trust anchor. + */ + + kDNSServiceFlagsInsecure = 0x200020, + /* + * A chain of trust cannot be built starting from a known trust anchor to the response. + */ + + kDNSServiceFlagsBogus = 0x200040, + /* + * If the response cannot be verified to be secure due to expired signatures, missing signatures etc., + * then the results are considered to be bogus. + */ + + kDNSServiceFlagsIndeterminate = 0x200080, + /* + * There is no valid trust anchor that can be used to determine whether a response is secure or not. + */ + + kDNSServiceFlagsUnicastResponse = 0x400000, + /* + * Request unicast response to query. + */ + kDNSServiceFlagsValidateOptional = 0x800000, + + /* + * This flag is identical to kDNSServiceFlagsValidate except for the case where the response + * cannot be validated. If this flag is set in DNSServiceQueryRecord or DNSServiceGetAddrInfo, + * the DNSSEC records will be requested for validation. If they cannot be received for some reason + * during the validation (e.g., zone is not signed, zone is signed but cannot be traced back to + * root, recursive server does not understand DNSSEC etc.), then this will fallback to the default + * behavior where the validation will not be performed and no DNSSEC results will be provided. + * + * If the zone is signed and there is a valid path to a known trust anchor configured in the system + * and the application requires DNSSEC validation irrespective of the DNSSEC awareness in the current + * network, then this option MUST not be used. This is only intended to be used during the transition + * period where the different nodes participating in the DNS resolution may not understand DNSSEC or + * managed properly (e.g. missing DS record) but still want to be able to resolve DNS successfully. + */ + + kDNSServiceFlagsWakeOnlyService = 0x1000000, + /* + * This flag is meaningful only in DNSServiceRegister. When set, the service will not be registered + * with sleep proxy server during sleep. + */ + + kDNSServiceFlagsThresholdOne = 0x2000000, + kDNSServiceFlagsThresholdFinder = 0x4000000, + kDNSServiceFlagsThresholdReached = kDNSServiceFlagsThresholdOne, + /* + * kDNSServiceFlagsThresholdOne is meaningful only in DNSServiceBrowse. When set, + * the system will stop issuing browse queries on the network once the number + * of answers returned is one or more. It will issue queries on the network + * again if the number of answers drops to zero. + * This flag is for Apple internal use only. Third party developers + * should not rely on this behavior being supported in any given software release. + * + * kDNSServiceFlagsThresholdFinder is meaningful only in DNSServiceBrowse. When set, + * the system will stop issuing browse queries on the network once the number + * of answers has reached the threshold set for Finder. + * It will issue queries on the network again if the number of answers drops below + * this threshold. + * This flag is for Apple internal use only. Third party developers + * should not rely on this behavior being supported in any given software release. + * + * When kDNSServiceFlagsThresholdReached is set in the client callback add or remove event, + * it indicates that the browse answer threshold has been reached and no + * browse requests will be generated on the network until the number of answers falls + * below the threshold value. Add and remove events can still occur based + * on incoming Bonjour traffic observed by the system. + * The set of services return to the client is not guaranteed to represent the + * entire set of services present on the network once the threshold has been reached. + * + * Note, while kDNSServiceFlagsThresholdReached and kDNSServiceFlagsThresholdOne + * have the same value, there isn't a conflict because kDNSServiceFlagsThresholdReached + * is only set in the callbacks and kDNSServiceFlagsThresholdOne is only set on + * input to a DNSServiceBrowse call. + */ +}; + +#define kDNSServiceOutputFlags (kDNSServiceFlagsValidate | kDNSServiceFlagsValidateOptional | kDNSServiceFlagsMoreComing | kDNSServiceFlagsAdd | kDNSServiceFlagsDefault) + /* All the output flags excluding the DNSSEC Status flags. Typically used to check DNSSEC Status */ + +/* Possible protocol values */ +enum +{ + /* for DNSServiceGetAddrInfo() */ + kDNSServiceProtocol_IPv4 = 0x01, + kDNSServiceProtocol_IPv6 = 0x02, + /* 0x04 and 0x08 reserved for future internetwork protocols */ + + /* for DNSServiceNATPortMappingCreate() */ + kDNSServiceProtocol_UDP = 0x10, + kDNSServiceProtocol_TCP = 0x20 + /* 0x40 and 0x80 reserved for future transport protocols, e.g. SCTP [RFC 2960] + * or DCCP [RFC 4340]. If future NAT gateways are created that support port + * mappings for these protocols, new constants will be defined here. + */ +}; + +/* + * The values for DNS Classes and Types are listed in RFC 1035, and are available + * on every OS in its DNS header file. Unfortunately every OS does not have the + * same header file containing DNS Class and Type constants, and the names of + * the constants are not consistent. For example, BIND 8 uses "T_A", + * BIND 9 uses "ns_t_a", Windows uses "DNS_TYPE_A", etc. + * For this reason, these constants are also listed here, so that code using + * the DNS-SD programming APIs can use these constants, so that the same code + * can compile on all our supported platforms. + */ + +enum +{ + kDNSServiceClass_IN = 1 /* Internet */ +}; + +enum +{ + kDNSServiceType_A = 1, /* Host address. */ + kDNSServiceType_NS = 2, /* Authoritative server. */ + kDNSServiceType_MD = 3, /* Mail destination. */ + kDNSServiceType_MF = 4, /* Mail forwarder. */ + kDNSServiceType_CNAME = 5, /* Canonical name. */ + kDNSServiceType_SOA = 6, /* Start of authority zone. */ + kDNSServiceType_MB = 7, /* Mailbox domain name. */ + kDNSServiceType_MG = 8, /* Mail group member. */ + kDNSServiceType_MR = 9, /* Mail rename name. */ + kDNSServiceType_NULL = 10, /* Null resource record. */ + kDNSServiceType_WKS = 11, /* Well known service. */ + kDNSServiceType_PTR = 12, /* Domain name pointer. */ + kDNSServiceType_HINFO = 13, /* Host information. */ + kDNSServiceType_MINFO = 14, /* Mailbox information. */ + kDNSServiceType_MX = 15, /* Mail routing information. */ + kDNSServiceType_TXT = 16, /* One or more text strings (NOT "zero or more..."). */ + kDNSServiceType_RP = 17, /* Responsible person. */ + kDNSServiceType_AFSDB = 18, /* AFS cell database. */ + kDNSServiceType_X25 = 19, /* X_25 calling address. */ + kDNSServiceType_ISDN = 20, /* ISDN calling address. */ + kDNSServiceType_RT = 21, /* Router. */ + kDNSServiceType_NSAP = 22, /* NSAP address. */ + kDNSServiceType_NSAP_PTR = 23, /* Reverse NSAP lookup (deprecated). */ + kDNSServiceType_SIG = 24, /* Security signature. */ + kDNSServiceType_KEY = 25, /* Security key. */ + kDNSServiceType_PX = 26, /* X.400 mail mapping. */ + kDNSServiceType_GPOS = 27, /* Geographical position (withdrawn). */ + kDNSServiceType_AAAA = 28, /* IPv6 Address. */ + kDNSServiceType_LOC = 29, /* Location Information. */ + kDNSServiceType_NXT = 30, /* Next domain (security). */ + kDNSServiceType_EID = 31, /* Endpoint identifier. */ + kDNSServiceType_NIMLOC = 32, /* Nimrod Locator. */ + kDNSServiceType_SRV = 33, /* Server Selection. */ + kDNSServiceType_ATMA = 34, /* ATM Address */ + kDNSServiceType_NAPTR = 35, /* Naming Authority PoinTeR */ + kDNSServiceType_KX = 36, /* Key Exchange */ + kDNSServiceType_CERT = 37, /* Certification record */ + kDNSServiceType_A6 = 38, /* IPv6 Address (deprecated) */ + kDNSServiceType_DNAME = 39, /* Non-terminal DNAME (for IPv6) */ + kDNSServiceType_SINK = 40, /* Kitchen sink (experimental) */ + kDNSServiceType_OPT = 41, /* EDNS0 option (meta-RR) */ + kDNSServiceType_APL = 42, /* Address Prefix List */ + kDNSServiceType_DS = 43, /* Delegation Signer */ + kDNSServiceType_SSHFP = 44, /* SSH Key Fingerprint */ + kDNSServiceType_IPSECKEY = 45, /* IPSECKEY */ + kDNSServiceType_RRSIG = 46, /* RRSIG */ + kDNSServiceType_NSEC = 47, /* Denial of Existence */ + kDNSServiceType_DNSKEY = 48, /* DNSKEY */ + kDNSServiceType_DHCID = 49, /* DHCP Client Identifier */ + kDNSServiceType_NSEC3 = 50, /* Hashed Authenticated Denial of Existence */ + kDNSServiceType_NSEC3PARAM = 51, /* Hashed Authenticated Denial of Existence */ + + kDNSServiceType_HIP = 55, /* Host Identity Protocol */ + + kDNSServiceType_SPF = 99, /* Sender Policy Framework for E-Mail */ + kDNSServiceType_UINFO = 100, /* IANA-Reserved */ + kDNSServiceType_UID = 101, /* IANA-Reserved */ + kDNSServiceType_GID = 102, /* IANA-Reserved */ + kDNSServiceType_UNSPEC = 103, /* IANA-Reserved */ + + kDNSServiceType_TKEY = 249, /* Transaction key */ + kDNSServiceType_TSIG = 250, /* Transaction signature. */ + kDNSServiceType_IXFR = 251, /* Incremental zone transfer. */ + kDNSServiceType_AXFR = 252, /* Transfer zone of authority. */ + kDNSServiceType_MAILB = 253, /* Transfer mailbox records. */ + kDNSServiceType_MAILA = 254, /* Transfer mail agent records. */ + kDNSServiceType_ANY = 255 /* Wildcard match. */ +}; + +/* possible error code values */ +enum +{ + kDNSServiceErr_NoError = 0, + kDNSServiceErr_Unknown = -65537, /* 0xFFFE FFFF */ + kDNSServiceErr_NoSuchName = -65538, + kDNSServiceErr_NoMemory = -65539, + kDNSServiceErr_BadParam = -65540, + kDNSServiceErr_BadReference = -65541, + kDNSServiceErr_BadState = -65542, + kDNSServiceErr_BadFlags = -65543, + kDNSServiceErr_Unsupported = -65544, + kDNSServiceErr_NotInitialized = -65545, + kDNSServiceErr_AlreadyRegistered = -65547, + kDNSServiceErr_NameConflict = -65548, + kDNSServiceErr_Invalid = -65549, + kDNSServiceErr_Firewall = -65550, + kDNSServiceErr_Incompatible = -65551, /* client library incompatible with daemon */ + kDNSServiceErr_BadInterfaceIndex = -65552, + kDNSServiceErr_Refused = -65553, + kDNSServiceErr_NoSuchRecord = -65554, + kDNSServiceErr_NoAuth = -65555, + kDNSServiceErr_NoSuchKey = -65556, + kDNSServiceErr_NATTraversal = -65557, + kDNSServiceErr_DoubleNAT = -65558, + kDNSServiceErr_BadTime = -65559, /* Codes up to here existed in Tiger */ + kDNSServiceErr_BadSig = -65560, + kDNSServiceErr_BadKey = -65561, + kDNSServiceErr_Transient = -65562, + kDNSServiceErr_ServiceNotRunning = -65563, /* Background daemon not running */ + kDNSServiceErr_NATPortMappingUnsupported = -65564, /* NAT doesn't support PCP, NAT-PMP or UPnP */ + kDNSServiceErr_NATPortMappingDisabled = -65565, /* NAT supports PCP, NAT-PMP or UPnP, but it's disabled by the administrator */ + kDNSServiceErr_NoRouter = -65566, /* No router currently configured (probably no network connectivity) */ + kDNSServiceErr_PollingMode = -65567, + kDNSServiceErr_Timeout = -65568 + + /* mDNS Error codes are in the range + * FFFE FF00 (-65792) to FFFE FFFF (-65537) */ +}; + +/* Maximum length, in bytes, of a service name represented as a */ +/* literal C-String, including the terminating NULL at the end. */ + +#define kDNSServiceMaxServiceName 64 + +/* Maximum length, in bytes, of a domain name represented as an *escaped* C-String */ +/* including the final trailing dot, and the C-String terminating NULL at the end. */ + +#define kDNSServiceMaxDomainName 1009 + +/* + * Notes on DNS Name Escaping + * -- or -- + * "Why is kDNSServiceMaxDomainName 1009, when the maximum legal domain name is 256 bytes?" + * + * All strings used in the DNS-SD APIs are UTF-8 strings. Apart from the exceptions noted below, + * the APIs expect the strings to be properly escaped, using the conventional DNS escaping rules: + * + * '\\' represents a single literal '\' in the name + * '\.' represents a single literal '.' in the name + * '\ddd', where ddd is a three-digit decimal value from 000 to 255, + * represents a single literal byte with that value. + * A bare unescaped '.' is a label separator, marking a boundary between domain and subdomain. + * + * The exceptions, that do not use escaping, are the routines where the full + * DNS name of a resource is broken, for convenience, into servicename/regtype/domain. + * In these routines, the "servicename" is NOT escaped. It does not need to be, since + * it is, by definition, just a single literal string. Any characters in that string + * represent exactly what they are. The "regtype" portion is, technically speaking, + * escaped, but since legal regtypes are only allowed to contain letters, digits, + * and hyphens, there is nothing to escape, so the issue is moot. The "domain" + * portion is also escaped, though most domains in use on the public Internet + * today, like regtypes, don't contain any characters that need to be escaped. + * As DNS-SD becomes more popular, rich-text domains for service discovery will + * become common, so software should be written to cope with domains with escaping. + * + * The servicename may be up to 63 bytes of UTF-8 text (not counting the C-String + * terminating NULL at the end). The regtype is of the form _service._tcp or + * _service._udp, where the "service" part is 1-15 characters, which may be + * letters, digits, or hyphens. The domain part of the three-part name may be + * any legal domain, providing that the resulting servicename+regtype+domain + * name does not exceed 256 bytes. + * + * For most software, these issues are transparent. When browsing, the discovered + * servicenames should simply be displayed as-is. When resolving, the discovered + * servicename/regtype/domain are simply passed unchanged to DNSServiceResolve(). + * When a DNSServiceResolve() succeeds, the returned fullname is already in + * the correct format to pass to standard system DNS APIs such as res_query(). + * For converting from servicename/regtype/domain to a single properly-escaped + * full DNS name, the helper function DNSServiceConstructFullName() is provided. + * + * The following (highly contrived) example illustrates the escaping process. + * Suppose you have an service called "Dr. Smith\Dr. Johnson", of type "_ftp._tcp" + * in subdomain "4th. Floor" of subdomain "Building 2" of domain "apple.com." + * The full (escaped) DNS name of this service's SRV record would be: + * Dr\.\032Smith\\Dr\.\032Johnson._ftp._tcp.4th\.\032Floor.Building\0322.apple.com. + */ + + +/* + * Constants for specifying an interface index + * + * Specific interface indexes are identified via a 32-bit unsigned integer returned + * by the if_nametoindex() family of calls. + * + * If the client passes 0 for interface index, that means "do the right thing", + * which (at present) means, "if the name is in an mDNS local multicast domain + * (e.g. 'local.', '254.169.in-addr.arpa.', '{8,9,A,B}.E.F.ip6.arpa.') then multicast + * on all applicable interfaces, otherwise send via unicast to the appropriate + * DNS server." Normally, most clients will use 0 for interface index to + * automatically get the default sensible behaviour. + * + * If the client passes a positive interface index, then for multicast names that + * indicates to do the operation only on that one interface. For unicast names the + * interface index is ignored unless kDNSServiceFlagsForceMulticast is also set. + * + * If the client passes kDNSServiceInterfaceIndexLocalOnly when registering + * a service, then that service will be found *only* by other local clients + * on the same machine that are browsing using kDNSServiceInterfaceIndexLocalOnly + * or kDNSServiceInterfaceIndexAny. + * If a client has a 'private' service, accessible only to other processes + * running on the same machine, this allows the client to advertise that service + * in a way such that it does not inadvertently appear in service lists on + * all the other machines on the network. + * + * If the client passes kDNSServiceInterfaceIndexLocalOnly when browsing + * then it will find *all* records registered on that same local machine. + * Clients explicitly wishing to discover *only* LocalOnly services can + * accomplish this by inspecting the interfaceIndex of each service reported + * to their DNSServiceBrowseReply() callback function, and discarding those + * where the interface index is not kDNSServiceInterfaceIndexLocalOnly. + * + * kDNSServiceInterfaceIndexP2P is meaningful only in Browse, QueryRecord, Register, + * and Resolve operations. It should not be used in other DNSService APIs. + * + * - If kDNSServiceInterfaceIndexP2P is passed to DNSServiceBrowse or + * DNSServiceQueryRecord, it restricts the operation to P2P. + * + * - If kDNSServiceInterfaceIndexP2P is passed to DNSServiceRegister, it is + * mapped internally to kDNSServiceInterfaceIndexAny with the kDNSServiceFlagsIncludeP2P + * set. + * + * - If kDNSServiceInterfaceIndexP2P is passed to DNSServiceResolve, it is + * mapped internally to kDNSServiceInterfaceIndexAny with the kDNSServiceFlagsIncludeP2P + * set, because resolving a P2P service may create and/or enable an interface whose + * index is not known a priori. The resolve callback will indicate the index of the + * interface via which the service can be accessed. + * + * If applications pass kDNSServiceInterfaceIndexAny to DNSServiceBrowse + * or DNSServiceQueryRecord, they must set the kDNSServiceFlagsIncludeP2P flag + * to include P2P. In this case, if a service instance or the record being queried + * is found over P2P, the resulting ADD event will indicate kDNSServiceInterfaceIndexP2P + * as the interface index. + */ + +#define kDNSServiceInterfaceIndexAny 0 +#define kDNSServiceInterfaceIndexLocalOnly ((uint32_t)-1) +#define kDNSServiceInterfaceIndexUnicast ((uint32_t)-2) +#define kDNSServiceInterfaceIndexP2P ((uint32_t)-3) + +typedef uint32_t DNSServiceFlags; +typedef uint32_t DNSServiceProtocol; +typedef int32_t DNSServiceErrorType; + + +/********************************************************************************************* +* +* Version checking +* +*********************************************************************************************/ + +/* DNSServiceGetProperty() Parameters: + * + * property: The requested property. + * Currently the only property defined is kDNSServiceProperty_DaemonVersion. + * + * result: Place to store result. + * For retrieving DaemonVersion, this should be the address of a uint32_t. + * + * size: Pointer to uint32_t containing size of the result location. + * For retrieving DaemonVersion, this should be sizeof(uint32_t). + * On return the uint32_t is updated to the size of the data returned. + * For DaemonVersion, the returned size is always sizeof(uint32_t), but + * future properties could be defined which return variable-sized results. + * + * return value: Returns kDNSServiceErr_NoError on success, or kDNSServiceErr_ServiceNotRunning + * if the daemon (or "system service" on Windows) is not running. + */ + +DNSServiceErrorType DNSSD_API DNSServiceGetProperty +( + const char *property, /* Requested property (i.e. kDNSServiceProperty_DaemonVersion) */ + void *result, /* Pointer to place to store result */ + uint32_t *size /* size of result location */ +); + +/* + * When requesting kDNSServiceProperty_DaemonVersion, the result pointer must point + * to a 32-bit unsigned integer, and the size parameter must be set to sizeof(uint32_t). + * + * On return, the 32-bit unsigned integer contains the version number, formatted as follows: + * Major part of the build number * 10000 + + * minor part of the build number * 100 + * + * For example, Mac OS X 10.4.9 has mDNSResponder-108.4, which would be represented as + * version 1080400. This allows applications to do simple greater-than and less-than comparisons: + * e.g. an application that requires at least mDNSResponder-108.4 can check: + * + * if (version >= 1080400) ... + * + * Example usage: + * + * uint32_t version; + * uint32_t size = sizeof(version); + * DNSServiceErrorType err = DNSServiceGetProperty(kDNSServiceProperty_DaemonVersion, &version, &size); + * if (!err) printf("Bonjour version is %d.%d\n", version / 10000, version / 100 % 100); + */ + +#define kDNSServiceProperty_DaemonVersion "DaemonVersion" + + +// Map the source port of the local UDP socket that was opened for sending the DNS query +// to the process ID of the application that triggered the DNS resolution. +// +/* DNSServiceGetPID() Parameters: + * + * srcport: Source port (in network byte order) of the UDP socket that was created by + * mDNSResponder to send the DNS query on the wire. + * + * pid: Process ID of the application that started the name resolution which triggered + * mDNSResponder to send the query on the wire. The value can be -1 if the srcport + * cannot be mapped. + * + * return value: Returns kDNSServiceErr_NoError on success, or kDNSServiceErr_ServiceNotRunning + * if the daemon is not running. The value of the pid is undefined if the return + * value has error. + */ +DNSServiceErrorType DNSSD_API DNSServiceGetPID +( + uint16_t srcport, + int32_t *pid +); + +/********************************************************************************************* +* +* Unix Domain Socket access, DNSServiceRef deallocation, and data processing functions +* +*********************************************************************************************/ + +/* DNSServiceRefSockFD() + * + * Access underlying Unix domain socket for an initialized DNSServiceRef. + * The DNS Service Discovery implementation uses this socket to communicate between the client and + * the mDNSResponder daemon. The application MUST NOT directly read from or write to this socket. + * Access to the socket is provided so that it can be used as a kqueue event source, a CFRunLoop + * event source, in a select() loop, etc. When the underlying event management subsystem (kqueue/ + * select/CFRunLoop etc.) indicates to the client that data is available for reading on the + * socket, the client should call DNSServiceProcessResult(), which will extract the daemon's + * reply from the socket, and pass it to the appropriate application callback. By using a run + * loop or select(), results from the daemon can be processed asynchronously. Alternatively, + * a client can choose to fork a thread and have it loop calling "DNSServiceProcessResult(ref);" + * If DNSServiceProcessResult() is called when no data is available for reading on the socket, it + * will block until data does become available, and then process the data and return to the caller. + * When data arrives on the socket, the client is responsible for calling DNSServiceProcessResult(ref) + * in a timely fashion -- if the client allows a large backlog of data to build up the daemon + * may terminate the connection. + * + * sdRef: A DNSServiceRef initialized by any of the DNSService calls. + * + * return value: The DNSServiceRef's underlying socket descriptor, or -1 on + * error. + */ + +int DNSSD_API DNSServiceRefSockFD(DNSServiceRef sdRef); + + +/* DNSServiceProcessResult() + * + * Read a reply from the daemon, calling the appropriate application callback. This call will + * block until the daemon's response is received. Use DNSServiceRefSockFD() in + * conjunction with a run loop or select() to determine the presence of a response from the + * server before calling this function to process the reply without blocking. Call this function + * at any point if it is acceptable to block until the daemon's response arrives. Note that the + * client is responsible for ensuring that DNSServiceProcessResult() is called whenever there is + * a reply from the daemon - the daemon may terminate its connection with a client that does not + * process the daemon's responses. + * + * sdRef: A DNSServiceRef initialized by any of the DNSService calls + * that take a callback parameter. + * + * return value: Returns kDNSServiceErr_NoError on success, otherwise returns + * an error code indicating the specific failure that occurred. + */ + +DNSServiceErrorType DNSSD_API DNSServiceProcessResult(DNSServiceRef sdRef); + + +/* DNSServiceRefDeallocate() + * + * Terminate a connection with the daemon and free memory associated with the DNSServiceRef. + * Any services or records registered with this DNSServiceRef will be deregistered. Any + * Browse, Resolve, or Query operations called with this reference will be terminated. + * + * Note: If the reference's underlying socket is used in a run loop or select() call, it should + * be removed BEFORE DNSServiceRefDeallocate() is called, as this function closes the reference's + * socket. + * + * Note: If the reference was initialized with DNSServiceCreateConnection(), any DNSRecordRefs + * created via this reference will be invalidated by this call - the resource records are + * deregistered, and their DNSRecordRefs may not be used in subsequent functions. Similarly, + * if the reference was initialized with DNSServiceRegister, and an extra resource record was + * added to the service via DNSServiceAddRecord(), the DNSRecordRef created by the Add() call + * is invalidated when this function is called - the DNSRecordRef may not be used in subsequent + * functions. + * + * Note: This call is to be used only with the DNSServiceRef defined by this API. + * + * sdRef: A DNSServiceRef initialized by any of the DNSService calls. + * + */ + +void DNSSD_API DNSServiceRefDeallocate(DNSServiceRef sdRef); + + +/********************************************************************************************* +* +* Domain Enumeration +* +*********************************************************************************************/ + +/* DNSServiceEnumerateDomains() + * + * Asynchronously enumerate domains available for browsing and registration. + * + * The enumeration MUST be cancelled via DNSServiceRefDeallocate() when no more domains + * are to be found. + * + * Note that the names returned are (like all of DNS-SD) UTF-8 strings, + * and are escaped using standard DNS escaping rules. + * (See "Notes on DNS Name Escaping" earlier in this file for more details.) + * A graphical browser displaying a hierarchical tree-structured view should cut + * the names at the bare dots to yield individual labels, then de-escape each + * label according to the escaping rules, and then display the resulting UTF-8 text. + * + * DNSServiceDomainEnumReply Callback Parameters: + * + * sdRef: The DNSServiceRef initialized by DNSServiceEnumerateDomains(). + * + * flags: Possible values are: + * kDNSServiceFlagsMoreComing + * kDNSServiceFlagsAdd + * kDNSServiceFlagsDefault + * + * interfaceIndex: Specifies the interface on which the domain exists. (The index for a given + * interface is determined via the if_nametoindex() family of calls.) + * + * errorCode: Will be kDNSServiceErr_NoError (0) on success, otherwise indicates + * the failure that occurred (other parameters are undefined if errorCode is nonzero). + * + * replyDomain: The name of the domain. + * + * context: The context pointer passed to DNSServiceEnumerateDomains. + * + */ + +typedef void (DNSSD_API *DNSServiceDomainEnumReply) +( + DNSServiceRef sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + DNSServiceErrorType errorCode, + const char *replyDomain, + void *context +); + + +/* DNSServiceEnumerateDomains() Parameters: + * + * sdRef: A pointer to an uninitialized DNSServiceRef. If the call succeeds + * then it initializes the DNSServiceRef, returns kDNSServiceErr_NoError, + * and the enumeration operation will run indefinitely until the client + * terminates it by passing this DNSServiceRef to DNSServiceRefDeallocate(). + * + * flags: Possible values are: + * kDNSServiceFlagsBrowseDomains to enumerate domains recommended for browsing. + * kDNSServiceFlagsRegistrationDomains to enumerate domains recommended + * for registration. + * + * interfaceIndex: If non-zero, specifies the interface on which to look for domains. + * (the index for a given interface is determined via the if_nametoindex() + * family of calls.) Most applications will pass 0 to enumerate domains on + * all interfaces. See "Constants for specifying an interface index" for more details. + * + * callBack: The function to be called when a domain is found or the call asynchronously + * fails. + * + * context: An application context pointer which is passed to the callback function + * (may be NULL). + * + * return value: Returns kDNSServiceErr_NoError on success (any subsequent, asynchronous + * errors are delivered to the callback), otherwise returns an error code indicating + * the error that occurred (the callback is not invoked and the DNSServiceRef + * is not initialized). + */ + +DNSServiceErrorType DNSSD_API DNSServiceEnumerateDomains +( + DNSServiceRef *sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + DNSServiceDomainEnumReply callBack, + void *context /* may be NULL */ +); + + +/********************************************************************************************* +* +* Service Registration +* +*********************************************************************************************/ + +/* Register a service that is discovered via Browse() and Resolve() calls. + * + * DNSServiceRegisterReply() Callback Parameters: + * + * sdRef: The DNSServiceRef initialized by DNSServiceRegister(). + * + * flags: When a name is successfully registered, the callback will be + * invoked with the kDNSServiceFlagsAdd flag set. When Wide-Area + * DNS-SD is in use, it is possible for a single service to get + * more than one success callback (e.g. one in the "local" multicast + * DNS domain, and another in a wide-area unicast DNS domain). + * If a successfully-registered name later suffers a name conflict + * or similar problem and has to be deregistered, the callback will + * be invoked with the kDNSServiceFlagsAdd flag not set. The callback + * is *not* invoked in the case where the caller explicitly terminates + * the service registration by calling DNSServiceRefDeallocate(ref); + * + * errorCode: Will be kDNSServiceErr_NoError on success, otherwise will + * indicate the failure that occurred (including name conflicts, + * if the kDNSServiceFlagsNoAutoRename flag was used when registering.) + * Other parameters are undefined if errorCode is nonzero. + * + * name: The service name registered (if the application did not specify a name in + * DNSServiceRegister(), this indicates what name was automatically chosen). + * + * regtype: The type of service registered, as it was passed to the callout. + * + * domain: The domain on which the service was registered (if the application did not + * specify a domain in DNSServiceRegister(), this indicates the default domain + * on which the service was registered). + * + * context: The context pointer that was passed to the callout. + * + */ + +typedef void (DNSSD_API *DNSServiceRegisterReply) +( + DNSServiceRef sdRef, + DNSServiceFlags flags, + DNSServiceErrorType errorCode, + const char *name, + const char *regtype, + const char *domain, + void *context +); + + +/* DNSServiceRegister() Parameters: + * + * sdRef: A pointer to an uninitialized DNSServiceRef. If the call succeeds + * then it initializes the DNSServiceRef, returns kDNSServiceErr_NoError, + * and the registration will remain active indefinitely until the client + * terminates it by passing this DNSServiceRef to DNSServiceRefDeallocate(). + * + * interfaceIndex: If non-zero, specifies the interface on which to register the service + * (the index for a given interface is determined via the if_nametoindex() + * family of calls.) Most applications will pass 0 to register on all + * available interfaces. See "Constants for specifying an interface index" for more details. + * + * flags: Indicates the renaming behavior on name conflict (most applications + * will pass 0). See flag definitions above for details. + * + * name: If non-NULL, specifies the service name to be registered. + * Most applications will not specify a name, in which case the computer + * name is used (this name is communicated to the client via the callback). + * If a name is specified, it must be 1-63 bytes of UTF-8 text. + * If the name is longer than 63 bytes it will be automatically truncated + * to a legal length, unless the NoAutoRename flag is set, + * in which case kDNSServiceErr_BadParam will be returned. + * + * regtype: The service type followed by the protocol, separated by a dot + * (e.g. "_ftp._tcp"). The service type must be an underscore, followed + * by 1-15 characters, which may be letters, digits, or hyphens. + * The transport protocol must be "_tcp" or "_udp". New service types + * should be registered at <http://www.dns-sd.org/ServiceTypes.html>. + * + * Additional subtypes of the primary service type (where a service + * type has defined subtypes) follow the primary service type in a + * comma-separated list, with no additional spaces, e.g. + * "_primarytype._tcp,_subtype1,_subtype2,_subtype3" + * Subtypes provide a mechanism for filtered browsing: A client browsing + * for "_primarytype._tcp" will discover all instances of this type; + * a client browsing for "_primarytype._tcp,_subtype2" will discover only + * those instances that were registered with "_subtype2" in their list of + * registered subtypes. + * + * The subtype mechanism can be illustrated with some examples using the + * dns-sd command-line tool: + * + * % dns-sd -R Simple _test._tcp "" 1001 & + * % dns-sd -R Better _test._tcp,HasFeatureA "" 1002 & + * % dns-sd -R Best _test._tcp,HasFeatureA,HasFeatureB "" 1003 & + * + * Now: + * % dns-sd -B _test._tcp # will find all three services + * % dns-sd -B _test._tcp,HasFeatureA # finds "Better" and "Best" + * % dns-sd -B _test._tcp,HasFeatureB # finds only "Best" + * + * Subtype labels may be up to 63 bytes long, and may contain any eight- + * bit byte values, including zero bytes. However, due to the nature of + * using a C-string-based API, conventional DNS escaping must be used for + * dots ('.'), commas (','), backslashes ('\') and zero bytes, as shown below: + * + * % dns-sd -R Test '_test._tcp,s\.one,s\,two,s\\three,s\000four' local 123 + * + * When a service is registered, all the clients browsing for the registered + * type ("regtype") will discover it. If the discovery should be + * restricted to a smaller set of well known peers, the service can be + * registered with additional data (group identifier) that is known + * only to a smaller set of peers. The group identifier should follow primary + * service type using a colon (":") as a delimeter. If subtypes are also present, + * it should be given before the subtype as shown below. + * + * % dns-sd -R _test1 _http._tcp:mygroup1 local 1001 + * % dns-sd -R _test2 _http._tcp:mygroup2 local 1001 + * % dns-sd -R _test3 _http._tcp:mygroup3,HasFeatureA local 1001 + * + * Now: + * % dns-sd -B _http._tcp:"mygroup1" # will discover only test1 + * % dns-sd -B _http._tcp:"mygroup2" # will discover only test2 + * % dns-sd -B _http._tcp:"mygroup3",HasFeatureA # will discover only test3 + * + * By specifying the group information, only the members of that group are + * discovered. + * + * The group identifier itself is not sent in clear. Only a hash of the group + * identifier is sent and the clients discover them anonymously. The group identifier + * may be up to 256 bytes long and may contain any eight bit values except comma which + * should be escaped. + * + * domain: If non-NULL, specifies the domain on which to advertise the service. + * Most applications will not specify a domain, instead automatically + * registering in the default domain(s). + * + * host: If non-NULL, specifies the SRV target host name. Most applications + * will not specify a host, instead automatically using the machine's + * default host name(s). Note that specifying a non-NULL host does NOT + * create an address record for that host - the application is responsible + * for ensuring that the appropriate address record exists, or creating it + * via DNSServiceRegisterRecord(). + * + * port: The port, in network byte order, on which the service accepts connections. + * Pass 0 for a "placeholder" service (i.e. a service that will not be discovered + * by browsing, but will cause a name conflict if another client tries to + * register that same name). Most clients will not use placeholder services. + * + * txtLen: The length of the txtRecord, in bytes. Must be zero if the txtRecord is NULL. + * + * txtRecord: The TXT record rdata. A non-NULL txtRecord MUST be a properly formatted DNS + * TXT record, i.e. <length byte> <data> <length byte> <data> ... + * Passing NULL for the txtRecord is allowed as a synonym for txtLen=1, txtRecord="", + * i.e. it creates a TXT record of length one containing a single empty string. + * RFC 1035 doesn't allow a TXT record to contain *zero* strings, so a single empty + * string is the smallest legal DNS TXT record. + * As with the other parameters, the DNSServiceRegister call copies the txtRecord + * data; e.g. if you allocated the storage for the txtRecord parameter with malloc() + * then you can safely free that memory right after the DNSServiceRegister call returns. + * + * callBack: The function to be called when the registration completes or asynchronously + * fails. The client MAY pass NULL for the callback - The client will NOT be notified + * of the default values picked on its behalf, and the client will NOT be notified of any + * asynchronous errors (e.g. out of memory errors, etc.) that may prevent the registration + * of the service. The client may NOT pass the NoAutoRename flag if the callback is NULL. + * The client may still deregister the service at any time via DNSServiceRefDeallocate(). + * + * context: An application context pointer which is passed to the callback function + * (may be NULL). + * + * return value: Returns kDNSServiceErr_NoError on success (any subsequent, asynchronous + * errors are delivered to the callback), otherwise returns an error code indicating + * the error that occurred (the callback is never invoked and the DNSServiceRef + * is not initialized). + */ + +DNSServiceErrorType DNSSD_API DNSServiceRegister +( + DNSServiceRef *sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + const char *name, /* may be NULL */ + const char *regtype, + const char *domain, /* may be NULL */ + const char *host, /* may be NULL */ + uint16_t port, /* In network byte order */ + uint16_t txtLen, + const void *txtRecord, /* may be NULL */ + DNSServiceRegisterReply callBack, /* may be NULL */ + void *context /* may be NULL */ +); + + +/* DNSServiceAddRecord() + * + * Add a record to a registered service. The name of the record will be the same as the + * registered service's name. + * The record can later be updated or deregistered by passing the RecordRef initialized + * by this function to DNSServiceUpdateRecord() or DNSServiceRemoveRecord(). + * + * Note that the DNSServiceAddRecord/UpdateRecord/RemoveRecord are *NOT* thread-safe + * with respect to a single DNSServiceRef. If you plan to have multiple threads + * in your program simultaneously add, update, or remove records from the same + * DNSServiceRef, then it's the caller's responsibility to use a mutext lock + * or take similar appropriate precautions to serialize those calls. + * + * Parameters; + * + * sdRef: A DNSServiceRef initialized by DNSServiceRegister(). + * + * RecordRef: A pointer to an uninitialized DNSRecordRef. Upon succesfull completion of this + * call, this ref may be passed to DNSServiceUpdateRecord() or DNSServiceRemoveRecord(). + * If the above DNSServiceRef is passed to DNSServiceRefDeallocate(), RecordRef is also + * invalidated and may not be used further. + * + * flags: Currently ignored, reserved for future use. + * + * rrtype: The type of the record (e.g. kDNSServiceType_TXT, kDNSServiceType_SRV, etc) + * + * rdlen: The length, in bytes, of the rdata. + * + * rdata: The raw rdata to be contained in the added resource record. + * + * ttl: The time to live of the resource record, in seconds. + * Most clients should pass 0 to indicate that the system should + * select a sensible default value. + * + * return value: Returns kDNSServiceErr_NoError on success, otherwise returns an + * error code indicating the error that occurred (the RecordRef is not initialized). + */ + +DNSServiceErrorType DNSSD_API DNSServiceAddRecord +( + DNSServiceRef sdRef, + DNSRecordRef *RecordRef, + DNSServiceFlags flags, + uint16_t rrtype, + uint16_t rdlen, + const void *rdata, + uint32_t ttl +); + + +/* DNSServiceUpdateRecord + * + * Update a registered resource record. The record must either be: + * - The primary txt record of a service registered via DNSServiceRegister() + * - A record added to a registered service via DNSServiceAddRecord() + * - An individual record registered by DNSServiceRegisterRecord() + * + * Parameters: + * + * sdRef: A DNSServiceRef that was initialized by DNSServiceRegister() + * or DNSServiceCreateConnection(). + * + * RecordRef: A DNSRecordRef initialized by DNSServiceAddRecord, or NULL to update the + * service's primary txt record. + * + * flags: Currently ignored, reserved for future use. + * + * rdlen: The length, in bytes, of the new rdata. + * + * rdata: The new rdata to be contained in the updated resource record. + * + * ttl: The time to live of the updated resource record, in seconds. + * Most clients should pass 0 to indicate that the system should + * select a sensible default value. + * + * return value: Returns kDNSServiceErr_NoError on success, otherwise returns an + * error code indicating the error that occurred. + */ + +DNSServiceErrorType DNSSD_API DNSServiceUpdateRecord +( + DNSServiceRef sdRef, + DNSRecordRef RecordRef, /* may be NULL */ + DNSServiceFlags flags, + uint16_t rdlen, + const void *rdata, + uint32_t ttl +); + + +/* DNSServiceRemoveRecord + * + * Remove a record previously added to a service record set via DNSServiceAddRecord(), or deregister + * an record registered individually via DNSServiceRegisterRecord(). + * + * Parameters: + * + * sdRef: A DNSServiceRef initialized by DNSServiceRegister() (if the + * record being removed was registered via DNSServiceAddRecord()) or by + * DNSServiceCreateConnection() (if the record being removed was registered via + * DNSServiceRegisterRecord()). + * + * recordRef: A DNSRecordRef initialized by a successful call to DNSServiceAddRecord() + * or DNSServiceRegisterRecord(). + * + * flags: Currently ignored, reserved for future use. + * + * return value: Returns kDNSServiceErr_NoError on success, otherwise returns an + * error code indicating the error that occurred. + */ + +DNSServiceErrorType DNSSD_API DNSServiceRemoveRecord +( + DNSServiceRef sdRef, + DNSRecordRef RecordRef, + DNSServiceFlags flags +); + + +/********************************************************************************************* +* +* Service Discovery +* +*********************************************************************************************/ + +/* Browse for instances of a service. + * + * DNSServiceBrowseReply() Parameters: + * + * sdRef: The DNSServiceRef initialized by DNSServiceBrowse(). + * + * flags: Possible values are kDNSServiceFlagsMoreComing and kDNSServiceFlagsAdd. + * See flag definitions for details. + * + * interfaceIndex: The interface on which the service is advertised. This index should + * be passed to DNSServiceResolve() when resolving the service. + * + * errorCode: Will be kDNSServiceErr_NoError (0) on success, otherwise will + * indicate the failure that occurred. Other parameters are undefined if + * the errorCode is nonzero. + * + * serviceName: The discovered service name. This name should be displayed to the user, + * and stored for subsequent use in the DNSServiceResolve() call. + * + * regtype: The service type, which is usually (but not always) the same as was passed + * to DNSServiceBrowse(). One case where the discovered service type may + * not be the same as the requested service type is when using subtypes: + * The client may want to browse for only those ftp servers that allow + * anonymous connections. The client will pass the string "_ftp._tcp,_anon" + * to DNSServiceBrowse(), but the type of the service that's discovered + * is simply "_ftp._tcp". The regtype for each discovered service instance + * should be stored along with the name, so that it can be passed to + * DNSServiceResolve() when the service is later resolved. + * + * domain: The domain of the discovered service instance. This may or may not be the + * same as the domain that was passed to DNSServiceBrowse(). The domain for each + * discovered service instance should be stored along with the name, so that + * it can be passed to DNSServiceResolve() when the service is later resolved. + * + * context: The context pointer that was passed to the callout. + * + */ + +typedef void (DNSSD_API *DNSServiceBrowseReply) +( + DNSServiceRef sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + DNSServiceErrorType errorCode, + const char *serviceName, + const char *regtype, + const char *replyDomain, + void *context +); + + +/* DNSServiceBrowse() Parameters: + * + * sdRef: A pointer to an uninitialized DNSServiceRef. If the call succeeds + * then it initializes the DNSServiceRef, returns kDNSServiceErr_NoError, + * and the browse operation will run indefinitely until the client + * terminates it by passing this DNSServiceRef to DNSServiceRefDeallocate(). + * + * flags: Currently ignored, reserved for future use. + * + * interfaceIndex: If non-zero, specifies the interface on which to browse for services + * (the index for a given interface is determined via the if_nametoindex() + * family of calls.) Most applications will pass 0 to browse on all available + * interfaces. See "Constants for specifying an interface index" for more details. + * + * regtype: The service type being browsed for followed by the protocol, separated by a + * dot (e.g. "_ftp._tcp"). The transport protocol must be "_tcp" or "_udp". + * A client may optionally specify a single subtype to perform filtered browsing: + * e.g. browsing for "_primarytype._tcp,_subtype" will discover only those + * instances of "_primarytype._tcp" that were registered specifying "_subtype" + * in their list of registered subtypes. Additionally, a group identifier may + * also be specified before the subtype e.g., _primarytype._tcp:GroupID, which + * will discover only the members that register the service with GroupID. See + * DNSServiceRegister for more details. + * + * domain: If non-NULL, specifies the domain on which to browse for services. + * Most applications will not specify a domain, instead browsing on the + * default domain(s). + * + * callBack: The function to be called when an instance of the service being browsed for + * is found, or if the call asynchronously fails. + * + * context: An application context pointer which is passed to the callback function + * (may be NULL). + * + * return value: Returns kDNSServiceErr_NoError on success (any subsequent, asynchronous + * errors are delivered to the callback), otherwise returns an error code indicating + * the error that occurred (the callback is not invoked and the DNSServiceRef + * is not initialized). + */ + +DNSServiceErrorType DNSSD_API DNSServiceBrowse +( + DNSServiceRef *sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + const char *regtype, + const char *domain, /* may be NULL */ + DNSServiceBrowseReply callBack, + void *context /* may be NULL */ +); + + +/* DNSServiceResolve() + * + * Resolve a service name discovered via DNSServiceBrowse() to a target host name, port number, and + * txt record. + * + * Note: Applications should NOT use DNSServiceResolve() solely for txt record monitoring - use + * DNSServiceQueryRecord() instead, as it is more efficient for this task. + * + * Note: When the desired results have been returned, the client MUST terminate the resolve by calling + * DNSServiceRefDeallocate(). + * + * Note: DNSServiceResolve() behaves correctly for typical services that have a single SRV record + * and a single TXT record. To resolve non-standard services with multiple SRV or TXT records, + * DNSServiceQueryRecord() should be used. + * + * DNSServiceResolveReply Callback Parameters: + * + * sdRef: The DNSServiceRef initialized by DNSServiceResolve(). + * + * flags: Possible values: kDNSServiceFlagsMoreComing + * + * interfaceIndex: The interface on which the service was resolved. + * + * errorCode: Will be kDNSServiceErr_NoError (0) on success, otherwise will + * indicate the failure that occurred. Other parameters are undefined if + * the errorCode is nonzero. + * + * fullname: The full service domain name, in the form <servicename>.<protocol>.<domain>. + * (This name is escaped following standard DNS rules, making it suitable for + * passing to standard system DNS APIs such as res_query(), or to the + * special-purpose functions included in this API that take fullname parameters. + * See "Notes on DNS Name Escaping" earlier in this file for more details.) + * + * hosttarget: The target hostname of the machine providing the service. This name can + * be passed to functions like gethostbyname() to identify the host's IP address. + * + * port: The port, in network byte order, on which connections are accepted for this service. + * + * txtLen: The length of the txt record, in bytes. + * + * txtRecord: The service's primary txt record, in standard txt record format. + * + * context: The context pointer that was passed to the callout. + * + * NOTE: In earlier versions of this header file, the txtRecord parameter was declared "const char *" + * This is incorrect, since it contains length bytes which are values in the range 0 to 255, not -128 to +127. + * Depending on your compiler settings, this change may cause signed/unsigned mismatch warnings. + * These should be fixed by updating your own callback function definition to match the corrected + * function signature using "const unsigned char *txtRecord". Making this change may also fix inadvertent + * bugs in your callback function, where it could have incorrectly interpreted a length byte with value 250 + * as being -6 instead, with various bad consequences ranging from incorrect operation to software crashes. + * If you need to maintain portable code that will compile cleanly with both the old and new versions of + * this header file, you should update your callback function definition to use the correct unsigned value, + * and then in the place where you pass your callback function to DNSServiceResolve(), use a cast to eliminate + * the compiler warning, e.g.: + * DNSServiceResolve(sd, flags, index, name, regtype, domain, (DNSServiceResolveReply)MyCallback, context); + * This will ensure that your code compiles cleanly without warnings (and more importantly, works correctly) + * with both the old header and with the new corrected version. + * + */ + +typedef void (DNSSD_API *DNSServiceResolveReply) +( + DNSServiceRef sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + DNSServiceErrorType errorCode, + const char *fullname, + const char *hosttarget, + uint16_t port, /* In network byte order */ + uint16_t txtLen, + const unsigned char *txtRecord, + void *context +); + + +/* DNSServiceResolve() Parameters + * + * sdRef: A pointer to an uninitialized DNSServiceRef. If the call succeeds + * then it initializes the DNSServiceRef, returns kDNSServiceErr_NoError, + * and the resolve operation will run indefinitely until the client + * terminates it by passing this DNSServiceRef to DNSServiceRefDeallocate(). + * + * flags: Specifying kDNSServiceFlagsForceMulticast will cause query to be + * performed with a link-local mDNS query, even if the name is an + * apparently non-local name (i.e. a name not ending in ".local.") + * + * interfaceIndex: The interface on which to resolve the service. If this resolve call is + * as a result of a currently active DNSServiceBrowse() operation, then the + * interfaceIndex should be the index reported in the DNSServiceBrowseReply + * callback. If this resolve call is using information previously saved + * (e.g. in a preference file) for later use, then use interfaceIndex 0, because + * the desired service may now be reachable via a different physical interface. + * See "Constants for specifying an interface index" for more details. + * + * name: The name of the service instance to be resolved, as reported to the + * DNSServiceBrowseReply() callback. + * + * regtype: The type of the service instance to be resolved, as reported to the + * DNSServiceBrowseReply() callback. + * + * domain: The domain of the service instance to be resolved, as reported to the + * DNSServiceBrowseReply() callback. + * + * callBack: The function to be called when a result is found, or if the call + * asynchronously fails. + * + * context: An application context pointer which is passed to the callback function + * (may be NULL). + * + * return value: Returns kDNSServiceErr_NoError on success (any subsequent, asynchronous + * errors are delivered to the callback), otherwise returns an error code indicating + * the error that occurred (the callback is never invoked and the DNSServiceRef + * is not initialized). + */ + +DNSServiceErrorType DNSSD_API DNSServiceResolve +( + DNSServiceRef *sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + const char *name, + const char *regtype, + const char *domain, + DNSServiceResolveReply callBack, + void *context /* may be NULL */ +); + + +/********************************************************************************************* +* +* Querying Individual Specific Records +* +*********************************************************************************************/ + +/* DNSServiceQueryRecord + * + * Query for an arbitrary DNS record. + * + * DNSServiceQueryRecordReply() Callback Parameters: + * + * sdRef: The DNSServiceRef initialized by DNSServiceQueryRecord(). + * + * flags: Possible values are kDNSServiceFlagsMoreComing and + * kDNSServiceFlagsAdd. The Add flag is NOT set for PTR records + * with a ttl of 0, i.e. "Remove" events. + * + * interfaceIndex: The interface on which the query was resolved (the index for a given + * interface is determined via the if_nametoindex() family of calls). + * See "Constants for specifying an interface index" for more details. + * + * errorCode: Will be kDNSServiceErr_NoError on success, otherwise will + * indicate the failure that occurred. Other parameters are undefined if + * errorCode is nonzero. + * + * fullname: The resource record's full domain name. + * + * rrtype: The resource record's type (e.g. kDNSServiceType_PTR, kDNSServiceType_SRV, etc) + * + * rrclass: The class of the resource record (usually kDNSServiceClass_IN). + * + * rdlen: The length, in bytes, of the resource record rdata. + * + * rdata: The raw rdata of the resource record. + * + * ttl: If the client wishes to cache the result for performance reasons, + * the TTL indicates how long the client may legitimately hold onto + * this result, in seconds. After the TTL expires, the client should + * consider the result no longer valid, and if it requires this data + * again, it should be re-fetched with a new query. Of course, this + * only applies to clients that cancel the asynchronous operation when + * they get a result. Clients that leave the asynchronous operation + * running can safely assume that the data remains valid until they + * get another callback telling them otherwise. + * + * context: The context pointer that was passed to the callout. + * + */ + +typedef void (DNSSD_API *DNSServiceQueryRecordReply) +( + DNSServiceRef sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + DNSServiceErrorType errorCode, + const char *fullname, + uint16_t rrtype, + uint16_t rrclass, + uint16_t rdlen, + const void *rdata, + uint32_t ttl, + void *context +); + + +/* DNSServiceQueryRecord() Parameters: + * + * sdRef: A pointer to an uninitialized DNSServiceRef. If the call succeeds + * then it initializes the DNSServiceRef, returns kDNSServiceErr_NoError, + * and the query operation will run indefinitely until the client + * terminates it by passing this DNSServiceRef to DNSServiceRefDeallocate(). + * + * flags: kDNSServiceFlagsForceMulticast or kDNSServiceFlagsLongLivedQuery. + * Pass kDNSServiceFlagsLongLivedQuery to create a "long-lived" unicast + * query to a unicast DNS server that implements the protocol. This flag + * has no effect on link-local multicast queries. + * + * interfaceIndex: If non-zero, specifies the interface on which to issue the query + * (the index for a given interface is determined via the if_nametoindex() + * family of calls.) Passing 0 causes the name to be queried for on all + * interfaces. See "Constants for specifying an interface index" for more details. + * + * fullname: The full domain name of the resource record to be queried for. + * + * rrtype: The numerical type of the resource record to be queried for + * (e.g. kDNSServiceType_PTR, kDNSServiceType_SRV, etc) + * + * rrclass: The class of the resource record (usually kDNSServiceClass_IN). + * + * callBack: The function to be called when a result is found, or if the call + * asynchronously fails. + * + * context: An application context pointer which is passed to the callback function + * (may be NULL). + * + * return value: Returns kDNSServiceErr_NoError on success (any subsequent, asynchronous + * errors are delivered to the callback), otherwise returns an error code indicating + * the error that occurred (the callback is never invoked and the DNSServiceRef + * is not initialized). + */ + +DNSServiceErrorType DNSSD_API DNSServiceQueryRecord +( + DNSServiceRef *sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + const char *fullname, + uint16_t rrtype, + uint16_t rrclass, + DNSServiceQueryRecordReply callBack, + void *context /* may be NULL */ +); + + +/********************************************************************************************* +* +* Unified lookup of both IPv4 and IPv6 addresses for a fully qualified hostname +* +*********************************************************************************************/ + +/* DNSServiceGetAddrInfo + * + * Queries for the IP address of a hostname by using either Multicast or Unicast DNS. + * + * DNSServiceGetAddrInfoReply() parameters: + * + * sdRef: The DNSServiceRef initialized by DNSServiceGetAddrInfo(). + * + * flags: Possible values are kDNSServiceFlagsMoreComing and + * kDNSServiceFlagsAdd. + * + * interfaceIndex: The interface to which the answers pertain. + * + * errorCode: Will be kDNSServiceErr_NoError on success, otherwise will + * indicate the failure that occurred. Other parameters are + * undefined if errorCode is nonzero. + * + * hostname: The fully qualified domain name of the host to be queried for. + * + * address: IPv4 or IPv6 address. + * + * ttl: If the client wishes to cache the result for performance reasons, + * the TTL indicates how long the client may legitimately hold onto + * this result, in seconds. After the TTL expires, the client should + * consider the result no longer valid, and if it requires this data + * again, it should be re-fetched with a new query. Of course, this + * only applies to clients that cancel the asynchronous operation when + * they get a result. Clients that leave the asynchronous operation + * running can safely assume that the data remains valid until they + * get another callback telling them otherwise. + * + * context: The context pointer that was passed to the callout. + * + */ + +typedef void (DNSSD_API *DNSServiceGetAddrInfoReply) +( + DNSServiceRef sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + DNSServiceErrorType errorCode, + const char *hostname, + const struct sockaddr *address, + uint32_t ttl, + void *context +); + + +/* DNSServiceGetAddrInfo() Parameters: + * + * sdRef: A pointer to an uninitialized DNSServiceRef. If the call succeeds then it + * initializes the DNSServiceRef, returns kDNSServiceErr_NoError, and the query + * begins and will last indefinitely until the client terminates the query + * by passing this DNSServiceRef to DNSServiceRefDeallocate(). + * + * flags: kDNSServiceFlagsForceMulticast or kDNSServiceFlagsLongLivedQuery. + * Pass kDNSServiceFlagsLongLivedQuery to create a "long-lived" unicast + * query to a unicast DNS server that implements the protocol. This flag + * has no effect on link-local multicast queries. + * + * interfaceIndex: The interface on which to issue the query. Passing 0 causes the query to be + * sent on all active interfaces via Multicast or the primary interface via Unicast. + * + * protocol: Pass in kDNSServiceProtocol_IPv4 to look up IPv4 addresses, or kDNSServiceProtocol_IPv6 + * to look up IPv6 addresses, or both to look up both kinds. If neither flag is + * set, the system will apply an intelligent heuristic, which is (currently) + * that it will attempt to look up both, except: + * + * * If "hostname" is a wide-area unicast DNS hostname (i.e. not a ".local." name) + * but this host has no routable IPv6 address, then the call will not try to + * look up IPv6 addresses for "hostname", since any addresses it found would be + * unlikely to be of any use anyway. Similarly, if this host has no routable + * IPv4 address, the call will not try to look up IPv4 addresses for "hostname". + * + * hostname: The fully qualified domain name of the host to be queried for. + * + * callBack: The function to be called when the query succeeds or fails asynchronously. + * + * context: An application context pointer which is passed to the callback function + * (may be NULL). + * + * return value: Returns kDNSServiceErr_NoError on success (any subsequent, asynchronous + * errors are delivered to the callback), otherwise returns an error code indicating + * the error that occurred. + */ + +DNSServiceErrorType DNSSD_API DNSServiceGetAddrInfo +( + DNSServiceRef *sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + DNSServiceProtocol protocol, + const char *hostname, + DNSServiceGetAddrInfoReply callBack, + void *context /* may be NULL */ +); + + +/********************************************************************************************* +* +* Special Purpose Calls: +* DNSServiceCreateConnection(), DNSServiceRegisterRecord(), DNSServiceReconfirmRecord() +* (most applications will not use these) +* +*********************************************************************************************/ + +/* DNSServiceCreateConnection() + * + * Create a connection to the daemon allowing efficient registration of + * multiple individual records. + * + * Parameters: + * + * sdRef: A pointer to an uninitialized DNSServiceRef. Deallocating + * the reference (via DNSServiceRefDeallocate()) severs the + * connection and deregisters all records registered on this connection. + * + * return value: Returns kDNSServiceErr_NoError on success, otherwise returns + * an error code indicating the specific failure that occurred (in which + * case the DNSServiceRef is not initialized). + */ + +DNSServiceErrorType DNSSD_API DNSServiceCreateConnection(DNSServiceRef *sdRef); + +/* DNSServiceRegisterRecord + * + * Register an individual resource record on a connected DNSServiceRef. + * + * Note that name conflicts occurring for records registered via this call must be handled + * by the client in the callback. + * + * DNSServiceRegisterRecordReply() parameters: + * + * sdRef: The connected DNSServiceRef initialized by + * DNSServiceCreateConnection(). + * + * RecordRef: The DNSRecordRef initialized by DNSServiceRegisterRecord(). If the above + * DNSServiceRef is passed to DNSServiceRefDeallocate(), this DNSRecordRef is + * invalidated, and may not be used further. + * + * flags: Currently unused, reserved for future use. + * + * errorCode: Will be kDNSServiceErr_NoError on success, otherwise will + * indicate the failure that occurred (including name conflicts.) + * Other parameters are undefined if errorCode is nonzero. + * + * context: The context pointer that was passed to the callout. + * + */ + +typedef void (DNSSD_API *DNSServiceRegisterRecordReply) +( + DNSServiceRef sdRef, + DNSRecordRef RecordRef, + DNSServiceFlags flags, + DNSServiceErrorType errorCode, + void *context +); + + +/* DNSServiceRegisterRecord() Parameters: + * + * sdRef: A DNSServiceRef initialized by DNSServiceCreateConnection(). + * + * RecordRef: A pointer to an uninitialized DNSRecordRef. Upon succesfull completion of this + * call, this ref may be passed to DNSServiceUpdateRecord() or DNSServiceRemoveRecord(). + * (To deregister ALL records registered on a single connected DNSServiceRef + * and deallocate each of their corresponding DNSServiceRecordRefs, call + * DNSServiceRefDeallocate()). + * + * flags: Possible values are kDNSServiceFlagsShared or kDNSServiceFlagsUnique + * (see flag type definitions for details). + * + * interfaceIndex: If non-zero, specifies the interface on which to register the record + * (the index for a given interface is determined via the if_nametoindex() + * family of calls.) Passing 0 causes the record to be registered on all interfaces. + * See "Constants for specifying an interface index" for more details. + * + * fullname: The full domain name of the resource record. + * + * rrtype: The numerical type of the resource record (e.g. kDNSServiceType_PTR, kDNSServiceType_SRV, etc) + * + * rrclass: The class of the resource record (usually kDNSServiceClass_IN) + * + * rdlen: Length, in bytes, of the rdata. + * + * rdata: A pointer to the raw rdata, as it is to appear in the DNS record. + * + * ttl: The time to live of the resource record, in seconds. + * Most clients should pass 0 to indicate that the system should + * select a sensible default value. + * + * callBack: The function to be called when a result is found, or if the call + * asynchronously fails (e.g. because of a name conflict.) + * + * context: An application context pointer which is passed to the callback function + * (may be NULL). + * + * return value: Returns kDNSServiceErr_NoError on success (any subsequent, asynchronous + * errors are delivered to the callback), otherwise returns an error code indicating + * the error that occurred (the callback is never invoked and the DNSRecordRef is + * not initialized). + */ + +DNSServiceErrorType DNSSD_API DNSServiceRegisterRecord +( + DNSServiceRef sdRef, + DNSRecordRef *RecordRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + const char *fullname, + uint16_t rrtype, + uint16_t rrclass, + uint16_t rdlen, + const void *rdata, + uint32_t ttl, + DNSServiceRegisterRecordReply callBack, + void *context /* may be NULL */ +); + + +/* DNSServiceReconfirmRecord + * + * Instruct the daemon to verify the validity of a resource record that appears + * to be out of date (e.g. because TCP connection to a service's target failed.) + * Causes the record to be flushed from the daemon's cache (as well as all other + * daemons' caches on the network) if the record is determined to be invalid. + * Use this routine conservatively. Reconfirming a record necessarily consumes + * network bandwidth, so this should not be done indiscriminately. + * + * Parameters: + * + * flags: Not currently used. + * + * interfaceIndex: Specifies the interface of the record in question. + * The caller must specify the interface. + * This API (by design) causes increased network traffic, so it requires + * the caller to be precise about which record should be reconfirmed. + * It is not possible to pass zero for the interface index to perform + * a "wildcard" reconfirmation, where *all* matching records are reconfirmed. + * + * fullname: The resource record's full domain name. + * + * rrtype: The resource record's type (e.g. kDNSServiceType_PTR, kDNSServiceType_SRV, etc) + * + * rrclass: The class of the resource record (usually kDNSServiceClass_IN). + * + * rdlen: The length, in bytes, of the resource record rdata. + * + * rdata: The raw rdata of the resource record. + * + */ + +DNSServiceErrorType DNSSD_API DNSServiceReconfirmRecord +( + DNSServiceFlags flags, + uint32_t interfaceIndex, + const char *fullname, + uint16_t rrtype, + uint16_t rrclass, + uint16_t rdlen, + const void *rdata +); + + +/********************************************************************************************* +* +* NAT Port Mapping +* +*********************************************************************************************/ + +/* DNSServiceNATPortMappingCreate + * + * Request a port mapping in the NAT gateway, which maps a port on the local machine + * to an external port on the NAT. The NAT should support either PCP, NAT-PMP or the + * UPnP/IGD protocol for this API to create a successful mapping. Note that this API + * currently supports IPv4 addresses/mappings only. If the NAT gateway supports PCP and + * returns an IPv6 address (incorrectly, since this API specifically requests IPv4 + * addresses), the DNSServiceNATPortMappingReply callback will be invoked with errorCode + * kDNSServiceErr_NATPortMappingUnsupported. + * + * The port mapping will be renewed indefinitely until the client process exits, or + * explicitly terminates the port mapping request by calling DNSServiceRefDeallocate(). + * The client callback will be invoked, informing the client of the NAT gateway's + * external IP address and the external port that has been allocated for this client. + * The client should then record this external IP address and port using whatever + * directory service mechanism it is using to enable peers to connect to it. + * (Clients advertising services using Wide-Area DNS-SD DO NOT need to use this API + * -- when a client calls DNSServiceRegister() NAT mappings are automatically created + * and the external IP address and port for the service are recorded in the global DNS. + * Only clients using some directory mechanism other than Wide-Area DNS-SD need to use + * this API to explicitly map their own ports.) + * + * It's possible that the client callback could be called multiple times, for example + * if the NAT gateway's IP address changes, or if a configuration change results in a + * different external port being mapped for this client. Over the lifetime of any long-lived + * port mapping, the client should be prepared to handle these notifications of changes + * in the environment, and should update its recorded address and/or port as appropriate. + * + * NOTE: There are two unusual aspects of how the DNSServiceNATPortMappingCreate API works, + * which were intentionally designed to help simplify client code: + * + * 1. It's not an error to request a NAT mapping when the machine is not behind a NAT gateway. + * In other NAT mapping APIs, if you request a NAT mapping and the machine is not behind a NAT + * gateway, then the API returns an error code -- it can't get you a NAT mapping if there's no + * NAT gateway. The DNSServiceNATPortMappingCreate API takes a different view. Working out + * whether or not you need a NAT mapping can be tricky and non-obvious, particularly on + * a machine with multiple active network interfaces. Rather than make every client recreate + * this logic for deciding whether a NAT mapping is required, the PortMapping API does that + * work for you. If the client calls the PortMapping API when the machine already has a + * routable public IP address, then instead of complaining about it and giving an error, + * the PortMapping API just invokes your callback, giving the machine's public address + * and your own port number. This means you don't need to write code to work out whether + * your client needs to call the PortMapping API -- just call it anyway, and if it wasn't + * necessary, no harm is done: + * + * - If the machine already has a routable public IP address, then your callback + * will just be invoked giving your own address and port. + * - If a NAT mapping is required and obtained, then your callback will be invoked + * giving you the external address and port. + * - If a NAT mapping is required but not obtained from the local NAT gateway, + * or the machine has no network connectivity, then your callback will be + * invoked giving zero address and port. + * + * 2. In other NAT mapping APIs, if a laptop computer is put to sleep and woken up on a new + * network, it's the client's job to notice this, and work out whether a NAT mapping + * is required on the new network, and make a new NAT mapping request if necessary. + * The DNSServiceNATPortMappingCreate API does this for you, automatically. + * The client just needs to make one call to the PortMapping API, and its callback will + * be invoked any time the mapping state changes. This property complements point (1) above. + * If the client didn't make a NAT mapping request just because it determined that one was + * not required at that particular moment in time, the client would then have to monitor + * for network state changes to determine if a NAT port mapping later became necessary. + * By unconditionally making a NAT mapping request, even when a NAT mapping not to be + * necessary, the PortMapping API will then begin monitoring network state changes on behalf of + * the client, and if a NAT mapping later becomes necessary, it will automatically create a NAT + * mapping and inform the client with a new callback giving the new address and port information. + * + * DNSServiceNATPortMappingReply() parameters: + * + * sdRef: The DNSServiceRef initialized by DNSServiceNATPortMappingCreate(). + * + * flags: Currently unused, reserved for future use. + * + * interfaceIndex: The interface through which the NAT gateway is reached. + * + * errorCode: Will be kDNSServiceErr_NoError on success. + * Will be kDNSServiceErr_DoubleNAT when the NAT gateway is itself behind one or + * more layers of NAT, in which case the other parameters have the defined values. + * For other failures, will indicate the failure that occurred, and the other + * parameters are undefined. + * + * externalAddress: Four byte IPv4 address in network byte order. + * + * protocol: Will be kDNSServiceProtocol_UDP or kDNSServiceProtocol_TCP or both. + * + * internalPort: The port on the local machine that was mapped. + * + * externalPort: The actual external port in the NAT gateway that was mapped. + * This is likely to be different than the requested external port. + * + * ttl: The lifetime of the NAT port mapping created on the gateway. + * This controls how quickly stale mappings will be garbage-collected + * if the client machine crashes, suffers a power failure, is disconnected + * from the network, or suffers some other unfortunate demise which + * causes it to vanish without explicitly removing its NAT port mapping. + * It's possible that the ttl value will differ from the requested ttl value. + * + * context: The context pointer that was passed to the callout. + * + */ + +typedef void (DNSSD_API *DNSServiceNATPortMappingReply) +( + DNSServiceRef sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + DNSServiceErrorType errorCode, + uint32_t externalAddress, /* four byte IPv4 address in network byte order */ + DNSServiceProtocol protocol, + uint16_t internalPort, /* In network byte order */ + uint16_t externalPort, /* In network byte order and may be different than the requested port */ + uint32_t ttl, /* may be different than the requested ttl */ + void *context +); + + +/* DNSServiceNATPortMappingCreate() Parameters: + * + * sdRef: A pointer to an uninitialized DNSServiceRef. If the call succeeds then it + * initializes the DNSServiceRef, returns kDNSServiceErr_NoError, and the nat + * port mapping will last indefinitely until the client terminates the port + * mapping request by passing this DNSServiceRef to DNSServiceRefDeallocate(). + * + * flags: Currently ignored, reserved for future use. + * + * interfaceIndex: The interface on which to create port mappings in a NAT gateway. Passing 0 causes + * the port mapping request to be sent on the primary interface. + * + * protocol: To request a port mapping, pass in kDNSServiceProtocol_UDP, or kDNSServiceProtocol_TCP, + * or (kDNSServiceProtocol_UDP | kDNSServiceProtocol_TCP) to map both. + * The local listening port number must also be specified in the internalPort parameter. + * To just discover the NAT gateway's external IP address, pass zero for protocol, + * internalPort, externalPort and ttl. + * + * internalPort: The port number in network byte order on the local machine which is listening for packets. + * + * externalPort: The requested external port in network byte order in the NAT gateway that you would + * like to map to the internal port. Pass 0 if you don't care which external port is chosen for you. + * + * ttl: The requested renewal period of the NAT port mapping, in seconds. + * If the client machine crashes, suffers a power failure, is disconnected from + * the network, or suffers some other unfortunate demise which causes it to vanish + * unexpectedly without explicitly removing its NAT port mappings, then the NAT gateway + * will garbage-collect old stale NAT port mappings when their lifetime expires. + * Requesting a short TTL causes such orphaned mappings to be garbage-collected + * more promptly, but consumes system resources and network bandwidth with + * frequent renewal packets to keep the mapping from expiring. + * Requesting a long TTL is more efficient on the network, but in the event of the + * client vanishing, stale NAT port mappings will not be garbage-collected as quickly. + * Most clients should pass 0 to use a system-wide default value. + * + * callBack: The function to be called when the port mapping request succeeds or fails asynchronously. + * + * context: An application context pointer which is passed to the callback function + * (may be NULL). + * + * return value: Returns kDNSServiceErr_NoError on success (any subsequent, asynchronous + * errors are delivered to the callback), otherwise returns an error code indicating + * the error that occurred. + * + * If you don't actually want a port mapped, and are just calling the API + * because you want to find out the NAT's external IP address (e.g. for UI + * display) then pass zero for protocol, internalPort, externalPort and ttl. + */ + +DNSServiceErrorType DNSSD_API DNSServiceNATPortMappingCreate +( + DNSServiceRef *sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + DNSServiceProtocol protocol, /* TCP and/or UDP */ + uint16_t internalPort, /* network byte order */ + uint16_t externalPort, /* network byte order */ + uint32_t ttl, /* time to live in seconds */ + DNSServiceNATPortMappingReply callBack, + void *context /* may be NULL */ +); + + +/********************************************************************************************* +* +* General Utility Functions +* +*********************************************************************************************/ + +/* DNSServiceConstructFullName() + * + * Concatenate a three-part domain name (as returned by the above callbacks) into a + * properly-escaped full domain name. Note that callbacks in the above functions ALREADY ESCAPE + * strings where necessary. + * + * Parameters: + * + * fullName: A pointer to a buffer that where the resulting full domain name is to be written. + * The buffer must be kDNSServiceMaxDomainName (1009) bytes in length to + * accommodate the longest legal domain name without buffer overrun. + * + * service: The service name - any dots or backslashes must NOT be escaped. + * May be NULL (to construct a PTR record name, e.g. + * "_ftp._tcp.apple.com."). + * + * regtype: The service type followed by the protocol, separated by a dot + * (e.g. "_ftp._tcp"). + * + * domain: The domain name, e.g. "apple.com.". Literal dots or backslashes, + * if any, must be escaped, e.g. "1st\. Floor.apple.com." + * + * return value: Returns kDNSServiceErr_NoError (0) on success, kDNSServiceErr_BadParam on error. + * + */ + +DNSServiceErrorType DNSSD_API DNSServiceConstructFullName +( + char * const fullName, + const char * const service, /* may be NULL */ + const char * const regtype, + const char * const domain +); + + +/********************************************************************************************* +* +* TXT Record Construction Functions +* +*********************************************************************************************/ + +/* + * A typical calling sequence for TXT record construction is something like: + * + * Client allocates storage for TXTRecord data (e.g. declare buffer on the stack) + * TXTRecordCreate(); + * TXTRecordSetValue(); + * TXTRecordSetValue(); + * TXTRecordSetValue(); + * ... + * DNSServiceRegister( ... TXTRecordGetLength(), TXTRecordGetBytesPtr() ... ); + * TXTRecordDeallocate(); + * Explicitly deallocate storage for TXTRecord data (if not allocated on the stack) + */ + + +/* TXTRecordRef + * + * Opaque internal data type. + * Note: Represents a DNS-SD TXT record. + */ + +typedef union _TXTRecordRef_t { char PrivateData[16]; char *ForceNaturalAlignment; } TXTRecordRef; + + +/* TXTRecordCreate() + * + * Creates a new empty TXTRecordRef referencing the specified storage. + * + * If the buffer parameter is NULL, or the specified storage size is not + * large enough to hold a key subsequently added using TXTRecordSetValue(), + * then additional memory will be added as needed using malloc(). + * + * On some platforms, when memory is low, malloc() may fail. In this + * case, TXTRecordSetValue() will return kDNSServiceErr_NoMemory, and this + * error condition will need to be handled as appropriate by the caller. + * + * You can avoid the need to handle this error condition if you ensure + * that the storage you initially provide is large enough to hold all + * the key/value pairs that are to be added to the record. + * The caller can precompute the exact length required for all of the + * key/value pairs to be added, or simply provide a fixed-sized buffer + * known in advance to be large enough. + * A no-value (key-only) key requires (1 + key length) bytes. + * A key with empty value requires (1 + key length + 1) bytes. + * A key with non-empty value requires (1 + key length + 1 + value length). + * For most applications, DNS-SD TXT records are generally + * less than 100 bytes, so in most cases a simple fixed-sized + * 256-byte buffer will be more than sufficient. + * Recommended size limits for DNS-SD TXT Records are discussed in + * <http://files.dns-sd.org/draft-cheshire-dnsext-dns-sd.txt> + * + * Note: When passing parameters to and from these TXT record APIs, + * the key name does not include the '=' character. The '=' character + * is the separator between the key and value in the on-the-wire + * packet format; it is not part of either the key or the value. + * + * txtRecord: A pointer to an uninitialized TXTRecordRef. + * + * bufferLen: The size of the storage provided in the "buffer" parameter. + * + * buffer: Optional caller-supplied storage used to hold the TXTRecord data. + * This storage must remain valid for as long as + * the TXTRecordRef. + */ + +void DNSSD_API TXTRecordCreate +( + TXTRecordRef *txtRecord, + uint16_t bufferLen, + void *buffer +); + + +/* TXTRecordDeallocate() + * + * Releases any resources allocated in the course of preparing a TXT Record + * using TXTRecordCreate()/TXTRecordSetValue()/TXTRecordRemoveValue(). + * Ownership of the buffer provided in TXTRecordCreate() returns to the client. + * + * txtRecord: A TXTRecordRef initialized by calling TXTRecordCreate(). + * + */ + +void DNSSD_API TXTRecordDeallocate +( + TXTRecordRef *txtRecord +); + + +/* TXTRecordSetValue() + * + * Adds a key (optionally with value) to a TXTRecordRef. If the "key" already + * exists in the TXTRecordRef, then the current value will be replaced with + * the new value. + * Keys may exist in four states with respect to a given TXT record: + * - Absent (key does not appear at all) + * - Present with no value ("key" appears alone) + * - Present with empty value ("key=" appears in TXT record) + * - Present with non-empty value ("key=value" appears in TXT record) + * For more details refer to "Data Syntax for DNS-SD TXT Records" in + * <http://files.dns-sd.org/draft-cheshire-dnsext-dns-sd.txt> + * + * txtRecord: A TXTRecordRef initialized by calling TXTRecordCreate(). + * + * key: A null-terminated string which only contains printable ASCII + * values (0x20-0x7E), excluding '=' (0x3D). Keys should be + * 9 characters or fewer (not counting the terminating null). + * + * valueSize: The size of the value. + * + * value: Any binary value. For values that represent + * textual data, UTF-8 is STRONGLY recommended. + * For values that represent textual data, valueSize + * should NOT include the terminating null (if any) + * at the end of the string. + * If NULL, then "key" will be added with no value. + * If non-NULL but valueSize is zero, then "key=" will be + * added with empty value. + * + * return value: Returns kDNSServiceErr_NoError on success. + * Returns kDNSServiceErr_Invalid if the "key" string contains + * illegal characters. + * Returns kDNSServiceErr_NoMemory if adding this key would + * exceed the available storage. + */ + +DNSServiceErrorType DNSSD_API TXTRecordSetValue +( + TXTRecordRef *txtRecord, + const char *key, + uint8_t valueSize, /* may be zero */ + const void *value /* may be NULL */ +); + + +/* TXTRecordRemoveValue() + * + * Removes a key from a TXTRecordRef. The "key" must be an + * ASCII string which exists in the TXTRecordRef. + * + * txtRecord: A TXTRecordRef initialized by calling TXTRecordCreate(). + * + * key: A key name which exists in the TXTRecordRef. + * + * return value: Returns kDNSServiceErr_NoError on success. + * Returns kDNSServiceErr_NoSuchKey if the "key" does not + * exist in the TXTRecordRef. + */ + +DNSServiceErrorType DNSSD_API TXTRecordRemoveValue +( + TXTRecordRef *txtRecord, + const char *key +); + + +/* TXTRecordGetLength() + * + * Allows you to determine the length of the raw bytes within a TXTRecordRef. + * + * txtRecord: A TXTRecordRef initialized by calling TXTRecordCreate(). + * + * return value: Returns the size of the raw bytes inside a TXTRecordRef + * which you can pass directly to DNSServiceRegister() or + * to DNSServiceUpdateRecord(). + * Returns 0 if the TXTRecordRef is empty. + */ + +uint16_t DNSSD_API TXTRecordGetLength +( + const TXTRecordRef *txtRecord +); + + +/* TXTRecordGetBytesPtr() + * + * Allows you to retrieve a pointer to the raw bytes within a TXTRecordRef. + * + * txtRecord: A TXTRecordRef initialized by calling TXTRecordCreate(). + * + * return value: Returns a pointer to the raw bytes inside the TXTRecordRef + * which you can pass directly to DNSServiceRegister() or + * to DNSServiceUpdateRecord(). + */ + +const void * DNSSD_API TXTRecordGetBytesPtr +( + const TXTRecordRef *txtRecord +); + + +/********************************************************************************************* +* +* TXT Record Parsing Functions +* +*********************************************************************************************/ + +/* + * A typical calling sequence for TXT record parsing is something like: + * + * Receive TXT record data in DNSServiceResolve() callback + * if (TXTRecordContainsKey(txtLen, txtRecord, "key")) then do something + * val1ptr = TXTRecordGetValuePtr(txtLen, txtRecord, "key1", &len1); + * val2ptr = TXTRecordGetValuePtr(txtLen, txtRecord, "key2", &len2); + * ... + * memcpy(myval1, val1ptr, len1); + * memcpy(myval2, val2ptr, len2); + * ... + * return; + * + * If you wish to retain the values after return from the DNSServiceResolve() + * callback, then you need to copy the data to your own storage using memcpy() + * or similar, as shown in the example above. + * + * If for some reason you need to parse a TXT record you built yourself + * using the TXT record construction functions above, then you can do + * that using TXTRecordGetLength and TXTRecordGetBytesPtr calls: + * TXTRecordGetValue(TXTRecordGetLength(x), TXTRecordGetBytesPtr(x), key, &len); + * + * Most applications only fetch keys they know about from a TXT record and + * ignore the rest. + * However, some debugging tools wish to fetch and display all keys. + * To do that, use the TXTRecordGetCount() and TXTRecordGetItemAtIndex() calls. + */ + +/* TXTRecordContainsKey() + * + * Allows you to determine if a given TXT Record contains a specified key. + * + * txtLen: The size of the received TXT Record. + * + * txtRecord: Pointer to the received TXT Record bytes. + * + * key: A null-terminated ASCII string containing the key name. + * + * return value: Returns 1 if the TXT Record contains the specified key. + * Otherwise, it returns 0. + */ + +int DNSSD_API TXTRecordContainsKey +( + uint16_t txtLen, + const void *txtRecord, + const char *key +); + + +/* TXTRecordGetValuePtr() + * + * Allows you to retrieve the value for a given key from a TXT Record. + * + * txtLen: The size of the received TXT Record + * + * txtRecord: Pointer to the received TXT Record bytes. + * + * key: A null-terminated ASCII string containing the key name. + * + * valueLen: On output, will be set to the size of the "value" data. + * + * return value: Returns NULL if the key does not exist in this TXT record, + * or exists with no value (to differentiate between + * these two cases use TXTRecordContainsKey()). + * Returns pointer to location within TXT Record bytes + * if the key exists with empty or non-empty value. + * For empty value, valueLen will be zero. + * For non-empty value, valueLen will be length of value data. + */ + +const void * DNSSD_API TXTRecordGetValuePtr +( + uint16_t txtLen, + const void *txtRecord, + const char *key, + uint8_t *valueLen +); + + +/* TXTRecordGetCount() + * + * Returns the number of keys stored in the TXT Record. The count + * can be used with TXTRecordGetItemAtIndex() to iterate through the keys. + * + * txtLen: The size of the received TXT Record. + * + * txtRecord: Pointer to the received TXT Record bytes. + * + * return value: Returns the total number of keys in the TXT Record. + * + */ + +uint16_t DNSSD_API TXTRecordGetCount +( + uint16_t txtLen, + const void *txtRecord +); + + +/* TXTRecordGetItemAtIndex() + * + * Allows you to retrieve a key name and value pointer, given an index into + * a TXT Record. Legal index values range from zero to TXTRecordGetCount()-1. + * It's also possible to iterate through keys in a TXT record by simply + * calling TXTRecordGetItemAtIndex() repeatedly, beginning with index zero + * and increasing until TXTRecordGetItemAtIndex() returns kDNSServiceErr_Invalid. + * + * On return: + * For keys with no value, *value is set to NULL and *valueLen is zero. + * For keys with empty value, *value is non-NULL and *valueLen is zero. + * For keys with non-empty value, *value is non-NULL and *valueLen is non-zero. + * + * txtLen: The size of the received TXT Record. + * + * txtRecord: Pointer to the received TXT Record bytes. + * + * itemIndex: An index into the TXT Record. + * + * keyBufLen: The size of the string buffer being supplied. + * + * key: A string buffer used to store the key name. + * On return, the buffer contains a null-terminated C string + * giving the key name. DNS-SD TXT keys are usually + * 9 characters or fewer. To hold the maximum possible + * key name, the buffer should be 256 bytes long. + * + * valueLen: On output, will be set to the size of the "value" data. + * + * value: On output, *value is set to point to location within TXT + * Record bytes that holds the value data. + * + * return value: Returns kDNSServiceErr_NoError on success. + * Returns kDNSServiceErr_NoMemory if keyBufLen is too short. + * Returns kDNSServiceErr_Invalid if index is greater than + * TXTRecordGetCount()-1. + */ + +DNSServiceErrorType DNSSD_API TXTRecordGetItemAtIndex +( + uint16_t txtLen, + const void *txtRecord, + uint16_t itemIndex, + uint16_t keyBufLen, + char *key, + uint8_t *valueLen, + const void **value +); + +#if _DNS_SD_LIBDISPATCH +/* + * DNSServiceSetDispatchQueue + * + * Allows you to schedule a DNSServiceRef on a serial dispatch queue for receiving asynchronous + * callbacks. It's the clients responsibility to ensure that the provided dispatch queue is running. + * + * A typical application that uses CFRunLoopRun or dispatch_main on its main thread will + * usually schedule DNSServiceRefs on its main queue (which is always a serial queue) + * using "DNSServiceSetDispatchQueue(sdref, dispatch_get_main_queue());" + * + * If there is any error during the processing of events, the application callback will + * be called with an error code. For shared connections, each subordinate DNSServiceRef + * will get its own error callback. Currently these error callbacks only happen + * if the mDNSResponder daemon is manually terminated or crashes, and the error + * code in this case is kDNSServiceErr_ServiceNotRunning. The application must call + * DNSServiceRefDeallocate to free the DNSServiceRef when it gets such an error code. + * These error callbacks are rare and should not normally happen on customer machines, + * but application code should be written defensively to handle such error callbacks + * gracefully if they occur. + * + * After using DNSServiceSetDispatchQueue on a DNSServiceRef, calling DNSServiceProcessResult + * on the same DNSServiceRef will result in undefined behavior and should be avoided. + * + * Once the application successfully schedules a DNSServiceRef on a serial dispatch queue using + * DNSServiceSetDispatchQueue, it cannot remove the DNSServiceRef from the dispatch queue, or use + * DNSServiceSetDispatchQueue a second time to schedule the DNSServiceRef onto a different serial dispatch + * queue. Once scheduled onto a dispatch queue a DNSServiceRef will deliver events to that queue until + * the application no longer requires that operation and terminates it using DNSServiceRefDeallocate. + * + * service: DNSServiceRef that was allocated and returned to the application, when the + * application calls one of the DNSService API. + * + * queue: dispatch queue where the application callback will be scheduled + * + * return value: Returns kDNSServiceErr_NoError on success. + * Returns kDNSServiceErr_NoMemory if it cannot create a dispatch source + * Returns kDNSServiceErr_BadParam if the service param is invalid or the + * queue param is invalid + */ + +DNSServiceErrorType DNSSD_API DNSServiceSetDispatchQueue +( + DNSServiceRef service, + dispatch_queue_t queue +); +#endif //_DNS_SD_LIBDISPATCH + +#if !defined(_WIN32) +typedef void (DNSSD_API *DNSServiceSleepKeepaliveReply) +( + DNSServiceRef sdRef, + DNSServiceErrorType errorCode, + void *context +); +DNSServiceErrorType DNSSD_API DNSServiceSleepKeepalive +( + DNSServiceRef *sdRef, + DNSServiceFlags flags, + int fd, + unsigned int timeout, + DNSServiceSleepKeepaliveReply callBack, + void *context +); +#endif + +#ifdef APPLE_OSX_mDNSResponder +/* DNSServiceCreateDelegateConnection() + * + * Create a delegate connection to the daemon allowing efficient registration of + * multiple individual records. + * + * Parameters: + * + * sdRef: A pointer to an uninitialized DNSServiceRef. Deallocating + * the reference (via DNSServiceRefDeallocate()) severs the + * connection and deregisters all records registered on this connection. + * + * pid : Process ID of the delegate + * + * uuid: UUID of the delegate + * + * Note that only one of the two arguments (pid or uuid) can be specified. If pid + * is zero, uuid will be assumed to be a valid value; otherwise pid will be used. + * + * return value: Returns kDNSServiceErr_NoError on success, otherwise returns + * an error code indicating the specific failure that occurred (in which + * case the DNSServiceRef is not initialized). kDNSServiceErr_NotAuth is + * returned to indicate that the calling process does not have entitlements + * to use this API. + */ +DNSServiceErrorType DNSSD_API DNSServiceCreateDelegateConnection(DNSServiceRef *sdRef, int32_t pid, uuid_t uuid); +#endif + +#ifdef __APPLE_API_PRIVATE + +#define kDNSServiceCompPrivateDNS "PrivateDNS" +#define kDNSServiceCompMulticastDNS "MulticastDNS" + +#endif //__APPLE_API_PRIVATE + +/* Some C compiler cleverness. We can make the compiler check certain things for us, + * and report errors at compile-time if anything is wrong. The usual way to do this would + * be to use a run-time "if" statement or the conventional run-time "assert" mechanism, but + * then you don't find out what's wrong until you run the software. This way, if the assertion + * condition is false, the array size is negative, and the complier complains immediately. + */ + +struct CompileTimeAssertionChecks_DNS_SD +{ + char assert0[(sizeof(union _TXTRecordRef_t) == 16) ? 1 : -1]; +}; + +#ifdef __cplusplus +} +#endif + +#endif /* _DNS_SD_H */ diff --git a/mDNSResponder/mDNSShared/dnsextd.8 b/mDNSResponder/mDNSShared/dnsextd.8 new file mode 100644 index 00000000..796caaba --- /dev/null +++ b/mDNSResponder/mDNSShared/dnsextd.8 @@ -0,0 +1,69 @@ +.\" -*- tab-width: 4 -*- +.\" +.\" Copyright (c) 2004 Apple Computer, Inc. All Rights Reserved. +.\" +.\" Licensed under the Apache License, Version 2.0 (the "License"); +.\" you may not use this file except in compliance with the License. +.\" You may obtain a copy of the License at +.\" +.\" http://www.apache.org/licenses/LICENSE-2.0 +.\" +.\" Unless required by applicable law or agreed to in writing, software +.\" distributed under the License is distributed on an "AS IS" BASIS, +.\" WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.\" See the License for the specific language governing permissions and +.\" limitations under the License. +.\" +.Dd August 2004 \" Date +.Dt dnsextd 8 \" Document Title +.Os Darwin \" Operating System +.\" +.Sh NAME +.Nm dnsextd +.Nd BIND Extension Daemon \" Name Description for whatis database +.\" +.Sh SYNOPSIS +.Nm +.\" +.Sh DESCRIPTION +.Nm +is a daemon invoked at boot time, running alongside BIND 9, +to implement two EDNS0 extensions to the standard DNS protocol. +.Pp +.Nm +allows clients to perform DNS Updates with an attached lease lifetime, +so that if the client crashes or is disconnected from the network, its +address records will be automatically deleted after the lease expires. +.Pp +.Nm +allows clients to perform long-lived queries. Instead of rapidly polling +the server to discover when information changes, long-lived queries +enable a client to indicate its interest in some set of data, and then +be notified asynchronously by the server whenever any of that data changes. +.Pp +.Nm +has no user-specifiable command-line argument, and users should not run +.Nm +manually. +.\" +.Sh SEE ALSO +.Xr mDNS 1 +.Xr mDNSResponder 8 +.Pp +For information on Dynamic DNS Update, see RFC 2136 +"Dynamic Updates in the Domain Name System (DNS UPDATE)" +.Pp +For information on Dynamic DNS Update Leases, see +.Pa http://files.dns-sd.org/draft-dns-update-leases.txt +.Pp +For information on Long-Lived Queries, see +.Pa http://files.dns-sd.org/draft-dns-llq.txt +.\" +.Sh BUGS +.Nm +bugs are tracked in Apple Radar component "mDNSResponder". +.\" +.Sh HISTORY +The +.Nm +daemon first appeared in Mac OS X 10.4 (Tiger). diff --git a/mDNSResponder/mDNSShared/dnsextd.c b/mDNSResponder/mDNSShared/dnsextd.c new file mode 100644 index 00000000..aa06650a --- /dev/null +++ b/mDNSResponder/mDNSShared/dnsextd.c @@ -0,0 +1,3150 @@ +/* -*- Mode: C; tab-width: 4 -*- + * + * Copyright (c) 2002-2004 Apple Computer, Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#if __APPLE__ +// In Mac OS X 10.5 and later trying to use the daemon function gives a “‘daemon’ is deprecated” +// error, which prevents compilation because we build with "-Werror". +// Since this is supposed to be portable cross-platform code, we don't care that daemon is +// deprecated on Mac OS X 10.5, so we use this preprocessor trick to eliminate the error message. +#define daemon yes_we_know_that_daemon_is_deprecated_in_os_x_10_5_thankyou +#endif + +#include <signal.h> +#include <pthread.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <stdio.h> +#include <syslog.h> +#include <string.h> +#include <sys/time.h> +#include <sys/resource.h> +#include <time.h> +#include <errno.h> + +#if __APPLE__ +#undef daemon +extern int daemon(int, int); +#endif + +// Solaris doesn't have daemon(), so we define it here +#ifdef NOT_HAVE_DAEMON +#include "../mDNSPosix/mDNSUNP.h" // For daemon() +#endif // NOT_HAVE_DAEMON + +#include "dnsextd.h" +#include "../mDNSShared/uds_daemon.h" +#include "../mDNSShared/dnssd_ipc.h" +#include "../mDNSCore/uDNS.h" +#include "../mDNSShared/DebugServices.h" + +// Compatibility workaround +#ifndef AF_LOCAL +#define AF_LOCAL AF_UNIX +#endif + +// +// Constants +// +mDNSexport const char ProgramName[] = "dnsextd"; + +#define LOOPBACK "127.0.0.1" +#if !defined(LISTENQ) +# define LISTENQ 128 // tcp connection backlog +#endif +#define RECV_BUFLEN 9000 +#define LEASETABLE_INIT_NBUCKETS 256 // initial hashtable size (doubles as table fills) +#define EXPIRATION_INTERVAL 300 // check for expired records every 5 minutes +#define SRV_TTL 7200 // TTL For _dns-update SRV records +#define CONFIG_FILE "/etc/dnsextd.conf" +#define TCP_SOCKET_FLAGS kTCPSocketFlags_UseTLS + +// LLQ Lease bounds (seconds) +#define LLQ_MIN_LEASE (15 * 60) +#define LLQ_MAX_LEASE (120 * 60) +#define LLQ_LEASE_FUDGE 60 + +// LLQ SOA poll interval (microseconds) +#define LLQ_MONITOR_ERR_INTERVAL (60 * 1000000) +#define LLQ_MONITOR_INTERVAL 250000 +#ifdef SIGINFO +#define INFO_SIGNAL SIGINFO +#else +#define INFO_SIGNAL SIGUSR1 +#endif + +#define SAME_INADDR(x,y) (*((mDNSu32 *)&x) == *((mDNSu32 *)&y)) + +// +// Data Structures +// Structs/fields that must be locked for thread safety are explicitly commented +// + +// args passed to UDP request handler thread as void* + +typedef struct +{ + PktMsg pkt; + struct sockaddr_in cliaddr; + DaemonInfo *d; + int sd; +} UDPContext; + +// args passed to TCP request handler thread as void* +typedef struct +{ + PktMsg pkt; + struct sockaddr_in cliaddr; + TCPSocket *sock; // socket connected to client + DaemonInfo *d; +} TCPContext; + +// args passed to UpdateAnswerList thread as void* +typedef struct +{ + DaemonInfo *d; + AnswerListElem *a; +} UpdateAnswerListArgs; + +// +// Global Variables +// + +// booleans to determine runtime output +// read-only after initialization (no mutex protection) +static mDNSBool foreground = 0; +static mDNSBool verbose = 0; + +// globals set via signal handler (accessed exclusively by main select loop and signal handler) +static mDNSBool terminate = 0; +static mDNSBool dumptable = 0; +static mDNSBool hangup = 0; + +// global for config file location +static char * cfgfile = NULL; + +// +// Logging Routines +// Log messages are delivered to syslog unless -f option specified +// + +// common message logging subroutine +mDNSlocal void PrintLog(const char *buffer) +{ + if (foreground) + { + fprintf(stderr,"%s\n", buffer); + fflush(stderr); + } + else + { + openlog("dnsextd", LOG_CONS, LOG_DAEMON); + syslog(LOG_ERR, "%s", buffer); + closelog(); + } +} + +// Verbose Logging (conditional on -v option) +mDNSlocal void VLog(const char *format, ...) +{ + char buffer[512]; + va_list ptr; + + if (!verbose) return; + va_start(ptr,format); + buffer[mDNS_vsnprintf((char *)buffer, sizeof(buffer), format, ptr)] = 0; + va_end(ptr); + PrintLog(buffer); +} + +// Unconditional Logging +mDNSlocal void Log(const char *format, ...) +{ + char buffer[512]; + va_list ptr; + + va_start(ptr,format); + buffer[mDNS_vsnprintf((char *)buffer, sizeof(buffer), format, ptr)] = 0; + va_end(ptr); + PrintLog(buffer); +} + +// Error Logging +// prints message "dnsextd <function>: <operation> - <error message>" +// must be compiled w/ -D_REENTRANT for thread-safe errno usage +mDNSlocal void LogErr(const char *fn, const char *operation) +{ + char buf[512], errbuf[256]; + strerror_r(errno, errbuf, sizeof(errbuf)); + snprintf(buf, sizeof(buf), "%s: %s - %s", fn, operation, errbuf); + PrintLog(buf); +} + +// +// Networking Utility Routines +// + +// Convert DNS Message Header from Network to Host byte order +mDNSlocal void HdrNToH(PktMsg *pkt) +{ + // Read the integer parts which are in IETF byte-order (MSB first, LSB second) + mDNSu8 *ptr = (mDNSu8 *)&pkt->msg.h.numQuestions; + pkt->msg.h.numQuestions = (mDNSu16)((mDNSu16)ptr[0] << 8 | ptr[1]); + pkt->msg.h.numAnswers = (mDNSu16)((mDNSu16)ptr[2] << 8 | ptr[3]); + pkt->msg.h.numAuthorities = (mDNSu16)((mDNSu16)ptr[4] << 8 | ptr[5]); + pkt->msg.h.numAdditionals = (mDNSu16)((mDNSu16)ptr[6] << 8 | ptr[7]); +} + +// Convert DNS Message Header from Host to Network byte order +mDNSlocal void HdrHToN(PktMsg *pkt) +{ + mDNSu16 numQuestions = pkt->msg.h.numQuestions; + mDNSu16 numAnswers = pkt->msg.h.numAnswers; + mDNSu16 numAuthorities = pkt->msg.h.numAuthorities; + mDNSu16 numAdditionals = pkt->msg.h.numAdditionals; + mDNSu8 *ptr = (mDNSu8 *)&pkt->msg.h.numQuestions; + + // Put all the integer values in IETF byte-order (MSB first, LSB second) + *ptr++ = (mDNSu8)(numQuestions >> 8); + *ptr++ = (mDNSu8)(numQuestions & 0xFF); + *ptr++ = (mDNSu8)(numAnswers >> 8); + *ptr++ = (mDNSu8)(numAnswers & 0xFF); + *ptr++ = (mDNSu8)(numAuthorities >> 8); + *ptr++ = (mDNSu8)(numAuthorities & 0xFF); + *ptr++ = (mDNSu8)(numAdditionals >> 8); + *ptr++ = (mDNSu8)(numAdditionals & 0xFF); +} + + +// Add socket to event loop + +mDNSlocal mStatus AddSourceToEventLoop( DaemonInfo * self, TCPSocket *sock, EventCallback callback, void *context ) +{ + EventSource * newSource; + mStatus err = mStatus_NoError; + + if ( self->eventSources.LinkOffset == 0 ) + { + InitLinkedList( &self->eventSources, offsetof( EventSource, next)); + } + + newSource = ( EventSource*) malloc( sizeof *newSource ); + if ( newSource == NULL ) + { + err = mStatus_NoMemoryErr; + goto exit; + } + + newSource->callback = callback; + newSource->context = context; + newSource->sock = sock; + newSource->fd = mDNSPlatformTCPGetFD( sock ); + + AddToTail( &self->eventSources, newSource ); + +exit: + + return err; +} + + +// Remove socket from event loop + +mDNSlocal mStatus RemoveSourceFromEventLoop( DaemonInfo * self, TCPSocket *sock ) +{ + EventSource * source; + mStatus err; + + for ( source = ( EventSource* ) self->eventSources.Head; source; source = source->next ) + { + if ( source->sock == sock ) + { + RemoveFromList( &self->eventSources, source ); + + free( source ); + err = mStatus_NoError; + goto exit; + } + } + + err = mStatus_NoSuchNameErr; + +exit: + + return err; +} + +// create a socket connected to nameserver +// caller terminates connection via close() +mDNSlocal TCPSocket *ConnectToServer(DaemonInfo *d) +{ + int ntries = 0, retry = 0; + + while (1) + { + mDNSIPPort port = zeroIPPort; + int fd; + + TCPSocket *sock = mDNSPlatformTCPSocket( NULL, 0, &port, mDNSfalse ); + if ( !sock ) { LogErr("ConnectToServer", "socket"); return NULL; } + fd = mDNSPlatformTCPGetFD( sock ); + if (!connect( fd, (struct sockaddr *)&d->ns_addr, sizeof(d->ns_addr))) return sock; + mDNSPlatformTCPCloseConnection( sock ); + if (++ntries < 10) + { + LogErr("ConnectToServer", "connect"); + Log("ConnectToServer - retrying connection"); + if (!retry) retry = 500000 + random() % 500000; + usleep(retry); + retry *= 2; + } + else { Log("ConnectToServer - %d failed attempts. Aborting.", ntries); return NULL; } + } +} + +// send an entire block of data over a connected socket +mDNSlocal int MySend(TCPSocket *sock, const void *msg, int len) +{ + int selectval, n, nsent = 0; + fd_set wset; + struct timeval timeout = { 3, 0 }; // until we remove all calls from main thread, keep timeout short + + while (nsent < len) + { + int fd; + + FD_ZERO(&wset); + + fd = mDNSPlatformTCPGetFD( sock ); + + FD_SET( fd, &wset ); + selectval = select( fd+1, NULL, &wset, NULL, &timeout); + if (selectval < 0) { LogErr("MySend", "select"); return -1; } + if (!selectval || !FD_ISSET(fd, &wset)) { Log("MySend - timeout"); return -1; } + + n = mDNSPlatformWriteTCP( sock, ( char* ) msg + nsent, len - nsent); + + if (n < 0) { LogErr("MySend", "send"); return -1; } + nsent += n; + } + return 0; +} + +// Transmit a DNS message, prefixed by its length, over TCP, blocking if necessary +mDNSlocal int SendPacket(TCPSocket *sock, PktMsg *pkt) +{ + // send the lenth, in network byte order + mDNSu16 len = htons((mDNSu16)pkt->len); + if (MySend(sock, &len, sizeof(len)) < 0) return -1; + + // send the message + VLog("SendPacket Q:%d A:%d A:%d A:%d ", + ntohs(pkt->msg.h.numQuestions), + ntohs(pkt->msg.h.numAnswers), + ntohs(pkt->msg.h.numAuthorities), + ntohs(pkt->msg.h.numAdditionals)); + return MySend(sock, &pkt->msg, pkt->len); +} + +// Receive len bytes, waiting until we have all of them. +// Returns number of bytes read (which should always be the number asked for). +static int my_recv(TCPSocket *sock, void *const buf, const int len, mDNSBool * closed) +{ + // Don't use "MSG_WAITALL"; it returns "Invalid argument" on some Linux versions; + // use an explicit while() loop instead. + // Also, don't try to do '+=' arithmetic on the original "void *" pointer -- + // arithmetic on "void *" pointers is compiler-dependent. + + fd_set rset; + struct timeval timeout = { 3, 0 }; // until we remove all calls from main thread, keep timeout short + int selectval, remaining = len; + char *ptr = (char *)buf; + ssize_t num_read; + + while (remaining) + { + int fd; + + fd = mDNSPlatformTCPGetFD( sock ); + + FD_ZERO(&rset); + FD_SET(fd, &rset); + selectval = select(fd+1, &rset, NULL, NULL, &timeout); + if (selectval < 0) { LogErr("my_recv", "select"); return -1; } + if (!selectval || !FD_ISSET(fd, &rset)) + { + Log("my_recv - timeout"); + return -1; + } + + num_read = mDNSPlatformReadTCP( sock, ptr, remaining, closed ); + + if (((num_read == 0) && *closed) || (num_read < 0) || (num_read > remaining)) return -1; + if (num_read == 0) return 0; + ptr += num_read; + remaining -= num_read; + } + return(len); +} + +// Return a DNS Message read off of a TCP socket, or NULL on failure +// If storage is non-null, result is placed in that buffer. Otherwise, +// returned value is allocated with Malloc, and contains sufficient extra +// storage for a Lease OPT RR + +mDNSlocal PktMsg* +RecvPacket +( + TCPSocket * sock, + PktMsg * storage, + mDNSBool * closed +) +{ + int nread; + int allocsize; + mDNSu16 msglen = 0; + PktMsg * pkt = NULL; + unsigned int srclen; + int fd; + mStatus err = 0; + + fd = mDNSPlatformTCPGetFD( sock ); + + nread = my_recv( sock, &msglen, sizeof( msglen ), closed ); + + require_action_quiet( nread != -1, exit, err = mStatus_UnknownErr ); + require_action_quiet( nread > 0, exit, err = mStatus_NoError ); + + msglen = ntohs( msglen ); + require_action_quiet( nread == sizeof( msglen ), exit, err = mStatus_UnknownErr; Log( "Could not read length field of message") ); + + if ( storage ) + { + require_action_quiet( msglen <= sizeof( storage->msg ), exit, err = mStatus_UnknownErr; Log( "RecvPacket: provided buffer too small." ) ); + pkt = storage; + } + else + { + // buffer extra space to add an OPT RR + + if ( msglen > sizeof(DNSMessage)) + { + allocsize = sizeof(PktMsg) - sizeof(DNSMessage) + msglen; + } + else + { + allocsize = sizeof(PktMsg); + } + + pkt = malloc(allocsize); + require_action_quiet( pkt, exit, err = mStatus_NoMemoryErr; LogErr( "RecvPacket", "malloc" ) ); + mDNSPlatformMemZero( pkt, sizeof( *pkt ) ); + } + + pkt->len = msglen; + srclen = sizeof(pkt->src); + + if ( getpeername( fd, ( struct sockaddr* ) &pkt->src, &srclen ) || ( srclen != sizeof( pkt->src ) ) ) + { + LogErr("RecvPacket", "getpeername"); + mDNSPlatformMemZero(&pkt->src, sizeof(pkt->src)); + } + + nread = my_recv(sock, &pkt->msg, msglen, closed ); + require_action_quiet( nread >= 0, exit, err = mStatus_UnknownErr ; LogErr( "RecvPacket", "recv" ) ); + require_action_quiet( nread == msglen, exit, err = mStatus_UnknownErr ; Log( "Could not read entire message" ) ); + require_action_quiet( pkt->len >= sizeof( DNSMessageHeader ), exit, err = mStatus_UnknownErr ; Log( "RecvPacket: Message too short (%d bytes)", pkt->len ) ); + +exit: + + if ( err && pkt ) + { + if ( pkt != storage ) + { + free(pkt); + } + + pkt = NULL; + } + + return pkt; +} + + +mDNSlocal DNSZone* +FindZone +( + DaemonInfo * self, + domainname * name +) +{ + DNSZone * zone; + + for ( zone = self->zones; zone; zone = zone->next ) + { + if ( SameDomainName( &zone->name, name ) ) + { + break; + } + } + + return zone; +} + + +mDNSlocal mDNSBool +ZoneHandlesName +( + const domainname * zname, + const domainname * dname +) +{ + mDNSu16 i = DomainNameLength( zname ); + mDNSu16 j = DomainNameLength( dname ); + + if ( ( i == ( MAX_DOMAIN_NAME + 1 ) ) || ( j == ( MAX_DOMAIN_NAME + 1 ) ) || ( i > j ) || ( memcmp( zname->c, dname->c + ( j - i ), i ) != 0 ) ) + { + return mDNSfalse; + } + + return mDNStrue; +} + + +mDNSlocal mDNSBool IsQuery( PktMsg * pkt ) +{ + return ( pkt->msg.h.flags.b[0] & kDNSFlag0_QROP_Mask ) == (mDNSu8) ( kDNSFlag0_QR_Query | kDNSFlag0_OP_StdQuery ); +} + + +mDNSlocal mDNSBool IsUpdate( PktMsg * pkt ) +{ + return ( pkt->msg.h.flags.b[0] & kDNSFlag0_QROP_Mask ) == (mDNSu8) ( kDNSFlag0_OP_Update ); +} + + +mDNSlocal mDNSBool IsNotify(PktMsg *pkt) +{ + return ( pkt->msg.h.flags.b[0] & kDNSFlag0_QROP_Mask ) == ( mDNSu8) ( kDNSFlag0_OP_Notify ); +} + + +mDNSlocal mDNSBool IsLLQRequest(PktMsg *pkt) +{ + const mDNSu8 *ptr = NULL, *end = (mDNSu8 *)&pkt->msg + pkt->len; + LargeCacheRecord lcr; + int i; + mDNSBool result = mDNSfalse; + + HdrNToH(pkt); + if ((mDNSu8)(pkt->msg.h.flags.b[0] & kDNSFlag0_QROP_Mask) != (mDNSu8)(kDNSFlag0_QR_Query | kDNSFlag0_OP_StdQuery)) goto end; + + if (!pkt->msg.h.numAdditionals) goto end; + ptr = LocateAdditionals(&pkt->msg, end); + if (!ptr) goto end; + + // find last Additional info. + for (i = 0; i < pkt->msg.h.numAdditionals; i++) + { + ptr = GetLargeResourceRecord(NULL, &pkt->msg, ptr, end, 0, kDNSRecordTypePacketAdd, &lcr); + if (!ptr) { Log("Unable to read additional record"); goto end; } + } + + if ( lcr.r.resrec.rrtype == kDNSType_OPT && lcr.r.resrec.rdlength >= DNSOpt_LLQData_Space && lcr.r.resrec.rdata->u.opt[0].opt == kDNSOpt_LLQ ) + { + result = mDNStrue; + } + +end: + HdrHToN(pkt); + return result; +} + +// !!!KRS implement properly +mDNSlocal mDNSBool IsLLQAck(PktMsg *pkt) +{ + if ((pkt->msg.h.flags.b[0] & kDNSFlag0_QROP_Mask ) == (mDNSu8) ( kDNSFlag0_QR_Response | kDNSFlag0_OP_StdQuery ) && + pkt->msg.h.numQuestions && !pkt->msg.h.numAnswers && !pkt->msg.h.numAuthorities) return mDNStrue; + return mDNSfalse; +} + + +mDNSlocal mDNSBool +IsPublicSRV +( + DaemonInfo * self, + DNSQuestion * q +) +{ + DNameListElem * elem; + mDNSBool ret = mDNSfalse; + int i = ( int ) DomainNameLength( &q->qname ) - 1; + + for ( elem = self->public_names; elem; elem = elem->next ) + { + int j = ( int ) DomainNameLength( &elem->name ) - 1; + + if ( i > j ) + { + for ( ; i >= 0; i--, j-- ) + { + if ( q->qname.c[ i ] != elem->name.c[ j ] ) + { + ret = mDNStrue; + goto exit; + } + } + } + } + +exit: + + return ret; +} + + +mDNSlocal void +SetZone +( + DaemonInfo * self, + PktMsg * pkt +) +{ + domainname zname; + mDNSu8 QR_OP; + const mDNSu8 * ptr = pkt->msg.data; + mDNSBool exception = mDNSfalse; + + // Initialize + + pkt->zone = NULL; + pkt->isZonePublic = mDNStrue; + zname.c[0] = '\0'; + + // Figure out what type of packet this is + + QR_OP = ( mDNSu8 ) ( pkt->msg.h.flags.b[0] & kDNSFlag0_QROP_Mask ); + + if ( IsQuery( pkt ) ) + { + DNSQuestion question; + + // It's a query + + ptr = getQuestion( &pkt->msg, ptr, ( ( mDNSu8* ) &pkt->msg ) + pkt->len, NULL, &question ); + + AppendDomainName( &zname, &question.qname ); + + exception = ( ( question.qtype == kDNSType_SOA ) || ( question.qtype == kDNSType_NS ) || ( ( question.qtype == kDNSType_SRV ) && IsPublicSRV( self, &question ) ) ); + } + else if ( IsUpdate( pkt ) ) + { + DNSQuestion question; + + // It's an update. The format of the zone section is the same as the format for the question section + // according to RFC 2136, so we'll just treat this as a question so we can get at the zone. + + ptr = getQuestion( &pkt->msg, ptr, ( ( mDNSu8* ) &pkt->msg ) + pkt->len, NULL, &question ); + + AppendDomainName( &zname, &question.qname ); + + exception = mDNSfalse; + } + + if ( zname.c[0] != '\0' ) + { + // Find the right zone + + for ( pkt->zone = self->zones; pkt->zone; pkt->zone = pkt->zone->next ) + { + if ( ZoneHandlesName( &pkt->zone->name, &zname ) ) + { + VLog( "found correct zone %##s for query", pkt->zone->name.c ); + + pkt->isZonePublic = ( ( pkt->zone->type == kDNSZonePublic ) || exception ); + + VLog( "zone %##s is %s", pkt->zone->name.c, ( pkt->isZonePublic ) ? "public" : "private" ); + + break; + } + } + } +} + + +mDNSlocal int +UDPServerTransaction(const DaemonInfo *d, const PktMsg *request, PktMsg *reply, mDNSBool *trunc) +{ + fd_set rset; + struct timeval timeout = { 3, 0 }; // until we remove all calls from main thread, keep timeout short + int sd; + int res; + mStatus err = mStatus_NoError; + + // Initialize + + *trunc = mDNSfalse; + + // Create a socket + + sd = socket( AF_INET, SOCK_DGRAM, 0 ); + require_action( sd >= 0, exit, err = mStatus_UnknownErr; LogErr( "UDPServerTransaction", "socket" ) ); + + // Send the packet to the nameserver + + VLog("UDPServerTransaction Q:%d A:%d A:%d A:%d ", + ntohs(request->msg.h.numQuestions), + ntohs(request->msg.h.numAnswers), + ntohs(request->msg.h.numAuthorities), + ntohs(request->msg.h.numAdditionals)); + res = sendto( sd, (char *)&request->msg, request->len, 0, ( struct sockaddr* ) &d->ns_addr, sizeof( d->ns_addr ) ); + require_action( res == (int) request->len, exit, err = mStatus_UnknownErr; LogErr( "UDPServerTransaction", "sendto" ) ); + + // Wait for reply + + FD_ZERO( &rset ); + FD_SET( sd, &rset ); + res = select( sd + 1, &rset, NULL, NULL, &timeout ); + require_action( res >= 0, exit, err = mStatus_UnknownErr; LogErr( "UDPServerTransaction", "select" ) ); + require_action( ( res > 0 ) && FD_ISSET( sd, &rset ), exit, err = mStatus_UnknownErr; Log( "UDPServerTransaction - timeout" ) ); + + // Receive reply + + reply->len = recvfrom( sd, &reply->msg, sizeof(reply->msg), 0, NULL, NULL ); + require_action( ( ( int ) reply->len ) >= 0, exit, err = mStatus_UnknownErr; LogErr( "UDPServerTransaction", "recvfrom" ) ); + require_action( reply->len >= sizeof( DNSMessageHeader ), exit, err = mStatus_UnknownErr; Log( "UDPServerTransaction - Message too short (%d bytes)", reply->len ) ); + + // Check for truncation bit + + if ( reply->msg.h.flags.b[0] & kDNSFlag0_TC ) + { + *trunc = mDNStrue; + } + +exit: + + if ( sd >= 0 ) + { + close( sd ); + } + + return err; +} + +// +// Dynamic Update Utility Routines +// + +// check if a request and server response complete a successful dynamic update +mDNSlocal mDNSBool SuccessfulUpdateTransaction(PktMsg *request, PktMsg *reply) +{ + char buf[32]; + char *vlogmsg = NULL; + + // check messages + if (!request || !reply) { vlogmsg = "NULL message"; goto failure; } + if (request->len < sizeof(DNSMessageHeader) || reply->len < sizeof(DNSMessageHeader)) { vlogmsg = "Malformatted message"; goto failure; } + + // check request operation + if ((request->msg.h.flags.b[0] & kDNSFlag0_QROP_Mask) != (request->msg.h.flags.b[0] & kDNSFlag0_QROP_Mask)) + { vlogmsg = "Request opcode not an update"; goto failure; } + + // check result + if ((reply->msg.h.flags.b[1] & kDNSFlag1_RC_Mask)) { vlogmsg = "Reply contains non-zero rcode"; goto failure; } + if ((reply->msg.h.flags.b[0] & kDNSFlag0_QROP_Mask) != (kDNSFlag0_OP_Update | kDNSFlag0_QR_Response)) + { vlogmsg = "Reply opcode not an update response"; goto failure; } + + VLog("Successful update from %s", inet_ntop(AF_INET, &request->src.sin_addr, buf, 32)); + return mDNStrue; + +failure: + VLog("Request %s: %s", inet_ntop(AF_INET, &request->src.sin_addr, buf, 32), vlogmsg); + return mDNSfalse; +} + +// Allocate an appropriately sized CacheRecord and copy data from original. +// Name pointer in CacheRecord object is set to point to the name specified +// +mDNSlocal CacheRecord *CopyCacheRecord(const CacheRecord *orig, domainname *name) +{ + CacheRecord *cr; + size_t size = sizeof(*cr); + if (orig->resrec.rdlength > InlineCacheRDSize) size += orig->resrec.rdlength - InlineCacheRDSize; + cr = malloc(size); + if (!cr) { LogErr("CopyCacheRecord", "malloc"); return NULL; } + memcpy(cr, orig, size); + cr->resrec.rdata = (RData*)&cr->smallrdatastorage; + cr->resrec.name = name; + + return cr; +} + + +// +// Lease Hashtable Utility Routines +// + +// double hash table size +// caller must lock table prior to invocation +mDNSlocal void RehashTable(DaemonInfo *d) +{ + RRTableElem *ptr, *tmp, **new; + int i, bucket, newnbuckets = d->nbuckets * 2; + + VLog("Rehashing lease table (new size %d buckets)", newnbuckets); + new = malloc(sizeof(RRTableElem *) * newnbuckets); + if (!new) { LogErr("RehashTable", "malloc"); return; } + mDNSPlatformMemZero(new, newnbuckets * sizeof(RRTableElem *)); + + for (i = 0; i < d->nbuckets; i++) + { + ptr = d->table[i]; + while (ptr) + { + bucket = ptr->rr.resrec.namehash % newnbuckets; + tmp = ptr; + ptr = ptr->next; + tmp->next = new[bucket]; + new[bucket] = tmp; + } + } + d->nbuckets = newnbuckets; + free(d->table); + d->table = new; +} + +// print entire contents of hashtable, invoked via SIGINFO +mDNSlocal void PrintLeaseTable(DaemonInfo *d) +{ + int i; + RRTableElem *ptr; + char rrbuf[MaxMsg], addrbuf[16]; + struct timeval now; + int hr, min, sec; + + if (gettimeofday(&now, NULL)) { LogErr("PrintTable", "gettimeofday"); return; } + if (pthread_mutex_lock(&d->tablelock)) { LogErr("PrintTable", "pthread_mutex_lock"); return; } + + Log("Dumping Lease Table Contents (table contains %d resource records)", d->nelems); + for (i = 0; i < d->nbuckets; i++) + { + for (ptr = d->table[i]; ptr; ptr = ptr->next) + { + hr = ((ptr->expire - now.tv_sec) / 60) / 60; + min = ((ptr->expire - now.tv_sec) / 60) % 60; + sec = (ptr->expire - now.tv_sec) % 60; + Log("Update from %s, Expires in %d:%d:%d\n\t%s", inet_ntop(AF_INET, &ptr->cli.sin_addr, addrbuf, 16), hr, min, sec, + GetRRDisplayString_rdb(&ptr->rr.resrec, &ptr->rr.resrec.rdata->u, rrbuf)); + } + } + pthread_mutex_unlock(&d->tablelock); +} + +// +// Startup SRV Registration Routines +// Register _dns-update._udp/_tcp.<zone> SRV records indicating the port on which +// the daemon accepts requests +// + +// delete all RRS of a given name/type +mDNSlocal mDNSu8 *putRRSetDeletion(DNSMessage *msg, mDNSu8 *ptr, mDNSu8 *limit, ResourceRecord *rr) +{ + ptr = putDomainNameAsLabels(msg, ptr, limit, rr->name); + if (!ptr || ptr + 10 >= limit) return NULL; // out of space + ptr[0] = (mDNSu8)(rr->rrtype >> 8); + ptr[1] = (mDNSu8)(rr->rrtype & 0xFF); + ptr[2] = (mDNSu8)((mDNSu16)kDNSQClass_ANY >> 8); + ptr[3] = (mDNSu8)((mDNSu16)kDNSQClass_ANY & 0xFF); + mDNSPlatformMemZero(ptr+4, sizeof(rr->rroriginalttl) + sizeof(rr->rdlength)); // zero ttl/rdata + msg->h.mDNS_numUpdates++; + return ptr + 10; +} + +mDNSlocal mDNSu8 *PutUpdateSRV(DaemonInfo *d, DNSZone * zone, PktMsg *pkt, mDNSu8 *ptr, char *regtype, mDNSIPPort port, mDNSBool registration) +{ + AuthRecord rr; + char hostname[1024], buf[MaxMsg]; + mDNSu8 *end = (mDNSu8 *)&pkt->msg + sizeof(DNSMessage); + + ( void ) d; + + mDNS_SetupResourceRecord(&rr, NULL, 0, kDNSType_SRV, SRV_TTL, kDNSRecordTypeUnique, AuthRecordAny, NULL, NULL); + rr.resrec.rrclass = kDNSClass_IN; + rr.resrec.rdata->u.srv.priority = 0; + rr.resrec.rdata->u.srv.weight = 0; + rr.resrec.rdata->u.srv.port = port; + if (gethostname(hostname, 1024) < 0 || !MakeDomainNameFromDNSNameString(&rr.resrec.rdata->u.srv.target, hostname)) + rr.resrec.rdata->u.srv.target.c[0] = '\0'; + + MakeDomainNameFromDNSNameString(&rr.namestorage, regtype); + AppendDomainName(&rr.namestorage, &zone->name); + VLog("%s %s", registration ? "Registering SRV record" : "Deleting existing RRSet", + GetRRDisplayString_rdb(&rr.resrec, &rr.resrec.rdata->u, buf)); + if (registration) ptr = PutResourceRecord(&pkt->msg, ptr, &pkt->msg.h.mDNS_numUpdates, &rr.resrec); + else ptr = putRRSetDeletion(&pkt->msg, ptr, end, &rr.resrec); + return ptr; +} + + +// perform dynamic update. +// specify deletion by passing false for the register parameter, otherwise register the records. +mDNSlocal int UpdateSRV(DaemonInfo *d, mDNSBool registration) +{ + TCPSocket *sock = NULL; + DNSZone * zone; + int err = mStatus_NoError; + + sock = ConnectToServer( d ); + require_action( sock, exit, err = mStatus_UnknownErr; Log( "UpdateSRV: ConnectToServer failed" ) ); + + for ( zone = d->zones; zone; zone = zone->next ) + { + PktMsg pkt; + mDNSu8 *ptr = pkt.msg.data; + mDNSu8 *end = (mDNSu8 *)&pkt.msg + sizeof(DNSMessage); + PktMsg *reply = NULL; + mDNSBool closed; + mDNSBool ok; + + // Initialize message + InitializeDNSMessage(&pkt.msg.h, zeroID, UpdateReqFlags); + pkt.src.sin_addr.s_addr = zerov4Addr.NotAnInteger; // address field set solely for verbose logging in subroutines + pkt.src.sin_family = AF_INET; + + // format message body + ptr = putZone(&pkt.msg, ptr, end, &zone->name, mDNSOpaque16fromIntVal(kDNSClass_IN)); + require_action( ptr, exit, err = mStatus_UnknownErr; Log("UpdateSRV: Error constructing lease expiration update" ) ); + + if ( zone->type == kDNSZonePrivate ) + { + ptr = PutUpdateSRV(d, zone, &pkt, ptr, "_dns-update-tls._tcp.", d->private_port, registration); + require_action( ptr, exit, err = mStatus_UnknownErr; Log("UpdateSRV: Error constructing lease expiration update" ) ); + ptr = PutUpdateSRV(d, zone, &pkt, ptr, "_dns-query-tls._tcp.", d->private_port, registration); + require_action( ptr, exit, err = mStatus_UnknownErr; Log("UpdateSRV: Error constructing lease expiration update" ) ); + ptr = PutUpdateSRV(d, zone, &pkt, ptr, "_dns-llq-tls._tcp.", d->private_port, registration); + require_action( ptr, exit, err = mStatus_UnknownErr; Log("UpdateSRV: Error constructing lease expiration update" ) ); + + if ( !registration ) + { + ptr = PutUpdateSRV(d, zone, &pkt, ptr, "_dns-update._udp.", d->llq_port, registration); + require_action( ptr, exit, err = mStatus_UnknownErr; Log("UpdateSRV: Error constructing lease expiration update" ) ); + ptr = PutUpdateSRV(d, zone, &pkt, ptr, "_dns-llq._udp.", d->llq_port, registration); + require_action( ptr, exit, err = mStatus_UnknownErr; Log("UpdateSRV: Error constructing lease expiration update" ) ); + } + } + else + { + if ( !registration ) + { + ptr = PutUpdateSRV(d, zone, &pkt, ptr, "_dns-update-tls.", d->private_port, registration); + require_action( ptr, exit, err = mStatus_UnknownErr; Log("UpdateSRV: Error constructing lease expiration update" ) ); + ptr = PutUpdateSRV(d, zone, &pkt, ptr, "_dns-query-tls.", d->private_port, registration); + require_action( ptr, exit, err = mStatus_UnknownErr; Log("UpdateSRV: Error constructing lease expiration update" ) ); + ptr = PutUpdateSRV(d, zone, &pkt, ptr, "_dns-llq-tls.", d->private_port, registration); + require_action( ptr, exit, err = mStatus_UnknownErr; Log("UpdateSRV: Error constructing lease expiration update" ) ); + } + + ptr = PutUpdateSRV(d, zone, &pkt, ptr, "_dns-update._udp.", d->llq_port, registration); + require_action( ptr, exit, err = mStatus_UnknownErr; Log("UpdateSRV: Error constructing lease expiration update" ) ); + ptr = PutUpdateSRV(d, zone, &pkt, ptr, "_dns-llq._udp.", d->llq_port, registration); + require_action( ptr, exit, err = mStatus_UnknownErr; Log("UpdateSRV: Error constructing lease expiration update" ) ); + } + + HdrHToN(&pkt); + + if ( zone->updateKeys ) + { + DNSDigest_SignMessage( &pkt.msg, &ptr, zone->updateKeys, 0 ); + require_action( ptr, exit, Log("UpdateSRV: Error constructing lease expiration update" ) ); + } + + pkt.len = ptr - (mDNSu8 *)&pkt.msg; + + // send message, receive reply + + err = SendPacket( sock, &pkt ); + require_action( !err, exit, Log( "UpdateSRV: SendPacket failed" ) ); + + reply = RecvPacket( sock, NULL, &closed ); + require_action( reply, exit, err = mStatus_UnknownErr; Log( "UpdateSRV: RecvPacket returned NULL" ) ); + + ok = SuccessfulUpdateTransaction( &pkt, reply ); + + if ( !ok ) + { + Log("SRV record registration failed with rcode %d", reply->msg.h.flags.b[1] & kDNSFlag1_RC_Mask); + } + + free( reply ); + } + +exit: + + if ( sock ) + { + mDNSPlatformTCPCloseConnection( sock ); + } + + return err; +} + +// wrapper routines/macros +#define ClearUpdateSRV(d) UpdateSRV(d, 0) + +// clear any existing records prior to registration +mDNSlocal int SetUpdateSRV(DaemonInfo *d) +{ + int err; + + err = ClearUpdateSRV(d); // clear any existing record + if (!err) err = UpdateSRV(d, 1); + return err; +} + +// +// Argument Parsing and Configuration +// + +mDNSlocal void PrintUsage(void) +{ + fprintf(stderr, "Usage: dnsextd [-f <config file>] [-vhd] ...\n" + "Use \"dnsextd -h\" for help\n"); +} + +mDNSlocal void PrintHelp(void) +{ + fprintf(stderr, "\n\n"); + PrintUsage(); + + fprintf(stderr, + "dnsextd is a daemon that implements DNS extensions supporting Dynamic DNS Update Leases\n" + "and Long Lived Queries, used in Wide-Area DNS Service Discovery, on behalf of name servers\n" + "that do not natively support these extensions. (See dns-sd.org for more info on DNS Service\n" + "Discovery, Update Leases, and Long Lived Queries.)\n\n" + + "dnsextd requires one argument,the zone, which is the domain for which Update Leases\n" + "and Long Lived Queries are to be administered. dnsextd communicates directly with the\n" + "primary master server for this zone.\n\n" + + "The options are as follows:\n\n" + + "-f Specify configuration file. The default is /etc/dnsextd.conf.\n\n" + + "-d Run daemon in foreground.\n\n" + + "-h Print help.\n\n" + + "-v Verbose output.\n\n" + ); +} + + +// Note: ProcessArgs called before process is daemonized, and therefore must open no descriptors +// returns 0 (success) if program is to continue execution +// output control arguments (-f, -v) do not affect this routine +mDNSlocal int ProcessArgs(int argc, char *argv[], DaemonInfo *d) +{ + DNSZone * zone; + int opt; + int err = 0; + + cfgfile = strdup( CONFIG_FILE ); + require_action( cfgfile, arg_error, err = mStatus_NoMemoryErr ); + + // defaults, may be overriden by command option + + // setup our sockaddr + + mDNSPlatformMemZero( &d->addr, sizeof( d->addr ) ); + d->addr.sin_addr.s_addr = zerov4Addr.NotAnInteger; + d->addr.sin_port = UnicastDNSPort.NotAnInteger; + d->addr.sin_family = AF_INET; +#ifndef NOT_HAVE_SA_LEN + d->addr.sin_len = sizeof( d->addr ); +#endif + + // setup nameserver's sockaddr + + mDNSPlatformMemZero(&d->ns_addr, sizeof(d->ns_addr)); + d->ns_addr.sin_family = AF_INET; + inet_pton( AF_INET, LOOPBACK, &d->ns_addr.sin_addr ); + d->ns_addr.sin_port = NSIPCPort.NotAnInteger; +#ifndef NOT_HAVE_SA_LEN + d->ns_addr.sin_len = sizeof( d->ns_addr ); +#endif + + // setup our ports + + d->private_port = PrivateDNSPort; + d->llq_port = DNSEXTPort; + + while ((opt = getopt(argc, argv, "f:hdv")) != -1) + { + switch(opt) + { + case 'f': free( cfgfile ); cfgfile = strdup( optarg ); require_action( cfgfile, arg_error, err = mStatus_NoMemoryErr ); break; + case 'h': PrintHelp(); return -1; + case 'd': foreground = 1; break; // Also used when launched via OS X's launchd mechanism + case 'v': verbose = 1; break; + default: goto arg_error; + } + } + + err = ParseConfig( d, cfgfile ); + require_noerr( err, arg_error ); + + // Make sure we've specified some zones + + require_action( d->zones, arg_error, err = mStatus_UnknownErr ); + + // if we have a shared secret, use it for the entire zone + + for ( zone = d->zones; zone; zone = zone->next ) + { + if ( zone->updateKeys ) + { + AssignDomainName( &zone->updateKeys->domain, &zone->name ); + } + } + + return 0; + +arg_error: + + PrintUsage(); + return -1; +} + + +// +// Initialization Routines +// + +// Allocate memory, initialize locks and bookkeeping variables +mDNSlocal int InitLeaseTable(DaemonInfo *d) +{ + if (pthread_mutex_init(&d->tablelock, NULL)) { LogErr("InitLeaseTable", "pthread_mutex_init"); return -1; } + d->nbuckets = LEASETABLE_INIT_NBUCKETS; + d->nelems = 0; + d->table = malloc(sizeof(RRTableElem *) * LEASETABLE_INIT_NBUCKETS); + if (!d->table) { LogErr("InitLeaseTable", "malloc"); return -1; } + mDNSPlatformMemZero(d->table, sizeof(RRTableElem *) * LEASETABLE_INIT_NBUCKETS); + return 0; +} + + +mDNSlocal int +SetupSockets +( + DaemonInfo * self +) +{ + static const int kOn = 1; + int sockpair[2]; + mDNSBool private = mDNSfalse; + struct sockaddr_in daddr; + DNSZone * zone; + mStatus err = 0; + + // set up sockets on which we all ns requests + + self->tcpsd = socket( AF_INET, SOCK_STREAM, 0 ); + require_action( dnssd_SocketValid(self->tcpsd), exit, err = mStatus_UnknownErr; LogErr( "SetupSockets", "socket" ) ); + +#if defined(SO_REUSEADDR) + err = setsockopt(self->tcpsd, SOL_SOCKET, SO_REUSEADDR, &kOn, sizeof(kOn)); + require_action( !err, exit, LogErr( "SetupSockets", "SO_REUSEADDR self->tcpsd" ) ); +#endif + + err = bind( self->tcpsd, ( struct sockaddr* ) &self->addr, sizeof( self->addr ) ); + require_action( !err, exit, LogErr( "SetupSockets", "bind self->tcpsd" ) ); + + err = listen( self->tcpsd, LISTENQ ); + require_action( !err, exit, LogErr( "SetupSockets", "listen" ) ); + + self->udpsd = socket( AF_INET, SOCK_DGRAM, 0 ); + require_action( dnssd_SocketValid(self->udpsd), exit, err = mStatus_UnknownErr; LogErr( "SetupSockets", "socket" ) ); + +#if defined(SO_REUSEADDR) + err = setsockopt(self->udpsd, SOL_SOCKET, SO_REUSEADDR, &kOn, sizeof(kOn)); + require_action( !err, exit, LogErr( "SetupSockets", "SO_REUSEADDR self->udpsd" ) ); +#endif + + err = bind( self->udpsd, ( struct sockaddr* ) &self->addr, sizeof( self->addr ) ); + require_action( !err, exit, LogErr( "SetupSockets", "bind self->udpsd" ) ); + + // set up sockets on which we receive llq requests + + mDNSPlatformMemZero(&self->llq_addr, sizeof(self->llq_addr)); + self->llq_addr.sin_family = AF_INET; + self->llq_addr.sin_addr.s_addr = zerov4Addr.NotAnInteger; + self->llq_addr.sin_port = ( self->llq_port.NotAnInteger ) ? self->llq_port.NotAnInteger : DNSEXTPort.NotAnInteger; + + if (self->llq_addr.sin_port == self->addr.sin_port) + { + self->llq_tcpsd = self->tcpsd; + self->llq_udpsd = self->udpsd; + } + else + { + self->llq_tcpsd = socket( AF_INET, SOCK_STREAM, 0 ); + require_action( dnssd_SocketValid(self->llq_tcpsd), exit, err = mStatus_UnknownErr; LogErr( "SetupSockets", "socket" ) ); + +#if defined(SO_REUSEADDR) + err = setsockopt(self->llq_tcpsd, SOL_SOCKET, SO_REUSEADDR, &kOn, sizeof(kOn)); + require_action( !err, exit, LogErr( "SetupSockets", "SO_REUSEADDR self->llq_tcpsd" ) ); +#endif + + err = bind( self->llq_tcpsd, ( struct sockaddr* ) &self->llq_addr, sizeof( self->llq_addr ) ); + require_action( !err, exit, LogErr( "SetupSockets", "bind self->llq_tcpsd" ) ); + + err = listen( self->llq_tcpsd, LISTENQ ); + require_action( !err, exit, LogErr( "SetupSockets", "listen" ) ); + + self->llq_udpsd = socket( AF_INET, SOCK_DGRAM, 0 ); + require_action( dnssd_SocketValid(self->llq_udpsd), exit, err = mStatus_UnknownErr; LogErr( "SetupSockets", "socket" ) ); + +#if defined(SO_REUSEADDR) + err = setsockopt(self->llq_udpsd, SOL_SOCKET, SO_REUSEADDR, &kOn, sizeof(kOn)); + require_action( !err, exit, LogErr( "SetupSockets", "SO_REUSEADDR self->llq_udpsd" ) ); +#endif + + err = bind(self->llq_udpsd, ( struct sockaddr* ) &self->llq_addr, sizeof( self->llq_addr ) ); + require_action( !err, exit, LogErr( "SetupSockets", "bind self->llq_udpsd" ) ); + } + + // set up Unix domain socket pair for LLQ polling thread to signal main thread that a change to the zone occurred + + err = socketpair( AF_LOCAL, SOCK_STREAM, 0, sockpair ); + require_action( !err, exit, LogErr( "SetupSockets", "socketpair" ) ); + + self->LLQEventListenSock = sockpair[0]; + self->LLQEventNotifySock = sockpair[1]; + + // set up socket on which we receive private requests + + self->llq_tcpsd = socket( AF_INET, SOCK_STREAM, 0 ); + require_action( dnssd_SocketValid(self->tlssd), exit, err = mStatus_UnknownErr; LogErr( "SetupSockets", "socket" ) ); + mDNSPlatformMemZero(&daddr, sizeof(daddr)); + daddr.sin_family = AF_INET; + daddr.sin_addr.s_addr = zerov4Addr.NotAnInteger; + daddr.sin_port = ( self->private_port.NotAnInteger ) ? self->private_port.NotAnInteger : PrivateDNSPort.NotAnInteger; + + self->tlssd = socket( AF_INET, SOCK_STREAM, 0 ); + require_action( dnssd_SocketValid(self->tlssd), exit, err = mStatus_UnknownErr; LogErr( "SetupSockets", "socket" ) ); + +#if defined(SO_REUSEADDR) + err = setsockopt(self->tlssd, SOL_SOCKET, SO_REUSEADDR, &kOn, sizeof(kOn)); + require_action( !err, exit, LogErr( "SetupSockets", "SO_REUSEADDR self->tlssd" ) ); +#endif + + err = bind( self->tlssd, ( struct sockaddr* ) &daddr, sizeof( daddr ) ); + require_action( !err, exit, LogErr( "SetupSockets", "bind self->tlssd" ) ); + + err = listen( self->tlssd, LISTENQ ); + require_action( !err, exit, LogErr( "SetupSockets", "listen" ) ); + + // Do we have any private zones? + + for ( zone = self->zones; zone; zone = zone->next ) + { + if ( zone->type == kDNSZonePrivate ) + { + private = mDNStrue; + break; + } + } + + if ( private ) + { + err = mDNSPlatformTLSSetupCerts(); + require_action( !err, exit, LogErr( "SetupSockets", "mDNSPlatformTLSSetupCerts" ) ); + } + +exit: + + return err; +} + +// +// periodic table updates +// + +// Delete a resource record from the nameserver via a dynamic update +// sd is a socket already connected to the server +mDNSlocal void DeleteOneRecord(DaemonInfo *d, CacheRecord *rr, domainname *zname, TCPSocket *sock) +{ + DNSZone * zone; + PktMsg pkt; + mDNSu8 *ptr = pkt.msg.data; + mDNSu8 *end = (mDNSu8 *)&pkt.msg + sizeof(DNSMessage); + char buf[MaxMsg]; + mDNSBool closed; + PktMsg *reply = NULL; + + VLog("Expiring record %s", GetRRDisplayString_rdb(&rr->resrec, &rr->resrec.rdata->u, buf)); + + InitializeDNSMessage(&pkt.msg.h, zeroID, UpdateReqFlags); + + ptr = putZone(&pkt.msg, ptr, end, zname, mDNSOpaque16fromIntVal(rr->resrec.rrclass)); + if (!ptr) goto end; + ptr = putDeletionRecord(&pkt.msg, ptr, &rr->resrec); + if (!ptr) goto end; + + HdrHToN(&pkt); + + zone = FindZone( d, zname ); + + if ( zone && zone->updateKeys) + { + DNSDigest_SignMessage(&pkt.msg, &ptr, zone->updateKeys, 0 ); + if (!ptr) goto end; + } + + pkt.len = ptr - (mDNSu8 *)&pkt.msg; + pkt.src.sin_addr.s_addr = zerov4Addr.NotAnInteger; // address field set solely for verbose logging in subroutines + pkt.src.sin_family = AF_INET; + if (SendPacket( sock, &pkt)) { Log("DeleteOneRecord: SendPacket failed"); } + reply = RecvPacket( sock, NULL, &closed ); + if (reply) HdrNToH(reply); + require_action( reply, end, Log( "DeleteOneRecord: RecvPacket returned NULL" ) ); + + if (!SuccessfulUpdateTransaction(&pkt, reply)) + Log("Expiration update failed with rcode %d", reply ? reply->msg.h.flags.b[1] & kDNSFlag1_RC_Mask : -1); + +end: + if (!ptr) { Log("DeleteOneRecord: Error constructing lease expiration update"); } + if (reply) free(reply); +} + +// iterate over table, deleting expired records (or all records if DeleteAll is true) +mDNSlocal void DeleteRecords(DaemonInfo *d, mDNSBool DeleteAll) +{ + struct timeval now; + int i; + TCPSocket *sock = ConnectToServer(d); + if (!sock) { Log("DeleteRecords: ConnectToServer failed"); return; } + if (gettimeofday(&now, NULL)) { LogErr("DeleteRecords ", "gettimeofday"); return; } + if (pthread_mutex_lock(&d->tablelock)) { LogErr("DeleteRecords", "pthread_mutex_lock"); return; } + + for (i = 0; i < d->nbuckets; i++) + { + RRTableElem **ptr = &d->table[i]; + while (*ptr) + { + if (DeleteAll || (*ptr)->expire - now.tv_sec < 0) + { + RRTableElem *fptr; + // delete record from server + DeleteOneRecord(d, &(*ptr)->rr, &(*ptr)->zone, sock); + fptr = *ptr; + *ptr = (*ptr)->next; + free(fptr); + d->nelems--; + } + else ptr = &(*ptr)->next; + } + } + pthread_mutex_unlock(&d->tablelock); + mDNSPlatformTCPCloseConnection( sock ); +} + +// +// main update request handling +// + +// Add, delete, or refresh records in table based on contents of a successfully completed dynamic update +mDNSlocal void UpdateLeaseTable(PktMsg *pkt, DaemonInfo *d, mDNSs32 lease) +{ + RRTableElem **rptr, *tmp; + int i, allocsize, bucket; + LargeCacheRecord lcr; + ResourceRecord *rr = &lcr.r.resrec; + const mDNSu8 *ptr, *end; + struct timeval tv; + DNSQuestion zone; + char buf[MaxMsg]; + + if (pthread_mutex_lock(&d->tablelock)) { LogErr("UpdateLeaseTable", "pthread_mutex_lock"); return; } + HdrNToH(pkt); + ptr = pkt->msg.data; + end = (mDNSu8 *)&pkt->msg + pkt->len; + ptr = getQuestion(&pkt->msg, ptr, end, 0, &zone); + if (!ptr) { Log("UpdateLeaseTable: cannot read zone"); goto cleanup; } + ptr = LocateAuthorities(&pkt->msg, end); + if (!ptr) { Log("UpdateLeaseTable: Format error"); goto cleanup; } + + for (i = 0; i < pkt->msg.h.mDNS_numUpdates; i++) + { + mDNSBool DeleteAllRRSets = mDNSfalse, DeleteOneRRSet = mDNSfalse, DeleteOneRR = mDNSfalse; + + ptr = GetLargeResourceRecord(NULL, &pkt->msg, ptr, end, 0, kDNSRecordTypePacketAns, &lcr); + if (!ptr || lcr.r.resrec.RecordType == kDNSRecordTypePacketNegative) { Log("UpdateLeaseTable: GetLargeResourceRecord failed"); goto cleanup; } + bucket = rr->namehash % d->nbuckets; + rptr = &d->table[bucket]; + + // handle deletions + if (rr->rrtype == kDNSQType_ANY && !rr->rroriginalttl && rr->rrclass == kDNSQClass_ANY && !rr->rdlength) + DeleteAllRRSets = mDNStrue; // delete all rrsets for a name + else if (!rr->rroriginalttl && rr->rrclass == kDNSQClass_ANY && !rr->rdlength) + DeleteOneRRSet = mDNStrue; + else if (!rr->rroriginalttl && rr->rrclass == kDNSClass_NONE) + DeleteOneRR = mDNStrue; + + if (DeleteAllRRSets || DeleteOneRRSet || DeleteOneRR) + { + while (*rptr) + { + if (SameDomainName((*rptr)->rr.resrec.name, rr->name) && + (DeleteAllRRSets || + (DeleteOneRRSet && (*rptr)->rr.resrec.rrtype == rr->rrtype) || + (DeleteOneRR && IdenticalResourceRecord(&(*rptr)->rr.resrec, rr)))) + { + tmp = *rptr; + VLog("Received deletion update for %s", GetRRDisplayString_rdb(&tmp->rr.resrec, &tmp->rr.resrec.rdata->u, buf)); + *rptr = (*rptr)->next; + free(tmp); + d->nelems--; + } + else rptr = &(*rptr)->next; + } + } + else if (lease > 0) + { + // see if add or refresh + while (*rptr && !IdenticalResourceRecord(&(*rptr)->rr.resrec, rr)) rptr = &(*rptr)->next; + if (*rptr) + { + // refresh + if (gettimeofday(&tv, NULL)) { LogErr("UpdateLeaseTable", "gettimeofday"); goto cleanup; } + (*rptr)->expire = tv.tv_sec + (unsigned)lease; + VLog("Refreshing lease for %s", GetRRDisplayString_rdb(&lcr.r.resrec, &lcr.r.resrec.rdata->u, buf)); + } + else + { + // New record - add to table + if (d->nelems > d->nbuckets) + { + RehashTable(d); + bucket = rr->namehash % d->nbuckets; + rptr = &d->table[bucket]; + } + if (gettimeofday(&tv, NULL)) { LogErr("UpdateLeaseTable", "gettimeofday"); goto cleanup; } + allocsize = sizeof(RRTableElem); + if (rr->rdlength > InlineCacheRDSize) allocsize += (rr->rdlength - InlineCacheRDSize); + tmp = malloc(allocsize); + if (!tmp) { LogErr("UpdateLeaseTable", "malloc"); goto cleanup; } + memcpy(&tmp->rr, &lcr.r, sizeof(CacheRecord) + rr->rdlength - InlineCacheRDSize); + tmp->rr.resrec.rdata = (RData *)&tmp->rr.smallrdatastorage; + AssignDomainName(&tmp->name, rr->name); + tmp->rr.resrec.name = &tmp->name; + tmp->expire = tv.tv_sec + (unsigned)lease; + tmp->cli.sin_addr = pkt->src.sin_addr; + AssignDomainName(&tmp->zone, &zone.qname); + tmp->next = d->table[bucket]; + d->table[bucket] = tmp; + d->nelems++; + VLog("Adding update for %s to lease table", GetRRDisplayString_rdb(&lcr.r.resrec, &lcr.r.resrec.rdata->u, buf)); + } + } + } + +cleanup: + pthread_mutex_unlock(&d->tablelock); + HdrHToN(pkt); +} + +// Given a successful reply from a server, create a new reply that contains lease information +// Replies are currently not signed !!!KRS change this +mDNSlocal PktMsg *FormatLeaseReply(DaemonInfo *d, PktMsg *orig, mDNSu32 lease) +{ + PktMsg *reply; + mDNSu8 *ptr, *end; + mDNSOpaque16 flags; + + (void)d; //unused + reply = malloc(sizeof(*reply)); + if (!reply) { LogErr("FormatLeaseReply", "malloc"); return NULL; } + flags.b[0] = kDNSFlag0_QR_Response | kDNSFlag0_OP_Update; + flags.b[1] = 0; + + InitializeDNSMessage(&reply->msg.h, orig->msg.h.id, flags); + reply->src.sin_addr.s_addr = zerov4Addr.NotAnInteger; // unused except for log messages + reply->src.sin_family = AF_INET; + ptr = reply->msg.data; + end = (mDNSu8 *)&reply->msg + sizeof(DNSMessage); + ptr = putUpdateLease(&reply->msg, ptr, lease); + if (!ptr) { Log("FormatLeaseReply: putUpdateLease failed"); free(reply); return NULL; } + reply->len = ptr - (mDNSu8 *)&reply->msg; + HdrHToN(reply); + return reply; +} + + +// pkt is thread-local, not requiring locking + +mDNSlocal PktMsg* +HandleRequest +( + DaemonInfo * self, + PktMsg * request +) +{ + PktMsg * reply = NULL; + PktMsg * leaseReply; + PktMsg buf; + char addrbuf[32]; + TCPSocket * sock = NULL; + mStatus err; + mDNSs32 lease = 0; + if ((request->msg.h.flags.b[0] & kDNSFlag0_QROP_Mask) == kDNSFlag0_OP_Update) + { + int i, adds = 0, dels = 0; + const mDNSu8 *ptr, *end = (mDNSu8 *)&request->msg + request->len; + HdrNToH(request); + lease = GetPktLease(&mDNSStorage, &request->msg, end); + ptr = LocateAuthorities(&request->msg, end); + for (i = 0; i < request->msg.h.mDNS_numUpdates; i++) + { + LargeCacheRecord lcr; + ptr = GetLargeResourceRecord(NULL, &request->msg, ptr, end, 0, kDNSRecordTypePacketAns, &lcr); + if (lcr.r.resrec.RecordType != kDNSRecordTypePacketNegative && lcr.r.resrec.rroriginalttl) adds++;else dels++; + } + HdrHToN(request); + if (adds && !lease) + { + static const mDNSOpaque16 UpdateRefused = { { kDNSFlag0_QR_Response | kDNSFlag0_OP_Update, kDNSFlag1_RC_Refused } }; + Log("Rejecting Update Request with %d additions but no lease", adds); + reply = malloc(sizeof(*reply)); + mDNSPlatformMemZero(&reply->src, sizeof(reply->src)); + reply->len = sizeof(DNSMessageHeader); + reply->zone = NULL; + reply->isZonePublic = 0; + InitializeDNSMessage(&reply->msg.h, request->msg.h.id, UpdateRefused); + return(reply); + } + if (lease > 7200) // Don't allow lease greater than two hours; typically 90-minute renewal period + lease = 7200; + } + // Send msg to server, read reply + + if ( request->len <= 512 ) + { + mDNSBool trunc; + + if ( UDPServerTransaction( self, request, &buf, &trunc) < 0 ) + { + Log("HandleRequest - UDPServerTransaction failed. Trying TCP"); + } + else if ( trunc ) + { + VLog("HandleRequest - answer truncated. Using TCP"); + } + else + { + reply = &buf; // success + } + } + + if ( !reply ) + { + mDNSBool closed; + int res; + + sock = ConnectToServer( self ); + require_action_quiet( sock, exit, err = mStatus_UnknownErr ; Log( "Discarding request from %s due to connection errors", inet_ntop( AF_INET, &request->src.sin_addr, addrbuf, 32 ) ) ); + + res = SendPacket( sock, request ); + require_action_quiet( res >= 0, exit, err = mStatus_UnknownErr ; Log( "Couldn't relay message from %s to server. Discarding.", inet_ntop(AF_INET, &request->src.sin_addr, addrbuf, 32 ) ) ); + + reply = RecvPacket( sock, &buf, &closed ); + } + + // IMPORTANT: reply is in network byte order at this point in the code + // We keep it this way because we send it back to the client in the same form + + // Is it an update? + + if ( reply && ( ( reply->msg.h.flags.b[0] & kDNSFlag0_QROP_Mask ) == ( kDNSFlag0_OP_Update | kDNSFlag0_QR_Response ) ) ) + { + char pingmsg[4]; + mDNSBool ok = SuccessfulUpdateTransaction( request, reply ); + require_action( ok, exit, err = mStatus_UnknownErr; VLog( "Message from %s not a successful update.", inet_ntop(AF_INET, &request->src.sin_addr, addrbuf, 32 ) ) ); + + UpdateLeaseTable( request, self, lease ); + + if ( lease > 0 ) + { + leaseReply = FormatLeaseReply( self, reply, lease ); + + if ( !leaseReply ) + { + Log("HandleRequest - unable to format lease reply"); + } + + // %%% Looks like a potential memory leak -- who frees the original reply? + reply = leaseReply; + } + + // tell the main thread there was an update so it can send LLQs + + if ( send( self->LLQEventNotifySock, pingmsg, sizeof( pingmsg ), 0 ) != sizeof( pingmsg ) ) + { + LogErr("HandleRequest", "send"); + } + } + +exit: + + if ( sock ) + { + mDNSPlatformTCPCloseConnection( sock ); + } + + if ( reply == &buf ) + { + reply = malloc( sizeof( *reply ) ); + + if ( reply ) + { + reply->len = buf.len; + memcpy(&reply->msg, &buf.msg, buf.len); + } + else + { + LogErr("HandleRequest", "malloc"); + } + } + + return reply; +} + + +// +// LLQ Support Routines +// + +// Set fields of an LLQ OPT Resource Record +mDNSlocal void FormatLLQOpt(AuthRecord *opt, int opcode, const mDNSOpaque64 *const id, mDNSs32 lease) +{ + mDNSPlatformMemZero(opt, sizeof(*opt)); + mDNS_SetupResourceRecord(opt, mDNSNULL, mDNSInterface_Any, kDNSType_OPT, kStandardTTL, kDNSRecordTypeKnownUnique, AuthRecordAny, mDNSNULL, mDNSNULL); + opt->resrec.rrclass = NormalMaxDNSMessageData; + opt->resrec.rdlength = sizeof(rdataOPT); // One option in this OPT record + opt->resrec.rdestimate = sizeof(rdataOPT); + opt->resrec.rdata->u.opt[0].opt = kDNSOpt_LLQ; + opt->resrec.rdata->u.opt[0].u.llq.vers = kLLQ_Vers; + opt->resrec.rdata->u.opt[0].u.llq.llqOp = opcode; + opt->resrec.rdata->u.opt[0].u.llq.err = LLQErr_NoError; + opt->resrec.rdata->u.opt[0].u.llq.id = *id; + opt->resrec.rdata->u.opt[0].u.llq.llqlease = lease; +} + +// Calculate effective remaining lease of an LLQ +mDNSlocal mDNSu32 LLQLease(LLQEntry *e) +{ + struct timeval t; + + gettimeofday(&t, NULL); + if (e->expire < t.tv_sec) return 0; + else return e->expire - t.tv_sec; +} + +mDNSlocal void DeleteLLQ(DaemonInfo *d, LLQEntry *e) +{ + int bucket = DomainNameHashValue(&e->qname) % LLQ_TABLESIZE; + LLQEntry **ptr = &d->LLQTable[bucket]; + AnswerListElem *a = e->AnswerList; + char addr[32]; + + inet_ntop(AF_INET, &e->cli.sin_addr, addr, 32); + VLog("Deleting LLQ table entry for %##s client %s", e->qname.c, addr); + + if (a && !(--a->refcount) && d->AnswerTableCount >= LLQ_TABLESIZE) + { + // currently, generating initial answers blocks the main thread, so we keep the answer list + // even if the ref count drops to zero. To prevent unbounded table growth, we free shared answers + // if the ref count drops to zero AND there are more table elements than buckets + // !!!KRS update this when we make the table dynamically growable + + CacheRecord *cr = a->KnownAnswers, *tmp; + AnswerListElem **tbl = &d->AnswerTable[bucket]; + + while (cr) + { + tmp = cr; + cr = cr->next; + free(tmp); + } + + while (*tbl && *tbl != a) tbl = &(*tbl)->next; + if (*tbl) { *tbl = (*tbl)->next; free(a); d->AnswerTableCount--; } + else Log("Error: DeleteLLQ - AnswerList not found in table"); + } + + // remove LLQ from table, free memory + while(*ptr && *ptr != e) ptr = &(*ptr)->next; + if (!*ptr) { Log("Error: DeleteLLQ - LLQ not in table"); return; } + *ptr = (*ptr)->next; + free(e); +} + +mDNSlocal int SendLLQ(DaemonInfo *d, PktMsg *pkt, struct sockaddr_in dst, TCPSocket *sock) +{ + char addr[32]; + int err = -1; + + HdrHToN(pkt); + + if ( sock ) + { + if ( SendPacket( sock, pkt ) != 0 ) + { + LogErr("DaemonInfo", "MySend"); + Log("Could not send response to client %s", inet_ntop(AF_INET, &dst.sin_addr, addr, 32)); + } + } + else + { + if (sendto(d->llq_udpsd, &pkt->msg, pkt->len, 0, (struct sockaddr *)&dst, sizeof(dst)) != (int)pkt->len) + { + LogErr("DaemonInfo", "sendto"); + Log("Could not send response to client %s", inet_ntop(AF_INET, &dst.sin_addr, addr, 32)); + } + } + + err = 0; + HdrNToH(pkt); + return err; +} + +mDNSlocal CacheRecord *AnswerQuestion(DaemonInfo *d, AnswerListElem *e) +{ + PktMsg q; + int i; + TCPSocket *sock = NULL; + const mDNSu8 *ansptr; + mDNSu8 *end = q.msg.data; + PktMsg buf, *reply = NULL; + LargeCacheRecord lcr; + CacheRecord *AnswerList = NULL; + mDNSu8 rcode; + + VLog("Querying server for %##s type %d", e->name.c, e->type); + + InitializeDNSMessage(&q.msg.h, zeroID, uQueryFlags); + + end = putQuestion(&q.msg, end, end + AbsoluteMaxDNSMessageData, &e->name, e->type, kDNSClass_IN); + if (!end) { Log("Error: AnswerQuestion - putQuestion returned NULL"); goto end; } + q.len = (int)(end - (mDNSu8 *)&q.msg); + + HdrHToN(&q); + + if (!e->UseTCP) + { + mDNSBool trunc; + + if (UDPServerTransaction(d, &q, &buf, &trunc) < 0) + Log("AnswerQuestion %##s - UDPServerTransaction failed. Trying TCP", e->name.c); + else if (trunc) + { VLog("AnswerQuestion %##s - answer truncated. Using TCP", e->name.c); e->UseTCP = mDNStrue; } + else reply = &buf; // success + } + + if (!reply) + { + mDNSBool closed; + + sock = ConnectToServer(d); + if (!sock) { Log("AnswerQuestion: ConnectToServer failed"); goto end; } + if (SendPacket( sock, &q)) { Log("AnswerQuestion: SendPacket failed"); mDNSPlatformTCPCloseConnection( sock ); goto end; } + reply = RecvPacket( sock, NULL, &closed ); + mDNSPlatformTCPCloseConnection( sock ); + require_action( reply, end, Log( "AnswerQuestion: RecvPacket returned NULL" ) ); + } + + HdrNToH(&q); + if (reply) HdrNToH(reply); + + if ((reply->msg.h.flags.b[0] & kDNSFlag0_QROP_Mask) != (kDNSFlag0_QR_Response | kDNSFlag0_OP_StdQuery)) + { Log("AnswerQuestion: %##s type %d - Invalid response flags from server"); goto end; } + rcode = (mDNSu8)(reply->msg.h.flags.b[1] & kDNSFlag1_RC_Mask); + if (rcode && rcode != kDNSFlag1_RC_NXDomain) { Log("AnswerQuestion: %##s type %d - non-zero rcode %d from server", e->name.c, e->type, rcode); goto end; } + + end = (mDNSu8 *)&reply->msg + reply->len; + ansptr = LocateAnswers(&reply->msg, end); + if (!ansptr) { Log("Error: AnswerQuestion - LocateAnswers returned NULL"); goto end; } + + for (i = 0; i < reply->msg.h.numAnswers; i++) + { + ansptr = GetLargeResourceRecord(NULL, &reply->msg, ansptr, end, 0, kDNSRecordTypePacketAns, &lcr); + if (!ansptr) { Log("AnswerQuestions: GetLargeResourceRecord returned NULL"); goto end; } + if (lcr.r.resrec.RecordType != kDNSRecordTypePacketNegative) + { + if (lcr.r.resrec.rrtype != e->type || lcr.r.resrec.rrclass != kDNSClass_IN || !SameDomainName(lcr.r.resrec.name, &e->name)) + { + Log("AnswerQuestion: response %##s type #d does not answer question %##s type #d. Discarding", + lcr.r.resrec.name->c, lcr.r.resrec.rrtype, e->name.c, e->type); + } + else + { + CacheRecord *cr = CopyCacheRecord(&lcr.r, &e->name); + if (!cr) { Log("Error: AnswerQuestion - CopyCacheRecord returned NULL"); goto end; } + cr->next = AnswerList; + AnswerList = cr; + } + } + } + +end: + if (reply && reply != &buf) free(reply); + return AnswerList; +} + +// Routine forks a thread to set EventList to contain Add/Remove events, and deletes any removes from the KnownAnswer list +mDNSlocal void *UpdateAnswerList(void *args) +{ + CacheRecord *cr, *NewAnswers, **na, **ka; // "new answer", "known answer" + DaemonInfo *d = ((UpdateAnswerListArgs *)args)->d; + AnswerListElem *a = ((UpdateAnswerListArgs *)args)->a; + + free(args); + args = NULL; + + // get up to date answers + NewAnswers = AnswerQuestion(d, a); + + // first pass - mark all answers for deletion + for (ka = &a->KnownAnswers; *ka; ka = &(*ka)->next) + (*ka)->resrec.rroriginalttl = (unsigned)-1; // -1 means delete + + // second pass - mark answers pre-existent + for (ka = &a->KnownAnswers; *ka; ka = &(*ka)->next) + { + for (na = &NewAnswers; *na; na = &(*na)->next) + { + if (IdenticalResourceRecord(&(*ka)->resrec, &(*na)->resrec)) + { (*ka)->resrec.rroriginalttl = 0; break; } // 0 means no change + } + } + + // third pass - add new records to Event list + na = &NewAnswers; + while (*na) + { + for (ka = &a->KnownAnswers; *ka; ka = &(*ka)->next) + if (IdenticalResourceRecord(&(*ka)->resrec, &(*na)->resrec)) break; + if (!*ka) + { + // answer is not in list - splice from NewAnswers list, add to Event list + cr = *na; + *na = (*na)->next; // splice from list + cr->next = a->EventList; // add spliced record to event list + a->EventList = cr; + cr->resrec.rroriginalttl = 1; // 1 means add + } + else na = &(*na)->next; + } + + // move all the removes from the answer list to the event list + ka = &a->KnownAnswers; + while (*ka) + { + if ((*ka)->resrec.rroriginalttl == (unsigned)-1) + { + cr = *ka; + *ka = (*ka)->next; + cr->next = a->EventList; + a->EventList = cr; + } + else ka = &(*ka)->next; + } + + // lastly, free the remaining records (known answers) in NewAnswers list + while (NewAnswers) + { + cr = NewAnswers; + NewAnswers = NewAnswers->next; + free(cr); + } + + return NULL; +} + +mDNSlocal void SendEvents(DaemonInfo *d, LLQEntry *e) +{ + PktMsg response; + CacheRecord *cr; + mDNSu8 *end = (mDNSu8 *)&response.msg.data; + mDNSOpaque16 msgID; + char rrbuf[MaxMsg], addrbuf[32]; + AuthRecord opt; + + // Should this really be random? Do we use the msgID on the receiving end? + msgID.NotAnInteger = random(); + if (verbose) inet_ntop(AF_INET, &e->cli.sin_addr, addrbuf, 32); + InitializeDNSMessage(&response.msg.h, msgID, ResponseFlags); + end = putQuestion(&response.msg, end, end + AbsoluteMaxDNSMessageData, &e->qname, e->qtype, kDNSClass_IN); + if (!end) { Log("Error: SendEvents - putQuestion returned NULL"); return; } + + // put adds/removes in packet + for (cr = e->AnswerList->EventList; cr; cr = cr->next) + { + if (verbose) GetRRDisplayString_rdb(&cr->resrec, &cr->resrec.rdata->u, rrbuf); + VLog("%s (%s): %s", addrbuf, (mDNSs32)cr->resrec.rroriginalttl < 0 ? "Remove" : "Add", rrbuf); + end = PutResourceRecordTTLJumbo(&response.msg, end, &response.msg.h.numAnswers, &cr->resrec, cr->resrec.rroriginalttl); + if (!end) { Log("Error: SendEvents - PutResourceRecordTTLJumbo returned NULL"); return; } + } + + FormatLLQOpt(&opt, kLLQOp_Event, &e->id, LLQLease(e)); + end = PutResourceRecordTTLJumbo(&response.msg, end, &response.msg.h.numAdditionals, &opt.resrec, 0); + if (!end) { Log("Error: SendEvents - PutResourceRecordTTLJumbo"); return; } + + response.len = (int)(end - (mDNSu8 *)&response.msg); + if (SendLLQ(d, &response, e->cli, NULL ) < 0) LogMsg("Error: SendEvents - SendLLQ"); +} + +mDNSlocal void PrintLLQAnswers(DaemonInfo *d) +{ + int i; + char rrbuf[MaxMsg]; + + Log("Printing LLQ Answer Table contents"); + + for (i = 0; i < LLQ_TABLESIZE; i++) + { + AnswerListElem *a = d->AnswerTable[i]; + while(a) + { + int ancount = 0; + const CacheRecord *rr = a->KnownAnswers; + while (rr) { ancount++; rr = rr->next; } + Log("%p : Question %##s; type %d; referenced by %d LLQs; %d answers:", a, a->name.c, a->type, a->refcount, ancount); + for (rr = a->KnownAnswers; rr; rr = rr->next) Log("\t%s", GetRRDisplayString_rdb(&rr->resrec, &rr->resrec.rdata->u, rrbuf)); + a = a->next; + } + } +} + +mDNSlocal void PrintLLQTable(DaemonInfo *d) +{ + LLQEntry *e; + char addr[32]; + int i; + + Log("Printing LLQ table contents"); + + for (i = 0; i < LLQ_TABLESIZE; i++) + { + e = d->LLQTable[i]; + while(e) + { + char *state; + + switch (e->state) + { + case RequestReceived: state = "RequestReceived"; break; + case ChallengeSent: state = "ChallengeSent"; break; + case Established: state = "Established"; break; + default: state = "unknown"; + } + inet_ntop(AF_INET, &e->cli.sin_addr, addr, 32); + + Log("LLQ from %s in state %s; %##s; type %d; orig lease %d; remaining lease %d; AnswerList %p)", + addr, state, e->qname.c, e->qtype, e->lease, LLQLease(e), e->AnswerList); + e = e->next; + } + } +} + +// Send events to clients as a result of a change in the zone +mDNSlocal void GenLLQEvents(DaemonInfo *d) +{ + LLQEntry **e; + int i; + struct timeval t; + UpdateAnswerListArgs *args; + + VLog("Generating LLQ Events"); + + gettimeofday(&t, NULL); + + // get all answers up to date + for (i = 0; i < LLQ_TABLESIZE; i++) + { + AnswerListElem *a = d->AnswerTable[i]; + while(a) + { + args = malloc(sizeof(*args)); + if (!args) { LogErr("GenLLQEvents", "malloc"); return; } + args->d = d; + args->a = a; + if (pthread_create(&a->tid, NULL, UpdateAnswerList, args) < 0) { LogErr("GenLLQEvents", "pthread_create"); return; } + usleep(1); + a = a->next; + } + } + + for (i = 0; i < LLQ_TABLESIZE; i++) + { + AnswerListElem *a = d->AnswerTable[i]; + while(a) + { + if (pthread_join(a->tid, NULL)) LogErr("GenLLQEvents", "pthread_join"); + a = a->next; + } + } + + // for each established LLQ, send events + for (i = 0; i < LLQ_TABLESIZE; i++) + { + e = &d->LLQTable[i]; + while(*e) + { + if ((*e)->expire < t.tv_sec) DeleteLLQ(d, *e); + else + { + if ((*e)->state == Established && (*e)->AnswerList->EventList) SendEvents(d, *e); + e = &(*e)->next; + } + } + } + + // now that all LLQs are updated, we move Add events from the Event list to the Known Answer list, and free Removes + for (i = 0; i < LLQ_TABLESIZE; i++) + { + AnswerListElem *a = d->AnswerTable[i]; + while(a) + { + if (a->EventList) + { + CacheRecord *cr = a->EventList, *tmp; + while (cr) + { + tmp = cr; + cr = cr->next; + if ((signed)tmp->resrec.rroriginalttl < 0) free(tmp); + else + { + tmp->next = a->KnownAnswers; + a->KnownAnswers = tmp; + tmp->resrec.rroriginalttl = 0; + } + } + a->EventList = NULL; + } + a = a->next; + } + } +} + +mDNSlocal void SetAnswerList(DaemonInfo *d, LLQEntry *e) +{ + int bucket = DomainNameHashValue(&e->qname) % LLQ_TABLESIZE; + AnswerListElem *a = d->AnswerTable[bucket]; + while (a && (a->type != e->qtype ||!SameDomainName(&a->name, &e->qname))) a = a->next; + if (!a) + { + a = malloc(sizeof(*a)); + if (!a) { LogErr("SetAnswerList", "malloc"); return; } + AssignDomainName(&a->name, &e->qname); + a->type = e->qtype; + a->refcount = 0; + a->EventList = NULL; + a->UseTCP = mDNSfalse; + a->next = d->AnswerTable[bucket]; + d->AnswerTable[bucket] = a; + d->AnswerTableCount++; + a->KnownAnswers = AnswerQuestion(d, a); + } + + e->AnswerList = a; + a->refcount++; +} + +// Allocate LLQ entry, insert into table +mDNSlocal LLQEntry *NewLLQ(DaemonInfo *d, struct sockaddr_in cli, domainname *qname, mDNSu16 qtype, mDNSu32 lease ) +{ + char addr[32]; + struct timeval t; + int bucket = DomainNameHashValue(qname) % LLQ_TABLESIZE; + LLQEntry *e; + + e = malloc(sizeof(*e)); + if (!e) { LogErr("NewLLQ", "malloc"); return NULL; } + + inet_ntop(AF_INET, &cli.sin_addr, addr, 32); + VLog("Allocating LLQ entry for client %s question %##s type %d", addr, qname->c, qtype); + + // initialize structure + e->cli = cli; + AssignDomainName(&e->qname, qname); + e->qtype = qtype; + e->id = zeroOpaque64; + e->state = RequestReceived; + e->AnswerList = NULL; + + if (lease < LLQ_MIN_LEASE) lease = LLQ_MIN_LEASE; + else if (lease > LLQ_MAX_LEASE) lease = LLQ_MAX_LEASE; + + gettimeofday(&t, NULL); + e->expire = t.tv_sec + (int)lease; + e->lease = lease; + + // add to table + e->next = d->LLQTable[bucket]; + d->LLQTable[bucket] = e; + + return e; +} + +// Handle a refresh request from client +mDNSlocal void LLQRefresh(DaemonInfo *d, LLQEntry *e, LLQOptData *llq, mDNSOpaque16 msgID, TCPSocket *sock ) +{ + AuthRecord opt; + PktMsg ack; + mDNSu8 *end = (mDNSu8 *)&ack.msg.data; + char addr[32]; + + inet_ntop(AF_INET, &e->cli.sin_addr, addr, 32); + VLog("%s LLQ for %##s from %s", llq->llqlease ? "Refreshing" : "Deleting", e->qname.c, addr); + + if (llq->llqlease) + { + struct timeval t; + if (llq->llqlease < LLQ_MIN_LEASE) llq->llqlease = LLQ_MIN_LEASE; + else if (llq->llqlease > LLQ_MAX_LEASE) llq->llqlease = LLQ_MIN_LEASE; + gettimeofday(&t, NULL); + e->expire = t.tv_sec + llq->llqlease; + } + + ack.src.sin_addr.s_addr = 0; // unused + InitializeDNSMessage(&ack.msg.h, msgID, ResponseFlags); + end = putQuestion(&ack.msg, end, end + AbsoluteMaxDNSMessageData, &e->qname, e->qtype, kDNSClass_IN); + if (!end) { Log("Error: putQuestion"); return; } + + FormatLLQOpt(&opt, kLLQOp_Refresh, &e->id, llq->llqlease ? LLQLease(e) : 0); + end = PutResourceRecordTTLJumbo(&ack.msg, end, &ack.msg.h.numAdditionals, &opt.resrec, 0); + if (!end) { Log("Error: PutResourceRecordTTLJumbo"); return; } + + ack.len = (int)(end - (mDNSu8 *)&ack.msg); + if (SendLLQ(d, &ack, e->cli, sock)) Log("Error: LLQRefresh"); + + if (llq->llqlease) e->state = Established; + else DeleteLLQ(d, e); +} + +// Complete handshake with Ack an initial answers +mDNSlocal void LLQCompleteHandshake(DaemonInfo *d, LLQEntry *e, LLQOptData *llq, mDNSOpaque16 msgID, TCPSocket *sock) +{ + char addr[32]; + CacheRecord *ptr; + AuthRecord opt; + PktMsg ack; + mDNSu8 *end = (mDNSu8 *)&ack.msg.data; + char rrbuf[MaxMsg], addrbuf[32]; + + inet_ntop(AF_INET, &e->cli.sin_addr, addr, 32); + + if (!mDNSSameOpaque64(&llq->id, &e->id) || + llq->vers != kLLQ_Vers || + llq->llqOp != kLLQOp_Setup || + llq->err != LLQErr_NoError || + llq->llqlease > e->lease + LLQ_LEASE_FUDGE || + llq->llqlease < e->lease - LLQ_LEASE_FUDGE) + { + Log("Incorrect challenge response from %s", addr); + return; + } + + if (e->state == Established) VLog("Retransmitting LLQ ack + answers for %##s", e->qname.c); + else VLog("Delivering LLQ ack + answers for %##s", e->qname.c); + + // format ack + answers + ack.src.sin_addr.s_addr = 0; // unused + InitializeDNSMessage(&ack.msg.h, msgID, ResponseFlags); + end = putQuestion(&ack.msg, end, end + AbsoluteMaxDNSMessageData, &e->qname, e->qtype, kDNSClass_IN); + if (!end) { Log("Error: putQuestion"); return; } + + if (e->state != Established) { SetAnswerList(d, e); e->state = Established; } + + if (verbose) inet_ntop(AF_INET, &e->cli.sin_addr, addrbuf, 32); + for (ptr = e->AnswerList->KnownAnswers; ptr; ptr = ptr->next) + { + if (verbose) GetRRDisplayString_rdb(&ptr->resrec, &ptr->resrec.rdata->u, rrbuf); + VLog("%s Intitial Answer - %s", addr, rrbuf); + end = PutResourceRecordTTLJumbo(&ack.msg, end, &ack.msg.h.numAnswers, &ptr->resrec, 1); + if (!end) { Log("Error: PutResourceRecordTTLJumbo"); return; } + } + + FormatLLQOpt(&opt, kLLQOp_Setup, &e->id, LLQLease(e)); + end = PutResourceRecordTTLJumbo(&ack.msg, end, &ack.msg.h.numAdditionals, &opt.resrec, 0); + if (!end) { Log("Error: PutResourceRecordTTLJumbo"); return; } + + ack.len = (int)(end - (mDNSu8 *)&ack.msg); + if (SendLLQ(d, &ack, e->cli, sock)) Log("Error: LLQCompleteHandshake"); +} + +mDNSlocal void LLQSetupChallenge(DaemonInfo *d, LLQEntry *e, LLQOptData *llq, mDNSOpaque16 msgID) +{ + struct timeval t; + PktMsg challenge; + mDNSu8 *end = challenge.msg.data; + AuthRecord opt; + + if (e->state == ChallengeSent) VLog("Retransmitting LLQ setup challenge for %##s", e->qname.c); + else VLog("Sending LLQ setup challenge for %##s", e->qname.c); + + if (!mDNSOpaque64IsZero(&llq->id)) { Log("Error: LLQSetupChallenge - nonzero ID"); return; } // server bug + if (llq->llqOp != kLLQOp_Setup) { Log("LLQSetupChallenge - incorrrect operation from client"); return; } // client error + + if (mDNSOpaque64IsZero(&e->id)) // don't regenerate random ID for retransmissions + { + // construct ID <time><random> + gettimeofday(&t, NULL); + e->id.l[0] = t.tv_sec; + e->id.l[1] = random(); + } + + // format response (query + LLQ opt rr) + challenge.src.sin_addr.s_addr = 0; // unused + InitializeDNSMessage(&challenge.msg.h, msgID, ResponseFlags); + end = putQuestion(&challenge.msg, end, end + AbsoluteMaxDNSMessageData, &e->qname, e->qtype, kDNSClass_IN); + if (!end) { Log("Error: putQuestion"); return; } + FormatLLQOpt(&opt, kLLQOp_Setup, &e->id, LLQLease(e)); + end = PutResourceRecordTTLJumbo(&challenge.msg, end, &challenge.msg.h.numAdditionals, &opt.resrec, 0); + if (!end) { Log("Error: PutResourceRecordTTLJumbo"); return; } + challenge.len = (int)(end - (mDNSu8 *)&challenge.msg); + if (SendLLQ(d, &challenge, e->cli, NULL)) { Log("Error: LLQSetupChallenge"); return; } + e->state = ChallengeSent; +} + +// Take action on an LLQ message from client. Entry must be initialized and in table +mDNSlocal void UpdateLLQ(DaemonInfo *d, LLQEntry *e, LLQOptData *llq, mDNSOpaque16 msgID, TCPSocket *sock ) +{ + switch(e->state) + { + case RequestReceived: + if ( sock ) + { + struct timeval t; + gettimeofday(&t, NULL); + e->id.l[0] = t.tv_sec; // construct ID <time><random> + e->id.l[1] = random(); + llq->id = e->id; + LLQCompleteHandshake( d, e, llq, msgID, sock ); + + // Set the state to established because we've just set the LLQ up using TCP + e->state = Established; + } + else + { + LLQSetupChallenge(d, e, llq, msgID); + } + return; + case ChallengeSent: + if (mDNSOpaque64IsZero(&llq->id)) LLQSetupChallenge(d, e, llq, msgID); // challenge sent and lost + else LLQCompleteHandshake(d, e, llq, msgID, sock ); + return; + case Established: + if (mDNSOpaque64IsZero(&llq->id)) + { + // client started over. reset state. + LLQEntry *newe = NewLLQ(d, e->cli, &e->qname, e->qtype, llq->llqlease ); + if (!newe) return; + DeleteLLQ(d, e); + LLQSetupChallenge(d, newe, llq, msgID); + return; + } + else if (llq->llqOp == kLLQOp_Setup) + { LLQCompleteHandshake(d, e, llq, msgID, sock); return; } // Ack lost + else if (llq->llqOp == kLLQOp_Refresh) + { LLQRefresh(d, e, llq, msgID, sock); return; } + else { Log("Unhandled message for established LLQ"); return; } + } +} + +mDNSlocal LLQEntry *LookupLLQ(DaemonInfo *d, struct sockaddr_in cli, domainname *qname, mDNSu16 qtype, const mDNSOpaque64 *const id) +{ + int bucket = bucket = DomainNameHashValue(qname) % LLQ_TABLESIZE; + LLQEntry *ptr = d->LLQTable[bucket]; + + while(ptr) + { + if (((ptr->state == ChallengeSent && mDNSOpaque64IsZero(id) && (cli.sin_port == ptr->cli.sin_port)) || // zero-id due to packet loss OK in state ChallengeSent + mDNSSameOpaque64(id, &ptr->id)) && // id match + (cli.sin_addr.s_addr == ptr->cli.sin_addr.s_addr) && (qtype == ptr->qtype) && SameDomainName(&ptr->qname, qname)) // same source, type, qname + return ptr; + ptr = ptr->next; + } + return NULL; +} + +mDNSlocal int +RecvNotify +( + DaemonInfo * d, + PktMsg * pkt +) +{ + int res; + int err = 0; + + pkt->msg.h.flags.b[0] |= kDNSFlag0_QR_Response; + + res = sendto( d->udpsd, &pkt->msg, pkt->len, 0, ( struct sockaddr* ) &pkt->src, sizeof( pkt->src ) ); + require_action( res == ( int ) pkt->len, exit, err = mStatus_UnknownErr; LogErr( "RecvNotify", "sendto" ) ); + +exit: + + return err; +} + + +mDNSlocal int RecvLLQ( DaemonInfo *d, PktMsg *pkt, TCPSocket *sock ) +{ + DNSQuestion q; + LargeCacheRecord opt; + int i, err = -1; + char addr[32]; + const mDNSu8 *qptr = pkt->msg.data; + const mDNSu8 *end = (mDNSu8 *)&pkt->msg + pkt->len; + const mDNSu8 *aptr; + LLQOptData *llq = NULL; + LLQEntry *e = NULL; + + HdrNToH(pkt); + aptr = LocateAdditionals(&pkt->msg, end); // Can't do this until after HdrNToH(pkt); + inet_ntop(AF_INET, &pkt->src.sin_addr, addr, 32); + + VLog("Received LLQ msg from %s", addr); + // sanity-check packet + if (!pkt->msg.h.numQuestions || !pkt->msg.h.numAdditionals) + { + Log("Malformatted LLQ from %s with %d questions, %d additionals", addr, pkt->msg.h.numQuestions, pkt->msg.h.numAdditionals); + goto end; + } + + // Locate the OPT record. + // According to RFC 2671, "One OPT pseudo-RR can be added to the additional data section of either a request or a response." + // This implies that there may be *at most* one OPT record per DNS message, in the Additional Section, + // but not necessarily the *last* entry in the Additional Section. + for (i = 0; i < pkt->msg.h.numAdditionals; i++) + { + aptr = GetLargeResourceRecord(NULL, &pkt->msg, aptr, end, 0, kDNSRecordTypePacketAdd, &opt); + if (!aptr) { Log("Malformatted LLQ from %s: could not get Additional record %d", addr, i); goto end; } + if (opt.r.resrec.RecordType != kDNSRecordTypePacketNegative && opt.r.resrec.rrtype == kDNSType_OPT) break; + } + + // validate OPT + if (opt.r.resrec.rrtype != kDNSType_OPT) { Log("Malformatted LLQ from %s: last Additional not an OPT RR", addr); goto end; } + if (opt.r.resrec.rdlength < pkt->msg.h.numQuestions * DNSOpt_LLQData_Space) { Log("Malformatted LLQ from %s: OPT RR to small (%d bytes for %d questions)", addr, opt.r.resrec.rdlength, pkt->msg.h.numQuestions); } + + // dispatch each question + for (i = 0; i < pkt->msg.h.numQuestions; i++) + { + qptr = getQuestion(&pkt->msg, qptr, end, 0, &q); + if (!qptr) { Log("Malformatted LLQ from %s: cannot read question %d", addr, i); goto end; } + llq = (LLQOptData *)&opt.r.resrec.rdata->u.opt[0].u.llq + i; // point into OptData at index i + if (llq->vers != kLLQ_Vers) { Log("LLQ from %s contains bad version %d (expected %d)", addr, llq->vers, kLLQ_Vers); goto end; } + + e = LookupLLQ(d, pkt->src, &q.qname, q.qtype, &llq->id); + if (!e) + { + // no entry - if zero ID, create new + e = NewLLQ(d, pkt->src, &q.qname, q.qtype, llq->llqlease ); + if (!e) goto end; + } + UpdateLLQ(d, e, llq, pkt->msg.h.id, sock); + } + err = 0; + +end: + HdrHToN(pkt); + return err; +} + + +mDNSlocal mDNSBool IsAuthorized( DaemonInfo * d, PktMsg * pkt, DomainAuthInfo ** key, mDNSu16 * rcode, mDNSu16 * tcode ) +{ + const mDNSu8 * lastPtr = NULL; + const mDNSu8 * ptr = NULL; + DomainAuthInfo * keys; + mDNSu8 * end = ( mDNSu8* ) &pkt->msg + pkt->len; + LargeCacheRecord lcr; + mDNSBool hasTSIG = mDNSfalse; + mDNSBool strip = mDNSfalse; + mDNSBool ok = mDNSfalse; + int i; + + // Unused parameters + + ( void ) d; + + HdrNToH(pkt); + + *key = NULL; + + if ( pkt->msg.h.numAdditionals ) + { + ptr = LocateAdditionals(&pkt->msg, end); + if (ptr) + { + for (i = 0; i < pkt->msg.h.numAdditionals; i++) + { + lastPtr = ptr; + ptr = GetLargeResourceRecord(NULL, &pkt->msg, ptr, end, 0, kDNSRecordTypePacketAdd, &lcr); + if (!ptr) + { + Log("Unable to read additional record"); + lastPtr = NULL; + break; + } + } + + hasTSIG = ( ptr && lcr.r.resrec.RecordType != kDNSRecordTypePacketNegative && lcr.r.resrec.rrtype == kDNSType_TSIG ); + } + else + { + LogMsg( "IsAuthorized: unable to find Additional section" ); + } + } + + // If we don't know what zone this is, then it's authorized. + + if ( !pkt->zone ) + { + ok = mDNStrue; + strip = mDNSfalse; + goto exit; + } + + if ( IsQuery( pkt ) ) + { + keys = pkt->zone->queryKeys; + strip = mDNStrue; + } + else if ( IsUpdate( pkt ) ) + { + keys = pkt->zone->updateKeys; + strip = mDNSfalse; + } + else + { + ok = mDNStrue; + strip = mDNSfalse; + goto exit; + } + + if ( pkt->isZonePublic ) + { + ok = mDNStrue; + goto exit; + } + + // If there are no keys, then we're authorized + + if ( ( hasTSIG && !keys ) || ( !hasTSIG && keys ) ) + { + Log( "Invalid TSIG spec %##s for zone %##s", lcr.r.resrec.name->c, pkt->zone->name.c ); + *rcode = kDNSFlag1_RC_NotAuth; + *tcode = TSIG_ErrBadKey; + strip = mDNStrue; + ok = mDNSfalse; + goto exit; + } + + // Find the right key + + for ( *key = keys; *key; *key = (*key)->next ) + { + if ( SameDomainName( lcr.r.resrec.name, &(*key)->keyname ) ) + { + break; + } + } + + if ( !(*key) ) + { + Log( "Invalid TSIG name %##s for zone %##s", lcr.r.resrec.name->c, pkt->zone->name.c ); + *rcode = kDNSFlag1_RC_NotAuth; + *tcode = TSIG_ErrBadKey; + strip = mDNStrue; + ok = mDNSfalse; + goto exit; + } + + // Okay, we have the correct key and a TSIG record. DNSDigest_VerifyMessage does the heavy + // lifting of message verification + + pkt->msg.h.numAdditionals--; + + HdrHToN( pkt ); + + ok = DNSDigest_VerifyMessage( &pkt->msg, ( mDNSu8* ) lastPtr, &lcr, (*key), rcode, tcode ); + + HdrNToH( pkt ); + + pkt->msg.h.numAdditionals++; + +exit: + + if ( hasTSIG && strip ) + { + // Strip the TSIG from the message + + pkt->msg.h.numAdditionals--; + pkt->len = lastPtr - ( mDNSu8* ) ( &pkt->msg ); + } + + HdrHToN(pkt); + + return ok; +} + +// request handler wrappers for TCP and UDP requests +// (read message off socket, fork thread that invokes main processing routine and handles cleanup) + +mDNSlocal void* +UDPMessageHandler +( + void * vptr +) +{ + UDPContext * context = ( UDPContext* ) vptr; + PktMsg * reply = NULL; + int res; + mStatus err; + + // !!!KRS strictly speaking, we shouldn't use TCP for a UDP request because the server + // may give us a long answer that would require truncation for UDP delivery to client + + reply = HandleRequest( context->d, &context->pkt ); + require_action( reply, exit, err = mStatus_UnknownErr ); + + res = sendto( context->sd, &reply->msg, reply->len, 0, ( struct sockaddr* ) &context->pkt.src, sizeof( context->pkt.src ) ); + require_action_quiet( res == ( int ) reply->len, exit, LogErr( "UDPMessageHandler", "sendto" ) ); + +exit: + + if ( reply ) + { + free( reply ); + } + + free( context ); + + pthread_exit( NULL ); + + return NULL; +} + + +mDNSlocal int +RecvUDPMessage +( + DaemonInfo * self, + int sd +) +{ + UDPContext * context = NULL; + pthread_t tid; + mDNSu16 rcode; + mDNSu16 tcode; + DomainAuthInfo * key; + unsigned int clisize = sizeof( context->cliaddr ); + int res; + mStatus err = mStatus_NoError; + + context = malloc( sizeof( UDPContext ) ); + require_action( context, exit, err = mStatus_NoMemoryErr ; LogErr( "RecvUDPMessage", "malloc" ) ); + + mDNSPlatformMemZero( context, sizeof( *context ) ); + context->d = self; + context->sd = sd; + + res = recvfrom(sd, &context->pkt.msg, sizeof(context->pkt.msg), 0, (struct sockaddr *)&context->cliaddr, &clisize); + + require_action( res >= 0, exit, err = mStatus_UnknownErr ; LogErr( "RecvUDPMessage", "recvfrom" ) ); + context->pkt.len = res; + require_action( clisize == sizeof( context->cliaddr ), exit, err = mStatus_UnknownErr ; Log( "Client address of unknown size %d", clisize ) ); + context->pkt.src = context->cliaddr; + + // Set the zone in the packet + + SetZone( context->d, &context->pkt ); + + // Notify messages handled by main thread + + if ( IsNotify( &context->pkt ) ) + { + int e = RecvNotify( self, &context->pkt ); + free(context); + return e; + } + else if ( IsAuthorized( context->d, &context->pkt, &key, &rcode, &tcode ) ) + { + if ( IsLLQRequest( &context->pkt ) ) + { + // LLQ messages handled by main thread + int e = RecvLLQ( self, &context->pkt, NULL ); + free(context); + return e; + } + + if ( IsLLQAck(&context->pkt ) ) + { + // !!!KRS need to do acks + retrans + + free(context); + return 0; + } + + err = pthread_create( &tid, NULL, UDPMessageHandler, context ); + require_action( !err, exit, LogErr( "RecvUDPMessage", "pthread_create" ) ); + + pthread_detach(tid); + } + else + { + PktMsg reply; + int e; + + memcpy( &reply, &context->pkt, sizeof( PktMsg ) ); + + reply.msg.h.flags.b[0] = kDNSFlag0_QR_Response | kDNSFlag0_AA | kDNSFlag0_RD; + reply.msg.h.flags.b[1] = kDNSFlag1_RA | kDNSFlag1_RC_NXDomain; + + e = sendto( sd, &reply.msg, reply.len, 0, ( struct sockaddr* ) &context->pkt.src, sizeof( context->pkt.src ) ); + require_action_quiet( e == ( int ) reply.len, exit, LogErr( "RecvUDPMessage", "sendto" ) ); + + err = mStatus_NoAuth; + } + +exit: + + if ( err && context ) + { + free( context ); + } + + return err; +} + + +mDNSlocal void +FreeTCPContext +( + TCPContext * context +) +{ + if ( context ) + { + if ( context->sock ) + { + mDNSPlatformTCPCloseConnection( context->sock ); + } + + free( context ); + } +} + + +mDNSlocal void* +TCPMessageHandler +( + void * vptr +) +{ + TCPContext * context = ( TCPContext* ) vptr; + PktMsg * reply = NULL; + int res; + char buf[32]; + + //!!!KRS if this read blocks indefinitely, we can run out of threads + // read the request + + reply = HandleRequest( context->d, &context->pkt ); + require_action_quiet( reply, exit, LogMsg( "TCPMessageHandler: No reply for client %s", inet_ntop( AF_INET, &context->cliaddr.sin_addr, buf, 32 ) ) ); + + // deliver reply to client + + res = SendPacket( context->sock, reply ); + require_action( res >= 0, exit, LogMsg("TCPMessageHandler: Unable to send reply to client %s", inet_ntop(AF_INET, &context->cliaddr.sin_addr, buf, 32 ) ) ); + +exit: + + FreeTCPContext( context ); + + if ( reply ) + { + free( reply ); + } + + pthread_exit(NULL); +} + + +mDNSlocal void +RecvTCPMessage +( + void * param +) +{ + TCPContext * context = ( TCPContext* ) param; + mDNSu16 rcode; + mDNSu16 tcode; + pthread_t tid; + DomainAuthInfo * key; + PktMsg * pkt; + mDNSBool closed; + mDNSBool freeContext = mDNStrue; + mStatus err = mStatus_NoError; + + // Receive a packet. It's okay if we don't actually read a packet, as long as the closed flag is + // set to false. This is because SSL/TLS layer might gobble up the first packet that we read off the + // wire. We'll let it do that, and wait for the next packet which will be ours. + + pkt = RecvPacket( context->sock, &context->pkt, &closed ); + if (pkt) HdrNToH(pkt); + require_action( pkt || !closed, exit, err = mStatus_UnknownErr; LogMsg( "client disconnected" ) ); + + if ( pkt ) + { + // Always do this, regardless of what kind of packet it is. If we wanted LLQ events to be sent over TCP, + // we would change this line of code. As it is now, we will reply to an LLQ via TCP, but then events + // are sent over UDP + + RemoveSourceFromEventLoop( context->d, context->sock ); + + // Set's the DNS Zone that is associated with this message + + SetZone( context->d, &context->pkt ); + + // IsAuthorized will make sure the message is authorized for the designated zone. + // After verifying the signature, it will strip the TSIG from the message + + if ( IsAuthorized( context->d, &context->pkt, &key, &rcode, &tcode ) ) + { + if ( IsLLQRequest( &context->pkt ) ) + { + // LLQ messages handled by main thread + RecvLLQ( context->d, &context->pkt, context->sock); + } + else + { + err = pthread_create( &tid, NULL, TCPMessageHandler, context ); + + if ( err ) + { + LogErr( "RecvTCPMessage", "pthread_create" ); + err = mStatus_NoError; + goto exit; + } + + // Let the thread free the context + + freeContext = mDNSfalse; + + pthread_detach(tid); + } + } + else + { + PktMsg reply; + + LogMsg( "Client %s Not authorized for zone %##s", inet_ntoa( context->pkt.src.sin_addr ), pkt->zone->name.c ); + + memcpy( &reply, &context->pkt, sizeof( PktMsg ) ); + + reply.msg.h.flags.b[0] = kDNSFlag0_QR_Response | kDNSFlag0_AA | kDNSFlag0_RD; + reply.msg.h.flags.b[1] = kDNSFlag1_RA | kDNSFlag1_RC_Refused; + + SendPacket( context->sock, &reply ); + } + } + else + { + freeContext = mDNSfalse; + } + +exit: + + if ( err ) + { + RemoveSourceFromEventLoop( context->d, context->sock ); + } + + if ( freeContext ) + { + FreeTCPContext( context ); + } +} + + +mDNSlocal int +AcceptTCPConnection +( + DaemonInfo * self, + int sd, + TCPSocketFlags flags +) +{ + TCPContext * context = NULL; + unsigned int clilen = sizeof( context->cliaddr); + int newSock; + mStatus err = mStatus_NoError; + + context = ( TCPContext* ) malloc( sizeof( TCPContext ) ); + require_action( context, exit, err = mStatus_NoMemoryErr; LogErr( "AcceptTCPConnection", "malloc" ) ); + mDNSPlatformMemZero( context, sizeof( sizeof( TCPContext ) ) ); + context->d = self; + newSock = accept( sd, ( struct sockaddr* ) &context->cliaddr, &clilen ); + require_action( newSock != -1, exit, err = mStatus_UnknownErr; LogErr( "AcceptTCPConnection", "accept" ) ); + + context->sock = mDNSPlatformTCPAccept( flags, newSock ); + require_action( context->sock, exit, err = mStatus_UnknownErr; LogErr( "AcceptTCPConnection", "mDNSPlatformTCPAccept" ) ); + + err = AddSourceToEventLoop( self, context->sock, RecvTCPMessage, context ); + require_action( !err, exit, LogErr( "AcceptTCPConnection", "AddSourceToEventLoop" ) ); + +exit: + + if ( err && context ) + { + free( context ); + context = NULL; + } + + return err; +} + + +// main event loop +// listen for incoming requests, periodically check table for expired records, respond to signals +mDNSlocal int Run(DaemonInfo *d) +{ + int staticMaxFD, nfds; + fd_set rset; + struct timeval timenow, timeout, EventTS, tablecheck = { 0, 0 }; + mDNSBool EventsPending = mDNSfalse; + + VLog("Listening for requests..."); + + staticMaxFD = 0; + + if ( d->tcpsd + 1 > staticMaxFD ) staticMaxFD = d->tcpsd + 1; + if ( d->udpsd + 1 > staticMaxFD ) staticMaxFD = d->udpsd + 1; + if ( d->tlssd + 1 > staticMaxFD ) staticMaxFD = d->tlssd + 1; + if ( d->llq_tcpsd + 1 > staticMaxFD ) staticMaxFD = d->llq_tcpsd + 1; + if ( d->llq_udpsd + 1 > staticMaxFD ) staticMaxFD = d->llq_udpsd + 1; + if ( d->LLQEventListenSock + 1 > staticMaxFD ) staticMaxFD = d->LLQEventListenSock + 1; + + while(1) + { + EventSource * source; + int maxFD; + + // set timeout + timeout.tv_sec = timeout.tv_usec = 0; + if (gettimeofday(&timenow, NULL)) { LogErr("Run", "gettimeofday"); return -1; } + + if (EventsPending) + { + if (timenow.tv_sec - EventTS.tv_sec >= 5) // if we've been waiting 5 seconds for a "quiet" period to send + { GenLLQEvents(d); EventsPending = mDNSfalse; } // events, we go ahead and do it now + else timeout.tv_usec = 500000; // else do events after 1/2 second with no new events or LLQs + } + if (!EventsPending) + { + // if no pending events, timeout when we need to check for expired records + if (tablecheck.tv_sec && timenow.tv_sec - tablecheck.tv_sec >= 0) + { DeleteRecords(d, mDNSfalse); tablecheck.tv_sec = 0; } // table check overdue + if (!tablecheck.tv_sec) tablecheck.tv_sec = timenow.tv_sec + EXPIRATION_INTERVAL; + timeout.tv_sec = tablecheck.tv_sec - timenow.tv_sec; + } + + FD_ZERO(&rset); + FD_SET( d->tcpsd, &rset ); + FD_SET( d->udpsd, &rset ); + FD_SET( d->tlssd, &rset ); + FD_SET( d->llq_tcpsd, &rset ); + FD_SET( d->llq_udpsd, &rset ); + FD_SET( d->LLQEventListenSock, &rset ); + + maxFD = staticMaxFD; + + for ( source = ( EventSource* ) d->eventSources.Head; source; source = source->next ) + { + FD_SET( source->fd, &rset ); + + if ( source->fd > maxFD ) + { + maxFD = source->fd; + } + } + + nfds = select( maxFD + 1, &rset, NULL, NULL, &timeout); + if (nfds < 0) + { + if (errno == EINTR) + { + if (terminate) + { + // close sockets to prevent clients from making new requests during shutdown + close( d->tcpsd ); + close( d->udpsd ); + close( d->tlssd ); + close( d->llq_tcpsd ); + close( d->llq_udpsd ); + d->tcpsd = d->udpsd = d->tlssd = d->llq_tcpsd = d->llq_udpsd = -1; + DeleteRecords(d, mDNStrue); + return 0; + } + else if (dumptable) + { + Log( "Received SIGINFO" ); + + PrintLeaseTable(d); + PrintLLQTable(d); + PrintLLQAnswers(d); + dumptable = 0; + } + else if (hangup) + { + int err; + + Log( "Received SIGHUP" ); + + err = ParseConfig( d, cfgfile ); + + if ( err ) + { + LogErr( "Run", "ParseConfig" ); + return -1; + } + + hangup = 0; + } + else + { + Log("Received unhandled signal - continuing"); + } + } + else + { + LogErr("Run", "select"); return -1; + } + } + else if (nfds) + { + if (FD_ISSET(d->udpsd, &rset)) RecvUDPMessage( d, d->udpsd ); + if (FD_ISSET(d->llq_udpsd, &rset)) RecvUDPMessage( d, d->llq_udpsd ); + if (FD_ISSET(d->tcpsd, &rset)) AcceptTCPConnection( d, d->tcpsd, 0 ); + if (FD_ISSET(d->llq_tcpsd, &rset)) AcceptTCPConnection( d, d->llq_tcpsd, 0 ); + if (FD_ISSET(d->tlssd, &rset)) AcceptTCPConnection( d, d->tlssd, TCP_SOCKET_FLAGS ); + if (FD_ISSET(d->LLQEventListenSock, &rset)) + { + // clear signalling data off socket + char buf[256]; + recv(d->LLQEventListenSock, buf, 256, 0); + if (!EventsPending) + { + EventsPending = mDNStrue; + if (gettimeofday(&EventTS, NULL)) { LogErr("Run", "gettimeofday"); return -1; } + } + } + + for ( source = ( EventSource* ) d->eventSources.Head; source; source = source->next ) + { + if ( FD_ISSET( source->fd, &rset ) ) + { + source->callback( source->context ); + break; // in case we removed this guy from the event loop + } + } + } + else + { + // timeout + if (EventsPending) { GenLLQEvents(d); EventsPending = mDNSfalse; } + else { DeleteRecords(d, mDNSfalse); tablecheck.tv_sec = 0; } + } + } + return 0; +} + +// signal handler sets global variables, which are inspected by main event loop +// (select automatically returns due to the handled signal) +mDNSlocal void HndlSignal(int sig) +{ + if (sig == SIGTERM || sig == SIGINT ) { terminate = 1; return; } + if (sig == INFO_SIGNAL) { dumptable = 1; return; } + if (sig == SIGHUP) { hangup = 1; return; } +} + +mDNSlocal mStatus +SetPublicSRV +( + DaemonInfo * d, + const char * name +) +{ + DNameListElem * elem; + mStatus err = mStatus_NoError; + + elem = ( DNameListElem* ) malloc( sizeof( DNameListElem ) ); + require_action( elem, exit, err = mStatus_NoMemoryErr ); + MakeDomainNameFromDNSNameString( &elem->name, name ); + elem->next = d->public_names; + d->public_names = elem; + +exit: + + return err; +} + + +int main(int argc, char *argv[]) +{ + int started_via_launchd = 0; + DaemonInfo *d; + struct rlimit rlim; + + Log("dnsextd starting"); + + d = malloc(sizeof(*d)); + if (!d) { LogErr("main", "malloc"); exit(1); } + mDNSPlatformMemZero(d, sizeof(DaemonInfo)); + + // Setup the public SRV record names + + SetPublicSRV(d, "_dns-update._udp."); + SetPublicSRV(d, "_dns-llq._udp."); + SetPublicSRV(d, "_dns-update-tls._tcp."); + SetPublicSRV(d, "_dns-query-tls._tcp."); + SetPublicSRV(d, "_dns-llq-tls._tcp."); + + // Setup signal handling + + if (signal(SIGHUP, HndlSignal) == SIG_ERR) perror("Can't catch SIGHUP"); + if (signal(SIGTERM, HndlSignal) == SIG_ERR) perror("Can't catch SIGTERM"); + if (signal(INFO_SIGNAL, HndlSignal) == SIG_ERR) perror("Can't catch SIGINFO"); + if (signal(SIGINT, HndlSignal) == SIG_ERR) perror("Can't catch SIGINT"); + if (signal(SIGPIPE, SIG_IGN ) == SIG_ERR) perror("Can't ignore SIGPIPE"); + + // remove open file limit + rlim.rlim_max = RLIM_INFINITY; + rlim.rlim_cur = RLIM_INFINITY; + if (setrlimit(RLIMIT_NOFILE, &rlim) < 0) + { + LogErr("main", "setrlimit"); + Log("Using default file descriptor resource limit"); + } + + if (argc > 1 && !strcasecmp(argv[1], "-launchd")) + { + Log("started_via_launchd"); + started_via_launchd = 1; + argv++; + argc--; + } + if (ProcessArgs(argc, argv, d) < 0) { LogErr("main", "ProcessArgs"); exit(1); } + + if (!foreground && !started_via_launchd) + { + if (daemon(0,0)) + { + LogErr("main", "daemon"); + foreground = 1; + } + } + + if (InitLeaseTable(d) < 0) { LogErr("main", "InitLeaseTable"); exit(1); } + if (SetupSockets(d) < 0) { LogErr("main", "SetupSockets"); exit(1); } + if (SetUpdateSRV(d) < 0) { LogErr("main", "SetUpdateSRV"); exit(1); } + + Run(d); + + Log("dnsextd stopping"); + + if (ClearUpdateSRV(d) < 0) { LogErr("main", "ClearUpdateSRV"); exit(1); } // clear update srv's even if Run or pthread_create returns an error + free(d); + exit(0); +} + + +// These are stubbed out implementations of up-call routines that the various platform support layers +// call. These routines are fully implemented in both mDNS.c and uDNS.c, but dnsextd doesn't +// link this code in. +// +// It's an error for these routines to actually be called, so perhaps we should log any call +// to them. +void mDNSCoreInitComplete( mDNS * const m, mStatus result) { ( void ) m; ( void ) result; } +void mDNS_ConfigChanged(mDNS *const m) { ( void ) m; } +void mDNSCoreMachineSleep(mDNS * const m, mDNSBool wake) { ( void ) m; ( void ) wake; } +void mDNSCoreReceive(mDNS *const m, void *const msg, const mDNSu8 *const end, + const mDNSAddr *const srcaddr, const mDNSIPPort srcport, + const mDNSAddr *const dstaddr, const mDNSIPPort dstport, const mDNSInterfaceID iid) +{ ( void ) m; ( void ) msg; ( void ) end; ( void ) srcaddr; ( void ) srcport; ( void ) dstaddr; ( void ) dstport; ( void ) iid; } +DNSServer *mDNS_AddDNSServer(mDNS *const m, const domainname *d, const mDNSInterfaceID interface, const int serviceID, const mDNSAddr *addr, const mDNSIPPort port, + mDNSu32 scoped, mDNSu32 timeout, mDNSBool cellIntf, mDNSu16 resGroupID, mDNSBool reqA, mDNSBool reqAAAA, mDNSBool reqDO) +{ ( void ) m; ( void ) d; ( void ) interface; ( void ) serviceID; ( void ) addr; ( void ) port; ( void ) scoped; ( void ) timeout; (void) cellIntf; + (void) resGroupID; (void) reqA; (void) reqAAAA; (void) reqDO; return(NULL); } +void mDNS_AddSearchDomain(const domainname *const domain, mDNSInterfaceID InterfaceID) { (void)domain; (void) InterfaceID;} +void mDNS_AddDynDNSHostName(mDNS *m, const domainname *fqdn, mDNSRecordCallback *StatusCallback, const void *StatusContext) +{ ( void ) m; ( void ) fqdn; ( void ) StatusCallback; ( void ) StatusContext; } +mDNSs32 mDNS_Execute (mDNS *const m) { ( void ) m; return 0; } +mDNSs32 mDNS_TimeNow(const mDNS *const m) { ( void ) m; return 0; } +mStatus mDNS_Deregister(mDNS *const m, AuthRecord *const rr) { ( void ) m; ( void ) rr; return 0; } +void mDNS_DeregisterInterface(mDNS *const m, NetworkInterfaceInfo *set, mDNSBool flapping) +{ ( void ) m; ( void ) set; ( void ) flapping; } +const char * const mDNS_DomainTypeNames[1] = {}; +mStatus mDNS_GetDomains(mDNS *const m, DNSQuestion *const question, mDNS_DomainType DomainType, const domainname *dom, + const mDNSInterfaceID InterfaceID, mDNSQuestionCallback *Callback, void *Context) +{ ( void ) m; ( void ) question; ( void ) DomainType; ( void ) dom; ( void ) InterfaceID; ( void ) Callback; ( void ) Context; return 0; } +mStatus mDNS_Register(mDNS *const m, AuthRecord *const rr) { ( void ) m; ( void ) rr; return 0; } +mStatus mDNS_RegisterInterface(mDNS *const m, NetworkInterfaceInfo *set, mDNSBool flapping) +{ ( void ) m; ( void ) set; ( void ) flapping; return 0; } +void mDNS_RemoveDynDNSHostName(mDNS *m, const domainname *fqdn) { ( void ) m; ( void ) fqdn; } +void mDNS_SetFQDN(mDNS * const m) { ( void ) m; } +void mDNS_SetPrimaryInterfaceInfo(mDNS *m, const mDNSAddr *v4addr, const mDNSAddr *v6addr, const mDNSAddr *router) +{ ( void ) m; ( void ) v4addr; ( void ) v6addr; ( void ) router; } +mStatus uDNS_SetupDNSConfig( mDNS *const m ) { ( void ) m; return 0; } +mStatus mDNS_SetSecretForDomain(mDNS *m, DomainAuthInfo *info, + const domainname *domain, const domainname *keyname, const char *b64keydata, const domainname *hostname, mDNSIPPort *port, mDNSBool autoTunnel) +{ ( void ) m; ( void ) info; ( void ) domain; ( void ) keyname; ( void ) b64keydata; ( void ) hostname; (void) port; ( void ) autoTunnel; return 0; } +mStatus mDNS_StopQuery(mDNS *const m, DNSQuestion *const question) { ( void ) m; ( void ) question; return 0; } +void TriggerEventCompletion(void); +void TriggerEventCompletion() {} +int AnonInfoAnswersQuestion(const ResourceRecord *const rr, const DNSQuestion *const q); +int AnonInfoAnswersQuestion(const ResourceRecord *const rr, const DNSQuestion *const q) { ( void ) rr; ( void ) q; return 1;} +mDNS mDNSStorage; + + +// For convenience when using the "strings" command, this is the last thing in the file +// The "@(#) " pattern is a special prefix the "what" command looks for +const char mDNSResponderVersionString_SCCS[] = "@(#) dnsextd " STRINGIFY(mDNSResponderVersion) " (" __DATE__ " " __TIME__ ")"; + +#if _BUILDING_XCODE_PROJECT_ +// If the process crashes, then this string will be magically included in the automatically-generated crash log +const char *__crashreporter_info__ = mDNSResponderVersionString_SCCS + 5; +asm (".desc ___crashreporter_info__, 0x10"); +#endif diff --git a/mDNSResponder/mDNSShared/dnsextd.conf b/mDNSResponder/mDNSShared/dnsextd.conf new file mode 100644 index 00000000..0379580d --- /dev/null +++ b/mDNSResponder/mDNSShared/dnsextd.conf @@ -0,0 +1,60 @@ +// ---------------------------------------------------------------------------- +// +// Instructions for /etc/dnsextd.conf (this file) +// +// In most cases, you should not need to change these default options in +// the "options" section below. The dnsextd daemon will receive DNS packets +// on port 53, and forward them on as appropriate to BIND on localhost:5030. +// +// You need to edit the "zone" statement below to give the name of your +// dynamic zone that will be accepting Wide-Area Bonjour DNS updates. +// +// ---------------------------------------------------------------------------- +// +// Instructions for /etc/named.conf +// +// In /etc/named.conf you will need to modify the "options" section to +// tell BIND to accept packets from localhost:5030, like this: +// +// listen-on port 5030 { 127.0.0.1; }; +// +// You also need a "zone" statement in /etc/named.conf to tell BIND the update +// policy for your dynamic zone. For example, within a small closed private +// network, you might allow anyone to perform updates. To do that, you just +// permit any and all updates coming from dnsextd on the same machine: +// +// zone "my-dynamic-subdomain.company.com." +// { type master; file "db.xxx"; allow-update { 127.0.0.1; }; }; +// +// On a machine connected to the Internet or other large open network, +// you'll want to limit updates to only users with keys. For example, +// you could choose to allow anyone with a DNS key on your server to +// perform updates in your dynamic zone, like this: +// +// key keyname. { algorithm hmac-md5; secret "abcdefghijklmnopqrstuv=="; }; +// zone "my-dynamic-subdomain.company.com." in +// { +// type master; +// file "db.my-dynamic-subdomain.company.com"; +// update-policy { grant * wildcard *.my-dynamic-subdomain.company.com.; }; +// }; +// +// You could use a single key which you give to all authorized users, but +// it is better (though more work) to create a unique key for each user. +// +// ---------------------------------------------------------------------------- + +options { +// This defaults to: * port 53 +// listen-on port 53 { 192.168.2.10; 127.0.0.1; }; +// This defaults to: 127.0.0.1:5030 +// nameserver address 127.0.0.1 port 5030; +// This defaults to: 5533 +// private port 5533; +// This defaults to: 5352 +// llq port 5352; +}; + +zone "my-dynamic-subdomain.company.com." { + type public; +}; diff --git a/mDNSResponder/mDNSShared/dnsextd.h b/mDNSResponder/mDNSShared/dnsextd.h new file mode 100644 index 00000000..67927c9b --- /dev/null +++ b/mDNSResponder/mDNSShared/dnsextd.h @@ -0,0 +1,163 @@ +/* -*- Mode: C; tab-width: 4 -*- + * + * Copyright (c) 2006 Apple Computer, Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#ifndef _dnsextd_h +#define _dnsextd_h + + +#include <mDNSEmbeddedAPI.h> +#include <DNSCommon.h> +#include <GenLinkedList.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> + + +#define LLQ_TABLESIZE 1024 // !!!KRS make this dynamically growable + + +typedef enum DNSZoneSpecType +{ + kDNSZonePublic, + kDNSZonePrivate +} DNSZoneSpecType; + + +typedef struct DNSZone +{ + domainname name; + DNSZoneSpecType type; + DomainAuthInfo * updateKeys; // linked list of keys for signing deletion updates + DomainAuthInfo * queryKeys; // linked list of keys for queries + struct DNSZone * next; +} DNSZone; + + +typedef struct +{ + struct sockaddr_in src; + size_t len; + DNSZone * zone; + mDNSBool isZonePublic; + DNSMessage msg; + // Note: extra storage for oversized (TCP) messages goes here +} PktMsg; + +// lease table entry +typedef struct RRTableElem +{ + struct RRTableElem *next; + struct sockaddr_in cli; // client's source address + long expire; // expiration time, in seconds since epoch + domainname zone; // from zone field of update message + domainname name; // name of the record + CacheRecord rr; // last field in struct allows for allocation of oversized RRs +} RRTableElem; + +typedef enum +{ + RequestReceived = 0, + ChallengeSent = 1, + Established = 2 +} LLQState; + +typedef struct AnswerListElem +{ + struct AnswerListElem *next; + domainname name; + mDNSu16 type; + CacheRecord *KnownAnswers; // All valid answers delivered to client + CacheRecord *EventList; // New answers (adds/removes) to be sent to client + int refcount; + mDNSBool UseTCP; // Use TCP if UDP would cause truncation + pthread_t tid; // Allow parallel list updates +} AnswerListElem; + +// llq table entry +typedef struct LLQEntry +{ + struct LLQEntry *next; + struct sockaddr_in cli; // clien'ts source address + domainname qname; + mDNSu16 qtype; + mDNSOpaque64 id; + LLQState state; + mDNSu32 lease; // original lease, in seconds + mDNSs32 expire; // expiration, absolute, in seconds since epoch + AnswerListElem *AnswerList; +} LLQEntry; + + +typedef void (*EventCallback)( void * context ); + +typedef struct EventSource +{ + EventCallback callback; + void * context; + TCPSocket * sock; + int fd; + mDNSBool markedForDeletion; + struct EventSource * next; +} EventSource; + + +// daemon-wide information +typedef struct +{ + // server variables - read only after initialization (no locking) + struct sockaddr_in addr; // the address we will bind to + struct sockaddr_in llq_addr; // the address we will receive llq requests on. + struct sockaddr_in ns_addr; // the real ns server address + int tcpsd; // listening TCP socket for dns requests + int udpsd; // listening UDP socket for dns requests + int tlssd; // listening TCP socket for private browsing + int llq_tcpsd; // listening TCP socket for llq service + int llq_udpsd; // listening UDP socket for llq service + DNameListElem * public_names; // list of public SRV names + DNSZone * zones; + + // daemon variables - read only after initialization (no locking) + mDNSIPPort private_port; // listening port for private messages + mDNSIPPort llq_port; // listening port for llq + + // lease table variables (locked via mutex after initialization) + RRTableElem **table; // hashtable for records with leases + pthread_mutex_t tablelock; // mutex for lease table + mDNSs32 nbuckets; // buckets allocated + mDNSs32 nelems; // elements in table + + // LLQ table variables + LLQEntry *LLQTable[LLQ_TABLESIZE]; // !!!KRS change this and RRTable to use a common data structure + AnswerListElem *AnswerTable[LLQ_TABLESIZE]; + int AnswerTableCount; + int LLQEventNotifySock; // Unix domain socket pair - update handling thread writes to EventNotifySock, which wakes + int LLQEventListenSock; // the main thread listening on EventListenSock, indicating that the zone has changed + + GenLinkedList eventSources; // linked list of EventSource's +} DaemonInfo; + + +int +ParseConfig +( + DaemonInfo * d, + const char * file +); + + +#endif diff --git a/mDNSResponder/mDNSShared/dnsextd_lexer.l b/mDNSResponder/mDNSShared/dnsextd_lexer.l new file mode 100644 index 00000000..5cac1060 --- /dev/null +++ b/mDNSResponder/mDNSShared/dnsextd_lexer.l @@ -0,0 +1,84 @@ +/* -*- Mode: C; tab-width: 4 -*- + * + * Copyright (c) 2006-2011 Apple Computer, Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +%{ +#include <string.h> +#include <stdio.h> +#include "dnsextd_parser.h" + + +extern YYSTYPE yylval; + +/* Mac OS X 10.4 has flex version 2.5.4, which doesn't define yylineno for us */ +/* Mac OS X 10.5 has flex version 2.5.33, which does define yylineno */ +#if YY_FLEX_MAJOR_VERSION <= 2 && YY_FLEX_MINOR_VERSION <= 5 && YY_FLEX_SUBMINOR_VERSION <= 4 +int yylineno = 1; +#endif +#define YY_NO_INPUT 1 +int yylex(void); + +static char* +StripQuotes + ( + const char * string + ) +{ + char * dup; + + dup = strdup( string + 1); + + dup[ strlen( dup ) - 1 ] = '\0'; + + return dup; +} + + +%} + +%option nounput +%% + +options return OPTIONS; +listen-on return LISTEN_ON; +nameserver return NAMESERVER; +port return PORT; +address return ADDRESS; +llq return LLQ; +public return PUBLIC; +private return PRIVATE; +key return KEY; +allow-update return ALLOWUPDATE; +allow-query return ALLOWQUERY; +algorithm return ALGORITHM; +secret return SECRET; +zone return ZONE; +type return TYPE; +allow return ALLOW; +\{ return OBRACE; +\} return EBRACE; +; return SEMICOLON; +IN return IN; +\* yylval.string = strdup(yytext); return WILDCARD; +[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+ yylval.string = strdup(yytext); return DOTTED_DECIMAL_ADDRESS; +[0123456789]+ yylval.number = atoi(yytext); return NUMBER; +[a-zA-Z0-9]+(\.[a-zA-Z0-9]+)* yylval.string = strdup(yytext); return HOSTNAME; +[a-zA-Z0-9\.]+([a-zA-Z0-9\.]+)* yylval.string = strdup(yytext); return DOMAINNAME; +\"([^"\\\r\n]*(\\.[^"\\\r\n]*)*)\" yylval.string = StripQuotes(yytext); return QUOTEDSTRING; +[\/][\/].* /* ignore C++ style comments */; +\n yylineno++; /* ignore EOL */; +[ \t]+ /* ignore whitespace */; +%% diff --git a/mDNSResponder/mDNSShared/dnsextd_parser.y b/mDNSResponder/mDNSShared/dnsextd_parser.y new file mode 100644 index 00000000..18c5990f --- /dev/null +++ b/mDNSResponder/mDNSShared/dnsextd_parser.y @@ -0,0 +1,585 @@ +/* -*- Mode: C; tab-width: 4 -*- + * + * Copyright (c) 2006 Apple Computer, Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +%{ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include "mDNSEmbeddedAPI.h" +#include "DebugServices.h" +#include "dnsextd.h" + +void yyerror( const char* error ); +int yylex(void); + + +typedef struct StringListElem +{ + char * string; + struct StringListElem * next; +} StringListElem; + + +typedef struct OptionsInfo +{ + char server_address[ 256 ]; + int server_port; + char source_address[ 256 ]; + int source_port; + int private_port; + int llq_port; +} OptionsInfo; + + +typedef struct ZoneInfo +{ + char name[ 256 ]; + char certificate_name[ 256 ]; + char allow_clients_file[ 256 ]; + char allow_clients[ 256 ]; + char key[ 256 ]; +} ZoneInfo; + + +typedef struct KeySpec +{ + char name[ 256 ]; + char algorithm[ 256 ]; + char secret[ 256 ]; + struct KeySpec * next; +} KeySpec; + + +typedef struct ZoneSpec +{ + char name[ 256 ]; + DNSZoneSpecType type; + StringListElem * allowUpdate; + StringListElem * allowQuery; + char key[ 256 ]; + struct ZoneSpec * next; +} ZoneSpec; + + +static StringListElem * g_stringList = NULL; +static KeySpec * g_keys; +static ZoneSpec * g_zones; +static ZoneSpec g_zoneSpec; +static const char * g_filename; + +#define YYPARSE_PARAM context + +void +SetupOptions + ( + OptionsInfo * info, + void * context + ); + +%} + +%union +{ + int number; + char * string; +} + +%token OPTIONS +%token LISTEN_ON +%token NAMESERVER +%token PORT +%token ADDRESS +%token LLQ +%token PUBLIC +%token PRIVATE +%token ALLOWUPDATE +%token ALLOWQUERY +%token KEY +%token ALGORITHM +%token SECRET +%token ISSUER +%token SERIAL +%token ZONE +%token TYPE +%token ALLOW +%token OBRACE +%token EBRACE +%token SEMICOLON +%token IN +%token <string> DOTTED_DECIMAL_ADDRESS +%token <string> WILDCARD +%token <string> DOMAINNAME +%token <string> HOSTNAME +%token <string> QUOTEDSTRING +%token <number> NUMBER + +%type <string> addressstatement +%type <string> networkaddress + +%% + +commands: + | + commands command SEMICOLON + ; + + +command: + options_set + | + zone_set + | + key_set + ; + + +options_set: + OPTIONS optionscontent + { + // SetupOptions( &g_optionsInfo, context ); + } + ; + +optionscontent: + OBRACE optionsstatements EBRACE + ; + +optionsstatements: + | + optionsstatements optionsstatement SEMICOLON + ; + + +optionsstatement: + statements + | + LISTEN_ON addresscontent + { + } + | + LISTEN_ON PORT NUMBER addresscontent + { + } + | + NAMESERVER ADDRESS networkaddress + { + } + | + NAMESERVER ADDRESS networkaddress PORT NUMBER + { + } + | + PRIVATE PORT NUMBER + { + ( ( DaemonInfo* ) context )->private_port = mDNSOpaque16fromIntVal( $3 ); + } + | + LLQ PORT NUMBER + { + ( ( DaemonInfo* ) context )->llq_port = mDNSOpaque16fromIntVal( $3 ); + } + ; + +key_set: + KEY QUOTEDSTRING OBRACE SECRET QUOTEDSTRING SEMICOLON EBRACE + { + KeySpec * keySpec; + + keySpec = ( KeySpec* ) malloc( sizeof( KeySpec ) ); + + if ( !keySpec ) + { + LogMsg("ERROR: memory allocation failure"); + YYABORT; + } + + strncpy( keySpec->name, $2, sizeof( keySpec->name ) ); + strncpy( keySpec->secret, $5, sizeof( keySpec->secret ) ); + + keySpec->next = g_keys; + g_keys = keySpec; + } + ; + +zone_set: + ZONE QUOTEDSTRING zonecontent + { + ZoneSpec * zoneSpec; + + zoneSpec = ( ZoneSpec* ) malloc( sizeof( ZoneSpec ) ); + + if ( !zoneSpec ) + { + LogMsg("ERROR: memory allocation failure"); + YYABORT; + } + + strncpy( zoneSpec->name, $2, sizeof( zoneSpec->name ) ); + zoneSpec->type = g_zoneSpec.type; + strcpy( zoneSpec->key, g_zoneSpec.key ); + zoneSpec->allowUpdate = g_zoneSpec.allowUpdate; + zoneSpec->allowQuery = g_zoneSpec.allowQuery; + + zoneSpec->next = g_zones; + g_zones = zoneSpec; + } + | + ZONE QUOTEDSTRING IN zonecontent + { + ZoneSpec * zoneSpec; + + zoneSpec = ( ZoneSpec* ) malloc( sizeof( ZoneSpec ) ); + + if ( !zoneSpec ) + { + LogMsg("ERROR: memory allocation failure"); + YYABORT; + } + + strncpy( zoneSpec->name, $2, sizeof( zoneSpec->name ) ); + zoneSpec->type = g_zoneSpec.type; + strcpy( zoneSpec->key, g_zoneSpec.key ); + zoneSpec->allowUpdate = g_zoneSpec.allowUpdate; + zoneSpec->allowQuery = g_zoneSpec.allowQuery; + + zoneSpec->next = g_zones; + g_zones = zoneSpec; + } + ; + +zonecontent: + OBRACE zonestatements EBRACE + +zonestatements: + | + zonestatements zonestatement SEMICOLON + ; + +zonestatement: + TYPE PUBLIC + { + g_zoneSpec.type = kDNSZonePublic; + } + | + TYPE PRIVATE + { + g_zoneSpec.type = kDNSZonePrivate; + } + | + ALLOWUPDATE keycontent + { + g_zoneSpec.allowUpdate = g_stringList; + g_stringList = NULL; + } + | + ALLOWQUERY keycontent + { + g_zoneSpec.allowQuery = g_stringList; + g_stringList = NULL; + } + ; + +addresscontent: + OBRACE addressstatements EBRACE + { + } + +addressstatements: + | + addressstatements addressstatement SEMICOLON + { + } + ; + +addressstatement: + DOTTED_DECIMAL_ADDRESS + { + } + ; + + +keycontent: + OBRACE keystatements EBRACE + { + } + +keystatements: + | + keystatements keystatement SEMICOLON + { + } + ; + +keystatement: + KEY DOMAINNAME + { + StringListElem * elem; + + elem = ( StringListElem* ) malloc( sizeof( StringListElem ) ); + + if ( !elem ) + { + LogMsg("ERROR: memory allocation failure"); + YYABORT; + } + + elem->string = $2; + + elem->next = g_stringList; + g_stringList = elem; + } + ; + + +networkaddress: + DOTTED_DECIMAL_ADDRESS + | + HOSTNAME + | + WILDCARD + ; + +block: + OBRACE zonestatements EBRACE SEMICOLON + ; + +statements: + | + statements statement + ; + +statement: + block + { + $<string>$ = NULL; + } + | + QUOTEDSTRING + { + $<string>$ = $1; + } +%% + +int yywrap(void); + +extern int yylineno; + +void yyerror( const char *str ) +{ + fprintf( stderr,"%s:%d: error: %s\n", g_filename, yylineno, str ); +} + +int yywrap() +{ + return 1; +} + + +int +ParseConfig + ( + DaemonInfo * d, + const char * file + ) + { + extern FILE * yyin; + DNSZone * zone; + DomainAuthInfo * key; + KeySpec * keySpec; + ZoneSpec * zoneSpec; + int err = 0; + + g_filename = file; + + // Tear down the current zone specifiers + + zone = d->zones; + + while ( zone ) + { + DNSZone * next = zone->next; + + key = zone->updateKeys; + + while ( key ) + { + DomainAuthInfo * nextKey = key->next; + + free( key ); + + key = nextKey; + } + + key = zone->queryKeys; + + while ( key ) + { + DomainAuthInfo * nextKey = key->next; + + free( key ); + + key = nextKey; + } + + free( zone ); + + zone = next; + } + + d->zones = NULL; + + yyin = fopen( file, "r" ); + require_action( yyin, exit, err = 0 ); + + err = yyparse( ( void* ) d ); + require_action( !err, exit, err = 1 ); + + for ( zoneSpec = g_zones; zoneSpec; zoneSpec = zoneSpec->next ) + { + StringListElem * elem; + mDNSu8 * ok; + + zone = ( DNSZone* ) malloc( sizeof( DNSZone ) ); + require_action( zone, exit, err = 1 ); + memset( zone, 0, sizeof( DNSZone ) ); + + zone->next = d->zones; + d->zones = zone; + + // Fill in the domainname + + ok = MakeDomainNameFromDNSNameString( &zone->name, zoneSpec->name ); + require_action( ok, exit, err = 1 ); + + // Fill in the type + + zone->type = zoneSpec->type; + + // Fill in the allow-update keys + + for ( elem = zoneSpec->allowUpdate; elem; elem = elem->next ) + { + mDNSBool found = mDNSfalse; + + for ( keySpec = g_keys; keySpec; keySpec = keySpec->next ) + { + if ( strcmp( elem->string, keySpec->name ) == 0 ) + { + DomainAuthInfo * authInfo = malloc( sizeof( DomainAuthInfo ) ); + mDNSs32 keylen; + require_action( authInfo, exit, err = 1 ); + memset( authInfo, 0, sizeof( DomainAuthInfo ) ); + + ok = MakeDomainNameFromDNSNameString( &authInfo->keyname, keySpec->name ); + if (!ok) { free(authInfo); err = 1; goto exit; } + + keylen = DNSDigest_ConstructHMACKeyfromBase64( authInfo, keySpec->secret ); + if (keylen < 0) { free(authInfo); err = 1; goto exit; } + + authInfo->next = zone->updateKeys; + zone->updateKeys = authInfo; + + found = mDNStrue; + + break; + } + } + + // Log this + require_action( found, exit, err = 1 ); + } + + // Fill in the allow-query keys + + for ( elem = zoneSpec->allowQuery; elem; elem = elem->next ) + { + mDNSBool found = mDNSfalse; + + for ( keySpec = g_keys; keySpec; keySpec = keySpec->next ) + { + if ( strcmp( elem->string, keySpec->name ) == 0 ) + { + DomainAuthInfo * authInfo = malloc( sizeof( DomainAuthInfo ) ); + mDNSs32 keylen; + require_action( authInfo, exit, err = 1 ); + memset( authInfo, 0, sizeof( DomainAuthInfo ) ); + + ok = MakeDomainNameFromDNSNameString( &authInfo->keyname, keySpec->name ); + if (!ok) { free(authInfo); err = 1; goto exit; } + + keylen = DNSDigest_ConstructHMACKeyfromBase64( authInfo, keySpec->secret ); + if (keylen < 0) { free(authInfo); err = 1; goto exit; } + + authInfo->next = zone->queryKeys; + zone->queryKeys = authInfo; + + found = mDNStrue; + + break; + } + } + + // Log this + require_action( found, exit, err = 1 ); + } + } + +exit: + + return err; + } + + +void +SetupOptions + ( + OptionsInfo * info, + void * context + ) + { + DaemonInfo * d = ( DaemonInfo* ) context; + + if ( strlen( info->source_address ) ) + { + inet_pton( AF_INET, info->source_address, &d->addr.sin_addr ); + } + + if ( info->source_port ) + { + d->addr.sin_port = htons( ( mDNSu16 ) info->source_port ); + } + + if ( strlen( info->server_address ) ) + { + inet_pton( AF_INET, info->server_address, &d->ns_addr.sin_addr ); + } + + if ( info->server_port ) + { + d->ns_addr.sin_port = htons( ( mDNSu16 ) info->server_port ); + } + + if ( info->private_port ) + { + d->private_port = mDNSOpaque16fromIntVal( info->private_port ); + } + + if ( info->llq_port ) + { + d->llq_port = mDNSOpaque16fromIntVal( info->llq_port ); + } + } diff --git a/mDNSResponder/mDNSShared/dnssd_clientlib.c b/mDNSResponder/mDNSShared/dnssd_clientlib.c new file mode 100644 index 00000000..cca58853 --- /dev/null +++ b/mDNSResponder/mDNSShared/dnssd_clientlib.c @@ -0,0 +1,366 @@ +/* -*- Mode: C; tab-width: 4 -*- + * + * Copyright (c) 2004, Apple Computer, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <stdlib.h> +#include <string.h> + +#include "dns_sd.h" + +#if MDNS_BUILDINGSHAREDLIBRARY || MDNS_BUILDINGSTUBLIBRARY +#pragma export on +#endif + +#if defined(_WIN32) +// disable warning "conversion from <data> to uint16_t" +#pragma warning(disable:4244) +#define strncasecmp _strnicmp +#define strcasecmp _stricmp +#endif + +/********************************************************************************************* +* +* Supporting Functions +* +*********************************************************************************************/ + +#define mDNSIsDigit(X) ((X) >= '0' && (X) <= '9') + +// DomainEndsInDot returns 1 if name ends with a dot, 0 otherwise +// (DNSServiceConstructFullName depends this returning 1 for true, rather than any non-zero value meaning true) + +static int DomainEndsInDot(const char *dom) +{ + while (dom[0] && dom[1]) + { + if (dom[0] == '\\') // advance past escaped byte sequence + { + if (mDNSIsDigit(dom[1]) && mDNSIsDigit(dom[2]) && mDNSIsDigit(dom[3])) + dom += 4; // If "\ddd" then skip four + else dom += 2; // else if "\x" then skip two + } + else dom++; // else goto next character + } + return (dom[0] == '.'); +} + +static uint8_t *InternalTXTRecordSearch +( + uint16_t txtLen, + const void *txtRecord, + const char *key, + unsigned long *keylen +) +{ + uint8_t *p = (uint8_t*)txtRecord; + uint8_t *e = p + txtLen; + *keylen = (unsigned long) strlen(key); + while (p<e) + { + uint8_t *x = p; + p += 1 + p[0]; + if (p <= e && *keylen <= x[0] && !strncasecmp(key, (char*)x+1, *keylen)) + if (*keylen == x[0] || x[1+*keylen] == '=') return(x); + } + return(NULL); +} + +/********************************************************************************************* +* +* General Utility Functions +* +*********************************************************************************************/ + +// Note: Need to make sure we don't write more than kDNSServiceMaxDomainName (1009) bytes to fullName +// In earlier builds this constant was defined to be 1005, so to avoid buffer overruns on clients +// compiled with that constant we'll actually limit the output to 1005 bytes. + +DNSServiceErrorType DNSSD_API DNSServiceConstructFullName +( + char *const fullName, + const char *const service, // May be NULL + const char *const regtype, + const char *const domain +) +{ + const size_t len = !regtype ? 0 : strlen(regtype) - DomainEndsInDot(regtype); + char *fn = fullName; + char *const lim = fullName + 1005; + const char *s = service; + const char *r = regtype; + const char *d = domain; + + // regtype must be at least "x._udp" or "x._tcp" + if (len < 6 || !domain || !domain[0]) return kDNSServiceErr_BadParam; + if (strncasecmp((regtype + len - 4), "_tcp", 4) && strncasecmp((regtype + len - 4), "_udp", 4)) return kDNSServiceErr_BadParam; + + if (service && *service) + { + while (*s) + { + unsigned char c = *s++; // Needs to be unsigned, or values like 0xFF will be interpreted as < 32 + if (c <= ' ') // Escape non-printable characters + { + if (fn+4 >= lim) goto fail; + *fn++ = '\\'; + *fn++ = '0' + (c / 100); + *fn++ = '0' + (c / 10) % 10; + c = '0' + (c ) % 10; + } + else if (c == '.' || (c == '\\')) // Escape dot and backslash literals + { + if (fn+2 >= lim) goto fail; + *fn++ = '\\'; + } + else + if (fn+1 >= lim) goto fail; + *fn++ = (char)c; + } + *fn++ = '.'; + } + + while (*r) if (fn+1 >= lim) goto fail;else *fn++ = *r++; + if (!DomainEndsInDot(regtype)) { if (fn+1 >= lim) goto fail;else *fn++ = '.';} + + while (*d) if (fn+1 >= lim) goto fail;else *fn++ = *d++; + if (!DomainEndsInDot(domain)) { if (fn+1 >= lim) goto fail;else *fn++ = '.';} + + *fn = '\0'; + return kDNSServiceErr_NoError; + +fail: + *fn = '\0'; + return kDNSServiceErr_BadParam; +} + +/********************************************************************************************* +* +* TXT Record Construction Functions +* +*********************************************************************************************/ + +typedef struct _TXTRecordRefRealType +{ + uint8_t *buffer; // Pointer to data + uint16_t buflen; // Length of buffer + uint16_t datalen; // Length currently in use + uint16_t malloced; // Non-zero if buffer was allocated via malloc() +} TXTRecordRefRealType; + +#define txtRec ((TXTRecordRefRealType*)txtRecord) + +// The opaque storage defined in the public dns_sd.h header is 16 bytes; +// make sure we don't exceed that. +struct CompileTimeAssertionCheck_dnssd_clientlib +{ + char assert0[(sizeof(TXTRecordRefRealType) <= 16) ? 1 : -1]; +}; + +void DNSSD_API TXTRecordCreate +( + TXTRecordRef *txtRecord, + uint16_t bufferLen, + void *buffer +) +{ + txtRec->buffer = buffer; + txtRec->buflen = buffer ? bufferLen : (uint16_t)0; + txtRec->datalen = 0; + txtRec->malloced = 0; +} + +void DNSSD_API TXTRecordDeallocate(TXTRecordRef *txtRecord) +{ + if (txtRec->malloced) free(txtRec->buffer); +} + +DNSServiceErrorType DNSSD_API TXTRecordSetValue +( + TXTRecordRef *txtRecord, + const char *key, + uint8_t valueSize, + const void *value +) +{ + uint8_t *start, *p; + const char *k; + unsigned long keysize, keyvalsize; + + for (k = key; *k; k++) if (*k < 0x20 || *k > 0x7E || *k == '=') return(kDNSServiceErr_Invalid); + keysize = (unsigned long)(k - key); + keyvalsize = 1 + keysize + (value ? (1 + valueSize) : 0); + if (keysize < 1 || keyvalsize > 255) return(kDNSServiceErr_Invalid); + (void)TXTRecordRemoveValue(txtRecord, key); + if (txtRec->datalen + keyvalsize > txtRec->buflen) + { + unsigned char *newbuf; + unsigned long newlen = txtRec->datalen + keyvalsize; + if (newlen > 0xFFFF) return(kDNSServiceErr_Invalid); + newbuf = malloc((size_t)newlen); + if (!newbuf) return(kDNSServiceErr_NoMemory); + memcpy(newbuf, txtRec->buffer, txtRec->datalen); + if (txtRec->malloced) free(txtRec->buffer); + txtRec->buffer = newbuf; + txtRec->buflen = (uint16_t)(newlen); + txtRec->malloced = 1; + } + start = txtRec->buffer + txtRec->datalen; + p = start + 1; + memcpy(p, key, keysize); + p += keysize; + if (value) + { + *p++ = '='; + memcpy(p, value, valueSize); + p += valueSize; + } + *start = (uint8_t)(p - start - 1); + txtRec->datalen += p - start; + return(kDNSServiceErr_NoError); +} + +DNSServiceErrorType DNSSD_API TXTRecordRemoveValue +( + TXTRecordRef *txtRecord, + const char *key +) +{ + unsigned long keylen, itemlen, remainder; + uint8_t *item = InternalTXTRecordSearch(txtRec->datalen, txtRec->buffer, key, &keylen); + if (!item) return(kDNSServiceErr_NoSuchKey); + itemlen = (unsigned long)(1 + item[0]); + remainder = (unsigned long)((txtRec->buffer + txtRec->datalen) - (item + itemlen)); + // Use memmove because memcpy behaviour is undefined for overlapping regions + memmove(item, item + itemlen, remainder); + txtRec->datalen -= itemlen; + return(kDNSServiceErr_NoError); +} + +uint16_t DNSSD_API TXTRecordGetLength (const TXTRecordRef *txtRecord) { return(txtRec->datalen); } +const void * DNSSD_API TXTRecordGetBytesPtr(const TXTRecordRef *txtRecord) { return(txtRec->buffer); } + +/********************************************************************************************* +* +* TXT Record Parsing Functions +* +*********************************************************************************************/ + +int DNSSD_API TXTRecordContainsKey +( + uint16_t txtLen, + const void *txtRecord, + const char *key +) +{ + unsigned long keylen; + return (InternalTXTRecordSearch(txtLen, txtRecord, key, &keylen) ? 1 : 0); +} + +const void * DNSSD_API TXTRecordGetValuePtr +( + uint16_t txtLen, + const void *txtRecord, + const char *key, + uint8_t *valueLen +) +{ + unsigned long keylen; + uint8_t *item = InternalTXTRecordSearch(txtLen, txtRecord, key, &keylen); + if (!item || item[0] <= keylen) return(NULL); // If key not found, or found with no value, return NULL + *valueLen = (uint8_t)(item[0] - (keylen + 1)); + return (item + 1 + keylen + 1); +} + +uint16_t DNSSD_API TXTRecordGetCount +( + uint16_t txtLen, + const void *txtRecord +) +{ + uint16_t count = 0; + uint8_t *p = (uint8_t*)txtRecord; + uint8_t *e = p + txtLen; + while (p<e) { p += 1 + p[0]; count++; } + return((p>e) ? (uint16_t)0 : count); +} + +DNSServiceErrorType DNSSD_API TXTRecordGetItemAtIndex +( + uint16_t txtLen, + const void *txtRecord, + uint16_t itemIndex, + uint16_t keyBufLen, + char *key, + uint8_t *valueLen, + const void **value +) +{ + uint16_t count = 0; + uint8_t *p = (uint8_t*)txtRecord; + uint8_t *e = p + txtLen; + while (p<e && count<itemIndex) { p += 1 + p[0]; count++; } // Find requested item + if (p<e && p + 1 + p[0] <= e) // If valid + { + uint8_t *x = p+1; + unsigned long len = 0; + e = p + 1 + p[0]; + while (x+len<e && x[len] != '=') len++; + if (len >= keyBufLen) return(kDNSServiceErr_NoMemory); + memcpy(key, x, len); + key[len] = 0; + if (x+len<e) // If we found '=' + { + *value = x + len + 1; + *valueLen = (uint8_t)(p[0] - (len + 1)); + } + else + { + *value = NULL; + *valueLen = 0; + } + return(kDNSServiceErr_NoError); + } + return(kDNSServiceErr_Invalid); +} + +/********************************************************************************************* +* +* SCCS-compatible version string +* +*********************************************************************************************/ + +// For convenience when using the "strings" command, this is the last thing in the file + +// Note: The C preprocessor stringify operator ('#') makes a string from its argument, without macro expansion +// e.g. If "version" is #define'd to be "4", then STRINGIFY_AWE(version) will return the string "version", not "4" +// To expand "version" to its value before making the string, use STRINGIFY(version) instead +#define STRINGIFY_ARGUMENT_WITHOUT_EXPANSION(s) # s +#define STRINGIFY(s) STRINGIFY_ARGUMENT_WITHOUT_EXPANSION(s) + +// NOT static -- otherwise the compiler may optimize it out +// The "@(#) " pattern is a special prefix the "what" command looks for +const char VersionString_SCCS_libdnssd[] = "@(#) libdns_sd " STRINGIFY(mDNSResponderVersion) " (" __DATE__ " " __TIME__ ")"; diff --git a/mDNSResponder/mDNSShared/dnssd_clientshim.c b/mDNSResponder/mDNSShared/dnssd_clientshim.c new file mode 100644 index 00000000..cb143104 --- /dev/null +++ b/mDNSResponder/mDNSShared/dnssd_clientshim.c @@ -0,0 +1,811 @@ +/* -*- Mode: C; tab-width: 4 -*- + * + * Copyright (c) 2003 Apple Computer, Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + + * This file defines a simple shim layer between a client calling the "/usr/include/dns_sd.h" APIs + * and an implementation of mDNSCore ("mDNSEmbeddedAPI.h" APIs) in the same address space. + * When the client calls a dns_sd.h function, the shim calls the corresponding mDNSEmbeddedAPI.h + * function, and when mDNSCore calls the shim's callback, we call through to the client's callback. + * The shim is responsible for two main things: + * - converting string parameters between C string format and native DNS format, + * - and for allocating and freeing memory. + */ + +#include "dns_sd.h" // Defines the interface to the client layer above +#include "mDNSEmbeddedAPI.h" // The interface we're building on top of +extern mDNS mDNSStorage; // We need to pass the address of this storage to the lower-layer functions + +#if MDNS_BUILDINGSHAREDLIBRARY || MDNS_BUILDINGSTUBLIBRARY +#pragma export on +#endif + +//************************************************************************************************************* +// General Utility Functions + +// All mDNS_DirectOP structures start with the pointer to the type-specific disposal function. +// Optional type-specific data follows these three fields +// When the client starts an operation, we return the address of the corresponding mDNS_DirectOP +// as the DNSServiceRef for the operation +// We stash the value in core context fields so we can get it back to recover our state in our callbacks, +// and pass it though to the client for it to recover its state + +typedef struct mDNS_DirectOP_struct mDNS_DirectOP; +typedef void mDNS_DirectOP_Dispose (mDNS_DirectOP *op); +struct mDNS_DirectOP_struct +{ + mDNS_DirectOP_Dispose *disposefn; +}; + +typedef struct +{ + mDNS_DirectOP_Dispose *disposefn; + DNSServiceRegisterReply callback; + void *context; + mDNSBool autoname; // Set if this name is tied to the Computer Name + mDNSBool autorename; // Set if we just got a name conflict and now need to automatically pick a new name + domainlabel name; + domainname host; + ServiceRecordSet s; +} mDNS_DirectOP_Register; + +typedef struct +{ + mDNS_DirectOP_Dispose *disposefn; + DNSServiceBrowseReply callback; + void *context; + DNSQuestion q; +} mDNS_DirectOP_Browse; + +typedef struct +{ + mDNS_DirectOP_Dispose *disposefn; + DNSServiceResolveReply callback; + void *context; + const ResourceRecord *SRV; + const ResourceRecord *TXT; + DNSQuestion qSRV; + DNSQuestion qTXT; +} mDNS_DirectOP_Resolve; + +typedef struct +{ + mDNS_DirectOP_Dispose *disposefn; + DNSServiceQueryRecordReply callback; + void *context; + DNSQuestion q; +} mDNS_DirectOP_QueryRecord; + +int DNSServiceRefSockFD(DNSServiceRef sdRef) +{ + (void)sdRef; // Unused + return(0); +} + +DNSServiceErrorType DNSServiceProcessResult(DNSServiceRef sdRef) +{ + (void)sdRef; // Unused + return(kDNSServiceErr_NoError); +} + +void DNSServiceRefDeallocate(DNSServiceRef sdRef) +{ + mDNS_DirectOP *op = (mDNS_DirectOP *)sdRef; + //LogMsg("DNSServiceRefDeallocate"); + op->disposefn(op); +} + +//************************************************************************************************************* +// Domain Enumeration + +// Not yet implemented, so don't include in stub library +// We DO include it in the actual Extension, so that if a later client compiled to use this +// is run against this Extension, it will get a reasonable error code instead of just +// failing to launch (Strong Link) or calling an unresolved symbol and crashing (Weak Link) +#if !MDNS_BUILDINGSTUBLIBRARY +DNSServiceErrorType DNSServiceEnumerateDomains +( + DNSServiceRef *sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + DNSServiceDomainEnumReply callback, + void *context /* may be NULL */ +) +{ + (void)sdRef; // Unused + (void)flags; // Unused + (void)interfaceIndex; // Unused + (void)callback; // Unused + (void)context; // Unused + return(kDNSServiceErr_Unsupported); +} +#endif + +//************************************************************************************************************* +// Register Service + +mDNSlocal void FreeDNSServiceRegistration(mDNS_DirectOP_Register *x) +{ + while (x->s.Extras) + { + ExtraResourceRecord *extras = x->s.Extras; + x->s.Extras = x->s.Extras->next; + if (extras->r.resrec.rdata != &extras->r.rdatastorage) + mDNSPlatformMemFree(extras->r.resrec.rdata); + mDNSPlatformMemFree(extras); + } + + if (x->s.RR_TXT.resrec.rdata != &x->s.RR_TXT.rdatastorage) + mDNSPlatformMemFree(x->s.RR_TXT.resrec.rdata); + + if (x->s.SubTypes) mDNSPlatformMemFree(x->s.SubTypes); + + mDNSPlatformMemFree(x); +} + +static void DNSServiceRegisterDispose(mDNS_DirectOP *op) +{ + mDNS_DirectOP_Register *x = (mDNS_DirectOP_Register*)op; + x->autorename = mDNSfalse; + // If mDNS_DeregisterService() returns mStatus_NoError, that means that the service was found in the list, + // is sending its goodbye packet, and we'll get an mStatus_MemFree message when we can free the memory. + // If mDNS_DeregisterService() returns an error, it means that the service had already been removed from + // the list, so we should go ahead and free the memory right now + if (mDNS_DeregisterService(&mDNSStorage, &x->s) != mStatus_NoError) + FreeDNSServiceRegistration(x); +} + +mDNSlocal void RegCallback(mDNS *const m, ServiceRecordSet *const sr, mStatus result) +{ + mDNS_DirectOP_Register *x = (mDNS_DirectOP_Register*)sr->ServiceContext; + + domainlabel name; + domainname type, dom; + char namestr[MAX_DOMAIN_LABEL+1]; // Unescaped name: up to 63 bytes plus C-string terminating NULL. + char typestr[MAX_ESCAPED_DOMAIN_NAME]; + char domstr [MAX_ESCAPED_DOMAIN_NAME]; + if (!DeconstructServiceName(sr->RR_SRV.resrec.name, &name, &type, &dom)) return; + if (!ConvertDomainLabelToCString_unescaped(&name, namestr)) return; + if (!ConvertDomainNameToCString(&type, typestr)) return; + if (!ConvertDomainNameToCString(&dom, domstr)) return; + + if (result == mStatus_NoError) + { + if (x->callback) + x->callback((DNSServiceRef)x, 0, result, namestr, typestr, domstr, x->context); + } + else if (result == mStatus_NameConflict) + { + if (x->autoname) mDNS_RenameAndReregisterService(m, sr, mDNSNULL); + else if (x->callback) + x->callback((DNSServiceRef)x, 0, result, namestr, typestr, domstr, x->context); + } + else if (result == mStatus_MemFree) + { + if (x->autorename) + { + x->autorename = mDNSfalse; + x->name = mDNSStorage.nicelabel; + mDNS_RenameAndReregisterService(m, &x->s, &x->name); + } + else + FreeDNSServiceRegistration(x); + } +} + +DNSServiceErrorType DNSServiceRegister +( + DNSServiceRef *sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + const char *name, /* may be NULL */ + const char *regtype, + const char *domain, /* may be NULL */ + const char *host, /* may be NULL */ + uint16_t notAnIntPort, + uint16_t txtLen, + const void *txtRecord, /* may be NULL */ + DNSServiceRegisterReply callback, /* may be NULL */ + void *context /* may be NULL */ +) +{ + mStatus err = mStatus_NoError; + const char *errormsg = "Unknown"; + domainlabel n; + domainname t, d, h, srv; + mDNSIPPort port; + unsigned int size = sizeof(RDataBody); + AuthRecord *SubTypes = mDNSNULL; + mDNSu32 NumSubTypes = 0; + mDNS_DirectOP_Register *x; + (void)flags; // Unused + (void)interfaceIndex; // Unused + + // Check parameters + if (!name) name = ""; + if (!name[0]) n = mDNSStorage.nicelabel; + else if (!MakeDomainLabelFromLiteralString(&n, name)) { errormsg = "Bad Instance Name"; goto badparam; } + if (!regtype || !*regtype || !MakeDomainNameFromDNSNameString(&t, regtype)) { errormsg = "Bad Service Type"; goto badparam; } + if (!MakeDomainNameFromDNSNameString(&d, (domain && *domain) ? domain : "local.")) { errormsg = "Bad Domain"; goto badparam; } + if (!MakeDomainNameFromDNSNameString(&h, (host && *host ) ? host : "")) { errormsg = "Bad Target Host"; goto badparam; } + if (!ConstructServiceName(&srv, &n, &t, &d)) { errormsg = "Bad Name"; goto badparam; } + port.NotAnInteger = notAnIntPort; + + // Allocate memory, and handle failure + if (size < txtLen) + size = txtLen; + x = (mDNS_DirectOP_Register *)mDNSPlatformMemAllocate(sizeof(*x) - sizeof(RDataBody) + size); + if (!x) { err = mStatus_NoMemoryErr; errormsg = "No memory"; goto fail; } + + // Set up object + x->disposefn = DNSServiceRegisterDispose; + x->callback = callback; + x->context = context; + x->autoname = (!name[0]); + x->autorename = mDNSfalse; + x->name = n; + x->host = h; + + // Do the operation + err = mDNS_RegisterService(&mDNSStorage, &x->s, + &x->name, &t, &d, // Name, type, domain + &x->host, port, // Host and port + txtRecord, txtLen, // TXT data, length + SubTypes, NumSubTypes, // Subtypes + mDNSInterface_Any, // Interface ID + RegCallback, x, 0); // Callback, context, flags + if (err) { mDNSPlatformMemFree(x); errormsg = "mDNS_RegisterService"; goto fail; } + + // Succeeded: Wrap up and return + *sdRef = (DNSServiceRef)x; + return(mStatus_NoError); + +badparam: + err = mStatus_BadParamErr; +fail: + LogMsg("DNSServiceBrowse(\"%s\", \"%s\") failed: %s (%ld)", regtype, domain, errormsg, err); + return(err); +} + +//************************************************************************************************************* +// Add / Update / Remove records from existing Registration + +// Not yet implemented, so don't include in stub library +// We DO include it in the actual Extension, so that if a later client compiled to use this +// is run against this Extension, it will get a reasonable error code instead of just +// failing to launch (Strong Link) or calling an unresolved symbol and crashing (Weak Link) +#if !MDNS_BUILDINGSTUBLIBRARY +DNSServiceErrorType DNSServiceAddRecord +( + DNSServiceRef sdRef, + DNSRecordRef *RecordRef, + DNSServiceFlags flags, + uint16_t rrtype, + uint16_t rdlen, + const void *rdata, + uint32_t ttl +) +{ + (void)sdRef; // Unused + (void)RecordRef; // Unused + (void)flags; // Unused + (void)rrtype; // Unused + (void)rdlen; // Unused + (void)rdata; // Unused + (void)ttl; // Unused + return(kDNSServiceErr_Unsupported); +} + +DNSServiceErrorType DNSServiceUpdateRecord +( + DNSServiceRef sdRef, + DNSRecordRef RecordRef, /* may be NULL */ + DNSServiceFlags flags, + uint16_t rdlen, + const void *rdata, + uint32_t ttl +) +{ + (void)sdRef; // Unused + (void)RecordRef; // Unused + (void)flags; // Unused + (void)rdlen; // Unused + (void)rdata; // Unused + (void)ttl; // Unused + return(kDNSServiceErr_Unsupported); +} + +DNSServiceErrorType DNSServiceRemoveRecord +( + DNSServiceRef sdRef, + DNSRecordRef RecordRef, + DNSServiceFlags flags +) +{ + (void)sdRef; // Unused + (void)RecordRef; // Unused + (void)flags; // Unused + return(kDNSServiceErr_Unsupported); +} +#endif + +//************************************************************************************************************* +// Browse for services + +static void DNSServiceBrowseDispose(mDNS_DirectOP *op) +{ + mDNS_DirectOP_Browse *x = (mDNS_DirectOP_Browse*)op; + //LogMsg("DNSServiceBrowseDispose"); + mDNS_StopBrowse(&mDNSStorage, &x->q); + mDNSPlatformMemFree(x); +} + +mDNSlocal void FoundInstance(mDNS *const m, DNSQuestion *question, const ResourceRecord *const answer, QC_result AddRecord) +{ + DNSServiceFlags flags = AddRecord ? kDNSServiceFlagsAdd : (DNSServiceFlags)0; + domainlabel name; + domainname type, domain; + char cname[MAX_DOMAIN_LABEL+1]; // Unescaped name: up to 63 bytes plus C-string terminating NULL. + char ctype[MAX_ESCAPED_DOMAIN_NAME]; + char cdom [MAX_ESCAPED_DOMAIN_NAME]; + mDNS_DirectOP_Browse *x = (mDNS_DirectOP_Browse*)question->QuestionContext; + (void)m; // Unused + + if (answer->rrtype != kDNSType_PTR) + { LogMsg("FoundInstance: Should not be called with rrtype %d (not a PTR record)", answer->rrtype); return; } + + if (!DeconstructServiceName(&answer->rdata->u.name, &name, &type, &domain)) + { + LogMsg("FoundInstance: %##s PTR %##s received from network is not valid DNS-SD service pointer", + answer->name->c, answer->rdata->u.name.c); + return; + } + + ConvertDomainLabelToCString_unescaped(&name, cname); + ConvertDomainNameToCString(&type, ctype); + ConvertDomainNameToCString(&domain, cdom); + if (x->callback) + x->callback((DNSServiceRef)x, flags, 0, 0, cname, ctype, cdom, x->context); +} + +DNSServiceErrorType DNSServiceBrowse +( + DNSServiceRef *sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + const char *regtype, + const char *domain, /* may be NULL */ + DNSServiceBrowseReply callback, + void *context /* may be NULL */ +) +{ + mStatus err = mStatus_NoError; + const char *errormsg = "Unknown"; + domainname t, d; + mDNS_DirectOP_Browse *x; + (void)flags; // Unused + (void)interfaceIndex; // Unused + + // Check parameters + if (!regtype[0] || !MakeDomainNameFromDNSNameString(&t, regtype)) { errormsg = "Illegal regtype"; goto badparam; } + if (!MakeDomainNameFromDNSNameString(&d, *domain ? domain : "local.")) { errormsg = "Illegal domain"; goto badparam; } + + // Allocate memory, and handle failure + x = (mDNS_DirectOP_Browse *)mDNSPlatformMemAllocate(sizeof(*x)); + if (!x) { err = mStatus_NoMemoryErr; errormsg = "No memory"; goto fail; } + + // Set up object + x->disposefn = DNSServiceBrowseDispose; + x->callback = callback; + x->context = context; + x->q.QuestionContext = x; + + // Do the operation + err = mDNS_StartBrowse(&mDNSStorage, &x->q, &t, &d, mDNSNULL, mDNSInterface_Any, flags, (flags & kDNSServiceFlagsForceMulticast) != 0, (flags & kDNSServiceFlagsBackgroundTrafficClass) != 0, FoundInstance, x); + if (err) { mDNSPlatformMemFree(x); errormsg = "mDNS_StartBrowse"; goto fail; } + + // Succeeded: Wrap up and return + *sdRef = (DNSServiceRef)x; + return(mStatus_NoError); + +badparam: + err = mStatus_BadParamErr; +fail: + LogMsg("DNSServiceBrowse(\"%s\", \"%s\") failed: %s (%ld)", regtype, domain, errormsg, err); + return(err); +} + +//************************************************************************************************************* +// Resolve Service Info + +static void DNSServiceResolveDispose(mDNS_DirectOP *op) +{ + mDNS_DirectOP_Resolve *x = (mDNS_DirectOP_Resolve*)op; + if (x->qSRV.ThisQInterval >= 0) mDNS_StopQuery(&mDNSStorage, &x->qSRV); + if (x->qTXT.ThisQInterval >= 0) mDNS_StopQuery(&mDNSStorage, &x->qTXT); + mDNSPlatformMemFree(x); +} + +mDNSlocal void FoundServiceInfo(mDNS *const m, DNSQuestion *question, const ResourceRecord *const answer, QC_result AddRecord) +{ + mDNS_DirectOP_Resolve *x = (mDNS_DirectOP_Resolve*)question->QuestionContext; + (void)m; // Unused + if (!AddRecord) + { + if (answer->rrtype == kDNSType_SRV && x->SRV == answer) x->SRV = mDNSNULL; + if (answer->rrtype == kDNSType_TXT && x->TXT == answer) x->TXT = mDNSNULL; + } + else + { + if (answer->rrtype == kDNSType_SRV) x->SRV = answer; + if (answer->rrtype == kDNSType_TXT) x->TXT = answer; + if (x->SRV && x->TXT && x->callback) + { + char fullname[MAX_ESCAPED_DOMAIN_NAME], targethost[MAX_ESCAPED_DOMAIN_NAME]; + ConvertDomainNameToCString(answer->name, fullname); + ConvertDomainNameToCString(&x->SRV->rdata->u.srv.target, targethost); + x->callback((DNSServiceRef)x, 0, 0, kDNSServiceErr_NoError, fullname, targethost, + x->SRV->rdata->u.srv.port.NotAnInteger, x->TXT->rdlength, (unsigned char*)x->TXT->rdata->u.txt.c, x->context); + } + } +} + +DNSServiceErrorType DNSServiceResolve +( + DNSServiceRef *sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + const char *name, + const char *regtype, + const char *domain, + DNSServiceResolveReply callback, + void *context /* may be NULL */ +) +{ + mStatus err = mStatus_NoError; + const char *errormsg = "Unknown"; + domainlabel n; + domainname t, d, srv; + mDNS_DirectOP_Resolve *x; + + (void)flags; // Unused + (void)interfaceIndex; // Unused + + // Check parameters + if (!name[0] || !MakeDomainLabelFromLiteralString(&n, name )) { errormsg = "Bad Instance Name"; goto badparam; } + if (!regtype[0] || !MakeDomainNameFromDNSNameString(&t, regtype)) { errormsg = "Bad Service Type"; goto badparam; } + if (!domain[0] || !MakeDomainNameFromDNSNameString(&d, domain )) { errormsg = "Bad Domain"; goto badparam; } + if (!ConstructServiceName(&srv, &n, &t, &d)) { errormsg = "Bad Name"; goto badparam; } + + // Allocate memory, and handle failure + x = (mDNS_DirectOP_Resolve *)mDNSPlatformMemAllocate(sizeof(*x)); + if (!x) { err = mStatus_NoMemoryErr; errormsg = "No memory"; goto fail; } + + // Set up object + x->disposefn = DNSServiceResolveDispose; + x->callback = callback; + x->context = context; + x->SRV = mDNSNULL; + x->TXT = mDNSNULL; + + x->qSRV.ThisQInterval = -1; // So that DNSServiceResolveDispose() knows whether to cancel this question + x->qSRV.InterfaceID = mDNSInterface_Any; + x->qSRV.flags = 0; + x->qSRV.Target = zeroAddr; + AssignDomainName(&x->qSRV.qname, &srv); + x->qSRV.qtype = kDNSType_SRV; + x->qSRV.qclass = kDNSClass_IN; + x->qSRV.LongLived = mDNSfalse; + x->qSRV.ExpectUnique = mDNStrue; + x->qSRV.ForceMCast = mDNSfalse; + x->qSRV.ReturnIntermed = mDNSfalse; + x->qSRV.SuppressUnusable = mDNSfalse; + x->qSRV.SearchListIndex = 0; + x->qSRV.AppendSearchDomains = 0; + x->qSRV.RetryWithSearchDomains = mDNSfalse; + x->qSRV.TimeoutQuestion = 0; + x->qSRV.WakeOnResolve = 0; + x->qSRV.UseBackgroundTrafficClass = (flags & kDNSServiceFlagsBackgroundTrafficClass) != 0; + x->qSRV.ValidationRequired = 0; + x->qSRV.ValidatingResponse = 0; + x->qSRV.ProxyQuestion = 0; + x->qSRV.qnameOrig = mDNSNULL; + x->qSRV.AnonInfo = mDNSNULL; + x->qSRV.pid = mDNSPlatformGetPID(); + x->qSRV.QuestionCallback = FoundServiceInfo; + x->qSRV.QuestionContext = x; + + x->qTXT.ThisQInterval = -1; // So that DNSServiceResolveDispose() knows whether to cancel this question + x->qTXT.InterfaceID = mDNSInterface_Any; + x->qTXT.flags = 0; + x->qTXT.Target = zeroAddr; + AssignDomainName(&x->qTXT.qname, &srv); + x->qTXT.qtype = kDNSType_TXT; + x->qTXT.qclass = kDNSClass_IN; + x->qTXT.LongLived = mDNSfalse; + x->qTXT.ExpectUnique = mDNStrue; + x->qTXT.ForceMCast = mDNSfalse; + x->qTXT.ReturnIntermed = mDNSfalse; + x->qTXT.SuppressUnusable = mDNSfalse; + x->qTXT.SearchListIndex = 0; + x->qTXT.AppendSearchDomains = 0; + x->qTXT.RetryWithSearchDomains = mDNSfalse; + x->qTXT.TimeoutQuestion = 0; + x->qTXT.WakeOnResolve = 0; + x->qTXT.UseBackgroundTrafficClass = (flags & kDNSServiceFlagsBackgroundTrafficClass) != 0; + x->qTXT.ValidationRequired = 0; + x->qTXT.ValidatingResponse = 0; + x->qTXT.ProxyQuestion = 0; + x->qTXT.qnameOrig = mDNSNULL; + x->qTXT.AnonInfo = mDNSNULL; + x->qTXT.pid = mDNSPlatformGetPID(); + x->qTXT.QuestionCallback = FoundServiceInfo; + x->qTXT.QuestionContext = x; + + err = mDNS_StartQuery(&mDNSStorage, &x->qSRV); + if (err) { DNSServiceResolveDispose((mDNS_DirectOP*)x); errormsg = "mDNS_StartQuery qSRV"; goto fail; } + err = mDNS_StartQuery(&mDNSStorage, &x->qTXT); + if (err) { DNSServiceResolveDispose((mDNS_DirectOP*)x); errormsg = "mDNS_StartQuery qTXT"; goto fail; } + + // Succeeded: Wrap up and return + *sdRef = (DNSServiceRef)x; + return(mStatus_NoError); + +badparam: + err = mStatus_BadParamErr; +fail: + LogMsg("DNSServiceResolve(\"%s\", \"%s\", \"%s\") failed: %s (%ld)", name, regtype, domain, errormsg, err); + return(err); +} + +//************************************************************************************************************* +// Connection-oriented calls + +// Not yet implemented, so don't include in stub library +// We DO include it in the actual Extension, so that if a later client compiled to use this +// is run against this Extension, it will get a reasonable error code instead of just +// failing to launch (Strong Link) or calling an unresolved symbol and crashing (Weak Link) +#if !MDNS_BUILDINGSTUBLIBRARY +DNSServiceErrorType DNSServiceCreateConnection(DNSServiceRef *sdRef) +{ + (void)sdRef; // Unused + return(kDNSServiceErr_Unsupported); +} + +DNSServiceErrorType DNSServiceRegisterRecord +( + DNSServiceRef sdRef, + DNSRecordRef *RecordRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + const char *fullname, + uint16_t rrtype, + uint16_t rrclass, + uint16_t rdlen, + const void *rdata, + uint32_t ttl, + DNSServiceRegisterRecordReply callback, + void *context /* may be NULL */ +) +{ + (void)sdRef; // Unused + (void)RecordRef; // Unused + (void)flags; // Unused + (void)interfaceIndex; // Unused + (void)fullname; // Unused + (void)rrtype; // Unused + (void)rrclass; // Unused + (void)rdlen; // Unused + (void)rdata; // Unused + (void)ttl; // Unused + (void)callback; // Unused + (void)context; // Unused + return(kDNSServiceErr_Unsupported); +} +#endif + +//************************************************************************************************************* +// DNSServiceQueryRecord + +static void DNSServiceQueryRecordDispose(mDNS_DirectOP *op) +{ + mDNS_DirectOP_QueryRecord *x = (mDNS_DirectOP_QueryRecord*)op; + if (x->q.ThisQInterval >= 0) mDNS_StopQuery(&mDNSStorage, &x->q); + mDNSPlatformMemFree(x); +} + +mDNSlocal void DNSServiceQueryRecordResponse(mDNS *const m, DNSQuestion *question, const ResourceRecord *const answer, QC_result AddRecord) +{ + mDNS_DirectOP_QueryRecord *x = (mDNS_DirectOP_QueryRecord*)question->QuestionContext; + char fullname[MAX_ESCAPED_DOMAIN_NAME]; + (void)m; // Unused + ConvertDomainNameToCString(answer->name, fullname); + x->callback((DNSServiceRef)x, AddRecord ? kDNSServiceFlagsAdd : (DNSServiceFlags)0, 0, kDNSServiceErr_NoError, + fullname, answer->rrtype, answer->rrclass, answer->rdlength, answer->rdata->u.data, answer->rroriginalttl, x->context); +} + +DNSServiceErrorType DNSServiceQueryRecord +( + DNSServiceRef *sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + const char *fullname, + uint16_t rrtype, + uint16_t rrclass, + DNSServiceQueryRecordReply callback, + void *context /* may be NULL */ +) +{ + mStatus err = mStatus_NoError; + const char *errormsg = "Unknown"; + mDNS_DirectOP_QueryRecord *x; + + (void)flags; // Unused + (void)interfaceIndex; // Unused + + // Allocate memory, and handle failure + x = (mDNS_DirectOP_QueryRecord *)mDNSPlatformMemAllocate(sizeof(*x)); + if (!x) { err = mStatus_NoMemoryErr; errormsg = "No memory"; goto fail; } + + // Set up object + x->disposefn = DNSServiceQueryRecordDispose; + x->callback = callback; + x->context = context; + + x->q.ThisQInterval = -1; // So that DNSServiceResolveDispose() knows whether to cancel this question + x->q.InterfaceID = mDNSInterface_Any; + x->q.flags = flags; + x->q.Target = zeroAddr; + MakeDomainNameFromDNSNameString(&x->q.qname, fullname); + x->q.qtype = rrtype; + x->q.qclass = rrclass; + x->q.LongLived = (flags & kDNSServiceFlagsLongLivedQuery) != 0; + x->q.ExpectUnique = mDNSfalse; + x->q.ForceMCast = (flags & kDNSServiceFlagsForceMulticast) != 0; + x->q.ReturnIntermed = (flags & kDNSServiceFlagsReturnIntermediates) != 0; + x->q.SuppressUnsable = (flags & kDNSServiceFlagsSuppressUnusable) != 0; + x->q.SearchListIndex = 0; + x->q.AppendSearchDomains = 0; + x->q.RetryWithSearchDomains = mDNSfalse; + x->q.TimeoutQuestion = 0; + x->q.WakeOnResolve = 0; + x->q.UseBackgroundTrafficClass = (flags & kDNSServiceFlagsBackgroundTrafficClass) != 0; + x->q.ValidationRequired = 0; + x->q.ValidatingResponse = 0; + x->q.ProxyQuestion = 0; + x->q.qnameOrig = mDNSNULL; + x->q.AnonInfo = mDNSNULL; + x->q.pid = mDNSPlatformGetPID(); + x->q.QuestionCallback = DNSServiceQueryRecordResponse; + x->q.QuestionContext = x; + + err = mDNS_StartQuery(&mDNSStorage, &x->q); + if (err) { DNSServiceResolveDispose((mDNS_DirectOP*)x); errormsg = "mDNS_StartQuery"; goto fail; } + + // Succeeded: Wrap up and return + *sdRef = (DNSServiceRef)x; + return(mStatus_NoError); + +fail: + LogMsg("DNSServiceQueryRecord(\"%s\", %d, %d) failed: %s (%ld)", fullname, rrtype, rrclass, errormsg, err); + return(err); +} + +//************************************************************************************************************* +// DNSServiceGetAddrInfo + +static void DNSServiceGetAddrInfoDispose(mDNS_DirectOP *op) +{ + mDNS_DirectOP_GetAddrInfo *x = (mDNS_DirectOP_GetAddrInfo*)op; + if (x->aQuery) DNSServiceRefDeallocate(x->aQuery); + mDNSPlatformMemFree(x); +} + +static void DNSSD_API DNSServiceGetAddrInfoResponse( + DNSServiceRef inRef, + DNSServiceFlags inFlags, + uint32_t inInterfaceIndex, + DNSServiceErrorType inErrorCode, + const char * inFullName, + uint16_t inRRType, + uint16_t inRRClass, + uint16_t inRDLen, + const void * inRData, + uint32_t inTTL, + void * inContext ) +{ + mDNS_DirectOP_GetAddrInfo * x = (mDNS_DirectOP_GetAddrInfo*)inContext; + struct sockaddr_in sa4; + + mDNSPlatformMemZero(&sa4, sizeof(sa4)); + if (inErrorCode == kDNSServiceErr_NoError && inRRType == kDNSServiceType_A) + { + sa4.sin_family = AF_INET; + mDNSPlatformMemCopy(&sa4.sin_addr.s_addr, inRData, 4); + } + + x->callback((DNSServiceRef)x, inFlags, inInterfaceIndex, inErrorCode, inFullName, + (const struct sockaddr *) &sa4, inTTL, x->context); +} + +DNSServiceErrorType DNSSD_API DNSServiceGetAddrInfo( + DNSServiceRef * outRef, + DNSServiceFlags inFlags, + uint32_t inInterfaceIndex, + DNSServiceProtocol inProtocol, + const char * inHostName, + DNSServiceGetAddrInfoReply inCallback, + void * inContext ) +{ + const char * errormsg = "Unknown"; + DNSServiceErrorType err; + mDNS_DirectOP_GetAddrInfo * x; + + // Allocate memory, and handle failure + x = (mDNS_DirectOP_GetAddrInfo *)mDNSPlatformMemAllocate(sizeof(*x)); + if (!x) { err = mStatus_NoMemoryErr; errormsg = "No memory"; goto fail; } + + // Set up object + x->disposefn = DNSServiceGetAddrInfoDispose; + x->callback = inCallback; + x->context = inContext; + x->aQuery = mDNSNULL; + + // Start the query. + // (It would probably be more efficient to code this using mDNS_StartQuery directly, + // instead of wrapping DNSServiceQueryRecord, which then unnecessarily allocates + // more memory and then just calls through to mDNS_StartQuery. -- SC June 2010) + err = DNSServiceQueryRecord(&x->aQuery, inFlags, inInterfaceIndex, inHostName, kDNSServiceType_A, + kDNSServiceClass_IN, DNSServiceGetAddrInfoResponse, x); + if (err) { DNSServiceGetAddrInfoDispose((mDNS_DirectOP*)x); errormsg = "DNSServiceQueryRecord"; goto fail; } + + *outRef = (DNSServiceRef)x; + return(mStatus_NoError); + +fail: + LogMsg("DNSServiceGetAddrInfo(\"%s\", %d) failed: %s (%ld)", inHostName, inProtocol, errormsg, err); + return(err); +} + +//************************************************************************************************************* +// DNSServiceReconfirmRecord + +// Not yet implemented, so don't include in stub library +// We DO include it in the actual Extension, so that if a later client compiled to use this +// is run against this Extension, it will get a reasonable error code instead of just +// failing to launch (Strong Link) or calling an unresolved symbol and crashing (Weak Link) +#if !MDNS_BUILDINGSTUBLIBRARY +DNSServiceErrorType DNSSD_API DNSServiceReconfirmRecord +( + DNSServiceFlags flags, + uint32_t interfaceIndex, + const char *fullname, + uint16_t rrtype, + uint16_t rrclass, + uint16_t rdlen, + const void *rdata +) +{ + (void)flags; // Unused + (void)interfaceIndex; // Unused + (void)fullname; // Unused + (void)rrtype; // Unused + (void)rrclass; // Unused + (void)rdlen; // Unused + (void)rdata; // Unused + return(kDNSServiceErr_Unsupported); +} + + +#endif // !MDNS_BUILDINGSTUBLIBRARY diff --git a/mDNSResponder/mDNSShared/dnssd_clientstub.c b/mDNSResponder/mDNSShared/dnssd_clientstub.c new file mode 100644 index 00000000..d21658fa --- /dev/null +++ b/mDNSResponder/mDNSShared/dnssd_clientstub.c @@ -0,0 +1,2363 @@ +/* -*- Mode: C; tab-width: 4 -*- + * + * Copyright (c) 2003-2004, Apple Computer, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <errno.h> +#include <stdlib.h> + +#if APPLE_OSX_mDNSResponder +#include <mach-o/dyld.h> +#include <uuid/uuid.h> +#include <TargetConditionals.h> +#endif + +#include "dnssd_ipc.h" + +static int gDaemonErr = kDNSServiceErr_NoError; + +#if defined(_WIN32) + + #define _SSIZE_T + #include <CommonServices.h> + #include <DebugServices.h> + #include <winsock2.h> + #include <ws2tcpip.h> + #include <windows.h> + #include <stdarg.h> + #include <stdio.h> + + #define sockaddr_mdns sockaddr_in + #define AF_MDNS AF_INET + +// Disable warning: "'type cast' : from data pointer 'void *' to function pointer" + #pragma warning(disable:4055) + +// Disable warning: "nonstandard extension, function/data pointer conversion in expression" + #pragma warning(disable:4152) + +extern BOOL IsSystemServiceDisabled(); + + #define sleep(X) Sleep((X) * 1000) + +static int g_initWinsock = 0; + #define LOG_WARNING kDebugLevelWarning + #define LOG_INFO kDebugLevelInfo +static void syslog( int priority, const char * message, ...) +{ + va_list args; + int len; + char * buffer; + DWORD err = WSAGetLastError(); + (void) priority; + va_start( args, message ); + len = _vscprintf( message, args ) + 1; + buffer = malloc( len * sizeof(char) ); + if ( buffer ) { vsprintf( buffer, message, args ); OutputDebugString( buffer ); free( buffer ); } + WSASetLastError( err ); +} +#else + + #include <sys/fcntl.h> // For O_RDWR etc. + #include <sys/time.h> + #include <sys/socket.h> + #include <syslog.h> + + #define sockaddr_mdns sockaddr_un + #define AF_MDNS AF_LOCAL + +#endif + +// <rdar://problem/4096913> Specifies how many times we'll try and connect to the server. + +#define DNSSD_CLIENT_MAXTRIES 4 + +// Uncomment the line below to use the old error return mechanism of creating a temporary named socket (e.g. in /var/tmp) +//#define USE_NAMED_ERROR_RETURN_SOCKET 1 + +// If the UDS client has not received a response from the daemon in 60 secs, it is unlikely to get one +// Note: Timeout of 3 secs should be sufficient in normal scenarios, but 60 secs is chosen as a safeguard since +// some clients may come up before mDNSResponder itself after a BOOT and on rare ocassions IOPM/Keychain/D2D calls +// in mDNSResponder's INIT may take a much longer time to return +#define DNSSD_CLIENT_TIMEOUT 60 + +#ifndef CTL_PATH_PREFIX +#define CTL_PATH_PREFIX "/var/tmp/dnssd_result_socket." +#endif + +typedef struct +{ + ipc_msg_hdr ipc_hdr; + DNSServiceFlags cb_flags; + uint32_t cb_interface; + DNSServiceErrorType cb_err; +} CallbackHeader; + +typedef struct _DNSServiceRef_t DNSServiceOp; +typedef struct _DNSRecordRef_t DNSRecord; + +#if !defined(_WIN32) +typedef struct +{ + void *AppCallback; // Client callback function and context + void *AppContext; +} SleepKAContext; +#endif + +// client stub callback to process message from server and deliver results to client application +typedef void (*ProcessReplyFn)(DNSServiceOp *const sdr, const CallbackHeader *const cbh, const char *msg, const char *const end); + +#define ValidatorBits 0x12345678 +#define DNSServiceRefValid(X) (dnssd_SocketValid((X)->sockfd) && (((X)->sockfd ^ (X)->validator) == ValidatorBits)) + +// When using kDNSServiceFlagsShareConnection, there is one primary _DNSServiceOp_t, and zero or more subordinates +// For the primary, the 'next' field points to the first subordinate, and its 'next' field points to the next, and so on. +// For the primary, the 'primary' field is NULL; for subordinates the 'primary' field points back to the associated primary +// +// _DNS_SD_LIBDISPATCH is defined where libdispatch/GCD is available. This does not mean that the application will use the +// DNSServiceSetDispatchQueue API. Hence any new code guarded with _DNS_SD_LIBDISPATCH should still be backwards compatible. +struct _DNSServiceRef_t +{ + DNSServiceOp *next; // For shared connection + DNSServiceOp *primary; // For shared connection + dnssd_sock_t sockfd; // Connected socket between client and daemon + dnssd_sock_t validator; // Used to detect memory corruption, double disposals, etc. + client_context_t uid; // For shared connection requests, each subordinate DNSServiceRef has its own ID, + // unique within the scope of the same shared parent DNSServiceRef + uint32_t op; // request_op_t or reply_op_t + uint32_t max_index; // Largest assigned record index - 0 if no additional records registered + uint32_t logcounter; // Counter used to control number of syslog messages we write + int *moreptr; // Set while DNSServiceProcessResult working on this particular DNSServiceRef + ProcessReplyFn ProcessReply; // Function pointer to the code to handle received messages + void *AppCallback; // Client callback function and context + void *AppContext; + DNSRecord *rec; +#if _DNS_SD_LIBDISPATCH + dispatch_source_t disp_source; + dispatch_queue_t disp_queue; +#endif + void *kacontext; +}; + +struct _DNSRecordRef_t +{ + DNSRecord *recnext; + void *AppContext; + DNSServiceRegisterRecordReply AppCallback; + DNSRecordRef recref; + uint32_t record_index; // index is unique to the ServiceDiscoveryRef + client_context_t uid; // For demultiplexing multiple DNSServiceRegisterRecord calls + DNSServiceOp *sdr; +}; + +// Write len bytes. Return 0 on success, -1 on error +static int write_all(dnssd_sock_t sd, char *buf, size_t len) +{ + // Don't use "MSG_WAITALL"; it returns "Invalid argument" on some Linux versions; use an explicit while() loop instead. + //if (send(sd, buf, len, MSG_WAITALL) != len) return -1; + while (len) + { + ssize_t num_written = send(sd, buf, (long)len, 0); + if (num_written < 0 || (size_t)num_written > len) + { + // Should never happen. If it does, it indicates some OS bug, + // or that the mDNSResponder daemon crashed (which should never happen). + #if !defined(__ppc__) && defined(SO_ISDEFUNCT) + int defunct; + socklen_t dlen = sizeof (defunct); + if (getsockopt(sd, SOL_SOCKET, SO_ISDEFUNCT, &defunct, &dlen) < 0) + syslog(LOG_WARNING, "dnssd_clientstub write_all: SO_ISDEFUNCT failed %d %s", dnssd_errno, dnssd_strerror(dnssd_errno)); + if (!defunct) + syslog(LOG_WARNING, "dnssd_clientstub write_all(%d) failed %ld/%ld %d %s", sd, + (long)num_written, (long)len, + (num_written < 0) ? dnssd_errno : 0, + (num_written < 0) ? dnssd_strerror(dnssd_errno) : ""); + else + syslog(LOG_INFO, "dnssd_clientstub write_all(%d) DEFUNCT", sd); + #else + syslog(LOG_WARNING, "dnssd_clientstub write_all(%d) failed %ld/%ld %d %s", sd, + (long)num_written, (long)len, + (num_written < 0) ? dnssd_errno : 0, + (num_written < 0) ? dnssd_strerror(dnssd_errno) : ""); + #endif + return -1; + } + buf += num_written; + len -= num_written; + } + return 0; +} + +enum { read_all_success = 0, read_all_fail = -1, read_all_wouldblock = -2 }; + +// Read len bytes. Return 0 on success, read_all_fail on error, or read_all_wouldblock for +static int read_all(dnssd_sock_t sd, char *buf, int len) +{ + // Don't use "MSG_WAITALL"; it returns "Invalid argument" on some Linux versions; use an explicit while() loop instead. + //if (recv(sd, buf, len, MSG_WAITALL) != len) return -1; + + while (len) + { + ssize_t num_read = recv(sd, buf, len, 0); + // It is valid to get an interrupted system call error e.g., somebody attaching + // in a debugger, retry without failing + if ((num_read < 0) && (errno == EINTR)) + { + syslog(LOG_INFO, "dnssd_clientstub read_all: EINTR continue"); + continue; + } + if ((num_read == 0) || (num_read < 0) || (num_read > len)) + { + int printWarn = 0; + int defunct = 0; + // Should never happen. If it does, it indicates some OS bug, + // or that the mDNSResponder daemon crashed (which should never happen). +#if defined(WIN32) + // <rdar://problem/7481776> Suppress logs for "A non-blocking socket operation + // could not be completed immediately" + if (WSAGetLastError() != WSAEWOULDBLOCK) + printWarn = 1; +#endif +#if !defined(__ppc__) && defined(SO_ISDEFUNCT) + { + socklen_t dlen = sizeof (defunct); + if (getsockopt(sd, SOL_SOCKET, SO_ISDEFUNCT, &defunct, &dlen) < 0) + syslog(LOG_WARNING, "dnssd_clientstub read_all: SO_ISDEFUNCT failed %d %s", dnssd_errno, dnssd_strerror(dnssd_errno)); + } + if (!defunct) + printWarn = 1; +#endif + if (printWarn) + syslog(LOG_WARNING, "dnssd_clientstub read_all(%d) failed %ld/%ld %d %s", sd, + (long)num_read, (long)len, + (num_read < 0) ? dnssd_errno : 0, + (num_read < 0) ? dnssd_strerror(dnssd_errno) : ""); + else if (defunct) + syslog(LOG_INFO, "dnssd_clientstub read_all(%d) DEFUNCT", sd); + return (num_read < 0 && dnssd_errno == dnssd_EWOULDBLOCK) ? read_all_wouldblock : read_all_fail; + } + buf += num_read; + len -= num_read; + } + return read_all_success; +} + +// Returns 1 if more bytes remain to be read on socket descriptor sd, 0 otherwise +static int more_bytes(dnssd_sock_t sd) +{ + struct timeval tv = { 0, 0 }; + fd_set readfds; + fd_set *fs; + int ret; + + if (sd < FD_SETSIZE) + { + fs = &readfds; + FD_ZERO(fs); + } + else + { + // Compute the number of integers needed for storing "sd". Internally fd_set is stored + // as an array of ints with one bit for each fd and hence we need to compute + // the number of ints needed rather than the number of bytes. If "sd" is 32, we need + // two ints and not just one. + int nfdbits = sizeof (int) * 8; + int nints = (sd/nfdbits) + 1; + fs = (fd_set *)calloc(nints, sizeof(int)); + if (fs == NULL) + { + syslog(LOG_WARNING, "dnssd_clientstub more_bytes: malloc failed"); + return 0; + } + } + FD_SET(sd, fs); + ret = select((int)sd+1, fs, (fd_set*)NULL, (fd_set*)NULL, &tv); + if (fs != &readfds) + free(fs); + return (ret > 0); +} + +// set_waitlimit() implements a timeout using select. It is called from deliver_request() before recv() OR accept() +// to ensure the UDS clients are not blocked in these system calls indefinitely. +// Note: Ideally one should never be blocked here, because it indicates either mDNSResponder daemon is not yet up/hung/ +// superbusy/crashed or some other OS bug. For eg: On Windows which suffers from 3rd party software +// (primarily 3rd party firewall software) interfering with proper functioning of the TCP protocol stack it is possible +// the next operation on this socket(recv/accept) is blocked since we depend on TCP to communicate with the system service. +static int set_waitlimit(dnssd_sock_t sock, int timeout) +{ + // To prevent stack corruption since select does not work with timeout if fds > FD_SETSIZE(1024) + if (!gDaemonErr && sock < FD_SETSIZE) + { + struct timeval tv; + fd_set set; + + FD_ZERO(&set); + FD_SET(sock, &set); + tv.tv_sec = timeout; + tv.tv_usec = 0; + if (!select((int)(sock + 1), &set, NULL, NULL, &tv)) + { + // Ideally one should never hit this case: See comments before set_waitlimit() + syslog(LOG_WARNING, "dnssd_clientstub set_waitlimit:_daemon timed out (%d secs) without any response: Socket %d", timeout, sock); + gDaemonErr = kDNSServiceErr_Timeout; + } + } + return gDaemonErr; +} + +/* create_hdr + * + * allocate and initialize an ipc message header. Value of len should initially be the + * length of the data, and is set to the value of the data plus the header. data_start + * is set to point to the beginning of the data section. SeparateReturnSocket should be + * non-zero for calls that can't receive an immediate error return value on their primary + * socket, and therefore require a separate return path for the error code result. + * if zero, the path to a control socket is appended at the beginning of the message buffer. + * data_start is set past this string. + */ +static ipc_msg_hdr *create_hdr(uint32_t op, size_t *len, char **data_start, int SeparateReturnSocket, DNSServiceOp *ref) +{ + char *msg = NULL; + ipc_msg_hdr *hdr; + int datalen; +#if !defined(USE_TCP_LOOPBACK) + char ctrl_path[64] = ""; // "/var/tmp/dnssd_result_socket.xxxxxxxxxx-xxx-xxxxxx" +#endif + + if (SeparateReturnSocket) + { +#if defined(USE_TCP_LOOPBACK) + *len += 2; // Allocate space for two-byte port number +#elif defined(USE_NAMED_ERROR_RETURN_SOCKET) + struct timeval tv; + if (gettimeofday(&tv, NULL) < 0) + { syslog(LOG_WARNING, "dnssd_clientstub create_hdr: gettimeofday failed %d %s", dnssd_errno, dnssd_strerror(dnssd_errno)); return NULL; } + sprintf(ctrl_path, "%s%d-%.3lx-%.6lu", CTL_PATH_PREFIX, (int)getpid(), + (unsigned long)(tv.tv_sec & 0xFFF), (unsigned long)(tv.tv_usec)); + *len += strlen(ctrl_path) + 1; +#else + *len += 1; // Allocate space for single zero byte (empty C string) +#endif + } + + datalen = (int) *len; + *len += sizeof(ipc_msg_hdr); + + // Write message to buffer + msg = malloc(*len); + if (!msg) { syslog(LOG_WARNING, "dnssd_clientstub create_hdr: malloc failed"); return NULL; } + + memset(msg, 0, *len); + hdr = (ipc_msg_hdr *)msg; + hdr->version = VERSION; + hdr->datalen = datalen; + hdr->ipc_flags = 0; + hdr->op = op; + hdr->client_context = ref->uid; + hdr->reg_index = 0; + *data_start = msg + sizeof(ipc_msg_hdr); +#if defined(USE_TCP_LOOPBACK) + // Put dummy data in for the port, since we don't know what it is yet. + // The data will get filled in before we send the message. This happens in deliver_request(). + if (SeparateReturnSocket) put_uint16(0, data_start); +#else + if (SeparateReturnSocket) put_string(ctrl_path, data_start); +#endif + return hdr; +} + +static void FreeDNSRecords(DNSServiceOp *sdRef) +{ + DNSRecord *rec = sdRef->rec; + while (rec) + { + DNSRecord *next = rec->recnext; + free(rec); + rec = next; + } +} + +static void FreeDNSServiceOp(DNSServiceOp *x) +{ + // We don't use our DNSServiceRefValid macro here because if we're cleaning up after a socket() call failed + // then sockfd could legitimately contain a failing value (e.g. dnssd_InvalidSocket) + if ((x->sockfd ^ x->validator) != ValidatorBits) + syslog(LOG_WARNING, "dnssd_clientstub attempt to dispose invalid DNSServiceRef %p %08X %08X", x, x->sockfd, x->validator); + else + { + x->next = NULL; + x->primary = NULL; + x->sockfd = dnssd_InvalidSocket; + x->validator = 0xDDDDDDDD; + x->op = request_op_none; + x->max_index = 0; + x->logcounter = 0; + x->moreptr = NULL; + x->ProcessReply = NULL; + x->AppCallback = NULL; + x->AppContext = NULL; +#if _DNS_SD_LIBDISPATCH + if (x->disp_source) dispatch_release(x->disp_source); + x->disp_source = NULL; + x->disp_queue = NULL; +#endif + // DNSRecords may have been added to subordinate sdRef e.g., DNSServiceRegister/DNSServiceAddRecord + // or on the main sdRef e.g., DNSServiceCreateConnection/DNSServiveRegisterRecord. DNSRecords may have + // been freed if the application called DNSRemoveRecord + FreeDNSRecords(x); + if (x->kacontext) + { + free(x->kacontext); + x->kacontext = NULL; + } + free(x); + } +} + +// Return a connected service ref (deallocate with DNSServiceRefDeallocate) +static DNSServiceErrorType ConnectToServer(DNSServiceRef *ref, DNSServiceFlags flags, uint32_t op, ProcessReplyFn ProcessReply, void *AppCallback, void *AppContext) +{ + int NumTries = 0; + + dnssd_sockaddr_t saddr; + DNSServiceOp *sdr; + + if (!ref) + { + syslog(LOG_WARNING, "dnssd_clientstub DNSService operation with NULL DNSServiceRef"); + return kDNSServiceErr_BadParam; + } + + if (flags & kDNSServiceFlagsShareConnection) + { + if (!*ref) + { + syslog(LOG_WARNING, "dnssd_clientstub kDNSServiceFlagsShareConnection used with NULL DNSServiceRef"); + return kDNSServiceErr_BadParam; + } + if (!DNSServiceRefValid(*ref) || ((*ref)->op != connection_request && (*ref)->op != connection_delegate_request) || (*ref)->primary) + { + syslog(LOG_WARNING, "dnssd_clientstub kDNSServiceFlagsShareConnection used with invalid DNSServiceRef %p %08X %08X op %d", + (*ref), (*ref)->sockfd, (*ref)->validator, (*ref)->op); + *ref = NULL; + return kDNSServiceErr_BadReference; + } + } + + #if defined(_WIN32) + if (!g_initWinsock) + { + WSADATA wsaData; + g_initWinsock = 1; + if (WSAStartup(MAKEWORD(2,2), &wsaData) != 0) { *ref = NULL; return kDNSServiceErr_ServiceNotRunning; } + } + // <rdar://problem/4096913> If the system service is disabled, we only want to try to connect once + if (IsSystemServiceDisabled()) + NumTries = DNSSD_CLIENT_MAXTRIES; + #endif + + sdr = malloc(sizeof(DNSServiceOp)); + if (!sdr) + { + syslog(LOG_WARNING, "dnssd_clientstub ConnectToServer: malloc failed"); + *ref = NULL; + return kDNSServiceErr_NoMemory; + } + sdr->next = NULL; + sdr->primary = NULL; + sdr->sockfd = dnssd_InvalidSocket; + sdr->validator = sdr->sockfd ^ ValidatorBits; + sdr->op = op; + sdr->max_index = 0; + sdr->logcounter = 0; + sdr->moreptr = NULL; + sdr->uid.u32[0] = 0; + sdr->uid.u32[1] = 0; + sdr->ProcessReply = ProcessReply; + sdr->AppCallback = AppCallback; + sdr->AppContext = AppContext; + sdr->rec = NULL; +#if _DNS_SD_LIBDISPATCH + sdr->disp_source = NULL; + sdr->disp_queue = NULL; +#endif + sdr->kacontext = NULL; + + if (flags & kDNSServiceFlagsShareConnection) + { + DNSServiceOp **p = &(*ref)->next; // Append ourselves to end of primary's list + while (*p) + p = &(*p)->next; + *p = sdr; + // Preincrement counter before we use it -- it helps with debugging if we know the all-zeroes ID should never appear + if (++(*ref)->uid.u32[0] == 0) + ++(*ref)->uid.u32[1]; // In parent DNSServiceOp increment UID counter + sdr->primary = *ref; // Set our primary pointer + sdr->sockfd = (*ref)->sockfd; // Inherit primary's socket + sdr->validator = (*ref)->validator; + sdr->uid = (*ref)->uid; + //printf("ConnectToServer sharing socket %d\n", sdr->sockfd); + } + else + { + #ifdef SO_NOSIGPIPE + const unsigned long optval = 1; + #endif + *ref = NULL; + sdr->sockfd = socket(AF_DNSSD, SOCK_STREAM, 0); + sdr->validator = sdr->sockfd ^ ValidatorBits; + if (!dnssd_SocketValid(sdr->sockfd)) + { + syslog(LOG_WARNING, "dnssd_clientstub ConnectToServer: socket failed %d %s", dnssd_errno, dnssd_strerror(dnssd_errno)); + FreeDNSServiceOp(sdr); + return kDNSServiceErr_NoMemory; + } + #ifdef SO_NOSIGPIPE + // Some environments (e.g. OS X) support turning off SIGPIPE for a socket + if (setsockopt(sdr->sockfd, SOL_SOCKET, SO_NOSIGPIPE, &optval, sizeof(optval)) < 0) + syslog(LOG_WARNING, "dnssd_clientstub ConnectToServer: SO_NOSIGPIPE failed %d %s", dnssd_errno, dnssd_strerror(dnssd_errno)); + #endif + #if defined(USE_TCP_LOOPBACK) + saddr.sin_family = AF_INET; + saddr.sin_addr.s_addr = inet_addr(MDNS_TCP_SERVERADDR); + saddr.sin_port = htons(MDNS_TCP_SERVERPORT); + #else + saddr.sun_family = AF_LOCAL; + strcpy(saddr.sun_path, MDNS_UDS_SERVERPATH); + #if !defined(__ppc__) && defined(SO_DEFUNCTOK) + { + int defunct = 1; + if (setsockopt(sdr->sockfd, SOL_SOCKET, SO_DEFUNCTOK, &defunct, sizeof(defunct)) < 0) + syslog(LOG_WARNING, "dnssd_clientstub ConnectToServer: SO_DEFUNCTOK failed %d %s", dnssd_errno, dnssd_strerror(dnssd_errno)); + } + #endif + #endif + + while (1) + { + int err = connect(sdr->sockfd, (struct sockaddr *) &saddr, sizeof(saddr)); + if (!err) + break; // If we succeeded, return sdr + // If we failed, then it may be because the daemon is still launching. + // This can happen for processes that launch early in the boot process, while the + // daemon is still coming up. Rather than fail here, we wait 1 sec and try again. + // If, after DNSSD_CLIENT_MAXTRIES, we still can't connect to the daemon, + // then we give up and return a failure code. + if (++NumTries < DNSSD_CLIENT_MAXTRIES) + { + syslog(LOG_WARNING, "dnssd_clientstub ConnectToServer: connect()-> No of tries: %d", NumTries); + sleep(1); // Sleep a bit, then try again + } + else + { + syslog(LOG_WARNING, "dnssd_clientstub ConnectToServer: connect() failed Socket:%d Err:%d Errno:%d %s", + sdr->sockfd, err, dnssd_errno, dnssd_strerror(dnssd_errno)); + dnssd_close(sdr->sockfd); + FreeDNSServiceOp(sdr); + return kDNSServiceErr_ServiceNotRunning; + } + } + //printf("ConnectToServer opened socket %d\n", sdr->sockfd); + } + + *ref = sdr; + return kDNSServiceErr_NoError; +} + +#define deliver_request_bailout(MSG) \ + do { syslog(LOG_WARNING, "dnssd_clientstub deliver_request: %s failed %d (%s)", (MSG), dnssd_errno, dnssd_strerror(dnssd_errno)); goto cleanup; } while(0) + +static DNSServiceErrorType deliver_request(ipc_msg_hdr *hdr, DNSServiceOp *sdr) +{ + uint32_t datalen = hdr->datalen; // We take a copy here because we're going to convert hdr->datalen to network byte order + #if defined(USE_TCP_LOOPBACK) || defined(USE_NAMED_ERROR_RETURN_SOCKET) + char *const data = (char *)hdr + sizeof(ipc_msg_hdr); + #endif + dnssd_sock_t listenfd = dnssd_InvalidSocket, errsd = dnssd_InvalidSocket; + DNSServiceErrorType err = kDNSServiceErr_Unknown; // Default for the "goto cleanup" cases + int MakeSeparateReturnSocket = 0; + + // Note: need to check hdr->op, not sdr->op. + // hdr->op contains the code for the specific operation we're currently doing, whereas sdr->op + // contains the original parent DNSServiceOp (e.g. for an add_record_request, hdr->op will be + // add_record_request but the parent sdr->op will be connection_request or reg_service_request) + if (sdr->primary || + hdr->op == reg_record_request || hdr->op == add_record_request || hdr->op == update_record_request || hdr->op == remove_record_request) + MakeSeparateReturnSocket = 1; + + if (!DNSServiceRefValid(sdr)) + { + if (hdr) + free(hdr); + syslog(LOG_WARNING, "dnssd_clientstub deliver_request: invalid DNSServiceRef %p %08X %08X", sdr, sdr->sockfd, sdr->validator); + return kDNSServiceErr_BadReference; + } + + if (!hdr) + { + syslog(LOG_WARNING, "dnssd_clientstub deliver_request: !hdr"); + return kDNSServiceErr_Unknown; + } + + if (MakeSeparateReturnSocket) + { + #if defined(USE_TCP_LOOPBACK) + { + union { uint16_t s; u_char b[2]; } port; + dnssd_sockaddr_t caddr; + dnssd_socklen_t len = (dnssd_socklen_t) sizeof(caddr); + listenfd = socket(AF_DNSSD, SOCK_STREAM, 0); + if (!dnssd_SocketValid(listenfd)) deliver_request_bailout("TCP socket"); + + caddr.sin_family = AF_INET; + caddr.sin_port = 0; + caddr.sin_addr.s_addr = inet_addr(MDNS_TCP_SERVERADDR); + if (bind(listenfd, (struct sockaddr*) &caddr, sizeof(caddr)) < 0) deliver_request_bailout("TCP bind"); + if (getsockname(listenfd, (struct sockaddr*) &caddr, &len) < 0) deliver_request_bailout("TCP getsockname"); + if (listen(listenfd, 1) < 0) deliver_request_bailout("TCP listen"); + port.s = caddr.sin_port; + data[0] = port.b[0]; // don't switch the byte order, as the + data[1] = port.b[1]; // daemon expects it in network byte order + } + #elif defined(USE_NAMED_ERROR_RETURN_SOCKET) + { + mode_t mask; + int bindresult; + dnssd_sockaddr_t caddr; + listenfd = socket(AF_DNSSD, SOCK_STREAM, 0); + if (!dnssd_SocketValid(listenfd)) deliver_request_bailout("USE_NAMED_ERROR_RETURN_SOCKET socket"); + + caddr.sun_family = AF_LOCAL; + // According to Stevens (section 3.2), there is no portable way to + // determine whether sa_len is defined on a particular platform. + #ifndef NOT_HAVE_SA_LEN + caddr.sun_len = sizeof(struct sockaddr_un); + #endif + strcpy(caddr.sun_path, data); + mask = umask(0); + bindresult = bind(listenfd, (struct sockaddr *)&caddr, sizeof(caddr)); + umask(mask); + if (bindresult < 0) deliver_request_bailout("USE_NAMED_ERROR_RETURN_SOCKET bind"); + if (listen(listenfd, 1) < 0) deliver_request_bailout("USE_NAMED_ERROR_RETURN_SOCKET listen"); + } + #else + { + dnssd_sock_t sp[2]; + if (socketpair(AF_DNSSD, SOCK_STREAM, 0, sp) < 0) deliver_request_bailout("socketpair"); + else + { + errsd = sp[0]; // We'll read our four-byte error code from sp[0] + listenfd = sp[1]; // We'll send sp[1] to the daemon + #if !defined(__ppc__) && defined(SO_DEFUNCTOK) + { + int defunct = 1; + if (setsockopt(errsd, SOL_SOCKET, SO_DEFUNCTOK, &defunct, sizeof(defunct)) < 0) + syslog(LOG_WARNING, "dnssd_clientstub ConnectToServer: SO_DEFUNCTOK failed %d %s", dnssd_errno, dnssd_strerror(dnssd_errno)); + } + #endif + } + } + #endif + } + +#if !defined(USE_TCP_LOOPBACK) && !defined(USE_NAMED_ERROR_RETURN_SOCKET) + // If we're going to make a separate error return socket, and pass it to the daemon + // using sendmsg, then we'll hold back one data byte to go with it. + // On some versions of Unix (including Leopard) sending a control message without + // any associated data does not work reliably -- e.g. one particular issue we ran + // into is that if the receiving program is in a kqueue loop waiting to be notified + // of the received message, it doesn't get woken up when the control message arrives. + if (MakeSeparateReturnSocket || sdr->op == send_bpf) + datalen--; // Okay to use sdr->op when checking for op == send_bpf +#endif + + // At this point, our listening socket is set up and waiting, if necessary, for the daemon to connect back to + ConvertHeaderBytes(hdr); + //syslog(LOG_WARNING, "dnssd_clientstub deliver_request writing %lu bytes", (unsigned long)(datalen + sizeof(ipc_msg_hdr))); + //if (MakeSeparateReturnSocket) syslog(LOG_WARNING, "dnssd_clientstub deliver_request name is %s", data); +#if TEST_SENDING_ONE_BYTE_AT_A_TIME + unsigned int i; + for (i=0; i<datalen + sizeof(ipc_msg_hdr); i++) + { + syslog(LOG_WARNING, "dnssd_clientstub deliver_request writing %d", i); + if (write_all(sdr->sockfd, ((char *)hdr)+i, 1) < 0) + { syslog(LOG_WARNING, "write_all (byte %u) failed", i); goto cleanup; } + usleep(10000); + } +#else + if (write_all(sdr->sockfd, (char *)hdr, datalen + sizeof(ipc_msg_hdr)) < 0) + { + // write_all already prints an error message if there is an error writing to + // the socket except for DEFUNCT. Logging here is unnecessary and also wrong + // in the case of DEFUNCT sockets + syslog(LOG_INFO, "dnssd_clientstub deliver_request ERROR: write_all(%d, %lu bytes) failed", + sdr->sockfd, (unsigned long)(datalen + sizeof(ipc_msg_hdr))); + goto cleanup; + } +#endif + + if (!MakeSeparateReturnSocket) + errsd = sdr->sockfd; + if (MakeSeparateReturnSocket || sdr->op == send_bpf) // Okay to use sdr->op when checking for op == send_bpf + { +#if defined(USE_TCP_LOOPBACK) || defined(USE_NAMED_ERROR_RETURN_SOCKET) + // At this point we may wait in accept for a few milliseconds waiting for the daemon to connect back to us, + // but that's okay -- the daemon should not take more than a few milliseconds to respond. + // set_waitlimit() ensures we do not block indefinitely just in case something is wrong + dnssd_sockaddr_t daddr; + dnssd_socklen_t len = sizeof(daddr); + if ((err = set_waitlimit(listenfd, DNSSD_CLIENT_TIMEOUT)) != kDNSServiceErr_NoError) + goto cleanup; + errsd = accept(listenfd, (struct sockaddr *)&daddr, &len); + if (!dnssd_SocketValid(errsd)) + deliver_request_bailout("accept"); +#else + + struct iovec vec = { ((char *)hdr) + sizeof(ipc_msg_hdr) + datalen, 1 }; // Send the last byte along with the SCM_RIGHTS + struct msghdr msg; + struct cmsghdr *cmsg; + char cbuf[CMSG_SPACE(4 * sizeof(dnssd_sock_t))]; + + msg.msg_name = 0; + msg.msg_namelen = 0; + msg.msg_iov = &vec; + msg.msg_iovlen = 1; + msg.msg_flags = 0; + if (MakeSeparateReturnSocket || sdr->op == send_bpf) // Okay to use sdr->op when checking for op == send_bpf + { + if (sdr->op == send_bpf) + { + int i; + char p[12]; // Room for "/dev/bpf999" with terminating null + for (i=0; i<100; i++) + { + snprintf(p, sizeof(p), "/dev/bpf%d", i); + listenfd = open(p, O_RDWR, 0); + //if (dnssd_SocketValid(listenfd)) syslog(LOG_WARNING, "Sending fd %d for %s", listenfd, p); + if (!dnssd_SocketValid(listenfd) && dnssd_errno != EBUSY) + syslog(LOG_WARNING, "Error opening %s %d (%s)", p, dnssd_errno, dnssd_strerror(dnssd_errno)); + if (dnssd_SocketValid(listenfd) || dnssd_errno != EBUSY) break; + } + } + msg.msg_control = cbuf; + msg.msg_controllen = CMSG_LEN(sizeof(dnssd_sock_t)); + + cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_len = CMSG_LEN(sizeof(dnssd_sock_t)); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + *((dnssd_sock_t *)CMSG_DATA(cmsg)) = listenfd; + } + +#if TEST_KQUEUE_CONTROL_MESSAGE_BUG + sleep(1); +#endif + +#if DEBUG_64BIT_SCM_RIGHTS + syslog(LOG_WARNING, "dnssd_clientstub sendmsg read sd=%d write sd=%d %ld %ld %ld/%ld/%ld/%ld", + errsd, listenfd, sizeof(dnssd_sock_t), sizeof(void*), + sizeof(struct cmsghdr) + sizeof(dnssd_sock_t), + CMSG_LEN(sizeof(dnssd_sock_t)), (long)CMSG_SPACE(sizeof(dnssd_sock_t)), + (long)((char*)CMSG_DATA(cmsg) + 4 - cbuf)); +#endif // DEBUG_64BIT_SCM_RIGHTS + + if (sendmsg(sdr->sockfd, &msg, 0) < 0) + { + syslog(LOG_WARNING, "dnssd_clientstub deliver_request ERROR: sendmsg failed read sd=%d write sd=%d errno %d (%s)", + errsd, listenfd, dnssd_errno, dnssd_strerror(dnssd_errno)); + err = kDNSServiceErr_Incompatible; + goto cleanup; + } + +#if DEBUG_64BIT_SCM_RIGHTS + syslog(LOG_WARNING, "dnssd_clientstub sendmsg read sd=%d write sd=%d okay", errsd, listenfd); +#endif // DEBUG_64BIT_SCM_RIGHTS + +#endif + // Close our end of the socketpair *before* calling read_all() to get the four-byte error code. + // Otherwise, if the daemon closes our socket (or crashes), we will have to wait for a timeout + // in read_all() because the socket is not closed (we still have an open reference to it) + // Note: listenfd is overwritten in the case of send_bpf above and that will be closed here + // for send_bpf operation. + dnssd_close(listenfd); + listenfd = dnssd_InvalidSocket; // Make sure we don't close it a second time in the cleanup handling below + } + + // At this point we may wait in read_all for a few milliseconds waiting for the daemon to send us the error code, + // but that's okay -- the daemon should not take more than a few milliseconds to respond. + // set_waitlimit() ensures we do not block indefinitely just in case something is wrong + if (sdr->op == send_bpf) // Okay to use sdr->op when checking for op == send_bpf + err = kDNSServiceErr_NoError; + else if ((err = set_waitlimit(errsd, DNSSD_CLIENT_TIMEOUT)) == kDNSServiceErr_NoError) + { + if (read_all(errsd, (char*)&err, (int)sizeof(err)) < 0) + err = kDNSServiceErr_ServiceNotRunning; // On failure read_all will have written a message to syslog for us + else + err = ntohl(err); + } + //syslog(LOG_WARNING, "dnssd_clientstub deliver_request: retrieved error code %d", err); + +cleanup: + if (MakeSeparateReturnSocket) + { + if (dnssd_SocketValid(listenfd)) dnssd_close(listenfd); + if (dnssd_SocketValid(errsd)) dnssd_close(errsd); +#if defined(USE_NAMED_ERROR_RETURN_SOCKET) + // syslog(LOG_WARNING, "dnssd_clientstub deliver_request: removing UDS: %s", data); + if (unlink(data) != 0) + syslog(LOG_WARNING, "dnssd_clientstub WARNING: unlink(\"%s\") failed errno %d (%s)", data, dnssd_errno, dnssd_strerror(dnssd_errno)); + // else syslog(LOG_WARNING, "dnssd_clientstub deliver_request: removed UDS: %s", data); +#endif + } + + free(hdr); + return err; +} + +int DNSSD_API DNSServiceRefSockFD(DNSServiceRef sdRef) +{ + if (!sdRef) { syslog(LOG_WARNING, "dnssd_clientstub DNSServiceRefSockFD called with NULL DNSServiceRef"); return dnssd_InvalidSocket; } + + if (!DNSServiceRefValid(sdRef)) + { + syslog(LOG_WARNING, "dnssd_clientstub DNSServiceRefSockFD called with invalid DNSServiceRef %p %08X %08X", + sdRef, sdRef->sockfd, sdRef->validator); + return dnssd_InvalidSocket; + } + + if (sdRef->primary) + { + syslog(LOG_WARNING, "dnssd_clientstub DNSServiceRefSockFD undefined for kDNSServiceFlagsShareConnection subordinate DNSServiceRef %p", sdRef); + return dnssd_InvalidSocket; + } + + return (int) sdRef->sockfd; +} + +#if _DNS_SD_LIBDISPATCH +static void CallbackWithError(DNSServiceRef sdRef, DNSServiceErrorType error) +{ + DNSServiceOp *sdr = sdRef; + DNSServiceOp *sdrNext; + DNSRecord *rec; + DNSRecord *recnext; + int morebytes; + + while (sdr) + { + // We can't touch the sdr after the callback as it can be deallocated in the callback + sdrNext = sdr->next; + morebytes = 1; + sdr->moreptr = &morebytes; + switch (sdr->op) + { + case resolve_request: + if (sdr->AppCallback) ((DNSServiceResolveReply) sdr->AppCallback)(sdr, 0, 0, error, NULL, 0, 0, 0, NULL, sdr->AppContext); + break; + case query_request: + if (sdr->AppCallback) ((DNSServiceQueryRecordReply)sdr->AppCallback)(sdr, 0, 0, error, NULL, 0, 0, 0, NULL, 0, sdr->AppContext); + break; + case addrinfo_request: + if (sdr->AppCallback) ((DNSServiceGetAddrInfoReply)sdr->AppCallback)(sdr, 0, 0, error, NULL, NULL, 0, sdr->AppContext); + break; + case browse_request: + if (sdr->AppCallback) ((DNSServiceBrowseReply) sdr->AppCallback)(sdr, 0, 0, error, NULL, 0, NULL, sdr->AppContext); + break; + case reg_service_request: + if (sdr->AppCallback) ((DNSServiceRegisterReply) sdr->AppCallback)(sdr, 0, error, NULL, 0, NULL, sdr->AppContext); + break; + case enumeration_request: + if (sdr->AppCallback) ((DNSServiceDomainEnumReply) sdr->AppCallback)(sdr, 0, 0, error, NULL, sdr->AppContext); + break; + case connection_request: + case connection_delegate_request: + // This means Register Record, walk the list of DNSRecords to do the callback + rec = sdr->rec; + while (rec) + { + recnext = rec->recnext; + if (rec->AppCallback) ((DNSServiceRegisterRecordReply)rec->AppCallback)(sdr, 0, 0, error, rec->AppContext); + // The Callback can call DNSServiceRefDeallocate which in turn frees sdr and all the records. + // Detect that and return early + if (!morebytes) {syslog(LOG_WARNING, "dnssdclientstub:Record: CallbackwithError morebytes zero"); return;} + rec = recnext; + } + break; + case port_mapping_request: + if (sdr->AppCallback) ((DNSServiceNATPortMappingReply)sdr->AppCallback)(sdr, 0, 0, error, 0, 0, 0, 0, 0, sdr->AppContext); + break; + default: + syslog(LOG_WARNING, "dnssd_clientstub CallbackWithError called with bad op %d", sdr->op); + } + // If DNSServiceRefDeallocate was called in the callback, morebytes will be zero. As the sdRef + // (and its subordinates) have been freed, we should not proceed further. Note that when we + // call the callback with a subordinate sdRef the application can call DNSServiceRefDeallocate + // on the main sdRef and DNSServiceRefDeallocate handles this case by walking all the sdRefs and + // clears the moreptr so that we can terminate here. + // + // If DNSServiceRefDeallocate was not called in the callback, then set moreptr to NULL so that + // we don't access the stack variable after we return from this function. + if (!morebytes) {syslog(LOG_WARNING, "dnssdclientstub:sdRef: CallbackwithError morebytes zero sdr %p", sdr); return;} + else {sdr->moreptr = NULL;} + sdr = sdrNext; + } +} +#endif // _DNS_SD_LIBDISPATCH + +// Handle reply from server, calling application client callback. If there is no reply +// from the daemon on the socket contained in sdRef, the call will block. +DNSServiceErrorType DNSSD_API DNSServiceProcessResult(DNSServiceRef sdRef) +{ + int morebytes = 0; + + if (!sdRef) { syslog(LOG_WARNING, "dnssd_clientstub DNSServiceProcessResult called with NULL DNSServiceRef"); return kDNSServiceErr_BadParam; } + + if (!DNSServiceRefValid(sdRef)) + { + syslog(LOG_WARNING, "dnssd_clientstub DNSServiceProcessResult called with invalid DNSServiceRef %p %08X %08X", sdRef, sdRef->sockfd, sdRef->validator); + return kDNSServiceErr_BadReference; + } + + if (sdRef->primary) + { + syslog(LOG_WARNING, "dnssd_clientstub DNSServiceProcessResult undefined for kDNSServiceFlagsShareConnection subordinate DNSServiceRef %p", sdRef); + return kDNSServiceErr_BadReference; + } + + if (!sdRef->ProcessReply) + { + static int num_logs = 0; + if (num_logs < 10) syslog(LOG_WARNING, "dnssd_clientstub DNSServiceProcessResult called with DNSServiceRef with no ProcessReply function"); + if (num_logs < 1000) num_logs++;else sleep(1); + return kDNSServiceErr_BadReference; + } + + do + { + CallbackHeader cbh; + char *data; + + // return NoError on EWOULDBLOCK. This will handle the case + // where a non-blocking socket is told there is data, but it was a false positive. + // On error, read_all will write a message to syslog for us, so don't need to duplicate that here + // Note: If we want to properly support using non-blocking sockets in the future + int result = read_all(sdRef->sockfd, (void *)&cbh.ipc_hdr, sizeof(cbh.ipc_hdr)); + if (result == read_all_fail) + { + // Set the ProcessReply to NULL before callback as the sdRef can get deallocated + // in the callback. + sdRef->ProcessReply = NULL; +#if _DNS_SD_LIBDISPATCH + // Call the callbacks with an error if using the dispatch API, as DNSServiceProcessResult + // is not called by the application and hence need to communicate the error. Cancel the + // source so that we don't get any more events + // Note: read_all fails if we could not read from the daemon which can happen if the + // daemon dies or the file descriptor is disconnected (defunct). + if (sdRef->disp_source) + { + dispatch_source_cancel(sdRef->disp_source); + dispatch_release(sdRef->disp_source); + sdRef->disp_source = NULL; + CallbackWithError(sdRef, kDNSServiceErr_ServiceNotRunning); + } +#endif + // Don't touch sdRef anymore as it might have been deallocated + return kDNSServiceErr_ServiceNotRunning; + } + else if (result == read_all_wouldblock) + { + if (morebytes && sdRef->logcounter < 100) + { + sdRef->logcounter++; + syslog(LOG_WARNING, "dnssd_clientstub DNSServiceProcessResult error: select indicated data was waiting but read_all returned EWOULDBLOCK"); + } + return kDNSServiceErr_NoError; + } + + ConvertHeaderBytes(&cbh.ipc_hdr); + if (cbh.ipc_hdr.version != VERSION) + { + syslog(LOG_WARNING, "dnssd_clientstub DNSServiceProcessResult daemon version %d does not match client version %d", cbh.ipc_hdr.version, VERSION); + sdRef->ProcessReply = NULL; + return kDNSServiceErr_Incompatible; + } + + data = malloc(cbh.ipc_hdr.datalen); + if (!data) return kDNSServiceErr_NoMemory; + if (read_all(sdRef->sockfd, data, cbh.ipc_hdr.datalen) < 0) // On error, read_all will write a message to syslog for us + { + // Set the ProcessReply to NULL before callback as the sdRef can get deallocated + // in the callback. + sdRef->ProcessReply = NULL; +#if _DNS_SD_LIBDISPATCH + // Call the callbacks with an error if using the dispatch API, as DNSServiceProcessResult + // is not called by the application and hence need to communicate the error. Cancel the + // source so that we don't get any more events + if (sdRef->disp_source) + { + dispatch_source_cancel(sdRef->disp_source); + dispatch_release(sdRef->disp_source); + sdRef->disp_source = NULL; + CallbackWithError(sdRef, kDNSServiceErr_ServiceNotRunning); + } +#endif + // Don't touch sdRef anymore as it might have been deallocated + free(data); + return kDNSServiceErr_ServiceNotRunning; + } + else + { + const char *ptr = data; + cbh.cb_flags = get_flags (&ptr, data + cbh.ipc_hdr.datalen); + cbh.cb_interface = get_uint32 (&ptr, data + cbh.ipc_hdr.datalen); + cbh.cb_err = get_error_code(&ptr, data + cbh.ipc_hdr.datalen); + + // CAUTION: We have to handle the case where the client calls DNSServiceRefDeallocate from within the callback function. + // To do this we set moreptr to point to morebytes. If the client does call DNSServiceRefDeallocate(), + // then that routine will clear morebytes for us, and cause us to exit our loop. + morebytes = more_bytes(sdRef->sockfd); + if (morebytes) + { + cbh.cb_flags |= kDNSServiceFlagsMoreComing; + sdRef->moreptr = &morebytes; + } + if (ptr) sdRef->ProcessReply(sdRef, &cbh, ptr, data + cbh.ipc_hdr.datalen); + // Careful code here: + // If morebytes is non-zero, that means we set sdRef->moreptr above, and the operation was not + // cancelled out from under us, so now we need to clear sdRef->moreptr so we don't leave a stray + // dangling pointer pointing to a long-gone stack variable. + // If morebytes is zero, then one of two thing happened: + // (a) morebytes was 0 above, so we didn't set sdRef->moreptr, so we don't need to clear it + // (b) morebytes was 1 above, and we set sdRef->moreptr, but the operation was cancelled (with DNSServiceRefDeallocate()), + // so we MUST NOT try to dereference our stale sdRef pointer. + if (morebytes) sdRef->moreptr = NULL; + } + free(data); + } while (morebytes); + + return kDNSServiceErr_NoError; +} + +void DNSSD_API DNSServiceRefDeallocate(DNSServiceRef sdRef) +{ + if (!sdRef) { syslog(LOG_WARNING, "dnssd_clientstub DNSServiceRefDeallocate called with NULL DNSServiceRef"); return; } + + if (!DNSServiceRefValid(sdRef)) // Also verifies dnssd_SocketValid(sdRef->sockfd) for us too + { + syslog(LOG_WARNING, "dnssd_clientstub DNSServiceRefDeallocate called with invalid DNSServiceRef %p %08X %08X", sdRef, sdRef->sockfd, sdRef->validator); + return; + } + + // If we're in the middle of a DNSServiceProcessResult() invocation for this DNSServiceRef, clear its morebytes flag to break it out of its while loop + if (sdRef->moreptr) *(sdRef->moreptr) = 0; + + if (sdRef->primary) // If this is a subordinate DNSServiceOp, just send a 'stop' command + { + DNSServiceOp **p = &sdRef->primary->next; + while (*p && *p != sdRef) p = &(*p)->next; + if (*p) + { + char *ptr; + size_t len = 0; + ipc_msg_hdr *hdr = create_hdr(cancel_request, &len, &ptr, 0, sdRef); + if (hdr) + { + ConvertHeaderBytes(hdr); + write_all(sdRef->sockfd, (char *)hdr, len); + free(hdr); + } + *p = sdRef->next; + FreeDNSServiceOp(sdRef); + } + } + else // else, make sure to terminate all subordinates as well + { +#if _DNS_SD_LIBDISPATCH + // The cancel handler will close the fd if a dispatch source has been set + if (sdRef->disp_source) + { + // By setting the ProcessReply to NULL, we make sure that we never call + // the application callbacks ever, after returning from this function. We + // assume that DNSServiceRefDeallocate is called from the serial queue + // that was passed to DNSServiceSetDispatchQueue. Hence, dispatch_source_cancel + // should cancel all the blocks on the queue and hence there should be no more + // callbacks when we return from this function. Setting ProcessReply to NULL + // provides extra protection. + sdRef->ProcessReply = NULL; + dispatch_source_cancel(sdRef->disp_source); + dispatch_release(sdRef->disp_source); + sdRef->disp_source = NULL; + } + // if disp_queue is set, it means it used the DNSServiceSetDispatchQueue API. In that case, + // when the source was cancelled, the fd was closed in the handler. Currently the source + // is cancelled only when the mDNSResponder daemon dies + else if (!sdRef->disp_queue) dnssd_close(sdRef->sockfd); +#else + dnssd_close(sdRef->sockfd); +#endif + // Free DNSRecords added in DNSRegisterRecord if they have not + // been freed in DNSRemoveRecord + while (sdRef) + { + DNSServiceOp *p = sdRef; + sdRef = sdRef->next; + // When there is an error reading from the daemon e.g., bad fd, CallbackWithError + // is called which sets moreptr. It might set the moreptr on a subordinate sdRef + // but the application might call DNSServiceRefDeallocate with the main sdRef from + // the callback. Hence, when we loop through the subordinate sdRefs, we need + // to clear the moreptr so that CallbackWithError can terminate itself instead of + // walking through the freed sdRefs. + if (p->moreptr) *(p->moreptr) = 0; + FreeDNSServiceOp(p); + } + } +} + +DNSServiceErrorType DNSSD_API DNSServiceGetProperty(const char *property, void *result, uint32_t *size) +{ + char *ptr; + size_t len = strlen(property) + 1; + ipc_msg_hdr *hdr; + DNSServiceOp *tmp; + uint32_t actualsize; + + DNSServiceErrorType err = ConnectToServer(&tmp, 0, getproperty_request, NULL, NULL, NULL); + if (err) return err; + + hdr = create_hdr(getproperty_request, &len, &ptr, 0, tmp); + if (!hdr) { DNSServiceRefDeallocate(tmp); return kDNSServiceErr_NoMemory; } + + put_string(property, &ptr); + err = deliver_request(hdr, tmp); // Will free hdr for us + if (read_all(tmp->sockfd, (char*)&actualsize, (int)sizeof(actualsize)) < 0) + { DNSServiceRefDeallocate(tmp); return kDNSServiceErr_ServiceNotRunning; } + + actualsize = ntohl(actualsize); + if (read_all(tmp->sockfd, (char*)result, actualsize < *size ? actualsize : *size) < 0) + { DNSServiceRefDeallocate(tmp); return kDNSServiceErr_ServiceNotRunning; } + DNSServiceRefDeallocate(tmp); + + // Swap version result back to local process byte order + if (!strcmp(property, kDNSServiceProperty_DaemonVersion) && *size >= 4) + *(uint32_t*)result = ntohl(*(uint32_t*)result); + + *size = actualsize; + return kDNSServiceErr_NoError; +} + +DNSServiceErrorType DNSSD_API DNSServiceGetPID(const uint16_t srcport, int32_t *pid) +{ + char *ptr; + ipc_msg_hdr *hdr; + DNSServiceOp *tmp; + size_t len = sizeof(int32_t); + + DNSServiceErrorType err = ConnectToServer(&tmp, 0, getpid_request, NULL, NULL, NULL); + if (err) + return err; + + hdr = create_hdr(getpid_request, &len, &ptr, 0, tmp); + if (!hdr) + { + DNSServiceRefDeallocate(tmp); + return kDNSServiceErr_NoMemory; + } + + put_uint16(srcport, &ptr); + err = deliver_request(hdr, tmp); // Will free hdr for us + + if (read_all(tmp->sockfd, (char*)pid, sizeof(int32_t)) < 0) + { + DNSServiceRefDeallocate(tmp); + return kDNSServiceErr_ServiceNotRunning; + } + + DNSServiceRefDeallocate(tmp); + return kDNSServiceErr_NoError; +} + +static void handle_resolve_response(DNSServiceOp *const sdr, const CallbackHeader *const cbh, const char *data, const char *end) +{ + char fullname[kDNSServiceMaxDomainName]; + char target[kDNSServiceMaxDomainName]; + uint16_t txtlen; + union { uint16_t s; u_char b[2]; } port; + unsigned char *txtrecord; + + get_string(&data, end, fullname, kDNSServiceMaxDomainName); + get_string(&data, end, target, kDNSServiceMaxDomainName); + if (!data || data + 2 > end) goto fail; + + port.b[0] = *data++; + port.b[1] = *data++; + txtlen = get_uint16(&data, end); + txtrecord = (unsigned char *)get_rdata(&data, end, txtlen); + + if (!data) goto fail; + ((DNSServiceResolveReply)sdr->AppCallback)(sdr, cbh->cb_flags, cbh->cb_interface, cbh->cb_err, fullname, target, port.s, txtlen, txtrecord, sdr->AppContext); + return; + // MUST NOT touch sdr after invoking AppCallback -- client is allowed to dispose it from within callback function +fail: + syslog(LOG_WARNING, "dnssd_clientstub handle_resolve_response: error reading result from daemon"); +} + +#if APPLE_OSX_mDNSResponder + +static int32_t libSystemVersion = 0; + +// Return true if the application linked against a version of libsystem where P2P +// interfaces were included by default when using kDNSServiceInterfaceIndexAny. +// Using 160.0.0 == 0xa00000 as the version threshold. +static int includeP2PWithIndexAny() +{ + if (libSystemVersion == 0) + libSystemVersion = NSVersionOfLinkTimeLibrary("System"); + + if (libSystemVersion < 0xa00000) + return 1; + else + return 0; +} + +#else // APPLE_OSX_mDNSResponder + +// always return false for non Apple platforms +static int includeP2PWithIndexAny() +{ + return 0; +} + +#endif // APPLE_OSX_mDNSResponder + +DNSServiceErrorType DNSSD_API DNSServiceResolve +( + DNSServiceRef *sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + const char *name, + const char *regtype, + const char *domain, + DNSServiceResolveReply callBack, + void *context +) +{ + char *ptr; + size_t len; + ipc_msg_hdr *hdr; + DNSServiceErrorType err; + + if (!name || !regtype || !domain || !callBack) return kDNSServiceErr_BadParam; + + // Need a real InterfaceID for WakeOnResolve + if ((flags & kDNSServiceFlagsWakeOnResolve) != 0 && + ((interfaceIndex == kDNSServiceInterfaceIndexAny) || + (interfaceIndex == kDNSServiceInterfaceIndexLocalOnly) || + (interfaceIndex == kDNSServiceInterfaceIndexUnicast) || + (interfaceIndex == kDNSServiceInterfaceIndexP2P))) + { + return kDNSServiceErr_BadParam; + } + + if ((interfaceIndex == kDNSServiceInterfaceIndexAny) && includeP2PWithIndexAny()) + flags |= kDNSServiceFlagsIncludeP2P; + + err = ConnectToServer(sdRef, flags, resolve_request, handle_resolve_response, callBack, context); + if (err) return err; // On error ConnectToServer leaves *sdRef set to NULL + + // Calculate total message length + len = sizeof(flags); + len += sizeof(interfaceIndex); + len += strlen(name) + 1; + len += strlen(regtype) + 1; + len += strlen(domain) + 1; + + hdr = create_hdr(resolve_request, &len, &ptr, (*sdRef)->primary ? 1 : 0, *sdRef); + if (!hdr) { DNSServiceRefDeallocate(*sdRef); *sdRef = NULL; return kDNSServiceErr_NoMemory; } + + put_flags(flags, &ptr); + put_uint32(interfaceIndex, &ptr); + put_string(name, &ptr); + put_string(regtype, &ptr); + put_string(domain, &ptr); + + err = deliver_request(hdr, *sdRef); // Will free hdr for us + if (err) { DNSServiceRefDeallocate(*sdRef); *sdRef = NULL; } + return err; +} + +static void handle_query_response(DNSServiceOp *const sdr, const CallbackHeader *const cbh, const char *data, const char *const end) +{ + uint32_t ttl; + char name[kDNSServiceMaxDomainName]; + uint16_t rrtype, rrclass, rdlen; + const char *rdata; + + get_string(&data, end, name, kDNSServiceMaxDomainName); + rrtype = get_uint16(&data, end); + rrclass = get_uint16(&data, end); + rdlen = get_uint16(&data, end); + rdata = get_rdata(&data, end, rdlen); + ttl = get_uint32(&data, end); + + if (!data) syslog(LOG_WARNING, "dnssd_clientstub handle_query_response: error reading result from daemon"); + else ((DNSServiceQueryRecordReply)sdr->AppCallback)(sdr, cbh->cb_flags, cbh->cb_interface, cbh->cb_err, name, rrtype, rrclass, rdlen, rdata, ttl, sdr->AppContext); + // MUST NOT touch sdr after invoking AppCallback -- client is allowed to dispose it from within callback function +} + +DNSServiceErrorType DNSSD_API DNSServiceQueryRecord +( + DNSServiceRef *sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + const char *name, + uint16_t rrtype, + uint16_t rrclass, + DNSServiceQueryRecordReply callBack, + void *context +) +{ + char *ptr; + size_t len; + ipc_msg_hdr *hdr; + DNSServiceErrorType err; + + if ((interfaceIndex == kDNSServiceInterfaceIndexAny) && includeP2PWithIndexAny()) + flags |= kDNSServiceFlagsIncludeP2P; + + err = ConnectToServer(sdRef, flags, query_request, handle_query_response, callBack, context); + if (err) return err; // On error ConnectToServer leaves *sdRef set to NULL + + if (!name) name = "\0"; + + // Calculate total message length + len = sizeof(flags); + len += sizeof(uint32_t); // interfaceIndex + len += strlen(name) + 1; + len += 2 * sizeof(uint16_t); // rrtype, rrclass + + hdr = create_hdr(query_request, &len, &ptr, (*sdRef)->primary ? 1 : 0, *sdRef); + if (!hdr) { DNSServiceRefDeallocate(*sdRef); *sdRef = NULL; return kDNSServiceErr_NoMemory; } + + put_flags(flags, &ptr); + put_uint32(interfaceIndex, &ptr); + put_string(name, &ptr); + put_uint16(rrtype, &ptr); + put_uint16(rrclass, &ptr); + + err = deliver_request(hdr, *sdRef); // Will free hdr for us + if (err) { DNSServiceRefDeallocate(*sdRef); *sdRef = NULL; } + return err; +} + +static void handle_addrinfo_response(DNSServiceOp *const sdr, const CallbackHeader *const cbh, const char *data, const char *const end) +{ + char hostname[kDNSServiceMaxDomainName]; + uint16_t rrtype, rrclass, rdlen; + const char *rdata; + uint32_t ttl; + + get_string(&data, end, hostname, kDNSServiceMaxDomainName); + rrtype = get_uint16(&data, end); + rrclass = get_uint16(&data, end); + rdlen = get_uint16(&data, end); + rdata = get_rdata (&data, end, rdlen); + ttl = get_uint32(&data, end); + + // We only generate client callbacks for A and AAAA results (including NXDOMAIN results for + // those types, if the client has requested those with the kDNSServiceFlagsReturnIntermediates). + // Other result types, specifically CNAME referrals, are not communicated to the client, because + // the DNSServiceGetAddrInfoReply interface doesn't have any meaningful way to communiate CNAME referrals. + if (!data) syslog(LOG_WARNING, "dnssd_clientstub handle_addrinfo_response: error reading result from daemon"); + else if (rrtype == kDNSServiceType_A || rrtype == kDNSServiceType_AAAA) + { + struct sockaddr_in sa4; + struct sockaddr_in6 sa6; + const struct sockaddr *const sa = (rrtype == kDNSServiceType_A) ? (struct sockaddr*)&sa4 : (struct sockaddr*)&sa6; + if (rrtype == kDNSServiceType_A) + { + memset(&sa4, 0, sizeof(sa4)); + #ifndef NOT_HAVE_SA_LEN + sa4.sin_len = sizeof(struct sockaddr_in); + #endif + sa4.sin_family = AF_INET; + // sin_port = 0; + if (!cbh->cb_err) memcpy(&sa4.sin_addr, rdata, rdlen); + } + else + { + memset(&sa6, 0, sizeof(sa6)); + #ifndef NOT_HAVE_SA_LEN + sa6.sin6_len = sizeof(struct sockaddr_in6); + #endif + sa6.sin6_family = AF_INET6; + // sin6_port = 0; + // sin6_flowinfo = 0; + // sin6_scope_id = 0; + if (!cbh->cb_err) + { + memcpy(&sa6.sin6_addr, rdata, rdlen); + if (IN6_IS_ADDR_LINKLOCAL(&sa6.sin6_addr)) sa6.sin6_scope_id = cbh->cb_interface; + } + } + // Validation results are always delivered separately from the actual results of the + // DNSServiceGetAddrInfo. Set the "addr" to NULL as per the documentation. + // + // Note: If we deliver validation results along with the "addr" in the future, we need + // a way to differentiate the negative response from validation-only response as both + // has zero address. + if (!(cbh->cb_flags & kDNSServiceFlagsValidate)) + ((DNSServiceGetAddrInfoReply)sdr->AppCallback)(sdr, cbh->cb_flags, cbh->cb_interface, cbh->cb_err, hostname, sa, ttl, sdr->AppContext); + else + ((DNSServiceGetAddrInfoReply)sdr->AppCallback)(sdr, cbh->cb_flags, cbh->cb_interface, cbh->cb_err, hostname, NULL, 0, sdr->AppContext); + // MUST NOT touch sdr after invoking AppCallback -- client is allowed to dispose it from within callback function + } +} + +DNSServiceErrorType DNSSD_API DNSServiceGetAddrInfo +( + DNSServiceRef *sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + uint32_t protocol, + const char *hostname, + DNSServiceGetAddrInfoReply callBack, + void *context /* may be NULL */ +) +{ + char *ptr; + size_t len; + ipc_msg_hdr *hdr; + DNSServiceErrorType err; + + if (!hostname) return kDNSServiceErr_BadParam; + + err = ConnectToServer(sdRef, flags, addrinfo_request, handle_addrinfo_response, callBack, context); + if (err) + { + return err; // On error ConnectToServer leaves *sdRef set to NULL + } + + // Calculate total message length + len = sizeof(flags); + len += sizeof(uint32_t); // interfaceIndex + len += sizeof(uint32_t); // protocol + len += strlen(hostname) + 1; + + hdr = create_hdr(addrinfo_request, &len, &ptr, (*sdRef)->primary ? 1 : 0, *sdRef); + if (!hdr) { DNSServiceRefDeallocate(*sdRef); *sdRef = NULL; return kDNSServiceErr_NoMemory; } + + put_flags(flags, &ptr); + put_uint32(interfaceIndex, &ptr); + put_uint32(protocol, &ptr); + put_string(hostname, &ptr); + + err = deliver_request(hdr, *sdRef); // Will free hdr for us + if (err) { DNSServiceRefDeallocate(*sdRef); *sdRef = NULL; } + return err; +} + +static void handle_browse_response(DNSServiceOp *const sdr, const CallbackHeader *const cbh, const char *data, const char *const end) +{ + char replyName[256], replyType[kDNSServiceMaxDomainName], replyDomain[kDNSServiceMaxDomainName]; + get_string(&data, end, replyName, 256); + get_string(&data, end, replyType, kDNSServiceMaxDomainName); + get_string(&data, end, replyDomain, kDNSServiceMaxDomainName); + if (!data) syslog(LOG_WARNING, "dnssd_clientstub handle_browse_response: error reading result from daemon"); + else ((DNSServiceBrowseReply)sdr->AppCallback)(sdr, cbh->cb_flags, cbh->cb_interface, cbh->cb_err, replyName, replyType, replyDomain, sdr->AppContext); + // MUST NOT touch sdr after invoking AppCallback -- client is allowed to dispose it from within callback function +} + +DNSServiceErrorType DNSSD_API DNSServiceBrowse +( + DNSServiceRef *sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + const char *regtype, + const char *domain, + DNSServiceBrowseReply callBack, + void *context +) +{ + char *ptr; + size_t len; + ipc_msg_hdr *hdr; + DNSServiceErrorType err; + + if ((interfaceIndex == kDNSServiceInterfaceIndexAny) && includeP2PWithIndexAny()) + flags |= kDNSServiceFlagsIncludeP2P; + + err = ConnectToServer(sdRef, flags, browse_request, handle_browse_response, callBack, context); + if (err) return err; // On error ConnectToServer leaves *sdRef set to NULL + + if (!domain) domain = ""; + len = sizeof(flags); + len += sizeof(interfaceIndex); + len += strlen(regtype) + 1; + len += strlen(domain) + 1; + + hdr = create_hdr(browse_request, &len, &ptr, (*sdRef)->primary ? 1 : 0, *sdRef); + if (!hdr) { DNSServiceRefDeallocate(*sdRef); *sdRef = NULL; return kDNSServiceErr_NoMemory; } + + put_flags(flags, &ptr); + put_uint32(interfaceIndex, &ptr); + put_string(regtype, &ptr); + put_string(domain, &ptr); + + err = deliver_request(hdr, *sdRef); // Will free hdr for us + if (err) { DNSServiceRefDeallocate(*sdRef); *sdRef = NULL; } + return err; +} + +DNSServiceErrorType DNSSD_API DNSServiceSetDefaultDomainForUser(DNSServiceFlags flags, const char *domain); +DNSServiceErrorType DNSSD_API DNSServiceSetDefaultDomainForUser(DNSServiceFlags flags, const char *domain) +{ + DNSServiceOp *tmp; + char *ptr; + size_t len = sizeof(flags) + strlen(domain) + 1; + ipc_msg_hdr *hdr; + DNSServiceErrorType err = ConnectToServer(&tmp, 0, setdomain_request, NULL, NULL, NULL); + if (err) return err; + + hdr = create_hdr(setdomain_request, &len, &ptr, 0, tmp); + if (!hdr) { DNSServiceRefDeallocate(tmp); return kDNSServiceErr_NoMemory; } + + put_flags(flags, &ptr); + put_string(domain, &ptr); + err = deliver_request(hdr, tmp); // Will free hdr for us + DNSServiceRefDeallocate(tmp); + return err; +} + +static void handle_regservice_response(DNSServiceOp *const sdr, const CallbackHeader *const cbh, const char *data, const char *const end) +{ + char name[256], regtype[kDNSServiceMaxDomainName], domain[kDNSServiceMaxDomainName]; + get_string(&data, end, name, 256); + get_string(&data, end, regtype, kDNSServiceMaxDomainName); + get_string(&data, end, domain, kDNSServiceMaxDomainName); + if (!data) syslog(LOG_WARNING, "dnssd_clientstub handle_regservice_response: error reading result from daemon"); + else ((DNSServiceRegisterReply)sdr->AppCallback)(sdr, cbh->cb_flags, cbh->cb_err, name, regtype, domain, sdr->AppContext); + // MUST NOT touch sdr after invoking AppCallback -- client is allowed to dispose it from within callback function +} + +DNSServiceErrorType DNSSD_API DNSServiceRegister +( + DNSServiceRef *sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + const char *name, + const char *regtype, + const char *domain, + const char *host, + uint16_t PortInNetworkByteOrder, + uint16_t txtLen, + const void *txtRecord, + DNSServiceRegisterReply callBack, + void *context +) +{ + char *ptr; + size_t len; + ipc_msg_hdr *hdr; + DNSServiceErrorType err; + union { uint16_t s; u_char b[2]; } port = { PortInNetworkByteOrder }; + + if (!name) name = ""; + if (!regtype) return kDNSServiceErr_BadParam; + if (!domain) domain = ""; + if (!host) host = ""; + if (!txtRecord) txtRecord = (void*)""; + + // No callback must have auto-rename + if (!callBack && (flags & kDNSServiceFlagsNoAutoRename)) return kDNSServiceErr_BadParam; + + if ((interfaceIndex == kDNSServiceInterfaceIndexAny) && includeP2PWithIndexAny()) + flags |= kDNSServiceFlagsIncludeP2P; + + err = ConnectToServer(sdRef, flags, reg_service_request, callBack ? handle_regservice_response : NULL, callBack, context); + if (err) return err; // On error ConnectToServer leaves *sdRef set to NULL + + len = sizeof(DNSServiceFlags); + len += sizeof(uint32_t); // interfaceIndex + len += strlen(name) + strlen(regtype) + strlen(domain) + strlen(host) + 4; + len += 2 * sizeof(uint16_t); // port, txtLen + len += txtLen; + + hdr = create_hdr(reg_service_request, &len, &ptr, (*sdRef)->primary ? 1 : 0, *sdRef); + if (!hdr) { DNSServiceRefDeallocate(*sdRef); *sdRef = NULL; return kDNSServiceErr_NoMemory; } + + // If it is going over a shared connection, then don't set the IPC_FLAGS_NOREPLY + // as it affects all the operations over the shared connection. This is not + // a normal case and hence receiving the response back from the daemon and + // discarding it in ConnectionResponse is okay. + + if (!(flags & kDNSServiceFlagsShareConnection) && !callBack) hdr->ipc_flags |= IPC_FLAGS_NOREPLY; + + put_flags(flags, &ptr); + put_uint32(interfaceIndex, &ptr); + put_string(name, &ptr); + put_string(regtype, &ptr); + put_string(domain, &ptr); + put_string(host, &ptr); + *ptr++ = port.b[0]; + *ptr++ = port.b[1]; + put_uint16(txtLen, &ptr); + put_rdata(txtLen, txtRecord, &ptr); + + err = deliver_request(hdr, *sdRef); // Will free hdr for us + if (err) { DNSServiceRefDeallocate(*sdRef); *sdRef = NULL; } + return err; +} + +static void handle_enumeration_response(DNSServiceOp *const sdr, const CallbackHeader *const cbh, const char *data, const char *const end) +{ + char domain[kDNSServiceMaxDomainName]; + get_string(&data, end, domain, kDNSServiceMaxDomainName); + if (!data) syslog(LOG_WARNING, "dnssd_clientstub handle_enumeration_response: error reading result from daemon"); + else ((DNSServiceDomainEnumReply)sdr->AppCallback)(sdr, cbh->cb_flags, cbh->cb_interface, cbh->cb_err, domain, sdr->AppContext); + // MUST NOT touch sdr after invoking AppCallback -- client is allowed to dispose it from within callback function +} + +DNSServiceErrorType DNSSD_API DNSServiceEnumerateDomains +( + DNSServiceRef *sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + DNSServiceDomainEnumReply callBack, + void *context +) +{ + char *ptr; + size_t len; + ipc_msg_hdr *hdr; + DNSServiceErrorType err; + + int f1 = (flags & kDNSServiceFlagsBrowseDomains) != 0; + int f2 = (flags & kDNSServiceFlagsRegistrationDomains) != 0; + if (f1 + f2 != 1) return kDNSServiceErr_BadParam; + + err = ConnectToServer(sdRef, flags, enumeration_request, handle_enumeration_response, callBack, context); + if (err) return err; // On error ConnectToServer leaves *sdRef set to NULL + + len = sizeof(DNSServiceFlags); + len += sizeof(uint32_t); + + hdr = create_hdr(enumeration_request, &len, &ptr, (*sdRef)->primary ? 1 : 0, *sdRef); + if (!hdr) { DNSServiceRefDeallocate(*sdRef); *sdRef = NULL; return kDNSServiceErr_NoMemory; } + + put_flags(flags, &ptr); + put_uint32(interfaceIndex, &ptr); + + err = deliver_request(hdr, *sdRef); // Will free hdr for us + if (err) { DNSServiceRefDeallocate(*sdRef); *sdRef = NULL; } + return err; +} + +static void ConnectionResponse(DNSServiceOp *const sdr, const CallbackHeader *const cbh, const char *const data, const char *const end) +{ + (void)data; // Unused + + //printf("ConnectionResponse got %d\n", cbh->ipc_hdr.op); + if (cbh->ipc_hdr.op != reg_record_reply_op) + { + // When using kDNSServiceFlagsShareConnection, need to search the list of associated DNSServiceOps + // to find the one this response is intended for, and then call through to its ProcessReply handler. + // We start with our first subordinate DNSServiceRef -- don't want to accidentally match the parent DNSServiceRef. + DNSServiceOp *op = sdr->next; + while (op && (op->uid.u32[0] != cbh->ipc_hdr.client_context.u32[0] || op->uid.u32[1] != cbh->ipc_hdr.client_context.u32[1])) + op = op->next; + // Note: We may sometimes not find a matching DNSServiceOp, in the case where the client has + // cancelled the subordinate DNSServiceOp, but there are still messages in the pipeline from the daemon + if (op && op->ProcessReply) op->ProcessReply(op, cbh, data, end); + // WARNING: Don't touch op or sdr after this -- client may have called DNSServiceRefDeallocate + return; + } + else + { + DNSRecordRef rec; + for (rec = sdr->rec; rec; rec = rec->recnext) + { + if (rec->uid.u32[0] == cbh->ipc_hdr.client_context.u32[0] && rec->uid.u32[1] == cbh->ipc_hdr.client_context.u32[1]) + break; + } + // The record might have been freed already and hence not an + // error if the record is not found. + if (!rec) + { + syslog(LOG_INFO, "ConnectionResponse: Record not found"); + return; + } + if (rec->sdr != sdr) + { + syslog(LOG_WARNING, "ConnectionResponse: Record sdr mismatch: rec %p sdr %p", rec->sdr, sdr); + return; + } + + if (sdr->op == connection_request || sdr->op == connection_delegate_request) + { + rec->AppCallback(rec->sdr, rec, cbh->cb_flags, cbh->cb_err, rec->AppContext); + } + else + { + syslog(LOG_WARNING, "dnssd_clientstub ConnectionResponse: sdr->op != connection_request"); + rec->AppCallback(rec->sdr, rec, 0, kDNSServiceErr_Unknown, rec->AppContext); + } + // MUST NOT touch sdr after invoking AppCallback -- client is allowed to dispose it from within callback function + } +} + +DNSServiceErrorType DNSSD_API DNSServiceCreateConnection(DNSServiceRef *sdRef) +{ + char *ptr; + size_t len = 0; + ipc_msg_hdr *hdr; + DNSServiceErrorType err = ConnectToServer(sdRef, 0, connection_request, ConnectionResponse, NULL, NULL); + if (err) return err; // On error ConnectToServer leaves *sdRef set to NULL + + hdr = create_hdr(connection_request, &len, &ptr, 0, *sdRef); + if (!hdr) { DNSServiceRefDeallocate(*sdRef); *sdRef = NULL; return kDNSServiceErr_NoMemory; } + + err = deliver_request(hdr, *sdRef); // Will free hdr for us + if (err) { DNSServiceRefDeallocate(*sdRef); *sdRef = NULL; } + return err; +} + +#if APPLE_OSX_mDNSResponder && !TARGET_IPHONE_SIMULATOR +DNSServiceErrorType DNSSD_API DNSServiceCreateDelegateConnection(DNSServiceRef *sdRef, int32_t pid, uuid_t uuid) +{ + char *ptr; + size_t len = 0; + ipc_msg_hdr *hdr; + + DNSServiceErrorType err = ConnectToServer(sdRef, 0, connection_delegate_request, ConnectionResponse, NULL, NULL); + if (err) + { + return err; // On error ConnectToServer leaves *sdRef set to NULL + } + + // Only one of the two options can be set. If pid is zero, uuid is used. + // If both are specified only pid will be used. We send across the pid + // so that the daemon knows what to read from the socket. + + len += sizeof(int32_t); + + hdr = create_hdr(connection_delegate_request, &len, &ptr, 0, *sdRef); + if (!hdr) + { + DNSServiceRefDeallocate(*sdRef); + *sdRef = NULL; + return kDNSServiceErr_NoMemory; + } + + if (pid && setsockopt((*sdRef)->sockfd, SOL_SOCKET, SO_DELEGATED, &pid, sizeof(pid)) == -1) + { + // Free the hdr in case we return before calling deliver_request() + if (hdr) + free(hdr); + DNSServiceRefDeallocate(*sdRef); + *sdRef = NULL; + return kDNSServiceErr_NoAuth; + } + + if (!pid && setsockopt((*sdRef)->sockfd, SOL_SOCKET, SO_DELEGATED_UUID, uuid, sizeof(uuid_t)) == -1) + { + // Free the hdr in case we return before calling deliver_request() + if (hdr) + free(hdr); + DNSServiceRefDeallocate(*sdRef); + *sdRef = NULL; + return kDNSServiceErr_NoAuth; + } + + put_uint32(pid, &ptr); + + err = deliver_request(hdr, *sdRef); // Will free hdr for us + if (err) + { + DNSServiceRefDeallocate(*sdRef); + *sdRef = NULL; + } + return err; +} +#elif TARGET_IPHONE_SIMULATOR // This hack is for Simulator platform only +DNSServiceErrorType DNSSD_API DNSServiceCreateDelegateConnection(DNSServiceRef *sdRef, int32_t pid, uuid_t uuid) +{ + (void) pid; + (void) uuid; + return DNSServiceCreateConnection(sdRef); +} +#endif + +DNSServiceErrorType DNSSD_API DNSServiceRegisterRecord +( + DNSServiceRef sdRef, + DNSRecordRef *RecordRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + const char *fullname, + uint16_t rrtype, + uint16_t rrclass, + uint16_t rdlen, + const void *rdata, + uint32_t ttl, + DNSServiceRegisterRecordReply callBack, + void *context +) +{ + char *ptr; + size_t len; + ipc_msg_hdr *hdr = NULL; + DNSRecordRef rref = NULL; + DNSRecord **p; + int f1 = (flags & kDNSServiceFlagsShared) != 0; + int f2 = (flags & kDNSServiceFlagsUnique) != 0; + if (f1 + f2 != 1) return kDNSServiceErr_BadParam; + + if ((interfaceIndex == kDNSServiceInterfaceIndexAny) && includeP2PWithIndexAny()) + flags |= kDNSServiceFlagsIncludeP2P; + + if (!sdRef) { syslog(LOG_WARNING, "dnssd_clientstub DNSServiceRegisterRecord called with NULL DNSServiceRef"); return kDNSServiceErr_BadParam; } + + if (!DNSServiceRefValid(sdRef)) + { + syslog(LOG_WARNING, "dnssd_clientstub DNSServiceRegisterRecord called with invalid DNSServiceRef %p %08X %08X", sdRef, sdRef->sockfd, sdRef->validator); + return kDNSServiceErr_BadReference; + } + + if (sdRef->op != connection_request && sdRef->op != connection_delegate_request) + { + syslog(LOG_WARNING, "dnssd_clientstub DNSServiceRegisterRecord called with non-DNSServiceCreateConnection DNSServiceRef %p %d", sdRef, sdRef->op); + return kDNSServiceErr_BadReference; + } + + *RecordRef = NULL; + + len = sizeof(DNSServiceFlags); + len += 2 * sizeof(uint32_t); // interfaceIndex, ttl + len += 3 * sizeof(uint16_t); // rrtype, rrclass, rdlen + len += strlen(fullname) + 1; + len += rdlen; + + // Bump up the uid. Normally for shared operations (kDNSServiceFlagsShareConnection), this + // is done in ConnectToServer. For DNSServiceRegisterRecord, ConnectToServer has already + // been called. As multiple DNSServiceRegisterRecords can be multiplexed over a single + // connection, we need a way to demultiplex the response so that the callback corresponding + // to the right DNSServiceRegisterRecord instance can be called. Use the same mechanism that + // is used by kDNSServiceFlagsShareConnection. create_hdr copies the uid value to ipc + // hdr->client_context which will be returned in the ipc response. + if (++sdRef->uid.u32[0] == 0) + ++sdRef->uid.u32[1]; + hdr = create_hdr(reg_record_request, &len, &ptr, 1, sdRef); + if (!hdr) return kDNSServiceErr_NoMemory; + + put_flags(flags, &ptr); + put_uint32(interfaceIndex, &ptr); + put_string(fullname, &ptr); + put_uint16(rrtype, &ptr); + put_uint16(rrclass, &ptr); + put_uint16(rdlen, &ptr); + put_rdata(rdlen, rdata, &ptr); + put_uint32(ttl, &ptr); + + rref = malloc(sizeof(DNSRecord)); + if (!rref) { free(hdr); return kDNSServiceErr_NoMemory; } + rref->AppContext = context; + rref->AppCallback = callBack; + rref->record_index = sdRef->max_index++; + rref->sdr = sdRef; + rref->recnext = NULL; + *RecordRef = rref; + // Remember the uid that we are sending across so that we can match + // when the response comes back. + rref->uid = sdRef->uid; + hdr->reg_index = rref->record_index; + + p = &(sdRef)->rec; + while (*p) p = &(*p)->recnext; + *p = rref; + + return deliver_request(hdr, sdRef); // Will free hdr for us +} + +// sdRef returned by DNSServiceRegister() +DNSServiceErrorType DNSSD_API DNSServiceAddRecord +( + DNSServiceRef sdRef, + DNSRecordRef *RecordRef, + DNSServiceFlags flags, + uint16_t rrtype, + uint16_t rdlen, + const void *rdata, + uint32_t ttl +) +{ + ipc_msg_hdr *hdr; + size_t len = 0; + char *ptr; + DNSRecordRef rref; + DNSRecord **p; + + if (!sdRef) { syslog(LOG_WARNING, "dnssd_clientstub DNSServiceAddRecord called with NULL DNSServiceRef"); return kDNSServiceErr_BadParam; } + if (!RecordRef) { syslog(LOG_WARNING, "dnssd_clientstub DNSServiceAddRecord called with NULL DNSRecordRef pointer"); return kDNSServiceErr_BadParam; } + if (sdRef->op != reg_service_request) + { + syslog(LOG_WARNING, "dnssd_clientstub DNSServiceAddRecord called with non-DNSServiceRegister DNSServiceRef %p %d", sdRef, sdRef->op); + return kDNSServiceErr_BadReference; + } + + if (!DNSServiceRefValid(sdRef)) + { + syslog(LOG_WARNING, "dnssd_clientstub DNSServiceAddRecord called with invalid DNSServiceRef %p %08X %08X", sdRef, sdRef->sockfd, sdRef->validator); + return kDNSServiceErr_BadReference; + } + + *RecordRef = NULL; + + len += 2 * sizeof(uint16_t); // rrtype, rdlen + len += rdlen; + len += sizeof(uint32_t); + len += sizeof(DNSServiceFlags); + + hdr = create_hdr(add_record_request, &len, &ptr, 1, sdRef); + if (!hdr) return kDNSServiceErr_NoMemory; + put_flags(flags, &ptr); + put_uint16(rrtype, &ptr); + put_uint16(rdlen, &ptr); + put_rdata(rdlen, rdata, &ptr); + put_uint32(ttl, &ptr); + + rref = malloc(sizeof(DNSRecord)); + if (!rref) { free(hdr); return kDNSServiceErr_NoMemory; } + rref->AppContext = NULL; + rref->AppCallback = NULL; + rref->record_index = sdRef->max_index++; + rref->sdr = sdRef; + rref->recnext = NULL; + *RecordRef = rref; + hdr->reg_index = rref->record_index; + + p = &(sdRef)->rec; + while (*p) p = &(*p)->recnext; + *p = rref; + + return deliver_request(hdr, sdRef); // Will free hdr for us +} + +// DNSRecordRef returned by DNSServiceRegisterRecord or DNSServiceAddRecord +DNSServiceErrorType DNSSD_API DNSServiceUpdateRecord +( + DNSServiceRef sdRef, + DNSRecordRef RecordRef, + DNSServiceFlags flags, + uint16_t rdlen, + const void *rdata, + uint32_t ttl +) +{ + ipc_msg_hdr *hdr; + size_t len = 0; + char *ptr; + + if (!sdRef) { syslog(LOG_WARNING, "dnssd_clientstub DNSServiceUpdateRecord called with NULL DNSServiceRef"); return kDNSServiceErr_BadParam; } + + if (!DNSServiceRefValid(sdRef)) + { + syslog(LOG_WARNING, "dnssd_clientstub DNSServiceUpdateRecord called with invalid DNSServiceRef %p %08X %08X", sdRef, sdRef->sockfd, sdRef->validator); + return kDNSServiceErr_BadReference; + } + + // Note: RecordRef is allowed to be NULL + + len += sizeof(uint16_t); + len += rdlen; + len += sizeof(uint32_t); + len += sizeof(DNSServiceFlags); + + hdr = create_hdr(update_record_request, &len, &ptr, 1, sdRef); + if (!hdr) return kDNSServiceErr_NoMemory; + hdr->reg_index = RecordRef ? RecordRef->record_index : TXT_RECORD_INDEX; + put_flags(flags, &ptr); + put_uint16(rdlen, &ptr); + put_rdata(rdlen, rdata, &ptr); + put_uint32(ttl, &ptr); + return deliver_request(hdr, sdRef); // Will free hdr for us +} + +DNSServiceErrorType DNSSD_API DNSServiceRemoveRecord +( + DNSServiceRef sdRef, + DNSRecordRef RecordRef, + DNSServiceFlags flags +) +{ + ipc_msg_hdr *hdr; + size_t len = 0; + char *ptr; + DNSServiceErrorType err; + + if (!sdRef) { syslog(LOG_WARNING, "dnssd_clientstub DNSServiceRemoveRecord called with NULL DNSServiceRef"); return kDNSServiceErr_BadParam; } + if (!RecordRef) { syslog(LOG_WARNING, "dnssd_clientstub DNSServiceRemoveRecord called with NULL DNSRecordRef"); return kDNSServiceErr_BadParam; } + if (!sdRef->max_index) { syslog(LOG_WARNING, "dnssd_clientstub DNSServiceRemoveRecord called with bad DNSServiceRef"); return kDNSServiceErr_BadReference; } + + if (!DNSServiceRefValid(sdRef)) + { + syslog(LOG_WARNING, "dnssd_clientstub DNSServiceRemoveRecord called with invalid DNSServiceRef %p %08X %08X", sdRef, sdRef->sockfd, sdRef->validator); + return kDNSServiceErr_BadReference; + } + + len += sizeof(flags); + hdr = create_hdr(remove_record_request, &len, &ptr, 1, sdRef); + if (!hdr) return kDNSServiceErr_NoMemory; + hdr->reg_index = RecordRef->record_index; + put_flags(flags, &ptr); + err = deliver_request(hdr, sdRef); // Will free hdr for us + if (!err) + { + // This RecordRef could have been allocated in DNSServiceRegisterRecord or DNSServiceAddRecord. + // If so, delink from the list before freeing + DNSRecord **p = &sdRef->rec; + while (*p && *p != RecordRef) p = &(*p)->recnext; + if (*p) *p = RecordRef->recnext; + free(RecordRef); + } + return err; +} + +DNSServiceErrorType DNSSD_API DNSServiceReconfirmRecord +( + DNSServiceFlags flags, + uint32_t interfaceIndex, + const char *fullname, + uint16_t rrtype, + uint16_t rrclass, + uint16_t rdlen, + const void *rdata +) +{ + char *ptr; + size_t len; + ipc_msg_hdr *hdr; + DNSServiceOp *tmp; + + DNSServiceErrorType err = ConnectToServer(&tmp, flags, reconfirm_record_request, NULL, NULL, NULL); + if (err) return err; + + len = sizeof(DNSServiceFlags); + len += sizeof(uint32_t); + len += strlen(fullname) + 1; + len += 3 * sizeof(uint16_t); + len += rdlen; + hdr = create_hdr(reconfirm_record_request, &len, &ptr, 0, tmp); + if (!hdr) { DNSServiceRefDeallocate(tmp); return kDNSServiceErr_NoMemory; } + + put_flags(flags, &ptr); + put_uint32(interfaceIndex, &ptr); + put_string(fullname, &ptr); + put_uint16(rrtype, &ptr); + put_uint16(rrclass, &ptr); + put_uint16(rdlen, &ptr); + put_rdata(rdlen, rdata, &ptr); + + err = deliver_request(hdr, tmp); // Will free hdr for us + DNSServiceRefDeallocate(tmp); + return err; +} + + +static void handle_port_mapping_response(DNSServiceOp *const sdr, const CallbackHeader *const cbh, const char *data, const char *const end) +{ + union { uint32_t l; u_char b[4]; } addr; + uint8_t protocol; + union { uint16_t s; u_char b[2]; } internalPort; + union { uint16_t s; u_char b[2]; } externalPort; + uint32_t ttl; + + if (!data || data + 13 > end) goto fail; + + addr.b[0] = *data++; + addr.b[1] = *data++; + addr.b[2] = *data++; + addr.b[3] = *data++; + protocol = *data++; + internalPort.b[0] = *data++; + internalPort.b[1] = *data++; + externalPort.b[0] = *data++; + externalPort.b[1] = *data++; + ttl = get_uint32(&data, end); + if (!data) goto fail; + + ((DNSServiceNATPortMappingReply)sdr->AppCallback)(sdr, cbh->cb_flags, cbh->cb_interface, cbh->cb_err, addr.l, protocol, internalPort.s, externalPort.s, ttl, sdr->AppContext); + return; + // MUST NOT touch sdr after invoking AppCallback -- client is allowed to dispose it from within callback function + + fail : + syslog(LOG_WARNING, "dnssd_clientstub handle_port_mapping_response: error reading result from daemon"); +} + +DNSServiceErrorType DNSSD_API DNSServiceNATPortMappingCreate +( + DNSServiceRef *sdRef, + DNSServiceFlags flags, + uint32_t interfaceIndex, + uint32_t protocol, /* TCP and/or UDP */ + uint16_t internalPortInNetworkByteOrder, + uint16_t externalPortInNetworkByteOrder, + uint32_t ttl, /* time to live in seconds */ + DNSServiceNATPortMappingReply callBack, + void *context /* may be NULL */ +) +{ + char *ptr; + size_t len; + ipc_msg_hdr *hdr; + union { uint16_t s; u_char b[2]; } internalPort = { internalPortInNetworkByteOrder }; + union { uint16_t s; u_char b[2]; } externalPort = { externalPortInNetworkByteOrder }; + + DNSServiceErrorType err = ConnectToServer(sdRef, flags, port_mapping_request, handle_port_mapping_response, callBack, context); + if (err) return err; // On error ConnectToServer leaves *sdRef set to NULL + + len = sizeof(flags); + len += sizeof(interfaceIndex); + len += sizeof(protocol); + len += sizeof(internalPort); + len += sizeof(externalPort); + len += sizeof(ttl); + + hdr = create_hdr(port_mapping_request, &len, &ptr, (*sdRef)->primary ? 1 : 0, *sdRef); + if (!hdr) { DNSServiceRefDeallocate(*sdRef); *sdRef = NULL; return kDNSServiceErr_NoMemory; } + + put_flags(flags, &ptr); + put_uint32(interfaceIndex, &ptr); + put_uint32(protocol, &ptr); + *ptr++ = internalPort.b[0]; + *ptr++ = internalPort.b[1]; + *ptr++ = externalPort.b[0]; + *ptr++ = externalPort.b[1]; + put_uint32(ttl, &ptr); + + err = deliver_request(hdr, *sdRef); // Will free hdr for us + if (err) { DNSServiceRefDeallocate(*sdRef); *sdRef = NULL; } + return err; +} + +#if _DNS_SD_LIBDISPATCH +DNSServiceErrorType DNSSD_API DNSServiceSetDispatchQueue +( + DNSServiceRef service, + dispatch_queue_t queue +) +{ + int dnssd_fd = DNSServiceRefSockFD(service); + if (dnssd_fd == dnssd_InvalidSocket) return kDNSServiceErr_BadParam; + if (!queue) + { + syslog(LOG_WARNING, "dnssd_clientstub: DNSServiceSetDispatchQueue dispatch queue NULL"); + return kDNSServiceErr_BadParam; + } + if (service->disp_queue) + { + syslog(LOG_WARNING, "dnssd_clientstub DNSServiceSetDispatchQueue dispatch queue set already"); + return kDNSServiceErr_BadParam; + } + if (service->disp_source) + { + syslog(LOG_WARNING, "DNSServiceSetDispatchQueue dispatch source set already"); + return kDNSServiceErr_BadParam; + } + service->disp_source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, dnssd_fd, 0, queue); + if (!service->disp_source) + { + syslog(LOG_WARNING, "DNSServiceSetDispatchQueue dispatch_source_create failed"); + return kDNSServiceErr_NoMemory; + } + service->disp_queue = queue; + dispatch_source_set_event_handler(service->disp_source, ^{DNSServiceProcessResult(service);}); + dispatch_source_set_cancel_handler(service->disp_source, ^{dnssd_close(dnssd_fd);}); + dispatch_resume(service->disp_source); + return kDNSServiceErr_NoError; +} +#endif // _DNS_SD_LIBDISPATCH + +#if !defined(_WIN32) + +static void DNSSD_API SleepKeepaliveCallback(DNSServiceRef sdRef, DNSRecordRef rec, const DNSServiceFlags flags, + DNSServiceErrorType errorCode, void *context) +{ + SleepKAContext *ka = (SleepKAContext *)context; + (void)rec; // Unused + (void)flags; // Unused + + if (sdRef->kacontext != context) + syslog(LOG_WARNING, "SleepKeepaliveCallback context mismatch"); + + if (ka->AppCallback) + ((DNSServiceSleepKeepaliveReply)ka->AppCallback)(sdRef, errorCode, ka->AppContext); +} + +DNSServiceErrorType DNSSD_API DNSServiceSleepKeepalive +( + DNSServiceRef *sdRef, + DNSServiceFlags flags, + int fd, + unsigned int timeout, + DNSServiceSleepKeepaliveReply callBack, + void *context +) +{ + char source_str[INET6_ADDRSTRLEN]; + char target_str[INET6_ADDRSTRLEN]; + struct sockaddr_storage lss; + struct sockaddr_storage rss; + socklen_t len1, len2; + unsigned int len, proxyreclen; + char buf[256]; + DNSServiceErrorType err; + DNSRecordRef record = NULL; + char name[10]; + char recname[128]; + SleepKAContext *ka; + unsigned int i, unique; + + + (void) flags; //unused + if (!timeout) return kDNSServiceErr_BadParam; + + + len1 = sizeof(lss); + if (getsockname(fd, (struct sockaddr *)&lss, &len1) < 0) + { + syslog(LOG_WARNING, "DNSServiceSleepKeepalive: getsockname %d\n", errno); + return kDNSServiceErr_BadParam; + } + + len2 = sizeof(rss); + if (getpeername(fd, (struct sockaddr *)&rss, &len2) < 0) + { + syslog(LOG_WARNING, "DNSServiceSleepKeepalive: getpeername %d\n", errno); + return kDNSServiceErr_BadParam; + } + + if (len1 != len2) + { + syslog(LOG_WARNING, "DNSServiceSleepKeepalive local/remote info not same"); + return kDNSServiceErr_Unknown; + } + + unique = 0; + if (lss.ss_family == AF_INET) + { + struct sockaddr_in *sl = (struct sockaddr_in *)&lss; + struct sockaddr_in *sr = (struct sockaddr_in *)&rss; + unsigned char *ptr = (unsigned char *)&sl->sin_addr; + + if (!inet_ntop(AF_INET, (const void *)&sr->sin_addr, target_str, sizeof (target_str))) + { + syslog(LOG_WARNING, "DNSServiceSleepKeepalive remote info failed %d", errno); + return kDNSServiceErr_Unknown; + } + if (!inet_ntop(AF_INET, (const void *)&sl->sin_addr, source_str, sizeof (source_str))) + { + syslog(LOG_WARNING, "DNSServiceSleepKeepalive local info failed %d", errno); + return kDNSServiceErr_Unknown; + } + // Sum of all bytes in the local address and port should result in a unique + // number in the local network + for (i = 0; i < sizeof(struct in_addr); i++) + unique += ptr[i]; + unique += sl->sin_port; + len = snprintf(buf+1, sizeof(buf) - 1, "t=%u h=%s d=%s l=%u r=%u", timeout, source_str, target_str, ntohs(sl->sin_port), ntohs(sr->sin_port)); + } + else + { + struct sockaddr_in6 *sl6 = (struct sockaddr_in6 *)&lss; + struct sockaddr_in6 *sr6 = (struct sockaddr_in6 *)&rss; + unsigned char *ptr = (unsigned char *)&sl6->sin6_addr; + + if (!inet_ntop(AF_INET6, (const void *)&sr6->sin6_addr, target_str, sizeof (target_str))) + { + syslog(LOG_WARNING, "DNSServiceSleepKeepalive remote6 info failed %d", errno); + return kDNSServiceErr_Unknown; + } + if (!inet_ntop(AF_INET6, (const void *)&sl6->sin6_addr, source_str, sizeof (source_str))) + { + syslog(LOG_WARNING, "DNSServiceSleepKeepalive local6 info failed %d", errno); + return kDNSServiceErr_Unknown; + } + for (i = 0; i < sizeof(struct in6_addr); i++) + unique += ptr[i]; + unique += sl6->sin6_port; + len = snprintf(buf+1, sizeof(buf) - 1, "t=%u H=%s D=%s l=%u r=%u", timeout, source_str, target_str, ntohs(sl6->sin6_port), ntohs(sr6->sin6_port)); + } + + if (len >= (sizeof(buf) - 1)) + { + syslog(LOG_WARNING, "DNSServiceSleepKeepalive could not fit local/remote info"); + return kDNSServiceErr_Unknown; + } + // Include the NULL byte also in the first byte. The total length of the record includes the + // first byte also. + buf[0] = len + 1; + proxyreclen = len + 2; + + len = snprintf(name, sizeof(name), "%u", unique); + if (len >= sizeof(name)) + { + syslog(LOG_WARNING, "DNSServiceSleepKeepalive could not fit unique"); + return kDNSServiceErr_Unknown; + } + + len = snprintf(recname, sizeof(recname), "%s.%s", name, "_keepalive._dns-sd._udp.local"); + if (len >= sizeof(recname)) + { + syslog(LOG_WARNING, "DNSServiceSleepKeepalive could not fit name"); + return kDNSServiceErr_Unknown; + } + + ka = malloc(sizeof(SleepKAContext)); + if (!ka) return kDNSServiceErr_NoMemory; + ka->AppCallback = callBack; + ka->AppContext = context; + + err = DNSServiceCreateConnection(sdRef); + if (err) + { + syslog(LOG_WARNING, "DNSServiceSleepKeepalive cannot create connection"); + free(ka); + return err; + } + + // we don't care about the "record". When sdRef gets deallocated later, it will be freed too + err = DNSServiceRegisterRecord(*sdRef, &record, kDNSServiceFlagsUnique, 0, recname, + kDNSServiceType_NULL, kDNSServiceClass_IN, proxyreclen, buf, kDNSServiceInterfaceIndexAny, SleepKeepaliveCallback, ka); + if (err) + { + syslog(LOG_WARNING, "DNSServiceSleepKeepalive cannot create connection"); + free(ka); + return err; + } + (*sdRef)->kacontext = ka; + return kDNSServiceErr_NoError; +} +#endif diff --git a/mDNSResponder/mDNSShared/dnssd_ipc.c b/mDNSResponder/mDNSShared/dnssd_ipc.c new file mode 100644 index 00000000..6059eb39 --- /dev/null +++ b/mDNSResponder/mDNSShared/dnssd_ipc.c @@ -0,0 +1,161 @@ +/* -*- Mode: C; tab-width: 4 -*- + * + * Copyright (c) 2003-2004, Apple Computer, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "dnssd_ipc.h" + +#if defined(_WIN32) + +char *win32_strerror(int inErrorCode) +{ + static char buffer[1024]; + DWORD n; + memset(buffer, 0, sizeof(buffer)); + n = FormatMessageA( + FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + (DWORD) inErrorCode, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + buffer, + sizeof(buffer), + NULL); + if (n > 0) + { + // Remove any trailing CR's or LF's since some messages have them. + while ((n > 0) && isspace(((unsigned char *) buffer)[n - 1])) + buffer[--n] = '\0'; + } + return buffer; +} + +#endif + +void put_uint32(const uint32_t l, char **ptr) +{ + (*ptr)[0] = (char)((l >> 24) & 0xFF); + (*ptr)[1] = (char)((l >> 16) & 0xFF); + (*ptr)[2] = (char)((l >> 8) & 0xFF); + (*ptr)[3] = (char)((l ) & 0xFF); + *ptr += sizeof(uint32_t); +} + +uint32_t get_uint32(const char **ptr, const char *end) +{ + if (!*ptr || *ptr + sizeof(uint32_t) > end) + { + *ptr = NULL; + return(0); + } + else + { + uint8_t *p = (uint8_t*) *ptr; + *ptr += sizeof(uint32_t); + return((uint32_t) ((uint32_t)p[0] << 24 | (uint32_t)p[1] << 16 | (uint32_t)p[2] << 8 | p[3])); + } +} + +void put_uint16(uint16_t s, char **ptr) +{ + (*ptr)[0] = (char)((s >> 8) & 0xFF); + (*ptr)[1] = (char)((s ) & 0xFF); + *ptr += sizeof(uint16_t); +} + +uint16_t get_uint16(const char **ptr, const char *end) +{ + if (!*ptr || *ptr + sizeof(uint16_t) > end) + { + *ptr = NULL; + return(0); + } + else + { + uint8_t *p = (uint8_t*) *ptr; + *ptr += sizeof(uint16_t); + return((uint16_t) ((uint16_t)p[0] << 8 | p[1])); + } +} + +int put_string(const char *str, char **ptr) +{ + if (!str) str = ""; + strcpy(*ptr, str); + *ptr += strlen(str) + 1; + return 0; +} + +int get_string(const char **ptr, const char *const end, char *buffer, int buflen) +{ + if (!*ptr) + { + *buffer = 0; + return(-1); + } + else + { + char *lim = buffer + buflen; // Calculate limit + while (*ptr < end && buffer < lim) + { + char c = *buffer++ = *(*ptr)++; + if (c == 0) return(0); // Success + } + if (buffer == lim) buffer--; + *buffer = 0; // Failed, so terminate string, + *ptr = NULL; // clear pointer, + return(-1); // and return failure indication + } +} + +void put_rdata(const int rdlen, const unsigned char *rdata, char **ptr) +{ + memcpy(*ptr, rdata, rdlen); + *ptr += rdlen; +} + +const char *get_rdata(const char **ptr, const char *end, int rdlen) +{ + if (!*ptr || *ptr + rdlen > end) + { + *ptr = NULL; + return(0); + } + else + { + const char *rd = *ptr; + *ptr += rdlen; + return rd; + } +} + +void ConvertHeaderBytes(ipc_msg_hdr *hdr) +{ + hdr->version = htonl(hdr->version); + hdr->datalen = htonl(hdr->datalen); + hdr->ipc_flags = htonl(hdr->ipc_flags); + hdr->op = htonl(hdr->op ); + hdr->reg_index = htonl(hdr->reg_index); +} diff --git a/mDNSResponder/mDNSShared/dnssd_ipc.h b/mDNSResponder/mDNSShared/dnssd_ipc.h new file mode 100644 index 00000000..360d703f --- /dev/null +++ b/mDNSResponder/mDNSShared/dnssd_ipc.h @@ -0,0 +1,221 @@ +/* -*- Mode: C; tab-width: 4 -*- + * + * Copyright (c) 2003-2004, Apple Computer, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef DNSSD_IPC_H +#define DNSSD_IPC_H + +#include "dns_sd.h" + +// +// Common cross platform services +// +#if defined(WIN32) +# include <winsock2.h> +# define dnssd_InvalidSocket INVALID_SOCKET +# define dnssd_SocketValid(s) ((s) != INVALID_SOCKET) +# define dnssd_EWOULDBLOCK WSAEWOULDBLOCK +# define dnssd_EINTR WSAEINTR +# define dnssd_ECONNRESET WSAECONNRESET +# define dnssd_sock_t SOCKET +# define dnssd_socklen_t int +# define dnssd_close(sock) closesocket(sock) +# define dnssd_errno WSAGetLastError() +# define dnssd_strerror(X) win32_strerror(X) +# define ssize_t int +# define getpid _getpid +# define unlink _unlink +extern char *win32_strerror(int inErrorCode); +#else +# include <sys/types.h> +# include <unistd.h> +# include <sys/un.h> +# include <string.h> +# include <stdio.h> +# include <stdlib.h> +# include <sys/stat.h> +# include <sys/socket.h> +# include <netinet/in.h> +# include <arpa/inet.h> +# define dnssd_InvalidSocket -1 +# define dnssd_SocketValid(s) ((s) >= 0) +# define dnssd_EWOULDBLOCK EWOULDBLOCK +# define dnssd_EINTR EINTR +# define dnssd_ECONNRESET ECONNRESET +# define dnssd_EPIPE EPIPE +# define dnssd_sock_t int +# define dnssd_socklen_t unsigned int +# define dnssd_close(sock) close(sock) +# define dnssd_errno errno +# define dnssd_strerror(X) strerror(X) +#endif + +#if defined(USE_TCP_LOOPBACK) +# define AF_DNSSD AF_INET +# define MDNS_TCP_SERVERADDR "127.0.0.1" +# define MDNS_TCP_SERVERPORT 5354 +# define LISTENQ 5 +# define dnssd_sockaddr_t struct sockaddr_in +#else +# define AF_DNSSD AF_LOCAL +# ifndef MDNS_UDS_SERVERPATH +# define MDNS_UDS_SERVERPATH "/var/run/mDNSResponder" +# endif +# define LISTENQ 100 +// longest legal control path length +# define MAX_CTLPATH 256 +# define dnssd_sockaddr_t struct sockaddr_un +#endif + +// Compatibility workaround +#ifndef AF_LOCAL +#define AF_LOCAL AF_UNIX +#endif + +// General UDS constants +#define TXT_RECORD_INDEX ((uint32_t)(-1)) // record index for default text record + +// IPC data encoding constants and types +#define VERSION 1 +#define IPC_FLAGS_NOREPLY 1 // set flag if no asynchronous replies are to be sent to client + +// Structure packing macro. If we're not using GNUC, it's not fatal. Most compilers naturally pack the on-the-wire +// structures correctly anyway, so a plain "struct" is usually fine. In the event that structures are not packed +// correctly, our compile-time assertion checks will catch it and prevent inadvertent generation of non-working code. +#ifndef packedstruct + #if ((__GNUC__ > 2) || ((__GNUC__ == 2) && (__GNUC_MINOR__ >= 9))) + #define packedstruct struct __attribute__((__packed__)) + #define packedunion union __attribute__((__packed__)) + #else + #define packedstruct struct + #define packedunion union + #endif +#endif + +typedef enum +{ + request_op_none = 0, // No request yet received on this connection + connection_request = 1, // connected socket via DNSServiceConnect() + reg_record_request, // reg/remove record only valid for connected sockets + remove_record_request, + enumeration_request, + reg_service_request, + browse_request, + resolve_request, + query_request, + reconfirm_record_request, + add_record_request, + update_record_request, + setdomain_request, // Up to here is in Tiger and B4W 1.0.3 + getproperty_request, // New in B4W 1.0.4 + port_mapping_request, // New in Leopard and B4W 2.0 + addrinfo_request, + send_bpf, // New in SL + getpid_request, + release_request, + connection_delegate_request, + + cancel_request = 63 +} request_op_t; + +typedef enum +{ + enumeration_reply_op = 64, + reg_service_reply_op, + browse_reply_op, + resolve_reply_op, + query_reply_op, + reg_record_reply_op, // Up to here is in Tiger and B4W 1.0.3 + getproperty_reply_op, // New in B4W 1.0.4 + port_mapping_reply_op, // New in Leopard and B4W 2.0 + addrinfo_reply_op +} reply_op_t; + +#if defined(_WIN64) +# pragma pack(push,4) +#endif + +// Define context object big enough to hold a 64-bit pointer, +// to accomodate 64-bit clients communicating with 32-bit daemon. +// There's no reason for the daemon to ever be a 64-bit process, but its clients might be +typedef packedunion +{ + void *context; + uint32_t u32[2]; +} client_context_t; + +typedef packedstruct +{ + uint32_t version; + uint32_t datalen; + uint32_t ipc_flags; + uint32_t op; // request_op_t or reply_op_t + client_context_t client_context; // context passed from client, returned by server in corresponding reply + uint32_t reg_index; // identifier for a record registered via DNSServiceRegisterRecord() on a + // socket connected by DNSServiceCreateConnection(). Must be unique in the scope of the connection, such that and + // index/socket pair uniquely identifies a record. (Used to select records for removal by DNSServiceRemoveRecord()) +} ipc_msg_hdr; + +#if defined(_WIN64) +# pragma pack(pop) +#endif + +// routines to write to and extract data from message buffers. +// caller responsible for bounds checking. +// ptr is the address of the pointer to the start of the field. +// it is advanced to point to the next field, or the end of the message + +void put_uint32(const uint32_t l, char **ptr); +uint32_t get_uint32(const char **ptr, const char *end); + +void put_uint16(uint16_t s, char **ptr); +uint16_t get_uint16(const char **ptr, const char *end); + +#define put_flags put_uint32 +#define get_flags get_uint32 + +#define put_error_code put_uint32 +#define get_error_code get_uint32 + +int put_string(const char *str, char **ptr); +int get_string(const char **ptr, const char *const end, char *buffer, int buflen); + +void put_rdata(const int rdlen, const unsigned char *rdata, char **ptr); +const char *get_rdata(const char **ptr, const char *end, int rdlen); // return value is rdata pointed to by *ptr - +// rdata is not copied from buffer. + +void ConvertHeaderBytes(ipc_msg_hdr *hdr); + +struct CompileTimeAssertionChecks_dnssd_ipc +{ + // Check that the compiler generated our on-the-wire packet format structure definitions + // properly packed, without adding padding bytes to align fields on 32-bit or 64-bit boundaries. + char assert0[(sizeof(client_context_t) == 8) ? 1 : -1]; + char assert1[(sizeof(ipc_msg_hdr) == 28) ? 1 : -1]; +}; + +#endif // DNSSD_IPC_H diff --git a/mDNSResponder/mDNSShared/mDNSDebug.c b/mDNSResponder/mDNSShared/mDNSDebug.c new file mode 100644 index 00000000..cb4da6ec --- /dev/null +++ b/mDNSResponder/mDNSShared/mDNSDebug.c @@ -0,0 +1,95 @@ +/* -*- Mode: C; tab-width: 4 -*- + * + * Copyright (c) 2003 Apple Computer, Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + + File: mDNSDebug.c + + Contains: Implementation of debugging utilities. Requires a POSIX environment. + + Version: 1.0 + + */ + +#include "mDNSDebug.h" + +#include <stdio.h> + +#if defined(WIN32) || defined(EFI32) || defined(EFI64) || defined(EFIX64) +// Need to add Windows/EFI syslog support here +#define LOG_PID 0x01 +#define LOG_CONS 0x02 +#define LOG_PERROR 0x20 +#else +#include <syslog.h> +#endif + +#include "mDNSEmbeddedAPI.h" + +mDNSexport int mDNS_LoggingEnabled = 0; +mDNSexport int mDNS_PacketLoggingEnabled = 0; +mDNSexport int mDNS_McastLoggingEnabled = 0; +mDNSexport int mDNS_McastTracingEnabled = 0; + +#if MDNS_DEBUGMSGS +mDNSexport int mDNS_DebugMode = mDNStrue; +#else +mDNSexport int mDNS_DebugMode = mDNSfalse; +#endif + +// Note, this uses mDNS_vsnprintf instead of standard "vsnprintf", because mDNS_vsnprintf knows +// how to print special data types like IP addresses and length-prefixed domain names +#if MDNS_DEBUGMSGS > 1 +mDNSexport void verbosedebugf_(const char *format, ...) +{ + char buffer[512]; + va_list ptr; + va_start(ptr,format); + buffer[mDNS_vsnprintf(buffer, sizeof(buffer), format, ptr)] = 0; + va_end(ptr); + mDNSPlatformWriteDebugMsg(buffer); +} +#endif + +// Log message with default "mDNSResponder" ident string at the start +mDNSlocal void LogMsgWithLevelv(mDNSLogLevel_t logLevel, const char *format, va_list ptr) +{ + char buffer[512]; + buffer[mDNS_vsnprintf((char *)buffer, sizeof(buffer), format, ptr)] = 0; + mDNSPlatformWriteLogMsg(ProgramName, buffer, logLevel); +} + +#define LOG_HELPER_BODY(L) \ + { \ + va_list ptr; \ + va_start(ptr,format); \ + LogMsgWithLevelv(L, format, ptr); \ + va_end(ptr); \ + } + +// see mDNSDebug.h +#if !MDNS_HAS_VA_ARG_MACROS +void LogMsg_(const char *format, ...) LOG_HELPER_BODY(MDNS_LOG_MSG) +void LogOperation_(const char *format, ...) LOG_HELPER_BODY(MDNS_LOG_OPERATION) +void LogSPS_(const char *format, ...) LOG_HELPER_BODY(MDNS_LOG_SPS) +void LogInfo_(const char *format, ...) LOG_HELPER_BODY(MDNS_LOG_INFO) +#endif + +#if MDNS_DEBUGMSGS +void debugf_(const char *format, ...) LOG_HELPER_BODY(MDNS_LOG_DEBUG) +#endif + +// Log message with default "mDNSResponder" ident string at the start +mDNSexport void LogMsgWithLevel(mDNSLogLevel_t logLevel, const char *format, ...) +LOG_HELPER_BODY(logLevel) diff --git a/mDNSResponder/mDNSShared/mDNSResponder.8 b/mDNSResponder/mDNSShared/mDNSResponder.8 new file mode 100644 index 00000000..48c1cf65 --- /dev/null +++ b/mDNSResponder/mDNSShared/mDNSResponder.8 @@ -0,0 +1,116 @@ +.\" -*- tab-width: 4 -*- +.\" +.\" Copyright (c) 2003-2004 Apple Computer, Inc. All Rights Reserved. +.\" +.\" Licensed under the Apache License, Version 2.0 (the "License"); +.\" you may not use this file except in compliance with the License. +.\" You may obtain a copy of the License at +.\" +.\" http://www.apache.org/licenses/LICENSE-2.0 +.\" +.\" Unless required by applicable law or agreed to in writing, software +.\" distributed under the License is distributed on an "AS IS" BASIS, +.\" WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.\" See the License for the specific language governing permissions and +.\" limitations under the License. +.\" +.Dd April 2004 \" Date +.Dt mDNSResponder 8 \" Document Title +.Os Darwin \" Operating System +.\" +.Sh NAME +.Nm mDNSResponder +.Nd Multicast and Unicast DNS daemon \" Name Description for whatis database +.\" +.Sh SYNOPSIS +.Nm +.\" +.Sh DESCRIPTION +.Nm +(also known as +.Nm mdnsd +on some systems) +is a daemon invoked at boot time to implement Multicast DNS and DNS Service Discovery. On +Mac OS X 10.6 (Snow Leopard), +.Nm +is also the system-wide Unicast DNS Resolver. +.Pp +.Nm +listens on UDP port 5353 for Multicast DNS Query packets. +When it receives a query for which it knows an answer, +.Nm +issues the appropriate Multicast DNS Reply packet. +.Pp +.Nm +also performs Unicast and Multicast DNS Queries on behalf of client processes, and +maintains a cache of the replies. +.Pp +.Nm +has no user-specifiable command-line argument, and users should not run +.Nm +manually. +.Pp +.Sh LOGGING +There are several methods with which to examine +.Nm Ns 's internal state for debugging and diagnostic purposes. The syslog(1) +logging levels map as follows: +.Pp +.Dl Error - Error messages +.Dl Warning - Client-initiated operations +.Dl Notice - Sleep proxy operations +.Dl Info - Informational messages +.Pp +By default, only log level Error is logged. +.Pp +A SIGUSR1 signal toggles additional logging, with Warning and Notice +enabled by default: +.Pp +.Dl % sudo killall -USR1 mDNSResponder +.Pp +Once this logging is enabled, users can additionally use syslog(1) +to change the log filter for the process. For example, to enable log levels Emergency - Debug: +.Pp +.Dl % sudo syslog -c mDNSResponder -d +.Pp +A SIGUSR2 signal toggles packet logging: +.Pp +.Dl % sudo killall -USR2 mDNSResponder +.Pp +A SIGINFO signal will dump a snapshot summary of the internal state to +.Pa /var/log/system.log Ns : +.Pp +.Dl % sudo killall -INFO mDNSResponder +.Sh FILES +.Pa /usr/sbin/mDNSResponder \" Pathname +.\" +.Pp +.Sh INFO +.Pp +For information on Multicast DNS, see +.Pa http://www.multicastdns.org/ +.Pp +For information on DNS Service Discovery, see +.Pa http://www.dns-sd.org/ +.Pp +For information on how to use the Multicast DNS and the +DNS Service Discovery APIs on Mac OS X and other platforms, see +.Pa http://developer.apple.com/bonjour/ +.Pp +For the source code to +.Nm , see +.Pa http://developer.apple.com/darwin/projects/bonjour/ +.\" +.Sh BUGS +.Nm +bugs are tracked in Apple Radar component "mDNSResponder". +.\" +.Sh HISTORY +The +.Nm +daemon first appeared in Mac OS X 10.2 (Jaguar). +.Pp +Also available from the Darwin open source repository +(though not officially supported by Apple) are +.Nm +daemons for other platforms, including Mac OS 9, Microsoft Windows, +Linux, FreeBSD, NetBSD, Solaris, and other POSIX systems. diff --git a/mDNSResponder/mDNSShared/uds_daemon.c b/mDNSResponder/mDNSShared/uds_daemon.c new file mode 100644 index 00000000..53677bdf --- /dev/null +++ b/mDNSResponder/mDNSShared/uds_daemon.c @@ -0,0 +1,6103 @@ +/* -*- Mode: C; tab-width: 4 -*- + * + * Copyright (c) 2003-2013 Apple Computer, Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#if defined(_WIN32) +#include <process.h> +#define usleep(X) Sleep(((X)+999)/1000) +#else +#include <fcntl.h> +#include <errno.h> +#include <sys/ioctl.h> +#include <sys/types.h> +#include <sys/time.h> +#include <sys/resource.h> +#endif + +#include <stdlib.h> +#include <stdio.h> + +#include "mDNSEmbeddedAPI.h" +#include "DNSCommon.h" +#include "uDNS.h" +#include "uds_daemon.h" + +// Normally we append search domains only for queries with a single label that are not +// fully qualified. This can be overridden to apply search domains for queries (that are +// not fully qualified) with any number of labels e.g., moon, moon.cs, moon.cs.be, etc. +mDNSBool AlwaysAppendSearchDomains = mDNSfalse; + +// Apple-specific functionality, not required for other platforms +#if APPLE_OSX_mDNSResponder +#include <sys/ucred.h> +#ifndef PID_FILE +#define PID_FILE "" +#endif +#endif + +#ifdef LOCAL_PEERPID +#include <sys/un.h> // for LOCAL_PEERPID +#include <sys/socket.h> // for getsockopt +#include <sys/proc_info.h> // for struct proc_bsdshortinfo +#include <libproc.h> // for proc_pidinfo() +#endif //LOCAL_PEERPID +//upto 16 characters of process name (defined in <sys/proc.h> but we do not want to include that file) +#define MAXCOMLEN 16 + +#if APPLE_OSX_mDNSResponder +#include <WebFilterDNS/WebFilterDNS.h> + +#if !NO_WCF + +int WCFIsServerRunning(WCFConnection *conn) __attribute__((weak_import)); +int WCFNameResolvesToAddr(WCFConnection *conn, char* domainName, struct sockaddr* address, uid_t userid) __attribute__((weak_import)); +int WCFNameResolvesToName(WCFConnection *conn, char* fromName, char* toName, uid_t userid) __attribute__((weak_import)); + +// Do we really need to define a macro for "if"? +#define CHECK_WCF_FUNCTION(X) if (X) +#endif // ! NO_WCF + +#else +#define NO_WCF 1 +#endif // APPLE_OSX_mDNSResponder + +// User IDs 0-500 are system-wide processes, not actual users in the usual sense +// User IDs for real user accounts start at 501 and count up from there +#define SystemUID(X) ((X) <= 500) + +#define MAX_ANONYMOUS_DATA 256 + +// *************************************************************************** +#if COMPILER_LIKES_PRAGMA_MARK +#pragma mark - +#pragma mark - Types and Data Structures +#endif + +typedef enum +{ + t_uninitialized, + t_morecoming, + t_complete, + t_error, + t_terminated +} transfer_state; + +typedef struct request_state request_state; + +typedef void (*req_termination_fn)(request_state *request); + +typedef struct registered_record_entry +{ + struct registered_record_entry *next; + mDNSu32 key; + client_context_t regrec_client_context; + request_state *request; + mDNSBool external_advertise; + mDNSInterfaceID origInterfaceID; + AuthRecord *rr; // Pointer to variable-sized AuthRecord (Why a pointer? Why not just embed it here?) +} registered_record_entry; + +// A single registered service: ServiceRecordSet + bookkeeping +// Note that we duplicate some fields from parent service_info object +// to facilitate cleanup, when instances and parent may be deallocated at different times. +typedef struct service_instance +{ + struct service_instance *next; + request_state *request; + AuthRecord *subtypes; + mDNSBool renameonmemfree; // Set on config change when we deregister original name + mDNSBool clientnotified; // Has client been notified of successful registration yet? + mDNSBool default_local; // is this the "local." from an empty-string registration? + mDNSBool external_advertise; // is this is being advertised externally? + domainname domain; + ServiceRecordSet srs; // note -- variable-sized object -- must be last field in struct +} service_instance; + +// for multi-domain default browsing +typedef struct browser_t +{ + struct browser_t *next; + domainname domain; + DNSQuestion q; +} browser_t; + +#ifdef _WIN32 + typedef unsigned int pid_t; + typedef unsigned int socklen_t; +#endif + +struct request_state +{ + request_state *next; + request_state *primary; // If this operation is on a shared socket, pointer to primary + // request_state for the original DNSServiceCreateConnection() operation + dnssd_sock_t sd; + pid_t process_id; // Client's PID value + char pid_name[MAXCOMLEN]; // Client's process name + char uuid[UUID_SIZE]; + mDNSBool validUUID; + dnssd_sock_t errsd; + mDNSu32 uid; + void * platform_data; + + // Note: On a shared connection these fields in the primary structure, including hdr, are re-used + // for each new request. This is because, until we've read the ipc_msg_hdr to find out what the + // operation is, we don't know if we're going to need to allocate a new request_state or not. + transfer_state ts; + mDNSu32 hdr_bytes; // bytes of header already read + ipc_msg_hdr hdr; + mDNSu32 data_bytes; // bytes of message data already read + char *msgbuf; // pointer to data storage to pass to free() + const char *msgptr; // pointer to data to be read from (may be modified) + char *msgend; // pointer to byte after last byte of message + + // reply, termination, error, and client context info + int no_reply; // don't send asynchronous replies to client + mDNSs32 time_blocked; // record time of a blocked client + int unresponsiveness_reports; + struct reply_state *replies; // corresponding (active) reply list + req_termination_fn terminate; + DNSServiceFlags flags; + + union + { + registered_record_entry *reg_recs; // list of registrations for a connection-oriented request + struct + { + mDNSInterfaceID interface_id; + mDNSBool default_domain; + mDNSBool ForceMCast; + domainname regtype; + browser_t *browsers; + const mDNSu8 *AnonData; + } browser; + struct + { + mDNSInterfaceID InterfaceID; + mDNSu16 txtlen; + void *txtdata; + mDNSIPPort port; + domainlabel name; + char type_as_string[MAX_ESCAPED_DOMAIN_NAME]; + domainname type; + mDNSBool default_domain; + domainname host; + mDNSBool autoname; // Set if this name is tied to the Computer Name + mDNSBool autorename; // Set if this client wants us to automatically rename on conflict + mDNSBool allowremotequery; // Respond to unicast queries from outside the local link? + int num_subtypes; + mDNSBool AnonData; + service_instance *instances; + } servicereg; + struct + { + mDNSInterfaceID interface_id; + mDNSu32 flags; + mDNSu32 protocol; + DNSQuestion q4; + DNSQuestion *q42; + DNSQuestion q6; + DNSQuestion *q62; + mDNSu8 v4ans; + mDNSu8 v6ans; + } addrinfo; + struct + { + mDNSIPPort ReqExt; // External port we originally requested, for logging purposes + NATTraversalInfo NATinfo; + } pm; + struct + { + DNSServiceFlags flags; + DNSQuestion q_all; + DNSQuestion q_default; + } enumeration; + struct + { + DNSQuestion q; + DNSQuestion *q2; + mDNSu8 ans; + } queryrecord; + struct + { + DNSQuestion qtxt; + DNSQuestion qsrv; + const ResourceRecord *txt; + const ResourceRecord *srv; + mDNSs32 ReportTime; + mDNSBool external_advertise; + } resolve; + } u; +}; + +// struct physically sits between ipc message header and call-specific fields in the message buffer +typedef struct +{ + DNSServiceFlags flags; // Note: This field is in NETWORK byte order + mDNSu32 ifi; // Note: This field is in NETWORK byte order + DNSServiceErrorType error; // Note: This field is in NETWORK byte order +} reply_hdr; + +typedef struct reply_state +{ + struct reply_state *next; // If there are multiple unsent replies + mDNSu32 totallen; + mDNSu32 nwriten; + ipc_msg_hdr mhdr[1]; + reply_hdr rhdr[1]; +} reply_state; + +// *************************************************************************** +#if COMPILER_LIKES_PRAGMA_MARK +#pragma mark - +#pragma mark - Globals +#endif + +// globals +mDNSexport mDNS mDNSStorage; +mDNSexport const char ProgramName[] = "mDNSResponder"; + +static dnssd_sock_t listenfd = dnssd_InvalidSocket; +static request_state *all_requests = NULL; +#ifdef LOCAL_PEERPID +struct proc_bsdshortinfo proc; +#endif //LOCAL_PEERPID +mDNSlocal void set_peer_pid(request_state *request); +mDNSlocal void LogMcastClientInfo(request_state *req); +mDNSlocal void GetMcastClients(request_state *req); +static mDNSu32 mcount; // tracks the current active mcast operations for McastLogging +static mDNSu32 i_mcount; // sets mcount when McastLogging is enabled(PROF signal is sent) +static mDNSu32 n_mrecords; // tracks the current active mcast records for McastLogging +static mDNSu32 n_mquests; // tracks the current active mcast questions for McastLogging + +// Note asymmetry here between registration and browsing. +// For service registrations we only automatically register in domains that explicitly appear in local configuration data +// (so AutoRegistrationDomains could equally well be called SCPrefRegDomains) +// For service browsing we also learn automatic browsing domains from the network, so for that case we have: +// 1. SCPrefBrowseDomains (local configuration data) +// 2. LocalDomainEnumRecords (locally-generated local-only PTR records -- equivalent to slElem->AuthRecs in uDNS.c) +// 3. AutoBrowseDomains, which is populated by tracking add/rmv events in AutomaticBrowseDomainChange, the callback function for our mDNS_GetDomains call. +// By creating and removing our own LocalDomainEnumRecords, we trigger AutomaticBrowseDomainChange callbacks just like domains learned from the network would. + +mDNSexport DNameListElem *AutoRegistrationDomains; // Domains where we automatically register for empty-string registrations + +static DNameListElem *SCPrefBrowseDomains; // List of automatic browsing domains read from SCPreferences for "empty string" browsing +static ARListElem *LocalDomainEnumRecords; // List of locally-generated PTR records to augment those we learn from the network +mDNSexport DNameListElem *AutoBrowseDomains; // List created from those local-only PTR records plus records we get from the network + +#define MSG_PAD_BYTES 5 // pad message buffer (read from client) with n zero'd bytes to guarantee + // n get_string() calls w/o buffer overrun +// initialization, setup/teardown functions + +// If a platform specifies its own PID file name, we use that +#ifndef PID_FILE +#define PID_FILE "/var/run/mDNSResponder.pid" +#endif + +mDNSlocal char *AnonDataToString(const mDNSu8 *ad, int adlen, char *adstr, int adstrlen); + +// *************************************************************************** +#if COMPILER_LIKES_PRAGMA_MARK +#pragma mark - +#pragma mark - General Utility Functions +#endif + +mDNSlocal void FatalError(char *errmsg) +{ + char* ptr = NULL; + LogMsg("%s: %s", errmsg, dnssd_strerror(dnssd_errno)); + *ptr = 0; // On OS X abort() doesn't generate a crash log, but writing to zero does + abort(); // On platforms where writing to zero doesn't generate an exception, abort instead +} + +mDNSlocal mDNSu32 dnssd_htonl(mDNSu32 l) +{ + mDNSu32 ret; + char *data = (char*) &ret; + put_uint32(l, &data); + return ret; +} + +// hack to search-replace perror's to LogMsg's +mDNSlocal void my_perror(char *errmsg) +{ + LogMsg("%s: %d (%s)", errmsg, dnssd_errno, dnssd_strerror(dnssd_errno)); +} + +// Throttled version of my_perror: Logs once every 250 msgs +mDNSlocal void my_throttled_perror(char *err_msg) +{ + static int uds_throttle_count = 0; + if ((uds_throttle_count++ % 250) == 0) + my_perror(err_msg); +} + +// LogMcastQuestion/LogMcastQ should be called after the DNSQuestion struct is initialized(especially for q->TargetQID) +// Hence all calls are made after mDNS_StartQuery()/mDNS_StopQuery()/mDNS_StopBrowse() is called. +mDNSlocal void LogMcastQuestion(mDNS *const m, const DNSQuestion *const q, request_state *req, q_state status) +{ + if (mDNSOpaque16IsZero(q->TargetQID)) // Check for Mcast Query + { + mDNSBool mflag = mDNSfalse; + if (status == q_start) + { + if (++mcount == 1) + mflag = mDNStrue; + } + else + { + mcount--; + } + LogMcast("%s: %##s (%s) (%s) Client(%d)[%s]", status ? "+Question" : "-Question", q->qname.c, DNSTypeName(q->qtype), + q->InterfaceID == mDNSInterface_LocalOnly ? "lo" : q->InterfaceID == mDNSInterface_P2P ? "p2p" : + q->InterfaceID == mDNSInterface_Any ? "any" : InterfaceNameForID(m, q->InterfaceID), + req->process_id, req->pid_name); + LogMcastStateInfo(m, mflag, mDNSfalse, mDNSfalse); + } + return; +} + +// LogMcastService/LogMcastS should be called after the AuthRecord struct is initialized +// Hence all calls are made after mDNS_Register()/ just before mDNS_Deregister() +mDNSlocal void LogMcastService(mDNS *const m, const AuthRecord *const ar, request_state *req, reg_state status) +{ + if (!AuthRecord_uDNS(ar)) // Check for Mcast Service + { + mDNSBool mflag = mDNSfalse; + if (status == reg_start) + { + if (++mcount == 1) + mflag = mDNStrue; + } + else + { + mcount--; + } + LogMcast("%s: %##s (%s) (%s) Client(%d)[%s]", status ? "+Service" : "-Service", ar->resrec.name->c, DNSTypeName(ar->resrec.rrtype), + ar->resrec.InterfaceID == mDNSInterface_LocalOnly ? "lo" : ar->resrec.InterfaceID == mDNSInterface_P2P ? "p2p" : + ar->resrec.InterfaceID == mDNSInterface_Any ? "all" : InterfaceNameForID(m, ar->resrec.InterfaceID), + req->process_id, req->pid_name); + LogMcastStateInfo(m, mflag, mDNSfalse, mDNSfalse); + } + return; +} + +// For complete Mcast State Log, pass mDNStrue to mstatelog in LogMcastStateInfo() +mDNSexport void LogMcastStateInfo(mDNS *const m, mDNSBool mflag, mDNSBool start, mDNSBool mstatelog) +{ + if (!mstatelog) + { + if (!all_requests) + { + LogMcastNoIdent("<None>"); + } + else + { + request_state *req, *r; + for (req = all_requests; req; req=req->next) + { + if (req->primary) // If this is a subbordinate operation, check that the parent is in the list + { + for (r = all_requests; r && r != req; r=r->next) + if (r == req->primary) + goto foundpar; + } + // For non-subbordinate operations, and subbordinate operations that have lost their parent, write out their info + GetMcastClients(req); + foundpar:; + } + LogMcastNoIdent("--- MCAST RECORDS COUNT[%d] MCAST QUESTIONS COUNT[%d] ---", n_mrecords, n_mquests); + n_mrecords = n_mquests = 0; // Reset the values + } + } + else + { + static mDNSu32 i_mpktnum; + i_mcount = 0; + if (start) + mcount = 0; + // mcount is initialized to 0 when the PROF signal is sent since mcount could have + // wrong value if MulticastLogging is disabled and then re-enabled + LogMcastNoIdent("--- START MCAST STATE LOG ---"); + if (!all_requests) + { + mcount = 0; + LogMcastNoIdent("<None>"); + } + else + { + request_state *req, *r; + for (req = all_requests; req; req=req->next) + { + if (req->primary) // If this is a subbordinate operation, check that the parent is in the list + { + for (r = all_requests; r && r != req; r=r->next) + if (r == req->primary) + goto foundparent; + LogMcastNoIdent("%3d: Orphan operation; parent not found in request list", req->sd); + } + // For non-subbordinate operations, and subbordinate operations that have lost their parent, write out their info + LogMcastClientInfo(req); + foundparent:; + } + if(!mcount) // To initially set mcount + mcount = i_mcount; + } + if (mcount == 0) + { + i_mpktnum = m->MPktNum; + LogMcastNoIdent("--- MCOUNT[%d]: IMPKTNUM[%d] ---", mcount, i_mpktnum); + } + if (mflag) + LogMcastNoIdent("--- MCOUNT[%d]: CMPKTNUM[%d] - IMPKTNUM[%d] = [%d]PKTS ---", mcount, m->MPktNum, i_mpktnum, (m->MPktNum - i_mpktnum)); + LogMcastNoIdent("--- END MCAST STATE LOG ---"); + } +} + +mDNSlocal void abort_request(request_state *req) +{ + if (req->terminate == (req_termination_fn) ~0) + { LogMsg("abort_request: ERROR: Attempt to abort operation %p with req->terminate %p", req, req->terminate); return; } + + // First stop whatever mDNSCore operation we were doing + // If this is actually a shared connection operation, then its req->terminate function will scan + // the all_requests list and terminate any subbordinate operations sharing this file descriptor + if (req->terminate) req->terminate(req); + + if (!dnssd_SocketValid(req->sd)) + { LogMsg("abort_request: ERROR: Attempt to abort operation %p with invalid fd %d", req, req->sd); return; } + + // Now, if this request_state is not subordinate to some other primary, close file descriptor and discard replies + if (!req->primary) + { + if (req->errsd != req->sd) LogOperation("%3d: Removing FD and closing errsd %d", req->sd, req->errsd); + else LogOperation("%3d: Removing FD", req->sd); + udsSupportRemoveFDFromEventLoop(req->sd, req->platform_data); // Note: This also closes file descriptor req->sd for us + if (req->errsd != req->sd) { dnssd_close(req->errsd); req->errsd = req->sd; } + + while (req->replies) // free pending replies + { + reply_state *ptr = req->replies; + req->replies = req->replies->next; + freeL("reply_state (abort)", ptr); + } + } + + // Set req->sd to something invalid, so that udsserver_idle knows to unlink and free this structure +#if APPLE_OSX_mDNSResponder && MACOSX_MDNS_MALLOC_DEBUGGING + // Don't use dnssd_InvalidSocket (-1) because that's the sentinel value MACOSX_MDNS_MALLOC_DEBUGGING uses + // for detecting when the memory for an object is inadvertently freed while the object is still on some list + req->sd = req->errsd = -2; +#else + req->sd = req->errsd = dnssd_InvalidSocket; +#endif + // We also set req->terminate to a bogus value so we know if abort_request() gets called again for this request + req->terminate = (req_termination_fn) ~0; +} + +mDNSlocal void AbortUnlinkAndFree(request_state *req) +{ + request_state **p = &all_requests; + abort_request(req); + while (*p && *p != req) p=&(*p)->next; + if (*p) { *p = req->next; freeL("request_state/AbortUnlinkAndFree", req); } + else LogMsg("AbortUnlinkAndFree: ERROR: Attempt to abort operation %p not in list", req); +} + +mDNSlocal reply_state *create_reply(const reply_op_t op, const size_t datalen, request_state *const request) +{ + reply_state *reply; + + if ((unsigned)datalen < sizeof(reply_hdr)) + { + LogMsg("ERROR: create_reply - data length less than length of required fields"); + return NULL; + } + + reply = mallocL("reply_state", sizeof(reply_state) + datalen - sizeof(reply_hdr)); + if (!reply) FatalError("ERROR: malloc"); + + reply->next = mDNSNULL; + reply->totallen = (mDNSu32)datalen + sizeof(ipc_msg_hdr); + reply->nwriten = 0; + + reply->mhdr->version = VERSION; + reply->mhdr->datalen = (mDNSu32)datalen; + reply->mhdr->ipc_flags = 0; + reply->mhdr->op = op; + reply->mhdr->client_context = request->hdr.client_context; + reply->mhdr->reg_index = 0; + + return reply; +} + +// Append a reply to the list in a request object +// If our request is sharing a connection, then we append our reply_state onto the primary's list +mDNSlocal void append_reply(request_state *req, reply_state *rep) +{ + request_state *r = req->primary ? req->primary : req; + reply_state **ptr = &r->replies; + while (*ptr) ptr = &(*ptr)->next; + *ptr = rep; + rep->next = NULL; +} + +// Generates a response message giving name, type, domain, plus interface index, +// suitable for a browse result or service registration result. +// On successful completion rep is set to point to a malloc'd reply_state struct +mDNSlocal mStatus GenerateNTDResponse(const domainname *const servicename, const mDNSInterfaceID id, + request_state *const request, reply_state **const rep, reply_op_t op, DNSServiceFlags flags, mStatus err) +{ + domainlabel name; + domainname type, dom; + *rep = NULL; + if (!DeconstructServiceName(servicename, &name, &type, &dom)) + return kDNSServiceErr_Invalid; + else + { + char namestr[MAX_DOMAIN_LABEL+1]; + char typestr[MAX_ESCAPED_DOMAIN_NAME]; + char domstr [MAX_ESCAPED_DOMAIN_NAME]; + int len; + char *data; + + ConvertDomainLabelToCString_unescaped(&name, namestr); + ConvertDomainNameToCString(&type, typestr); + ConvertDomainNameToCString(&dom, domstr); + + // Calculate reply data length + len = sizeof(DNSServiceFlags); + len += sizeof(mDNSu32); // if index + len += sizeof(DNSServiceErrorType); + len += (int) (strlen(namestr) + 1); + len += (int) (strlen(typestr) + 1); + len += (int) (strlen(domstr) + 1); + + // Build reply header + *rep = create_reply(op, len, request); + (*rep)->rhdr->flags = dnssd_htonl(flags); + (*rep)->rhdr->ifi = dnssd_htonl(mDNSPlatformInterfaceIndexfromInterfaceID(&mDNSStorage, id, mDNSfalse)); + (*rep)->rhdr->error = dnssd_htonl(err); + + // Build reply body + data = (char *)&(*rep)->rhdr[1]; + put_string(namestr, &data); + put_string(typestr, &data); + put_string(domstr, &data); + + return mStatus_NoError; + } +} + +// Special support to enable the DNSServiceBrowse call made by Bonjour Browser +// Remove after Bonjour Browser is updated to use DNSServiceQueryRecord instead of DNSServiceBrowse +mDNSlocal void GenerateBonjourBrowserResponse(const domainname *const servicename, const mDNSInterfaceID id, + request_state *const request, reply_state **const rep, reply_op_t op, DNSServiceFlags flags, mStatus err) +{ + char namestr[MAX_DOMAIN_LABEL+1]; + char typestr[MAX_ESCAPED_DOMAIN_NAME]; + static const char domstr[] = "."; + int len; + char *data; + + *rep = NULL; + + // 1. Put first label in namestr + ConvertDomainLabelToCString_unescaped((const domainlabel *)servicename, namestr); + + // 2. Put second label and "local" into typestr + mDNS_snprintf(typestr, sizeof(typestr), "%#s.local.", SecondLabel(servicename)); + + // Calculate reply data length + len = sizeof(DNSServiceFlags); + len += sizeof(mDNSu32); // if index + len += sizeof(DNSServiceErrorType); + len += (int) (strlen(namestr) + 1); + len += (int) (strlen(typestr) + 1); + len += (int) (strlen(domstr) + 1); + + // Build reply header + *rep = create_reply(op, len, request); + (*rep)->rhdr->flags = dnssd_htonl(flags); + (*rep)->rhdr->ifi = dnssd_htonl(mDNSPlatformInterfaceIndexfromInterfaceID(&mDNSStorage, id, mDNSfalse)); + (*rep)->rhdr->error = dnssd_htonl(err); + + // Build reply body + data = (char *)&(*rep)->rhdr[1]; + put_string(namestr, &data); + put_string(typestr, &data); + put_string(domstr, &data); +} + +// Returns a resource record (allocated w/ malloc) containing the data found in an IPC message +// Data must be in the following format: flags, interfaceIndex, name, rrtype, rrclass, rdlen, rdata, (optional) ttl +// (ttl only extracted/set if ttl argument is non-zero). Returns NULL for a bad-parameter error +mDNSlocal AuthRecord *read_rr_from_ipc_msg(request_state *request, int GetTTL, int validate_flags) +{ + DNSServiceFlags flags = get_flags(&request->msgptr, request->msgend); + mDNSu32 interfaceIndex = get_uint32(&request->msgptr, request->msgend); + char name[256]; + int str_err = get_string(&request->msgptr, request->msgend, name, sizeof(name)); + mDNSu16 type = get_uint16(&request->msgptr, request->msgend); + mDNSu16 class = get_uint16(&request->msgptr, request->msgend); + mDNSu16 rdlen = get_uint16(&request->msgptr, request->msgend); + const char *rdata = get_rdata (&request->msgptr, request->msgend, rdlen); + mDNSu32 ttl = GetTTL ? get_uint32(&request->msgptr, request->msgend) : 0; + int storage_size = rdlen > sizeof(RDataBody) ? rdlen : sizeof(RDataBody); + AuthRecord *rr; + mDNSInterfaceID InterfaceID; + AuthRecType artype; + + request->flags = flags; + + if (str_err) { LogMsg("ERROR: read_rr_from_ipc_msg - get_string"); return NULL; } + + if (!request->msgptr) { LogMsg("Error reading Resource Record from client"); return NULL; } + + if (validate_flags && + !((flags & kDNSServiceFlagsShared) == kDNSServiceFlagsShared) && + !((flags & kDNSServiceFlagsUnique) == kDNSServiceFlagsUnique)) + { + LogMsg("ERROR: Bad resource record flags (must be kDNSServiceFlagsShared or kDNSServiceFlagsUnique)"); + return NULL; + } + + rr = mallocL("AuthRecord/read_rr_from_ipc_msg", sizeof(AuthRecord) - sizeof(RDataBody) + storage_size); + if (!rr) FatalError("ERROR: malloc"); + + InterfaceID = mDNSPlatformInterfaceIDfromInterfaceIndex(&mDNSStorage, interfaceIndex); + if (InterfaceID == mDNSInterface_LocalOnly) + artype = AuthRecordLocalOnly; + else if (InterfaceID == mDNSInterface_P2P) + artype = AuthRecordP2P; + else if ((InterfaceID == mDNSInterface_Any) && (flags & kDNSServiceFlagsIncludeP2P) + && (flags & kDNSServiceFlagsIncludeAWDL)) + artype = AuthRecordAnyIncludeAWDLandP2P; + else if ((InterfaceID == mDNSInterface_Any) && (flags & kDNSServiceFlagsIncludeP2P)) + artype = AuthRecordAnyIncludeP2P; + else if ((InterfaceID == mDNSInterface_Any) && (flags & kDNSServiceFlagsIncludeAWDL)) + artype = AuthRecordAnyIncludeAWDL; + else + artype = AuthRecordAny; + + mDNS_SetupResourceRecord(rr, mDNSNULL, InterfaceID, type, 0, + (mDNSu8) ((flags & kDNSServiceFlagsShared) ? kDNSRecordTypeShared : kDNSRecordTypeUnique), artype, mDNSNULL, mDNSNULL); + + if (!MakeDomainNameFromDNSNameString(&rr->namestorage, name)) + { + LogMsg("ERROR: bad name: %s", name); + freeL("AuthRecord/read_rr_from_ipc_msg", rr); + return NULL; + } + + if (flags & kDNSServiceFlagsAllowRemoteQuery) rr->AllowRemoteQuery = mDNStrue; + rr->resrec.rrclass = class; + rr->resrec.rdlength = rdlen; + rr->resrec.rdata->MaxRDLength = rdlen; + mDNSPlatformMemCopy(rr->resrec.rdata->u.data, rdata, rdlen); + if (GetTTL) rr->resrec.rroriginalttl = ttl; + rr->resrec.namehash = DomainNameHashValue(rr->resrec.name); + SetNewRData(&rr->resrec, mDNSNULL, 0); // Sets rr->rdatahash for us + return rr; +} + +mDNSlocal int build_domainname_from_strings(domainname *srv, char *name, char *regtype, char *domain) +{ + domainlabel n; + domainname d, t; + + if (!MakeDomainLabelFromLiteralString(&n, name)) return -1; + if (!MakeDomainNameFromDNSNameString(&t, regtype)) return -1; + if (!MakeDomainNameFromDNSNameString(&d, domain)) return -1; + if (!ConstructServiceName(srv, &n, &t, &d)) return -1; + return 0; +} + +mDNSlocal void send_all(dnssd_sock_t s, const char *ptr, int len) +{ + int n = send(s, ptr, len, 0); + // On a freshly-created Unix Domain Socket, the kernel should *never* fail to buffer a small write for us + // (four bytes for a typical error code return, 12 bytes for DNSServiceGetProperty(DaemonVersion)). + // If it does fail, we don't attempt to handle this failure, but we do log it so we know something is wrong. + if (n < len) + LogMsg("ERROR: send_all(%d) wrote %d of %d errno %d (%s)", + s, n, len, dnssd_errno, dnssd_strerror(dnssd_errno)); +} + +#if 0 +mDNSlocal mDNSBool AuthorizedDomain(const request_state * const request, const domainname * const d, const DNameListElem * const doms) +{ + const DNameListElem *delem = mDNSNULL; + int bestDelta = -1; // the delta of the best match, lower is better + int dLabels = 0; + mDNSBool allow = mDNSfalse; + + if (SystemUID(request->uid)) return mDNStrue; + + dLabels = CountLabels(d); + for (delem = doms; delem; delem = delem->next) + { + if (delem->uid) + { + int delemLabels = CountLabels(&delem->name); + int delta = dLabels - delemLabels; + if ((bestDelta == -1 || delta <= bestDelta) && SameDomainName(&delem->name, SkipLeadingLabels(d, delta))) + { + bestDelta = delta; + allow = (allow || (delem->uid == request->uid)); + } + } + } + + return bestDelta == -1 ? mDNStrue : allow; +} +#endif + +// *************************************************************************** +#if COMPILER_LIKES_PRAGMA_MARK +#pragma mark - +#pragma mark - external helpers +#endif + +mDNSlocal mDNSBool callExternalHelpers(mDNSInterfaceID InterfaceID, const domainname *const domain, DNSServiceFlags flags) +{ +#if APPLE_OSX_mDNSResponder + + if ( ((InterfaceID == mDNSInterface_Any) && (flags & (kDNSServiceFlagsIncludeP2P | kDNSServiceFlagsIncludeAWDL)) && IsLocalDomain(domain)) + || mDNSPlatformInterfaceIsD2D(InterfaceID)) + { + return mDNStrue; + } + else + return mDNSfalse; + +#else + (void) InterfaceID; + (void) domain; + (void) flags; + + return mDNSfalse; +#endif // APPLE_OSX_mDNSResponder +} + +mDNSlocal void external_start_advertising_helper(service_instance *const instance) +{ + AuthRecord *st = instance->subtypes; + ExtraResourceRecord *e; + int i; + + if (mDNSIPPortIsZero(instance->request->u.servicereg.port)) + { + LogInfo("external_start_advertising_helper: Not registering service with port number zero"); + return; + } + + if (instance->external_advertise) LogMsg("external_start_advertising_helper: external_advertise already set!"); + + for ( i = 0; i < instance->request->u.servicereg.num_subtypes; i++) + external_start_advertising_service(&st[i].resrec, instance->request->flags); + + external_start_advertising_service(&instance->srs.RR_PTR.resrec, instance->request->flags); + external_start_advertising_service(&instance->srs.RR_SRV.resrec, instance->request->flags); + external_start_advertising_service(&instance->srs.RR_TXT.resrec, instance->request->flags); + + for (e = instance->srs.Extras; e; e = e->next) + external_start_advertising_service(&e->r.resrec, instance->request->flags); + + instance->external_advertise = mDNStrue; +} + +mDNSlocal void external_stop_advertising_helper(service_instance *const instance) +{ + AuthRecord *st = instance->subtypes; + ExtraResourceRecord *e; + int i; + + if (!instance->external_advertise) return; + + LogInfo("external_stop_advertising_helper: calling external_stop_advertising_service"); + + for ( i = 0; i < instance->request->u.servicereg.num_subtypes; i++) + external_stop_advertising_service(&st[i].resrec, instance->request->flags); + + external_stop_advertising_service(&instance->srs.RR_PTR.resrec, instance->request->flags); + external_stop_advertising_service(&instance->srs.RR_SRV.resrec, instance->request->flags); + external_stop_advertising_service(&instance->srs.RR_TXT.resrec, instance->request->flags); + + for (e = instance->srs.Extras; e; e = e->next) + external_stop_advertising_service(&e->r.resrec, instance->request->flags); + + instance->external_advertise = mDNSfalse; +} + +// *************************************************************************** +#if COMPILER_LIKES_PRAGMA_MARK +#pragma mark - +#pragma mark - DNSServiceRegister +#endif + +mDNSexport void FreeExtraRR(mDNS *const m, AuthRecord *const rr, mStatus result) +{ + ExtraResourceRecord *extra = (ExtraResourceRecord *)rr->RecordContext; + (void)m; // Unused + + if (result != mStatus_MemFree) { LogMsg("Error: FreeExtraRR invoked with unexpected error %d", result); return; } + + LogInfo(" FreeExtraRR %s", RRDisplayString(m, &rr->resrec)); + + if (rr->resrec.rdata != &rr->rdatastorage) + freeL("Extra RData", rr->resrec.rdata); + freeL("ExtraResourceRecord/FreeExtraRR", extra); +} + +mDNSlocal void unlink_and_free_service_instance(service_instance *srv) +{ + ExtraResourceRecord *e = srv->srs.Extras, *tmp; + + external_stop_advertising_helper(srv); + + // clear pointers from parent struct + if (srv->request) + { + service_instance **p = &srv->request->u.servicereg.instances; + while (*p) + { + if (*p == srv) { *p = (*p)->next; break; } + p = &(*p)->next; + } + } + + while (e) + { + e->r.RecordContext = e; + tmp = e; + e = e->next; + FreeExtraRR(&mDNSStorage, &tmp->r, mStatus_MemFree); + } + + if (srv->srs.RR_TXT.resrec.rdata != &srv->srs.RR_TXT.rdatastorage) + freeL("TXT RData", srv->srs.RR_TXT.resrec.rdata); + + if (srv->subtypes) + { + freeL("ServiceSubTypes", srv->subtypes); + srv->subtypes = NULL; + } + if (srv->srs.AnonData) + { + freeL("Anonymous", (void *)srv->srs.AnonData); + srv->srs.AnonData = NULL; + } + freeL("service_instance", srv); +} + +// Count how many other service records we have locally with the same name, but different rdata. +// For auto-named services, we can have at most one per machine -- if we allowed two auto-named services of +// the same type on the same machine, we'd get into an infinite autoimmune-response loop of continuous renaming. +mDNSexport int CountPeerRegistrations(mDNS *const m, ServiceRecordSet *const srs) +{ + int count = 0; + ResourceRecord *r = &srs->RR_SRV.resrec; + AuthRecord *rr; + + for (rr = m->ResourceRecords; rr; rr=rr->next) + if (rr->resrec.rrtype == kDNSType_SRV && SameDomainName(rr->resrec.name, r->name) && !IdenticalSameNameRecord(&rr->resrec, r)) + count++; + + verbosedebugf("%d peer registrations for %##s", count, r->name->c); + return(count); +} + +mDNSexport int CountExistingRegistrations(domainname *srv, mDNSIPPort port) +{ + int count = 0; + AuthRecord *rr; + for (rr = mDNSStorage.ResourceRecords; rr; rr=rr->next) + if (rr->resrec.rrtype == kDNSType_SRV && + mDNSSameIPPort(rr->resrec.rdata->u.srv.port, port) && + SameDomainName(rr->resrec.name, srv)) + count++; + return(count); +} + +mDNSlocal void SendServiceRemovalNotification(ServiceRecordSet *const srs) +{ + reply_state *rep; + service_instance *instance = srs->ServiceContext; + if (GenerateNTDResponse(srs->RR_SRV.resrec.name, srs->RR_SRV.resrec.InterfaceID, instance->request, &rep, reg_service_reply_op, 0, mStatus_NoError) != mStatus_NoError) + LogMsg("%3d: SendServiceRemovalNotification: %##s is not valid DNS-SD SRV name", instance->request->sd, srs->RR_SRV.resrec.name->c); + else { append_reply(instance->request, rep); instance->clientnotified = mDNSfalse; } +} + +// service registration callback performs three duties - frees memory for deregistered services, +// handles name conflicts, and delivers completed registration information to the client +mDNSlocal void regservice_callback(mDNS *const m, ServiceRecordSet *const srs, mStatus result) +{ + mStatus err; + mDNSBool SuppressError = mDNSfalse; + service_instance *instance; + reply_state *rep; + (void)m; // Unused + + if (!srs) { LogMsg("regservice_callback: srs is NULL %d", result); return; } + + instance = srs->ServiceContext; + if (!instance) { LogMsg("regservice_callback: srs->ServiceContext is NULL %d", result); return; } + + // don't send errors up to client for wide-area, empty-string registrations + if (instance->request && + instance->request->u.servicereg.default_domain && + !instance->default_local) + SuppressError = mDNStrue; + + if (mDNS_LoggingEnabled) + { + const char *const fmt = + (result == mStatus_NoError) ? "%s DNSServiceRegister(%##s, %u) REGISTERED" : + (result == mStatus_MemFree) ? "%s DNSServiceRegister(%##s, %u) DEREGISTERED" : + (result == mStatus_NameConflict) ? "%s DNSServiceRegister(%##s, %u) NAME CONFLICT" : + "%s DNSServiceRegister(%##s, %u) %s %d"; + char prefix[16] = "---:"; + if (instance->request) mDNS_snprintf(prefix, sizeof(prefix), "%3d:", instance->request->sd); + LogOperation(fmt, prefix, srs->RR_SRV.resrec.name->c, mDNSVal16(srs->RR_SRV.resrec.rdata->u.srv.port), + SuppressError ? "suppressed error" : "CALLBACK", result); + } + + if (!instance->request && result != mStatus_MemFree) { LogMsg("regservice_callback: instance->request is NULL %d", result); return; } + + if (result == mStatus_NoError) + { + if (instance->request->u.servicereg.allowremotequery) + { + ExtraResourceRecord *e; + srs->RR_ADV.AllowRemoteQuery = mDNStrue; + srs->RR_PTR.AllowRemoteQuery = mDNStrue; + srs->RR_SRV.AllowRemoteQuery = mDNStrue; + srs->RR_TXT.AllowRemoteQuery = mDNStrue; + for (e = instance->srs.Extras; e; e = e->next) e->r.AllowRemoteQuery = mDNStrue; + } + + if (GenerateNTDResponse(srs->RR_SRV.resrec.name, srs->RR_SRV.resrec.InterfaceID, instance->request, &rep, reg_service_reply_op, kDNSServiceFlagsAdd, result) != mStatus_NoError) + LogMsg("%3d: regservice_callback: %##s is not valid DNS-SD SRV name", instance->request->sd, srs->RR_SRV.resrec.name->c); + else { append_reply(instance->request, rep); instance->clientnotified = mDNStrue; } + + if (callExternalHelpers(instance->request->u.servicereg.InterfaceID, &instance->domain, instance->request->flags)) + { + LogInfo("regservice_callback: calling external_start_advertising_helper()"); + external_start_advertising_helper(instance); + } + if (instance->request->u.servicereg.autoname && CountPeerRegistrations(m, srs) == 0) + RecordUpdatedNiceLabel(m, 0); // Successfully got new name, tell user immediately + } + else if (result == mStatus_MemFree) + { + if (instance->request && instance->renameonmemfree) + { + external_stop_advertising_helper(instance); + instance->renameonmemfree = 0; + err = mDNS_RenameAndReregisterService(m, srs, &instance->request->u.servicereg.name); + if (err) LogMsg("ERROR: regservice_callback - RenameAndReregisterService returned %d", err); + // error should never happen - safest to log and continue + } + else + unlink_and_free_service_instance(instance); + } + else if (result == mStatus_NameConflict) + { + if (instance->request->u.servicereg.autorename) + { + external_stop_advertising_helper(instance); + if (instance->request->u.servicereg.autoname && CountPeerRegistrations(m, srs) == 0) + { + // On conflict for an autoname service, rename and reregister *all* autoname services + IncrementLabelSuffix(&m->nicelabel, mDNStrue); + mDNS_ConfigChanged(m); // Will call back into udsserver_handle_configchange() + } + else // On conflict for a non-autoname service, rename and reregister just that one service + { + if (instance->clientnotified) SendServiceRemovalNotification(srs); + mDNS_RenameAndReregisterService(m, srs, mDNSNULL); + } + } + else + { + if (!SuppressError) + { + if (GenerateNTDResponse(srs->RR_SRV.resrec.name, srs->RR_SRV.resrec.InterfaceID, instance->request, &rep, reg_service_reply_op, kDNSServiceFlagsAdd, result) != mStatus_NoError) + LogMsg("%3d: regservice_callback: %##s is not valid DNS-SD SRV name", instance->request->sd, srs->RR_SRV.resrec.name->c); + else { append_reply(instance->request, rep); instance->clientnotified = mDNStrue; } + } + unlink_and_free_service_instance(instance); + } + } + else // Not mStatus_NoError, mStatus_MemFree, or mStatus_NameConflict + { + if (!SuppressError) + { + if (GenerateNTDResponse(srs->RR_SRV.resrec.name, srs->RR_SRV.resrec.InterfaceID, instance->request, &rep, reg_service_reply_op, kDNSServiceFlagsAdd, result) != mStatus_NoError) + LogMsg("%3d: regservice_callback: %##s is not valid DNS-SD SRV name", instance->request->sd, srs->RR_SRV.resrec.name->c); + else { append_reply(instance->request, rep); instance->clientnotified = mDNStrue; } + } + } +} + +mDNSlocal void regrecord_callback(mDNS *const m, AuthRecord *rr, mStatus result) +{ + (void)m; // Unused + if (!rr->RecordContext) // parent struct already freed by termination callback + { + if (result == mStatus_NoError) + LogMsg("Error: regrecord_callback: successful registration of orphaned record %s", ARDisplayString(m, rr)); + else + { + if (result != mStatus_MemFree) LogMsg("regrecord_callback: error %d received after parent termination", result); + + // We come here when the record is being deregistered either from DNSServiceRemoveRecord or connection_termination. + // If the record has been updated, we need to free the rdata. Everytime we call mDNS_Update, it calls update_callback + // with the old rdata (so that we can free it) and stores the new rdata in "rr->resrec.rdata". This means, we need + // to free the latest rdata for which the update_callback was never called with. + if (rr->resrec.rdata != &rr->rdatastorage) freeL("RData/regrecord_callback", rr->resrec.rdata); + freeL("AuthRecord/regrecord_callback", rr); + } + } + else + { + registered_record_entry *re = rr->RecordContext; + request_state *request = re->request; + + if (mDNS_LoggingEnabled) + { + char *fmt = (result == mStatus_NoError) ? "%3d: DNSServiceRegisterRecord(%u %s) REGISTERED" : + (result == mStatus_MemFree) ? "%3d: DNSServiceRegisterRecord(%u %s) DEREGISTERED" : + (result == mStatus_NameConflict) ? "%3d: DNSServiceRegisterRecord(%u %s) NAME CONFLICT" : + "%3d: DNSServiceRegisterRecord(%u %s) %d"; + LogOperation(fmt, request->sd, re->key, RRDisplayString(m, &rr->resrec), result); + } + + if (result != mStatus_MemFree) + { + int len = sizeof(DNSServiceFlags) + sizeof(mDNSu32) + sizeof(DNSServiceErrorType); + reply_state *reply = create_reply(reg_record_reply_op, len, request); + reply->mhdr->client_context = re->regrec_client_context; + reply->rhdr->flags = dnssd_htonl(0); + reply->rhdr->ifi = dnssd_htonl(mDNSPlatformInterfaceIndexfromInterfaceID(m, rr->resrec.InterfaceID, mDNSfalse)); + reply->rhdr->error = dnssd_htonl(result); + append_reply(request, reply); + } + + if (result) + { + // If this is a callback to a keepalive record, do not free it. + if (result == mStatus_BadStateErr) + { + LogInfo("regrecord_callback: Callback with error code mStatus_BadStateErr - not freeing the record."); + } + else + { + // unlink from list, free memory + registered_record_entry **ptr = &request->u.reg_recs; + while (*ptr && (*ptr) != re) ptr = &(*ptr)->next; + if (!*ptr) { LogMsg("regrecord_callback - record not in list!"); return; } + *ptr = (*ptr)->next; + freeL("registered_record_entry AuthRecord regrecord_callback", re->rr); + freeL("registered_record_entry regrecord_callback", re); + } + } + else + { + if (re->external_advertise) LogMsg("regrecord_callback: external_advertise already set!"); + + if (callExternalHelpers(re->origInterfaceID, &rr->namestorage, request->flags)) + { + LogInfo("regrecord_callback: calling external_start_advertising_service"); + external_start_advertising_service(&rr->resrec, request->flags); + re->external_advertise = mDNStrue; + } + } + } +} + +// set_peer_pid() is called after mem is allocated for each new request in NewRequest() +// This accounts for 2 places (connect_callback, request_callback) +mDNSlocal void set_peer_pid(request_state *request) +{ + pid_t p = (pid_t) -1; + socklen_t len = sizeof(p); + request->pid_name[0] = '\0'; + request->process_id = -1; +#ifdef LOCAL_PEERPID + if (request->sd < 0) + return; + // to extract the pid value + if (getsockopt(request->sd, SOL_LOCAL, LOCAL_PEERPID, &p, &len) != 0) + return; + // to extract the process name from the pid value + if (proc_pidinfo(p, PROC_PIDT_SHORTBSDINFO, 1, &proc, PROC_PIDT_SHORTBSDINFO_SIZE) == 0) + return; + mDNSPlatformStrCopy(request->pid_name, proc.pbsi_comm); + request->process_id = p; +#else // !LOCAL_PEERPID + len = 0; + if (request->sd < 0) + return; + LogInfo("set_peer_pid: Not Supported on this version of OS"); +#endif // LOCAL_PEERPID +} + +mDNSlocal void connection_termination(request_state *request) +{ + // When terminating a shared connection, we need to scan the all_requests list + // and terminate any subbordinate operations sharing this file descriptor + request_state **req = &all_requests; + + LogOperation("%3d: DNSServiceCreateConnection STOP PID[%d](%s)", request->sd, request->process_id, request->pid_name); + + while (*req) + { + if ((*req)->primary == request) + { + // Since we're already doing a list traversal, we unlink the request directly instead of using AbortUnlinkAndFree() + request_state *tmp = *req; + if (tmp->primary == tmp) LogMsg("connection_termination ERROR (*req)->primary == *req for %p %d", tmp, tmp->sd); + if (tmp->replies) LogMsg("connection_termination ERROR How can subordinate req %p %d have replies queued?", tmp, tmp->sd); + abort_request(tmp); + *req = tmp->next; + freeL("request_state/connection_termination", tmp); + } + else + req = &(*req)->next; + } + + while (request->u.reg_recs) + { + registered_record_entry *ptr = request->u.reg_recs; + LogOperation("%3d: DNSServiceRegisterRecord(%u %s) STOP PID[%d](%s)", request->sd, ptr->key, RRDisplayString(&mDNSStorage, &ptr->rr->resrec), request->process_id, request->pid_name); + request->u.reg_recs = request->u.reg_recs->next; + ptr->rr->RecordContext = NULL; + if (ptr->external_advertise) + { + ptr->external_advertise = mDNSfalse; + external_stop_advertising_service(&ptr->rr->resrec, request->flags); + } + LogMcastS(&mDNSStorage, ptr->rr, request, reg_stop); + mDNS_Deregister(&mDNSStorage, ptr->rr); // Will free ptr->rr for us + freeL("registered_record_entry/connection_termination", ptr); + } +} + +mDNSlocal void handle_cancel_request(request_state *request) +{ + request_state **req = &all_requests; + LogOperation("%3d: Cancel %08X %08X", request->sd, request->hdr.client_context.u32[1], request->hdr.client_context.u32[0]); + while (*req) + { + if ((*req)->primary == request && + (*req)->hdr.client_context.u32[0] == request->hdr.client_context.u32[0] && + (*req)->hdr.client_context.u32[1] == request->hdr.client_context.u32[1]) + { + // Since we're already doing a list traversal, we unlink the request directly instead of using AbortUnlinkAndFree() + request_state *tmp = *req; + abort_request(tmp); + *req = tmp->next; + freeL("request_state/handle_cancel_request", tmp); + } + else + req = &(*req)->next; + } +} + +mDNSlocal mStatus handle_regrecord_request(request_state *request) +{ + mStatus err = mStatus_BadParamErr; + AuthRecord *rr = read_rr_from_ipc_msg(request, 1, 1); + if (rr) + { + registered_record_entry *re; + // Don't allow non-local domains to be regsitered as LocalOnly. Allowing this would permit + // clients to register records such as www.bigbank.com A w.x.y.z to redirect Safari. + if (rr->resrec.InterfaceID == mDNSInterface_LocalOnly && !IsLocalDomain(rr->resrec.name) && + rr->resrec.rrclass == kDNSClass_IN && (rr->resrec.rrtype == kDNSType_A || rr->resrec.rrtype == kDNSType_AAAA || + rr->resrec.rrtype == kDNSType_CNAME)) + { + freeL("AuthRecord/handle_regrecord_request", rr); + return (mStatus_BadParamErr); + } + // allocate registration entry, link into list + re = mallocL("registered_record_entry", sizeof(registered_record_entry)); + if (!re) + FatalError("ERROR: malloc"); + re->key = request->hdr.reg_index; + re->rr = rr; + re->regrec_client_context = request->hdr.client_context; + re->request = request; + re->external_advertise = mDNSfalse; + rr->RecordContext = re; + rr->RecordCallback = regrecord_callback; + + re->origInterfaceID = rr->resrec.InterfaceID; + if (rr->resrec.InterfaceID == mDNSInterface_P2P) + rr->resrec.InterfaceID = mDNSInterface_Any; +#if 0 + if (!AuthorizedDomain(request, rr->resrec.name, AutoRegistrationDomains)) return (mStatus_NoError); +#endif + if (rr->resrec.rroriginalttl == 0) + rr->resrec.rroriginalttl = DefaultTTLforRRType(rr->resrec.rrtype); + + LogOperation("%3d: DNSServiceRegisterRecord(%u %s) START PID[%d](%s)", request->sd, re->key, RRDisplayString(&mDNSStorage, &rr->resrec), + request->process_id, request->pid_name); + + err = mDNS_Register(&mDNSStorage, rr); + if (err) + { + LogOperation("%3d: DNSServiceRegisterRecord(%u %s) ERROR (%d)", request->sd, re->key, RRDisplayString(&mDNSStorage, &rr->resrec), err); + freeL("registered_record_entry", re); + freeL("registered_record_entry/AuthRecord", rr); + } + else + { + LogMcastS(&mDNSStorage, rr, request, reg_start); + re->next = request->u.reg_recs; + request->u.reg_recs = re; + } + } + return(err); +} + +mDNSlocal void UpdateDeviceInfoRecord(mDNS *const m); + +mDNSlocal void regservice_termination_callback(request_state *request) +{ + if (!request) + { + LogMsg("regservice_termination_callback context is NULL"); + return; + } + while (request->u.servicereg.instances) + { + service_instance *p = request->u.servicereg.instances; + request->u.servicereg.instances = request->u.servicereg.instances->next; + // only safe to free memory if registration is not valid, i.e. deregister fails (which invalidates p) + LogOperation("%3d: DNSServiceRegister(%##s, %u) STOP PID[%d](%s)", request->sd, p->srs.RR_SRV.resrec.name->c, + mDNSVal16(p->srs.RR_SRV.resrec.rdata->u.srv.port), request->process_id, request->pid_name); + + external_stop_advertising_helper(p); + + // Clear backpointer *before* calling mDNS_DeregisterService/unlink_and_free_service_instance + // We don't need unlink_and_free_service_instance to cut its element from the list, because we're already advancing + // request->u.servicereg.instances as we work our way through the list, implicitly cutting one element at a time + // We can't clear p->request *after* the calling mDNS_DeregisterService/unlink_and_free_service_instance + // because by then we might have already freed p + p->request = NULL; + LogMcastS(&mDNSStorage, &p->srs.RR_SRV, request, reg_stop); + if (mDNS_DeregisterService(&mDNSStorage, &p->srs)) + { + unlink_and_free_service_instance(p); + // Don't touch service_instance *p after this -- it's likely to have been freed already + } + } + if (request->u.servicereg.txtdata) + { + freeL("service_info txtdata", request->u.servicereg.txtdata); + request->u.servicereg.txtdata = NULL; + } + if (request->u.servicereg.autoname) + { + // Clear autoname before calling UpdateDeviceInfoRecord() so it doesn't mistakenly include this in its count of active autoname registrations + request->u.servicereg.autoname = mDNSfalse; + UpdateDeviceInfoRecord(&mDNSStorage); + } +} + +mDNSlocal request_state *LocateSubordinateRequest(request_state *request) +{ + request_state *req; + for (req = all_requests; req; req = req->next) + if (req->primary == request && + req->hdr.client_context.u32[0] == request->hdr.client_context.u32[0] && + req->hdr.client_context.u32[1] == request->hdr.client_context.u32[1]) return(req); + return(request); +} + +mDNSlocal mStatus add_record_to_service(request_state *request, service_instance *instance, mDNSu16 rrtype, mDNSu16 rdlen, const char *rdata, mDNSu32 ttl) +{ + ServiceRecordSet *srs = &instance->srs; + mStatus result; + mDNSu32 coreFlags = 0; // translate to corresponding mDNSCore flag definitions + int size = rdlen > sizeof(RDataBody) ? rdlen : sizeof(RDataBody); + ExtraResourceRecord *extra = mallocL("ExtraResourceRecord", sizeof(*extra) - sizeof(RDataBody) + size); + if (!extra) { my_perror("ERROR: malloc"); return mStatus_NoMemoryErr; } + + mDNSPlatformMemZero(extra, sizeof(ExtraResourceRecord)); // OK if oversized rdata not zero'd + extra->r.resrec.rrtype = rrtype; + extra->r.rdatastorage.MaxRDLength = (mDNSu16) size; + extra->r.resrec.rdlength = rdlen; + mDNSPlatformMemCopy(&extra->r.rdatastorage.u.data, rdata, rdlen); + // use InterfaceID value from DNSServiceRegister() call that created the original service + extra->r.resrec.InterfaceID = request->u.servicereg.InterfaceID; + + if (request->flags & kDNSServiceFlagsIncludeP2P) + coreFlags |= coreFlagIncludeP2P; + if (request->flags & kDNSServiceFlagsIncludeAWDL) + coreFlags |= coreFlagIncludeAWDL; + + result = mDNS_AddRecordToService(&mDNSStorage, srs, extra, &extra->r.rdatastorage, ttl, coreFlags); + if (result) + { + freeL("ExtraResourceRecord/add_record_to_service", extra); + return result; + } + LogMcastS(&mDNSStorage, &srs->RR_PTR, request, reg_start); + + extra->ClientID = request->hdr.reg_index; + if ( instance->external_advertise + && callExternalHelpers(request->u.servicereg.InterfaceID, &instance->domain, request->flags)) + { + LogInfo("add_record_to_service: calling external_start_advertising_service"); + external_start_advertising_service(&extra->r.resrec, request->flags); + } + return result; +} + +mDNSlocal mStatus handle_add_request(request_state *request) +{ + service_instance *i; + mStatus result = mStatus_UnknownErr; + DNSServiceFlags flags = get_flags (&request->msgptr, request->msgend); + mDNSu16 rrtype = get_uint16(&request->msgptr, request->msgend); + mDNSu16 rdlen = get_uint16(&request->msgptr, request->msgend); + const char *rdata = get_rdata (&request->msgptr, request->msgend, rdlen); + mDNSu32 ttl = get_uint32(&request->msgptr, request->msgend); + if (!ttl) ttl = DefaultTTLforRRType(rrtype); + (void)flags; // Unused + + if (!request->msgptr) { LogMsg("%3d: DNSServiceAddRecord(unreadable parameters)", request->sd); return(mStatus_BadParamErr); } + + // If this is a shared connection, check if the operation actually applies to a subordinate request_state object + if (request->terminate == connection_termination) request = LocateSubordinateRequest(request); + + if (request->terminate != regservice_termination_callback) + { LogMsg("%3d: DNSServiceAddRecord(not a registered service ref)", request->sd); return(mStatus_BadParamErr); } + + // For a service registered with zero port, don't allow adding records. This mostly happens due to a bug + // in the application. See radar://9165807. + if (mDNSIPPortIsZero(request->u.servicereg.port)) + { LogMsg("%3d: DNSServiceAddRecord: adding record to a service registered with zero port", request->sd); return(mStatus_BadParamErr); } + + LogOperation("%3d: DNSServiceAddRecord(%X, %##s, %s, %d)", request->sd, flags, + (request->u.servicereg.instances) ? request->u.servicereg.instances->srs.RR_SRV.resrec.name->c : NULL, DNSTypeName(rrtype), rdlen); + + for (i = request->u.servicereg.instances; i; i = i->next) + { + result = add_record_to_service(request, i, rrtype, rdlen, rdata, ttl); + if (result && i->default_local) break; + else result = mStatus_NoError; // suppress non-local default errors + } + + return(result); +} + +mDNSlocal void update_callback(mDNS *const m, AuthRecord *const rr, RData *oldrd, mDNSu16 oldrdlen) +{ + mDNSBool external_advertise = (rr->UpdateContext) ? *((mDNSBool *)rr->UpdateContext) : mDNSfalse; + (void)m; // Unused + + // There are three cases. + // + // 1. We have updated the primary TXT record of the service + // 2. We have updated the TXT record that was added to the service using DNSServiceAddRecord + // 3. We have updated the TXT record that was registered using DNSServiceRegisterRecord + // + // external_advertise is set if we have advertised at least once during the initial addition + // of the record in all of the three cases above. We should have checked for InterfaceID/LocalDomain + // checks during the first time and hence we don't do any checks here + if (external_advertise) + { + ResourceRecord ext = rr->resrec; + DNSServiceFlags flags = 0; + + // Since we don't have a copy of the flags value used when the record was registered, + // we'll have to derive it from the ARType field. + if (rr->ARType == AuthRecordAnyIncludeP2P) + flags |= kDNSServiceFlagsIncludeP2P; + else if (rr->ARType == AuthRecordAnyIncludeAWDL) + flags |= kDNSServiceFlagsIncludeAWDL; + + if (ext.rdlength == oldrdlen && mDNSPlatformMemSame(&ext.rdata->u, &oldrd->u, oldrdlen)) goto exit; + SetNewRData(&ext, oldrd, oldrdlen); + external_stop_advertising_service(&ext, flags); + LogInfo("update_callback: calling external_start_advertising_service"); + external_start_advertising_service(&rr->resrec, flags); + } +exit: + if (oldrd != &rr->rdatastorage) freeL("RData/update_callback", oldrd); +} + +mDNSlocal mStatus update_record(AuthRecord *rr, mDNSu16 rdlen, const char *rdata, mDNSu32 ttl, const mDNSBool *const external_advertise) +{ + mStatus result; + const int rdsize = rdlen > sizeof(RDataBody) ? rdlen : sizeof(RDataBody); + RData *newrd = mallocL("RData/update_record", sizeof(RData) - sizeof(RDataBody) + rdsize); + if (!newrd) FatalError("ERROR: malloc"); + newrd->MaxRDLength = (mDNSu16) rdsize; + mDNSPlatformMemCopy(&newrd->u, rdata, rdlen); + + // BIND named (name daemon) doesn't allow TXT records with zero-length rdata. This is strictly speaking correct, + // since RFC 1035 specifies a TXT record as "One or more <character-string>s", not "Zero or more <character-string>s". + // Since some legacy apps try to create zero-length TXT records, we'll silently correct it here. + if (rr->resrec.rrtype == kDNSType_TXT && rdlen == 0) { rdlen = 1; newrd->u.txt.c[0] = 0; } + + if (external_advertise) rr->UpdateContext = (void *)external_advertise; + + result = mDNS_Update(&mDNSStorage, rr, ttl, rdlen, newrd, update_callback); + if (result) { LogMsg("update_record: Error %d for %s", (int)result, ARDisplayString(&mDNSStorage, rr)); freeL("RData/update_record", newrd); } + return result; +} + +mDNSlocal mStatus handle_update_request(request_state *request) +{ + const ipc_msg_hdr *const hdr = &request->hdr; + mStatus result = mStatus_BadReferenceErr; + service_instance *i; + AuthRecord *rr = NULL; + + // get the message data + DNSServiceFlags flags = get_flags (&request->msgptr, request->msgend); // flags unused + mDNSu16 rdlen = get_uint16(&request->msgptr, request->msgend); + const char *rdata = get_rdata (&request->msgptr, request->msgend, rdlen); + mDNSu32 ttl = get_uint32(&request->msgptr, request->msgend); + (void)flags; // Unused + + if (!request->msgptr) { LogMsg("%3d: DNSServiceUpdateRecord(unreadable parameters)", request->sd); return(mStatus_BadParamErr); } + + // If this is a shared connection, check if the operation actually applies to a subordinate request_state object + if (request->terminate == connection_termination) request = LocateSubordinateRequest(request); + + if (request->terminate == connection_termination) + { + // update an individually registered record + registered_record_entry *reptr; + for (reptr = request->u.reg_recs; reptr; reptr = reptr->next) + { + if (reptr->key == hdr->reg_index) + { + result = update_record(reptr->rr, rdlen, rdata, ttl, &reptr->external_advertise); + LogOperation("%3d: DNSServiceUpdateRecord(%##s, %s)", + request->sd, reptr->rr->resrec.name->c, reptr->rr ? DNSTypeName(reptr->rr->resrec.rrtype) : "<NONE>"); + goto end; + } + } + result = mStatus_BadReferenceErr; + goto end; + } + + if (request->terminate != regservice_termination_callback) + { LogMsg("%3d: DNSServiceUpdateRecord(not a registered service ref)", request->sd); return(mStatus_BadParamErr); } + + // For a service registered with zero port, only SRV record is initialized. Don't allow any updates. + if (mDNSIPPortIsZero(request->u.servicereg.port)) + { LogMsg("%3d: DNSServiceUpdateRecord: updating the record of a service registered with zero port", request->sd); return(mStatus_BadParamErr); } + + // update the saved off TXT data for the service + if (hdr->reg_index == TXT_RECORD_INDEX) + { + if (request->u.servicereg.txtdata) + { freeL("service_info txtdata", request->u.servicereg.txtdata); request->u.servicereg.txtdata = NULL; } + if (rdlen > 0) + { + request->u.servicereg.txtdata = mallocL("service_info txtdata", rdlen); + if (!request->u.servicereg.txtdata) FatalError("ERROR: handle_update_request - malloc"); + mDNSPlatformMemCopy(request->u.servicereg.txtdata, rdata, rdlen); + } + request->u.servicereg.txtlen = rdlen; + } + + // update a record from a service record set + for (i = request->u.servicereg.instances; i; i = i->next) + { + if (hdr->reg_index == TXT_RECORD_INDEX) rr = &i->srs.RR_TXT; + else + { + ExtraResourceRecord *e; + for (e = i->srs.Extras; e; e = e->next) + if (e->ClientID == hdr->reg_index) { rr = &e->r; break; } + } + + if (!rr) { result = mStatus_BadReferenceErr; goto end; } + result = update_record(rr, rdlen, rdata, ttl, &i->external_advertise); + if (result && i->default_local) goto end; + else result = mStatus_NoError; // suppress non-local default errors + } + +end: + if (request->terminate == regservice_termination_callback) + LogOperation("%3d: DNSServiceUpdateRecord(%##s, %s)", request->sd, + (request->u.servicereg.instances) ? request->u.servicereg.instances->srs.RR_SRV.resrec.name->c : NULL, + rr ? DNSTypeName(rr->resrec.rrtype) : "<NONE>"); + + return(result); +} + +// remove a resource record registered via DNSServiceRegisterRecord() +mDNSlocal mStatus remove_record(request_state *request) +{ + mStatus err = mStatus_UnknownErr; + registered_record_entry *e, **ptr = &request->u.reg_recs; + + while (*ptr && (*ptr)->key != request->hdr.reg_index) ptr = &(*ptr)->next; + if (!*ptr) { LogMsg("%3d: DNSServiceRemoveRecord(%u) not found", request->sd, request->hdr.reg_index); return mStatus_BadReferenceErr; } + e = *ptr; + *ptr = e->next; // unlink + + LogOperation("%3d: DNSServiceRemoveRecord(%u %s)", request->sd, e->key, RRDisplayString(&mDNSStorage, &e->rr->resrec)); + e->rr->RecordContext = NULL; + if (e->external_advertise) + { + external_stop_advertising_service(&e->rr->resrec, request->flags); + e->external_advertise = mDNSfalse; + } + LogMcastS(&mDNSStorage, e->rr, request, reg_stop); + err = mDNS_Deregister(&mDNSStorage, e->rr); // Will free e->rr for us; we're responsible for freeing e + if (err) + { + LogMsg("ERROR: remove_record, mDNS_Deregister: %d", err); + freeL("registered_record_entry AuthRecord remove_record", e->rr); + } + freeL("registered_record_entry remove_record", e); + return err; +} + +mDNSlocal mStatus remove_extra(const request_state *const request, service_instance *const serv, mDNSu16 *const rrtype) +{ + mStatus err = mStatus_BadReferenceErr; + ExtraResourceRecord *ptr; + + for (ptr = serv->srs.Extras; ptr; ptr = ptr->next) + { + if (ptr->ClientID == request->hdr.reg_index) // found match + { + *rrtype = ptr->r.resrec.rrtype; + if (serv->external_advertise) external_stop_advertising_service(&ptr->r.resrec, request->flags); + err = mDNS_RemoveRecordFromService(&mDNSStorage, &serv->srs, ptr, FreeExtraRR, ptr); + break; + } + } + return err; +} + +mDNSlocal mStatus handle_removerecord_request(request_state *request) +{ + mStatus err = mStatus_BadReferenceErr; + get_flags(&request->msgptr, request->msgend); // flags unused + + if (!request->msgptr) { LogMsg("%3d: DNSServiceRemoveRecord(unreadable parameters)", request->sd); return(mStatus_BadParamErr); } + + // If this is a shared connection, check if the operation actually applies to a subordinate request_state object + if (request->terminate == connection_termination) request = LocateSubordinateRequest(request); + + if (request->terminate == connection_termination) + err = remove_record(request); // remove individually registered record + else if (request->terminate != regservice_termination_callback) + { LogMsg("%3d: DNSServiceRemoveRecord(not a registered service ref)", request->sd); return(mStatus_BadParamErr); } + else + { + service_instance *i; + mDNSu16 rrtype = 0; + LogOperation("%3d: DNSServiceRemoveRecord(%##s, %s)", request->sd, + (request->u.servicereg.instances) ? request->u.servicereg.instances->srs.RR_SRV.resrec.name->c : NULL, + rrtype ? DNSTypeName(rrtype) : "<NONE>"); + for (i = request->u.servicereg.instances; i; i = i->next) + { + err = remove_extra(request, i, &rrtype); + if (err && i->default_local) break; + else err = mStatus_NoError; // suppress non-local default errors + } + } + + return(err); +} + +// If there's a comma followed by another character, +// FindFirstSubType overwrites the comma with a nul and returns the pointer to the next character. +// Otherwise, it returns a pointer to the final nul at the end of the string +mDNSlocal char *FindFirstSubType(char *p, char **AnonData) +{ + while (*p) + { + if (p[0] == '\\' && p[1]) + { + p += 2; + } + else if (p[0] == ',' && p[1]) + { + *p++ = 0; + return(p); + } + else if (p[0] == ':' && p[1]) + { + *p++ = 0; + *AnonData = p; + } + else + { + p++; + } + } + return(p); +} + +// If there's a comma followed by another character, +// FindNextSubType overwrites the comma with a nul and returns the pointer to the next character. +// If it finds an illegal unescaped dot in the subtype name, it returns mDNSNULL +// Otherwise, it returns a pointer to the final nul at the end of the string +mDNSlocal char *FindNextSubType(char *p) +{ + while (*p) + { + if (p[0] == '\\' && p[1]) // If escape character + p += 2; // ignore following character + else if (p[0] == ',') // If we found a comma + { + if (p[1]) *p++ = 0; + return(p); + } + else if (p[0] == '.') + return(mDNSNULL); + else p++; + } + return(p); +} + +// Returns -1 if illegal subtype found +mDNSexport mDNSs32 ChopSubTypes(char *regtype, char **AnonData) +{ + mDNSs32 NumSubTypes = 0; + char *stp = FindFirstSubType(regtype, AnonData); + while (stp && *stp) // If we found a comma... + { + if (*stp == ',') return(-1); + NumSubTypes++; + stp = FindNextSubType(stp); + } + if (!stp) return(-1); + return(NumSubTypes); +} + +mDNSexport AuthRecord *AllocateSubTypes(mDNSs32 NumSubTypes, char *p, char **AnonData) +{ + AuthRecord *st = mDNSNULL; + // + // "p" is pointing at the regtype e.g., _http._tcp followed by ":<AnonData>" indicated + // by AnonData being non-NULL which is in turn follwed by ",<SubTypes>" indicated by + // NumSubTypes being non-zero. We need to skip the initial regtype to get to the actual + // data that we want. When we come here, ChopSubTypes has null terminated like this e.g., + // + // _http._tcp<NULL><AnonData><NULL><SubType1><NULL><SubType2><NULL> etc. + // + // 1. If we have Anonymous data and subtypes, skip the regtype (e.g., "_http._tcp") + // to get the AnonData and then skip the AnonData to get to the SubType. + // + // 2. If we have only SubTypes, skip the regtype to get to the SubType data. + // + // 3. If we have only AnonData, skip the regtype to get to the AnonData. + // + // 4. If we don't have AnonData or NumStypes, it is a noop. + // + if (AnonData) + { + int len; + + // Skip the regtype + while (*p) p++; + p++; + + len = strlen(p) + 1; + *AnonData = mallocL("Anonymous", len); + if (!(*AnonData)) + { + return (mDNSNULL); + } + mDNSPlatformMemCopy(*AnonData, p, len); + } + if (NumSubTypes) + { + mDNSs32 i; + st = mallocL("ServiceSubTypes", NumSubTypes * sizeof(AuthRecord)); + if (!st) return(mDNSNULL); + for (i = 0; i < NumSubTypes; i++) + { + mDNS_SetupResourceRecord(&st[i], mDNSNULL, mDNSInterface_Any, kDNSQType_ANY, kStandardTTL, 0, AuthRecordAny, mDNSNULL, mDNSNULL); + // First time through we skip the regtype or AnonData. Subsequently, the + // previous subtype. + while (*p) p++; + p++; + if (!MakeDomainNameFromDNSNameString(&st[i].namestorage, p)) + { + freeL("ServiceSubTypes", st); + if (*AnonData) + freeL("AnonymousData", *AnonData); + return(mDNSNULL); + } + } + } + // If NumSubTypes is zero and AnonData is non-NULL, we still return NULL but AnonData has been + // initialized. The caller knows how to handle this. + return(st); +} + +mDNSlocal mStatus register_service_instance(request_state *request, const domainname *domain) +{ + service_instance **ptr, *instance; + const int extra_size = (request->u.servicereg.txtlen > sizeof(RDataBody)) ? (request->u.servicereg.txtlen - sizeof(RDataBody)) : 0; + const mDNSBool DomainIsLocal = SameDomainName(domain, &localdomain); + mStatus result; + mDNSInterfaceID interfaceID = request->u.servicereg.InterfaceID; + mDNSu32 coreFlags = 0; + + if (request->flags & kDNSServiceFlagsIncludeP2P) + coreFlags |= coreFlagIncludeP2P; + if (request->flags & kDNSServiceFlagsIncludeAWDL) + coreFlags |= coreFlagIncludeAWDL; + + // Client guarantees that record names are unique, so we can skip sending out initial + // probe messages. Standard name conflict resolution is still done if a conflict is discovered. + if (request->flags & kDNSServiceFlagsKnownUnique) + coreFlags |= coreFlagKnownUnique; + + if (request->flags & kDNSServiceFlagsWakeOnlyService) + coreFlags |= coreFlagWakeOnly; + + // If the client specified an interface, but no domain, then we honor the specified interface for the "local" (mDNS) + // registration but for the wide-area registrations we don't (currently) have any concept of a wide-area unicast + // registrations scoped to a specific interface, so for the automatic domains we add we must *not* specify an interface. + // (Specifying an interface with an apparently wide-area domain (i.e. something other than "local") + // currently forces the registration to use mDNS multicast despite the apparently wide-area domain.) + if (request->u.servicereg.default_domain && !DomainIsLocal) interfaceID = mDNSInterface_Any; + + for (ptr = &request->u.servicereg.instances; *ptr; ptr = &(*ptr)->next) + { + if (SameDomainName(&(*ptr)->domain, domain)) + { + LogMsg("register_service_instance: domain %##s already registered for %#s.%##s", + domain->c, &request->u.servicereg.name, &request->u.servicereg.type); + return mStatus_AlreadyRegistered; + } + } + + instance = mallocL("service_instance", sizeof(*instance) + extra_size); + if (!instance) { my_perror("ERROR: malloc"); return mStatus_NoMemoryErr; } + + instance->next = mDNSNULL; + instance->request = request; + instance->renameonmemfree = 0; + instance->clientnotified = mDNSfalse; + instance->default_local = (request->u.servicereg.default_domain && DomainIsLocal); + instance->external_advertise = mDNSfalse; + AssignDomainName(&instance->domain, domain); + + instance->srs.AnonData = mDNSNULL; + if (!request->u.servicereg.AnonData) + { + instance->subtypes = AllocateSubTypes(request->u.servicereg.num_subtypes, request->u.servicereg.type_as_string, mDNSNULL); + } + else + { + char *AnonData = mDNSNULL; + instance->subtypes = AllocateSubTypes(request->u.servicereg.num_subtypes, request->u.servicereg.type_as_string, &AnonData); + if (AnonData) + instance->srs.AnonData = (const mDNSu8 *)AnonData; + } + + if (request->u.servicereg.num_subtypes && !instance->subtypes) + { + unlink_and_free_service_instance(instance); + instance = NULL; + FatalError("ERROR: malloc"); + } + + result = mDNS_RegisterService(&mDNSStorage, &instance->srs, + &request->u.servicereg.name, &request->u.servicereg.type, domain, + request->u.servicereg.host.c[0] ? &request->u.servicereg.host : NULL, + request->u.servicereg.port, + request->u.servicereg.txtdata, request->u.servicereg.txtlen, + instance->subtypes, request->u.servicereg.num_subtypes, + interfaceID, regservice_callback, instance, coreFlags); + + if (!result) + { + *ptr = instance; // Append this to the end of our request->u.servicereg.instances list + LogOperation("%3d: DNSServiceRegister(%##s, %u) ADDED", instance->request->sd, + instance->srs.RR_SRV.resrec.name->c, mDNSVal16(request->u.servicereg.port)); + LogMcastS(&mDNSStorage, &instance->srs.RR_SRV, request, reg_start); + } + else + { + LogMsg("register_service_instance %#s.%##s%##s error %d", + &request->u.servicereg.name, &request->u.servicereg.type, domain->c, result); + unlink_and_free_service_instance(instance); + } + + return result; +} + +mDNSlocal void udsserver_default_reg_domain_changed(const DNameListElem *const d, const mDNSBool add) +{ + request_state *request; + +#if APPLE_OSX_mDNSResponder + machserver_automatic_registration_domain_changed(&d->name, add); +#endif // APPLE_OSX_mDNSResponder + + LogMsg("%s registration domain %##s", add ? "Adding" : "Removing", d->name.c); + for (request = all_requests; request; request = request->next) + { + if (request->terminate != regservice_termination_callback) continue; + if (!request->u.servicereg.default_domain) continue; + if (!d->uid || SystemUID(request->uid) || request->uid == d->uid) + { + service_instance **ptr = &request->u.servicereg.instances; + while (*ptr && !SameDomainName(&(*ptr)->domain, &d->name)) ptr = &(*ptr)->next; + if (add) + { + // If we don't already have this domain in our list for this registration, add it now + if (!*ptr) register_service_instance(request, &d->name); + else debugf("udsserver_default_reg_domain_changed %##s already in list, not re-adding", &d->name); + } + else + { + // Normally we should not fail to find the specified instance + // One case where this can happen is if a uDNS update fails for some reason, + // and regservice_callback then calls unlink_and_free_service_instance and disposes of that instance. + if (!*ptr) + LogMsg("udsserver_default_reg_domain_changed domain %##s not found for service %#s type %s", + &d->name, request->u.servicereg.name.c, request->u.servicereg.type_as_string); + else + { + DNameListElem *p; + for (p = AutoRegistrationDomains; p; p=p->next) + if (!p->uid || SystemUID(request->uid) || request->uid == p->uid) + if (SameDomainName(&d->name, &p->name)) break; + if (p) debugf("udsserver_default_reg_domain_changed %##s still in list, not removing", &d->name); + else + { + mStatus err; + service_instance *si = *ptr; + *ptr = si->next; + if (si->clientnotified) SendServiceRemovalNotification(&si->srs); // Do this *before* clearing si->request backpointer + // Now that we've cut this service_instance from the list, we MUST clear the si->request backpointer. + // Otherwise what can happen is this: While our mDNS_DeregisterService is in the + // process of completing asynchronously, the client cancels the entire operation, so + // regservice_termination_callback then runs through the whole list deregistering each + // instance, clearing the backpointers, and then disposing the parent request_state object. + // However, because this service_instance isn't in the list any more, regservice_termination_callback + // has no way to find it and clear its backpointer, and then when our mDNS_DeregisterService finally + // completes later with a mStatus_MemFree message, it calls unlink_and_free_service_instance() with + // a service_instance with a stale si->request backpointer pointing to memory that's already been freed. + si->request = NULL; + err = mDNS_DeregisterService(&mDNSStorage, &si->srs); + if (err) { LogMsg("udsserver_default_reg_domain_changed err %d", err); unlink_and_free_service_instance(si); } + } + } + } + } + } +} + +// Don't allow normal and anonymous registration to coexist. +mDNSlocal mDNSBool CheckForMixedRegistrations(domainname *regtype, domainname *domain, mDNSBool AnonData) +{ + request_state *request; + + // We only care about local domains where the anonymous extension is + // implemented. + if (!SameDomainName(domain, (const domainname *) "\x5" "local")) + { + return mDNStrue; + } + + for (request = all_requests; request; request = request->next) + { + service_instance *ptr; + + if (request->terminate != regservice_termination_callback) continue; + for (ptr = request->u.servicereg.instances; ptr ; ptr = ptr->next) + { + if (!SameDomainName(&ptr->domain, (const domainname *)"\x5" "local") || + !SameDomainName(&request->u.servicereg.type, regtype)) + { + continue; + } + + // If we are about to register a anonymous registraion, we dont't want to + // allow the regular ones and vice versa. + if (AnonData) + { + if (!ptr->srs.AnonData) + { + LogMsg("CheckForMixedRegistrations: Normal registration already exists for %##s", regtype->c); + return mDNSfalse; + } + } + else + { + // Allow multiple regular registrations + if (ptr->srs.AnonData) + { + LogMsg("CheckForMixedRegistrations: Anonymous registration already exists for %##s", regtype->c); + return mDNSfalse; + } + } + } + } + return mDNStrue; +} + +mDNSlocal mStatus handle_regservice_request(request_state *request) +{ + char name[256]; // Lots of spare space for extra-long names that we'll auto-truncate down to 63 bytes + char domain[MAX_ESCAPED_DOMAIN_NAME], host[MAX_ESCAPED_DOMAIN_NAME]; + char type_as_string[MAX_ESCAPED_DOMAIN_NAME]; + domainname d, srv; + mStatus err; + char *AnonData = mDNSNULL; + + DNSServiceFlags flags = get_flags(&request->msgptr, request->msgend); + mDNSu32 interfaceIndex = get_uint32(&request->msgptr, request->msgend); + mDNSInterfaceID InterfaceID; + + // Map kDNSServiceInterfaceIndexP2P to kDNSServiceInterfaceIndexAny with the + // kDNSServiceFlagsIncludeP2P flag set. + if (interfaceIndex == kDNSServiceInterfaceIndexP2P) + { + LogOperation("handle_regservice_request: mapping kDNSServiceInterfaceIndexP2P to kDNSServiceInterfaceIndexAny + kDNSServiceFlagsIncludeP2P"); + flags |= kDNSServiceFlagsIncludeP2P; + interfaceIndex = kDNSServiceInterfaceIndexAny; + } + + InterfaceID = mDNSPlatformInterfaceIDfromInterfaceIndex(&mDNSStorage, interfaceIndex); + if (interfaceIndex && !InterfaceID) + { LogMsg("ERROR: handle_regservice_request - Couldn't find interfaceIndex %d", interfaceIndex); return(mStatus_BadParamErr); } + + if (get_string(&request->msgptr, request->msgend, name, sizeof(name)) < 0 || + get_string(&request->msgptr, request->msgend, type_as_string, MAX_ESCAPED_DOMAIN_NAME) < 0 || + get_string(&request->msgptr, request->msgend, domain, MAX_ESCAPED_DOMAIN_NAME) < 0 || + get_string(&request->msgptr, request->msgend, host, MAX_ESCAPED_DOMAIN_NAME) < 0) + { LogMsg("ERROR: handle_regservice_request - Couldn't read name/regtype/domain"); return(mStatus_BadParamErr); } + + request->flags = flags; + request->u.servicereg.InterfaceID = InterfaceID; + request->u.servicereg.instances = NULL; + request->u.servicereg.txtlen = 0; + request->u.servicereg.txtdata = NULL; + mDNSPlatformStrCopy(request->u.servicereg.type_as_string, type_as_string); + + if (request->msgptr + 2 > request->msgend) request->msgptr = NULL; + else + { + request->u.servicereg.port.b[0] = *request->msgptr++; + request->u.servicereg.port.b[1] = *request->msgptr++; + } + + request->u.servicereg.txtlen = get_uint16(&request->msgptr, request->msgend); + if (request->u.servicereg.txtlen) + { + request->u.servicereg.txtdata = mallocL("service_info txtdata", request->u.servicereg.txtlen); + if (!request->u.servicereg.txtdata) FatalError("ERROR: handle_regservice_request - malloc"); + mDNSPlatformMemCopy(request->u.servicereg.txtdata, get_rdata(&request->msgptr, request->msgend, request->u.servicereg.txtlen), request->u.servicereg.txtlen); + } + + if (!request->msgptr) { LogMsg("%3d: DNSServiceRegister(unreadable parameters)", request->sd); return(mStatus_BadParamErr); } + + // Check for sub-types after the service type + request->u.servicereg.num_subtypes = ChopSubTypes(request->u.servicereg.type_as_string, &AnonData); // Note: Modifies regtype string to remove trailing subtypes + if (request->u.servicereg.num_subtypes < 0) + { + LogMsg("ERROR: handle_regservice_request - ChopSubTypes failed %s", request->u.servicereg.type_as_string); + return(mStatus_BadParamErr); + } + if (AnonData) + { + int AnonDataLen = strlen(AnonData); + if (AnonDataLen > MAX_ANONYMOUS_DATA) + { + LogMsg("ERROR: handle_regservice_request: AnonDataLen %d", AnonDataLen); + return(mStatus_BadParamErr); + } + request->u.servicereg.AnonData = mDNStrue; + } + else + { + request->u.servicereg.AnonData = mDNSfalse; + } + + // Don't try to construct "domainname t" until *after* ChopSubTypes has worked its magic + if (!*request->u.servicereg.type_as_string || !MakeDomainNameFromDNSNameString(&request->u.servicereg.type, request->u.servicereg.type_as_string)) + { LogMsg("ERROR: handle_regservice_request - type_as_string bad %s", request->u.servicereg.type_as_string); return(mStatus_BadParamErr); } + + if (!name[0]) + { + request->u.servicereg.name = mDNSStorage.nicelabel; + request->u.servicereg.autoname = mDNStrue; + } + else + { + // If the client is allowing AutoRename, then truncate name to legal length before converting it to a DomainLabel + if ((flags & kDNSServiceFlagsNoAutoRename) == 0) + { + int newlen = TruncateUTF8ToLength((mDNSu8*)name, mDNSPlatformStrLen(name), MAX_DOMAIN_LABEL); + name[newlen] = 0; + } + if (!MakeDomainLabelFromLiteralString(&request->u.servicereg.name, name)) + { LogMsg("ERROR: handle_regservice_request - name bad %s", name); return(mStatus_BadParamErr); } + request->u.servicereg.autoname = mDNSfalse; + } + + if (*domain) + { + request->u.servicereg.default_domain = mDNSfalse; + if (!MakeDomainNameFromDNSNameString(&d, domain)) + { LogMsg("ERROR: handle_regservice_request - domain bad %s", domain); return(mStatus_BadParamErr); } + } + else + { + request->u.servicereg.default_domain = mDNStrue; + MakeDomainNameFromDNSNameString(&d, "local."); + } + + // We don't allow the anonymous and the regular ones to coexist + if (!CheckForMixedRegistrations(&request->u.servicereg.type, &d, request->u.servicereg.AnonData)) + { + return(mStatus_BadParamErr); + } + + if (!ConstructServiceName(&srv, &request->u.servicereg.name, &request->u.servicereg.type, &d)) + { + LogMsg("ERROR: handle_regservice_request - Couldn't ConstructServiceName from, “%#s” “%##s” “%##s”", + request->u.servicereg.name.c, request->u.servicereg.type.c, d.c); return(mStatus_BadParamErr); + } + + if (!MakeDomainNameFromDNSNameString(&request->u.servicereg.host, host)) + { LogMsg("ERROR: handle_regservice_request - host bad %s", host); return(mStatus_BadParamErr); } + request->u.servicereg.autorename = (flags & kDNSServiceFlagsNoAutoRename ) == 0; + request->u.servicereg.allowremotequery = (flags & kDNSServiceFlagsAllowRemoteQuery) != 0; + + // Some clients use mDNS for lightweight copy protection, registering a pseudo-service with + // a port number of zero. When two instances of the protected client are allowed to run on one + // machine, we don't want to see misleading "Bogus client" messages in syslog and the console. + if (!mDNSIPPortIsZero(request->u.servicereg.port)) + { + int count = CountExistingRegistrations(&srv, request->u.servicereg.port); + if (count) + LogMsg("Client application[%d](%s) registered %d identical instances of service %##s port %u.", request->process_id, + request->pid_name, count+1, srv.c, mDNSVal16(request->u.servicereg.port)); + } + + LogOperation("%3d: DNSServiceRegister(%X, %d, \"%s\", \"%s\", \"%s\", \"%s\", %u) START PID[%d](%s)", + request->sd, flags, interfaceIndex, name, request->u.servicereg.type_as_string, domain, host, + mDNSVal16(request->u.servicereg.port), request->process_id, request->pid_name); + + // We need to unconditionally set request->terminate, because even if we didn't successfully + // start any registrations right now, subsequent configuration changes may cause successful + // registrations to be added, and we'll need to cancel them before freeing this memory. + // We also need to set request->terminate first, before adding additional service instances, + // because the uds_validatelists uses the request->terminate function pointer to determine + // what kind of request this is, and therefore what kind of list validation is required. + request->terminate = regservice_termination_callback; + + err = register_service_instance(request, &d); + +#if 0 + err = AuthorizedDomain(request, &d, AutoRegistrationDomains) ? register_service_instance(request, &d) : mStatus_NoError; +#endif + if (!err) + { + if (request->u.servicereg.autoname) UpdateDeviceInfoRecord(&mDNSStorage); + + if (!*domain) + { + DNameListElem *ptr; + // Note that we don't report errors for non-local, non-explicit domains + for (ptr = AutoRegistrationDomains; ptr; ptr = ptr->next) + if (!ptr->uid || SystemUID(request->uid) || request->uid == ptr->uid) + register_service_instance(request, &ptr->name); + } + } + + return(err); +} + +// *************************************************************************** +#if COMPILER_LIKES_PRAGMA_MARK +#pragma mark - +#pragma mark - DNSServiceBrowse +#endif + +mDNSlocal void FoundInstance(mDNS *const m, DNSQuestion *question, const ResourceRecord *const answer, QC_result AddRecord) +{ + DNSServiceFlags flags = AddRecord ? kDNSServiceFlagsAdd : 0; + request_state *req = question->QuestionContext; + reply_state *rep; + (void)m; // Unused + + if (answer->rrtype != kDNSType_PTR) + { LogMsg("%3d: FoundInstance: Should not be called with rrtype %d (not a PTR record)", req->sd, answer->rrtype); return; } + + if (mDNSOpaque16IsZero(question->TargetQID) && (question->BrowseThreshold > 0) && (question->CurrentAnswers >= question->BrowseThreshold)) + { + flags |= kDNSServiceFlagsThresholdReached; + } + + if (GenerateNTDResponse(&answer->rdata->u.name, answer->InterfaceID, req, &rep, browse_reply_op, flags, mStatus_NoError) != mStatus_NoError) + { + if (SameDomainName(&req->u.browser.regtype, (const domainname*)"\x09_services\x07_dns-sd\x04_udp")) + { + // Special support to enable the DNSServiceBrowse call made by Bonjour Browser + // Remove after Bonjour Browser is updated to use DNSServiceQueryRecord instead of DNSServiceBrowse + GenerateBonjourBrowserResponse(&answer->rdata->u.name, answer->InterfaceID, req, &rep, browse_reply_op, flags, mStatus_NoError); + goto bonjourbrowserhack; + } + + LogMsg("%3d: FoundInstance: %##s PTR %##s received from network is not valid DNS-SD service pointer", + req->sd, answer->name->c, answer->rdata->u.name.c); + return; + } + +bonjourbrowserhack: + + LogOperation("%3d: DNSServiceBrowse(%##s, %s) RESULT %s %d: %s", + req->sd, question->qname.c, DNSTypeName(question->qtype), AddRecord ? "Add" : "Rmv", + mDNSPlatformInterfaceIndexfromInterfaceID(m, answer->InterfaceID, mDNSfalse), RRDisplayString(m, answer)); + + append_reply(req, rep); +} + +mDNSlocal mStatus add_domain_to_browser(request_state *info, const domainname *d) +{ + browser_t *b, *p; + mStatus err; + + for (p = info->u.browser.browsers; p; p = p->next) + { + if (SameDomainName(&p->domain, d)) + { debugf("add_domain_to_browser %##s already in list", d->c); return mStatus_AlreadyRegistered; } + } + + b = mallocL("browser_t", sizeof(*b)); + if (!b) return mStatus_NoMemoryErr; + AssignDomainName(&b->domain, d); + err = mDNS_StartBrowse(&mDNSStorage, &b->q, &info->u.browser.regtype, d, info->u.browser.AnonData, info->u.browser.interface_id, info->flags, + info->u.browser.ForceMCast, (info->flags & kDNSServiceFlagsBackgroundTrafficClass) != 0, FoundInstance, info); + if (err) + { + LogMsg("mDNS_StartBrowse returned %d for type %##s domain %##s", err, info->u.browser.regtype.c, d->c); + freeL("browser_t/add_domain_to_browser", b); + } + else + { + b->next = info->u.browser.browsers; + info->u.browser.browsers = b; + LogOperation("%3d: DNSServiceBrowse(%##s) START PID[%d](%s)", info->sd, b->q.qname.c, info->process_id, + info->pid_name); + LogMcastQ(&mDNSStorage, &b->q, info, q_start); + if (callExternalHelpers(info->u.browser.interface_id, &b->domain, info->flags)) + { + domainname tmp; + ConstructServiceName(&tmp, NULL, &info->u.browser.regtype, &b->domain); + LogInfo("add_domain_to_browser: calling external_start_browsing_for_service()"); + external_start_browsing_for_service(info->u.browser.interface_id, &tmp, kDNSType_PTR, info->flags); + } + } + return err; +} + +mDNSlocal void browse_termination_callback(request_state *info) +{ + if (info->u.browser.default_domain) + { + // Stop the domain enumeration queries to discover the WAB legacy browse domains + LogInfo("%3d: DNSServiceBrowse Cancel WAB PID[%d](%s)", info->sd, info->process_id, info->pid_name); + uDNS_StopWABQueries(&mDNSStorage, UDNS_WAB_LBROWSE_QUERY); + } + if (info->u.browser.AnonData) + freeL("Anonymous", (void *)info->u.browser.AnonData); + while (info->u.browser.browsers) + { + browser_t *ptr = info->u.browser.browsers; + + if (callExternalHelpers(info->u.browser.interface_id, &ptr->domain, info->flags)) + { + domainname tmp; + ConstructServiceName(&tmp, NULL, &info->u.browser.regtype, &ptr->domain); + LogInfo("browse_termination_callback: calling external_stop_browsing_for_service()"); + external_stop_browsing_for_service(info->u.browser.interface_id, &tmp, kDNSType_PTR, info->flags); + } + + info->u.browser.browsers = ptr->next; + LogOperation("%3d: DNSServiceBrowse(%##s) STOP PID[%d](%s)", info->sd, ptr->q.qname.c, info->process_id, info->pid_name); + mDNS_StopBrowse(&mDNSStorage, &ptr->q); // no need to error-check result + LogMcastQ(&mDNSStorage, &ptr->q, info, q_stop); + freeL("browser_t/browse_termination_callback", ptr); + } +} + +mDNSlocal void udsserver_automatic_browse_domain_changed(const DNameListElem *const d, const mDNSBool add) +{ + request_state *request; + debugf("udsserver_automatic_browse_domain_changed: %s default browse domain %##s", add ? "Adding" : "Removing", d->name.c); + +#if APPLE_OSX_mDNSResponder + machserver_automatic_browse_domain_changed(&d->name, add); +#endif // APPLE_OSX_mDNSResponder + + for (request = all_requests; request; request = request->next) + { + if (request->terminate != browse_termination_callback) continue; // Not a browse operation + if (!request->u.browser.default_domain) continue; // Not an auto-browse operation + if (!d->uid || SystemUID(request->uid) || request->uid == d->uid) + { + browser_t **ptr = &request->u.browser.browsers; + while (*ptr && !SameDomainName(&(*ptr)->domain, &d->name)) ptr = &(*ptr)->next; + if (add) + { + // If we don't already have this domain in our list for this browse operation, add it now + if (!*ptr) add_domain_to_browser(request, &d->name); + else debugf("udsserver_automatic_browse_domain_changed %##s already in list, not re-adding", &d->name); + } + else + { + if (!*ptr) LogMsg("udsserver_automatic_browse_domain_changed ERROR %##s not found", &d->name); + else + { + DNameListElem *p; + for (p = AutoBrowseDomains; p; p=p->next) + if (!p->uid || SystemUID(request->uid) || request->uid == p->uid) + if (SameDomainName(&d->name, &p->name)) break; + if (p) debugf("udsserver_automatic_browse_domain_changed %##s still in list, not removing", &d->name); + else + { + browser_t *rem = *ptr; + *ptr = (*ptr)->next; + mDNS_StopQueryWithRemoves(&mDNSStorage, &rem->q); + freeL("browser_t/udsserver_automatic_browse_domain_changed", rem); + } + } + } + } + } +} + +mDNSlocal void FreeARElemCallback(mDNS *const m, AuthRecord *const rr, mStatus result) +{ + (void)m; // unused + if (result == mStatus_MemFree) + { + // On shutdown, mDNS_Close automatically deregisters all records + // Since in this case no one has called DeregisterLocalOnlyDomainEnumPTR to cut the record + // from the LocalDomainEnumRecords list, we do this here before we free the memory. + // (This should actually no longer be necessary, now that we do the proper cleanup in + // udsserver_exit. To confirm this, we'll log an error message if we do find a record that + // hasn't been cut from the list yet. If these messages don't appear, we can delete this code.) + ARListElem **ptr = &LocalDomainEnumRecords; + while (*ptr && &(*ptr)->ar != rr) ptr = &(*ptr)->next; + if (*ptr) { *ptr = (*ptr)->next; LogMsg("FreeARElemCallback: Have to cut %s", ARDisplayString(m, rr)); } + mDNSPlatformMemFree(rr->RecordContext); + } +} + +// RegisterLocalOnlyDomainEnumPTR and DeregisterLocalOnlyDomainEnumPTR largely duplicate code in +// "FoundDomain" in uDNS.c for creating and destroying these special mDNSInterface_LocalOnly records. +// We may want to turn the common code into a subroutine. + +mDNSlocal void RegisterLocalOnlyDomainEnumPTR(mDNS *m, const domainname *d, int type) +{ + // allocate/register legacy and non-legacy _browse PTR record + mStatus err; + ARListElem *ptr = mDNSPlatformMemAllocate(sizeof(*ptr)); + + debugf("Incrementing %s refcount for %##s", + (type == mDNS_DomainTypeBrowse ) ? "browse domain " : + (type == mDNS_DomainTypeRegistration ) ? "registration dom" : + (type == mDNS_DomainTypeBrowseAutomatic) ? "automatic browse" : "?", d->c); + + mDNS_SetupResourceRecord(&ptr->ar, mDNSNULL, mDNSInterface_LocalOnly, kDNSType_PTR, 7200, kDNSRecordTypeShared, AuthRecordLocalOnly, FreeARElemCallback, ptr); + MakeDomainNameFromDNSNameString(&ptr->ar.namestorage, mDNS_DomainTypeNames[type]); + AppendDNSNameString (&ptr->ar.namestorage, "local"); + AssignDomainName(&ptr->ar.resrec.rdata->u.name, d); + err = mDNS_Register(m, &ptr->ar); + if (err) + { + LogMsg("SetSCPrefsBrowseDomain: mDNS_Register returned error %d", err); + mDNSPlatformMemFree(ptr); + } + else + { + ptr->next = LocalDomainEnumRecords; + LocalDomainEnumRecords = ptr; + } +} + +mDNSlocal void DeregisterLocalOnlyDomainEnumPTR(mDNS *m, const domainname *d, int type) +{ + ARListElem **ptr = &LocalDomainEnumRecords; + domainname lhs; // left-hand side of PTR, for comparison + + debugf("Decrementing %s refcount for %##s", + (type == mDNS_DomainTypeBrowse ) ? "browse domain " : + (type == mDNS_DomainTypeRegistration ) ? "registration dom" : + (type == mDNS_DomainTypeBrowseAutomatic) ? "automatic browse" : "?", d->c); + + MakeDomainNameFromDNSNameString(&lhs, mDNS_DomainTypeNames[type]); + AppendDNSNameString (&lhs, "local"); + + while (*ptr) + { + if (SameDomainName(&(*ptr)->ar.resrec.rdata->u.name, d) && SameDomainName((*ptr)->ar.resrec.name, &lhs)) + { + ARListElem *rem = *ptr; + *ptr = (*ptr)->next; + mDNS_Deregister(m, &rem->ar); + return; + } + else ptr = &(*ptr)->next; + } +} + +mDNSlocal void AddAutoBrowseDomain(const mDNSu32 uid, const domainname *const name) +{ + DNameListElem *new = mDNSPlatformMemAllocate(sizeof(DNameListElem)); + if (!new) { LogMsg("ERROR: malloc"); return; } + AssignDomainName(&new->name, name); + new->uid = uid; + new->next = AutoBrowseDomains; + AutoBrowseDomains = new; + udsserver_automatic_browse_domain_changed(new, mDNStrue); +} + +mDNSlocal void RmvAutoBrowseDomain(const mDNSu32 uid, const domainname *const name) +{ + DNameListElem **p = &AutoBrowseDomains; + while (*p && (!SameDomainName(&(*p)->name, name) || (*p)->uid != uid)) p = &(*p)->next; + if (!*p) LogMsg("RmvAutoBrowseDomain: Got remove event for domain %##s not in list", name->c); + else + { + DNameListElem *ptr = *p; + *p = ptr->next; + udsserver_automatic_browse_domain_changed(ptr, mDNSfalse); + mDNSPlatformMemFree(ptr); + } +} + +mDNSlocal void SetPrefsBrowseDomains(mDNS *m, DNameListElem *browseDomains, mDNSBool add) +{ + DNameListElem *d; + for (d = browseDomains; d; d = d->next) + { + if (add) + { + RegisterLocalOnlyDomainEnumPTR(m, &d->name, mDNS_DomainTypeBrowse); + AddAutoBrowseDomain(d->uid, &d->name); + } + else + { + DeregisterLocalOnlyDomainEnumPTR(m, &d->name, mDNS_DomainTypeBrowse); + RmvAutoBrowseDomain(d->uid, &d->name); + } + } +} + +#if APPLE_OSX_mDNSResponder + +mDNSlocal void UpdateDeviceInfoRecord(mDNS *const m) +{ + int num_autoname = 0; + request_state *req; + for (req = all_requests; req; req = req->next) + if (req->terminate == regservice_termination_callback && req->u.servicereg.autoname) + num_autoname++; + + // If DeviceInfo record is currently registered, see if we need to deregister it + if (m->DeviceInfo.resrec.RecordType != kDNSRecordTypeUnregistered) + if (num_autoname == 0 || !SameDomainLabelCS(m->DeviceInfo.resrec.name->c, m->nicelabel.c)) + { + LogOperation("UpdateDeviceInfoRecord Deregister %##s", m->DeviceInfo.resrec.name); + mDNS_Deregister(m, &m->DeviceInfo); + } + + // If DeviceInfo record is not currently registered, see if we need to register it + if (m->DeviceInfo.resrec.RecordType == kDNSRecordTypeUnregistered) + if (num_autoname > 0) + { + mDNS_SetupResourceRecord(&m->DeviceInfo, mDNSNULL, mDNSNULL, kDNSType_TXT, kStandardTTL, kDNSRecordTypeAdvisory, AuthRecordAny, mDNSNULL, mDNSNULL); + ConstructServiceName(&m->DeviceInfo.namestorage, &m->nicelabel, &DeviceInfoName, &localdomain); + m->DeviceInfo.resrec.rdlength = initializeDeviceInfoTXT(m, m->DeviceInfo.resrec.rdata->u.data); + LogOperation("UpdateDeviceInfoRecord Register %##s", m->DeviceInfo.resrec.name); + mDNS_Register(m, &m->DeviceInfo); + } +} +#else // APPLE_OSX_mDNSResponder +mDNSlocal void UpdateDeviceInfoRecord(mDNS *const m) +{ + (void)m; // unused +} +#endif // APPLE_OSX_mDNSResponder + +mDNSexport void udsserver_handle_configchange(mDNS *const m) +{ + request_state *req; + service_instance *ptr; + DNameListElem *RegDomains = NULL; + DNameListElem *BrowseDomains = NULL; + DNameListElem *p; + + UpdateDeviceInfoRecord(m); + + // For autoname services, see if the default service name has changed, necessitating an automatic update + for (req = all_requests; req; req = req->next) + if (req->terminate == regservice_termination_callback) + if (req->u.servicereg.autoname && !SameDomainLabelCS(req->u.servicereg.name.c, m->nicelabel.c)) + { + req->u.servicereg.name = m->nicelabel; + for (ptr = req->u.servicereg.instances; ptr; ptr = ptr->next) + { + ptr->renameonmemfree = 1; + if (ptr->clientnotified) SendServiceRemovalNotification(&ptr->srs); + LogInfo("udsserver_handle_configchange: Calling deregister for Service %##s", ptr->srs.RR_PTR.resrec.name->c); + if (mDNS_DeregisterService_drt(m, &ptr->srs, mDNS_Dereg_rapid)) + regservice_callback(m, &ptr->srs, mStatus_MemFree); // If service deregistered already, we can re-register immediately + } + } + + // Let the platform layer get the current DNS information + mDNS_Lock(m); + mDNSPlatformSetDNSConfig(m, mDNSfalse, mDNSfalse, mDNSNULL, &RegDomains, &BrowseDomains, mDNSfalse); + mDNS_Unlock(m); + + // Any automatic registration domains are also implicitly automatic browsing domains + if (RegDomains) SetPrefsBrowseDomains(m, RegDomains, mDNStrue); // Add the new list first + if (AutoRegistrationDomains) SetPrefsBrowseDomains(m, AutoRegistrationDomains, mDNSfalse); // Then clear the old list + + // Add any new domains not already in our AutoRegistrationDomains list + for (p=RegDomains; p; p=p->next) + { + DNameListElem **pp = &AutoRegistrationDomains; + while (*pp && ((*pp)->uid != p->uid || !SameDomainName(&(*pp)->name, &p->name))) pp = &(*pp)->next; + if (!*pp) // If not found in our existing list, this is a new default registration domain + { + RegisterLocalOnlyDomainEnumPTR(m, &p->name, mDNS_DomainTypeRegistration); + udsserver_default_reg_domain_changed(p, mDNStrue); + } + else // else found same domainname in both old and new lists, so no change, just delete old copy + { + DNameListElem *del = *pp; + *pp = (*pp)->next; + mDNSPlatformMemFree(del); + } + } + + // Delete any domains in our old AutoRegistrationDomains list that are now gone + while (AutoRegistrationDomains) + { + DNameListElem *del = AutoRegistrationDomains; + AutoRegistrationDomains = AutoRegistrationDomains->next; // Cut record from list FIRST, + DeregisterLocalOnlyDomainEnumPTR(m, &del->name, mDNS_DomainTypeRegistration); + udsserver_default_reg_domain_changed(del, mDNSfalse); // before calling udsserver_default_reg_domain_changed() + mDNSPlatformMemFree(del); + } + + // Now we have our new updated automatic registration domain list + AutoRegistrationDomains = RegDomains; + + // Add new browse domains to internal list + if (BrowseDomains) SetPrefsBrowseDomains(m, BrowseDomains, mDNStrue); + + // Remove old browse domains from internal list + if (SCPrefBrowseDomains) + { + SetPrefsBrowseDomains(m, SCPrefBrowseDomains, mDNSfalse); + while (SCPrefBrowseDomains) + { + DNameListElem *fptr = SCPrefBrowseDomains; + SCPrefBrowseDomains = SCPrefBrowseDomains->next; + mDNSPlatformMemFree(fptr); + } + } + + // Replace the old browse domains array with the new array + SCPrefBrowseDomains = BrowseDomains; +} + +mDNSlocal void AutomaticBrowseDomainChange(mDNS *const m, DNSQuestion *q, const ResourceRecord *const answer, QC_result AddRecord) +{ + (void)m; // unused; + (void)q; // unused + + LogOperation("AutomaticBrowseDomainChange: %s automatic browse domain %##s", + AddRecord ? "Adding" : "Removing", answer->rdata->u.name.c); + + if (AddRecord) AddAutoBrowseDomain(0, &answer->rdata->u.name); + else RmvAutoBrowseDomain(0, &answer->rdata->u.name); +} + +mDNSlocal mStatus handle_browse_request(request_state *request) +{ + char regtype[MAX_ESCAPED_DOMAIN_NAME], domain[MAX_ESCAPED_DOMAIN_NAME]; + domainname typedn, d, temp; + mDNSs32 NumSubTypes; + char *AnonData = mDNSNULL; + mStatus err = mStatus_NoError; + int AnonDataLen; + + DNSServiceFlags flags = get_flags(&request->msgptr, request->msgend); + mDNSu32 interfaceIndex = get_uint32(&request->msgptr, request->msgend); + mDNSInterfaceID InterfaceID = mDNSPlatformInterfaceIDfromInterfaceIndex(&mDNSStorage, interfaceIndex); + if (interfaceIndex && !InterfaceID) return(mStatus_BadParamErr); + + if (get_string(&request->msgptr, request->msgend, regtype, MAX_ESCAPED_DOMAIN_NAME) < 0 || + get_string(&request->msgptr, request->msgend, domain, MAX_ESCAPED_DOMAIN_NAME) < 0) return(mStatus_BadParamErr); + + if (!request->msgptr) { LogMsg("%3d: DNSServiceBrowse(unreadable parameters)", request->sd); return(mStatus_BadParamErr); } + + request->flags = flags; + typedn.c[0] = 0; + NumSubTypes = ChopSubTypes(regtype, &AnonData); // Note: Modifies regtype string to remove trailing subtypes + if (NumSubTypes < 0 || NumSubTypes > 1) + return(mStatus_BadParamErr); + AnonDataLen = 0; + if (AnonData) + { + AnonDataLen = strlen(AnonData); + if (AnonDataLen > MAX_ANONYMOUS_DATA) + { + LogMsg("handle_browse_request: AnonDataLen %d", AnonDataLen); + return(mStatus_BadParamErr); + } + // Account for the null byte + AnonDataLen += 1; + } + if (NumSubTypes == 1) + { + if (!AppendDNSNameString(&typedn, regtype + strlen(regtype) + 1 + AnonDataLen)) + return(mStatus_BadParamErr); + } + + if (!regtype[0] || !AppendDNSNameString(&typedn, regtype)) return(mStatus_BadParamErr); + + if (!MakeDomainNameFromDNSNameString(&temp, regtype)) return(mStatus_BadParamErr); + // For over-long service types, we only allow domain "local" + if (temp.c[0] > 15 && domain[0] == 0) mDNSPlatformStrCopy(domain, "local."); + + // Set up browser info + request->u.browser.ForceMCast = (flags & kDNSServiceFlagsForceMulticast) != 0; + request->u.browser.interface_id = InterfaceID; + AssignDomainName(&request->u.browser.regtype, &typedn); + request->u.browser.default_domain = !domain[0]; + request->u.browser.browsers = NULL; + + LogOperation("%3d: DNSServiceBrowse(%X, %d, \"%##s\", \"%s\") START PID[%d](%s)", + request->sd, request->flags, interfaceIndex, request->u.browser.regtype.c, domain, request->process_id, request->pid_name); + + if (request->u.browser.default_domain) + { + // Start the domain enumeration queries to discover the WAB browse domains + LogInfo("%3d: DNSServiceBrowse Start WAB PID[%d](%s)", request->sd, request->process_id, request->pid_name); + uDNS_StartWABQueries(&mDNSStorage, UDNS_WAB_LBROWSE_QUERY); + } + request->u.browser.AnonData = mDNSNULL; + if (AnonData) + { + int len = strlen(AnonData) + 1; + request->u.browser.AnonData = mallocL("Anonymous", len); + if (!request->u.browser.AnonData) + return mStatus_NoMemoryErr; + else + mDNSPlatformMemCopy((void *)request->u.browser.AnonData, AnonData, len); + } + // We need to unconditionally set request->terminate, because even if we didn't successfully + // start any browses right now, subsequent configuration changes may cause successful + // browses to be added, and we'll need to cancel them before freeing this memory. + request->terminate = browse_termination_callback; + + if (domain[0]) + { + if (!MakeDomainNameFromDNSNameString(&d, domain)) return(mStatus_BadParamErr); + err = add_domain_to_browser(request, &d); + } + else + { + DNameListElem *sdom; + for (sdom = AutoBrowseDomains; sdom; sdom = sdom->next) + if (!sdom->uid || SystemUID(request->uid) || request->uid == sdom->uid) + { + err = add_domain_to_browser(request, &sdom->name); + if (err) + { + if (SameDomainName(&sdom->name, &localdomain)) break; + else err = mStatus_NoError; // suppress errors for non-local "default" domains + } + } + } + + return(err); +} + +// *************************************************************************** +#if COMPILER_LIKES_PRAGMA_MARK +#pragma mark - +#pragma mark - DNSServiceResolve +#endif + +mDNSlocal void resolve_result_callback(mDNS *const m, DNSQuestion *question, const ResourceRecord *const answer, QC_result AddRecord) +{ + size_t len = 0; + char fullname[MAX_ESCAPED_DOMAIN_NAME], target[MAX_ESCAPED_DOMAIN_NAME]; + char *data; + reply_state *rep; + request_state *req = question->QuestionContext; + (void)m; // Unused + + LogOperation("%3d: DNSServiceResolve(%##s) %s %s", req->sd, question->qname.c, AddRecord ? "ADD" : "RMV", RRDisplayString(m, answer)); + + if (!AddRecord) + { + if (req->u.resolve.srv == answer) req->u.resolve.srv = mDNSNULL; + if (req->u.resolve.txt == answer) req->u.resolve.txt = mDNSNULL; + return; + } + + if (answer->rrtype == kDNSType_SRV) req->u.resolve.srv = answer; + if (answer->rrtype == kDNSType_TXT) req->u.resolve.txt = answer; + + if (!req->u.resolve.txt || !req->u.resolve.srv) return; // only deliver result to client if we have both answers + + ConvertDomainNameToCString(answer->name, fullname); + ConvertDomainNameToCString(&req->u.resolve.srv->rdata->u.srv.target, target); + + // calculate reply length + len += sizeof(DNSServiceFlags); + len += sizeof(mDNSu32); // interface index + len += sizeof(DNSServiceErrorType); + len += strlen(fullname) + 1; + len += strlen(target) + 1; + len += 2 * sizeof(mDNSu16); // port, txtLen + len += req->u.resolve.txt->rdlength; + + // allocate/init reply header + rep = create_reply(resolve_reply_op, len, req); + rep->rhdr->flags = dnssd_htonl(0); + rep->rhdr->ifi = dnssd_htonl(mDNSPlatformInterfaceIndexfromInterfaceID(m, answer->InterfaceID, mDNSfalse)); + rep->rhdr->error = dnssd_htonl(kDNSServiceErr_NoError); + + data = (char *)&rep->rhdr[1]; + + // write reply data to message + put_string(fullname, &data); + put_string(target, &data); + *data++ = req->u.resolve.srv->rdata->u.srv.port.b[0]; + *data++ = req->u.resolve.srv->rdata->u.srv.port.b[1]; + put_uint16(req->u.resolve.txt->rdlength, &data); + put_rdata (req->u.resolve.txt->rdlength, req->u.resolve.txt->rdata->u.data, &data); + + LogOperation("%3d: DNSServiceResolve(%s) RESULT %s:%d", req->sd, fullname, target, mDNSVal16(req->u.resolve.srv->rdata->u.srv.port)); + append_reply(req, rep); +} + +mDNSlocal void resolve_termination_callback(request_state *request) +{ + LogOperation("%3d: DNSServiceResolve(%##s) STOP PID[%d](%s)", request->sd, request->u.resolve.qtxt.qname.c, request->process_id, request->pid_name); + mDNS_StopQuery(&mDNSStorage, &request->u.resolve.qtxt); + mDNS_StopQuery(&mDNSStorage, &request->u.resolve.qsrv); + LogMcastQ(&mDNSStorage, &request->u.resolve.qsrv, request, q_stop); + if (request->u.resolve.external_advertise) + external_stop_resolving_service(request->u.resolve.qsrv.InterfaceID, &request->u.resolve.qsrv.qname, request->flags); +} + +mDNSlocal mStatus handle_resolve_request(request_state *request) +{ + char name[256], regtype[MAX_ESCAPED_DOMAIN_NAME], domain[MAX_ESCAPED_DOMAIN_NAME]; + domainname fqdn; + mStatus err; + + // extract the data from the message + DNSServiceFlags flags = get_flags(&request->msgptr, request->msgend); + mDNSu32 interfaceIndex = get_uint32(&request->msgptr, request->msgend); + mDNSInterfaceID InterfaceID; + + // Map kDNSServiceInterfaceIndexP2P to kDNSServiceInterfaceIndexAny with the kDNSServiceFlagsIncludeP2P + // flag set so that the resolve will run over P2P interfaces that are not yet created. + if (interfaceIndex == kDNSServiceInterfaceIndexP2P) + { + LogOperation("handle_resolve_request: mapping kDNSServiceInterfaceIndexP2P to kDNSServiceInterfaceIndexAny + kDNSServiceFlagsIncludeP2P"); + flags |= kDNSServiceFlagsIncludeP2P; + interfaceIndex = kDNSServiceInterfaceIndexAny; + } + + InterfaceID = mDNSPlatformInterfaceIDfromInterfaceIndex(&mDNSStorage, interfaceIndex); + if (interfaceIndex && !InterfaceID) + { LogMsg("ERROR: handle_resolve_request bad interfaceIndex %d", interfaceIndex); return(mStatus_BadParamErr); } + + if (get_string(&request->msgptr, request->msgend, name, 256) < 0 || + get_string(&request->msgptr, request->msgend, regtype, MAX_ESCAPED_DOMAIN_NAME) < 0 || + get_string(&request->msgptr, request->msgend, domain, MAX_ESCAPED_DOMAIN_NAME) < 0) + { LogMsg("ERROR: handle_resolve_request - Couldn't read name/regtype/domain"); return(mStatus_BadParamErr); } + + if (!request->msgptr) { LogMsg("%3d: DNSServiceResolve(unreadable parameters)", request->sd); return(mStatus_BadParamErr); } + + if (build_domainname_from_strings(&fqdn, name, regtype, domain) < 0) + { LogMsg("ERROR: handle_resolve_request bad “%s” “%s” “%s”", name, regtype, domain); return(mStatus_BadParamErr); } + + mDNSPlatformMemZero(&request->u.resolve, sizeof(request->u.resolve)); + + request->flags = flags; + + // format questions + request->u.resolve.qsrv.InterfaceID = InterfaceID; + request->u.resolve.qsrv.flags = flags; + request->u.resolve.qsrv.Target = zeroAddr; + AssignDomainName(&request->u.resolve.qsrv.qname, &fqdn); + request->u.resolve.qsrv.qtype = kDNSType_SRV; + request->u.resolve.qsrv.qclass = kDNSClass_IN; + request->u.resolve.qsrv.LongLived = (flags & kDNSServiceFlagsLongLivedQuery ) != 0; + request->u.resolve.qsrv.ExpectUnique = mDNStrue; + request->u.resolve.qsrv.ForceMCast = (flags & kDNSServiceFlagsForceMulticast ) != 0; + request->u.resolve.qsrv.ReturnIntermed = (flags & kDNSServiceFlagsReturnIntermediates) != 0; + request->u.resolve.qsrv.SuppressUnusable = mDNSfalse; + request->u.resolve.qsrv.SearchListIndex = 0; + request->u.resolve.qsrv.AppendSearchDomains = 0; + request->u.resolve.qsrv.RetryWithSearchDomains = mDNSfalse; + request->u.resolve.qsrv.TimeoutQuestion = 0; + request->u.resolve.qsrv.WakeOnResolve = (flags & kDNSServiceFlagsWakeOnResolve) != 0; + request->u.resolve.qsrv.UseBackgroundTrafficClass = (flags & kDNSServiceFlagsBackgroundTrafficClass) != 0; + request->u.resolve.qsrv.ValidationRequired = 0; + request->u.resolve.qsrv.ValidatingResponse = 0; + request->u.resolve.qsrv.ProxyQuestion = 0; + request->u.resolve.qsrv.qnameOrig = mDNSNULL; + request->u.resolve.qsrv.AnonInfo = mDNSNULL; + request->u.resolve.qsrv.pid = request->process_id; + request->u.resolve.qsrv.QuestionCallback = resolve_result_callback; + request->u.resolve.qsrv.QuestionContext = request; + + request->u.resolve.qtxt.InterfaceID = InterfaceID; + request->u.resolve.qtxt.flags = flags; + request->u.resolve.qtxt.Target = zeroAddr; + AssignDomainName(&request->u.resolve.qtxt.qname, &fqdn); + request->u.resolve.qtxt.qtype = kDNSType_TXT; + request->u.resolve.qtxt.qclass = kDNSClass_IN; + request->u.resolve.qtxt.LongLived = (flags & kDNSServiceFlagsLongLivedQuery ) != 0; + request->u.resolve.qtxt.ExpectUnique = mDNStrue; + request->u.resolve.qtxt.ForceMCast = (flags & kDNSServiceFlagsForceMulticast ) != 0; + request->u.resolve.qtxt.ReturnIntermed = (flags & kDNSServiceFlagsReturnIntermediates) != 0; + request->u.resolve.qtxt.SuppressUnusable = mDNSfalse; + request->u.resolve.qtxt.SearchListIndex = 0; + request->u.resolve.qtxt.AppendSearchDomains = 0; + request->u.resolve.qtxt.RetryWithSearchDomains = mDNSfalse; + request->u.resolve.qtxt.TimeoutQuestion = 0; + request->u.resolve.qtxt.WakeOnResolve = 0; + request->u.resolve.qtxt.UseBackgroundTrafficClass = (flags & kDNSServiceFlagsBackgroundTrafficClass) != 0; + request->u.resolve.qtxt.ValidationRequired = 0; + request->u.resolve.qtxt.ValidatingResponse = 0; + request->u.resolve.qtxt.ProxyQuestion = 0; + request->u.resolve.qtxt.qnameOrig = mDNSNULL; + request->u.resolve.qtxt.AnonInfo = mDNSNULL; + request->u.resolve.qtxt.pid = request->process_id; + request->u.resolve.qtxt.QuestionCallback = resolve_result_callback; + request->u.resolve.qtxt.QuestionContext = request; + + request->u.resolve.ReportTime = NonZeroTime(mDNS_TimeNow(&mDNSStorage) + 130 * mDNSPlatformOneSecond); + + request->u.resolve.external_advertise = mDNSfalse; + +#if 0 + if (!AuthorizedDomain(request, &fqdn, AutoBrowseDomains)) return(mStatus_NoError); +#endif + + // ask the questions + LogOperation("%3d: DNSServiceResolve(%X %d %##s) START PID[%d](%s)", request->sd, flags, interfaceIndex, + request->u.resolve.qsrv.qname.c, request->process_id, request->pid_name); + err = mDNS_StartQuery(&mDNSStorage, &request->u.resolve.qsrv); + + if (!err) + { + err = mDNS_StartQuery(&mDNSStorage, &request->u.resolve.qtxt); + if (err) + { + mDNS_StopQuery(&mDNSStorage, &request->u.resolve.qsrv); + } + else + { + request->terminate = resolve_termination_callback; + LogMcastQ(&mDNSStorage, &request->u.resolve.qsrv, request, q_start); + if (callExternalHelpers(InterfaceID, &fqdn, flags)) + { + request->u.resolve.external_advertise = mDNStrue; + LogInfo("handle_resolve_request: calling external_start_resolving_service()"); + external_start_resolving_service(InterfaceID, &fqdn, flags); + } + } + } + + return(err); +} + +// *************************************************************************** +#if COMPILER_LIKES_PRAGMA_MARK +#pragma mark - +#pragma mark - DNSServiceQueryRecord +#endif + +// mDNS operation functions. Each operation has 3 associated functions - a request handler that parses +// the client's request and makes the appropriate mDNSCore call, a result handler (passed as a callback +// to the mDNSCore routine) that sends results back to the client, and a termination routine that aborts +// the mDNSCore operation if the client dies or closes its socket. + +// Returns -1 to tell the caller that it should not try to reissue the query anymore +// Returns 1 on successfully appending a search domain and the caller should reissue the new query +// Returns 0 when there are no more search domains and the caller should reissue the query +mDNSlocal int AppendNewSearchDomain(mDNS *const m, DNSQuestion *question) +{ + domainname *sd; + mStatus err; + + // Sanity check: The caller already checks this. We use -1 to indicate that we have searched all + // the domains and should try the single label query directly on the wire. + if (question->SearchListIndex == -1) + { + LogMsg("AppendNewSearchDomain: question %##s (%s) SearchListIndex is -1", question->qname.c, DNSTypeName(question->qtype)); + return -1; + } + + if (!question->AppendSearchDomains) + { + LogMsg("AppendNewSearchDomain: question %##s (%s) AppendSearchDoamins is 0", question->qname.c, DNSTypeName(question->qtype)); + return -1; + } + + // Save the original name, before we modify them below. + if (!question->qnameOrig) + { + question->qnameOrig = mallocL("AppendNewSearchDomain", sizeof(domainname)); + if (!question->qnameOrig) { LogMsg("AppendNewSearchDomain: ERROR!! malloc failure"); return -1; } + question->qnameOrig->c[0] = 0; + AssignDomainName(question->qnameOrig, &question->qname); + LogInfo("AppendSearchDomain: qnameOrig %##s", question->qnameOrig->c); + } + + sd = uDNS_GetNextSearchDomain(m, question->InterfaceID, &question->SearchListIndex, !question->AppendLocalSearchDomains); + // We use -1 to indicate that we have searched all the domains and should try the single label + // query directly on the wire. uDNS_GetNextSearchDomain should never return a negative value + if (question->SearchListIndex == -1) + { + LogMsg("AppendNewSearchDomain: ERROR!! uDNS_GetNextSearchDomain returned -1"); + return -1; + } + + // Not a common case. Perhaps, we should try the next search domain if it exceeds ? + if (sd && (DomainNameLength(question->qnameOrig) + DomainNameLength(sd)) > MAX_DOMAIN_NAME) + { + LogMsg("AppendNewSearchDomain: ERROR!! exceeding max domain length for %##s (%s) SearchDomain %##s length %d, Question name length %d", question->qnameOrig->c, DNSTypeName(question->qtype), sd->c, DomainNameLength(question->qnameOrig), DomainNameLength(sd)); + return -1; + } + + // if there are no more search domains and we have already tried this question + // without appending search domains, then we are done. + if (!sd && !ApplySearchDomainsFirst(question)) + { + LogInfo("AppnedNewSearchDomain: No more search domains for question with name %##s (%s), not trying anymore", question->qname.c, DNSTypeName(question->qtype)); + return -1; + } + + // Stop the question before changing the name as negative cache entries could be pointing at this question. + // Even if we don't change the question in the case of returning 0, the caller is going to restart the + // question. + err = mDNS_StopQuery(&mDNSStorage, question); + if (err) { LogMsg("AppendNewSearchDomain: ERROR!! %##s %s mDNS_StopQuery: %d, while retrying with search domains", question->qname.c, DNSTypeName(question->qtype), (int)err); } + + AssignDomainName(&question->qname, question->qnameOrig); + if (sd) + { + AppendDomainName(&question->qname, sd); + LogInfo("AppnedNewSearchDomain: Returning question with name %##s, SearchListIndex %d", question->qname.c, question->SearchListIndex); + return 1; + } + + // Try the question as single label + LogInfo("AppnedNewSearchDomain: No more search domains for question with name %##s (%s), trying one last time", question->qname.c, DNSTypeName(question->qtype)); + return 0; +} + +#if APPLE_OSX_mDNSResponder + +mDNSlocal mDNSBool DomainInSearchList(const domainname *domain, mDNSBool excludeLocal) +{ + const SearchListElem *s; + int qcount, scount; + + qcount = CountLabels(domain); + for (s=SearchList; s; s=s->next) + { + if (excludeLocal && SameDomainName(&s->domain, &localdomain)) + continue; + scount = CountLabels(&s->domain); + if (qcount >= scount) + { + // Note: When qcount == scount, we do a complete match of the domain + // which is expected by the callers. + const domainname *d = SkipLeadingLabels(domain, (qcount - scount)); + if (SameDomainName(&s->domain, d)) + { + return mDNStrue; + } + } + } + return mDNSfalse; +} + +// The caller already checks that this is a dotlocal question. +mDNSlocal mDNSBool ShouldDeliverNegativeResponse(mDNS *const m, DNSQuestion *question) +{ + mDNSu16 qtype; + + // If the question matches the search domain exactly or the search domain is a + // subdomain of the question, it is most likely a valid unicast domain and hence + // don't suppress negative responses. + // + // If the user has configured ".local" as a search domain, we don't want + // to deliver a negative response for names ending in ".local" as that would + // prevent bonjour discovery. Passing mDNStrue for the last argument excludes + // ".local" search domains. + if (DomainInSearchList(&question->qname, mDNStrue)) + { + LogOperation("ShouldDeliverNegativeResponse: Question %##s (%s) in SearchList", question->qname.c, DNSTypeName(question->qtype)); + return mDNStrue; + } + + // Deliver negative response for A/AAAA if there was a positive response for AAAA/A respectively. + if (question->qtype != kDNSType_A && question->qtype != kDNSType_AAAA) + { + LogOperation("ShouldDeliverNegativeResponse: Question %##s (%s) not answering local question with negative unicast response", + question->qname.c, DNSTypeName(question->qtype)); + return mDNSfalse; + } + qtype = (question->qtype == kDNSType_A ? kDNSType_AAAA : kDNSType_A); + if (!mDNS_CheckForCacheRecord(m, question, qtype)) + { + LogOperation("ShouldDeliverNegativeResponse:Question %##s (%s) not answering local question with negative unicast response" + " (can't find positive record)", question->qname.c, DNSTypeName(question->qtype)); + return mDNSfalse; + } + LogOperation("ShouldDeliverNegativeResponse:Question %##s (%s) answering local with negative unicast response (found positive record)", + question->qname.c, DNSTypeName(question->qtype)); + return mDNStrue; +} + +// Workaround for networks using Microsoft Active Directory using "local" as a private internal +// top-level domain +mDNSlocal mStatus SendAdditionalQuery(DNSQuestion *q, request_state *request, mStatus err) +{ +#ifndef UNICAST_DISABLED + extern domainname ActiveDirectoryPrimaryDomain; + DNSQuestion **question2; + #define VALID_MSAD_SRV_TRANSPORT(T) (SameDomainLabel((T)->c, (const mDNSu8 *)"\x4_tcp") || SameDomainLabel((T)->c, (const mDNSu8 *)"\x4_udp")) + #define VALID_MSAD_SRV(Q) ((Q)->qtype == kDNSType_SRV && VALID_MSAD_SRV_TRANSPORT(SecondLabel(&(Q)->qname))) + + question2 = mDNSNULL; + if (request->hdr.op == query_request) + question2 = &request->u.queryrecord.q2; + else if (request->hdr.op == addrinfo_request) + { + if (q->qtype == kDNSType_A) + question2 = &request->u.addrinfo.q42; + else if (q->qtype == kDNSType_AAAA) + question2 = &request->u.addrinfo.q62; + } + if (!question2) + { + LogMsg("SendAdditionalQuery: question2 NULL for %##s (%s)", q->qname.c, DNSTypeName(q->qtype)); + return mStatus_BadParamErr; + } + + // Sanity check: If we already sent an additonal query, we don't need to send one more. + // + // 1. When the application calls DNSServiceQueryRecord or DNSServiceGetAddrInfo with a .local name, this function + // is called to see whether a unicast query should be sent or not. + // + // 2. As a result of appending search domains, the question may be end up with a .local suffix even though it + // was not a .local name to start with. In that case, queryrecord_result_callback calls this function to + // send the additional query. + // + // Thus, it should not be called more than once. + if (*question2) + { + LogInfo("SendAdditionalQuery: question2 already sent for %##s (%s), no more q2", q->qname.c, DNSTypeName(q->qtype)); + return err; + } + + if (!q->ForceMCast && SameDomainLabel(LastLabel(&q->qname), (const mDNSu8 *)&localdomain)) + if (q->qtype == kDNSType_A || q->qtype == kDNSType_AAAA || VALID_MSAD_SRV(q)) + { + DNSQuestion *q2; + int labels = CountLabels(&q->qname); + q2 = mallocL("DNSQuestion", sizeof(DNSQuestion)); + if (!q2) FatalError("ERROR: SendAdditionalQuery malloc"); + *question2 = q2; + *q2 = *q; + q2->InterfaceID = mDNSInterface_Unicast; + q2->ExpectUnique = mDNStrue; + // Always set the QuestionContext to indicate that this question should be stopped + // before freeing. Don't rely on "q". + q2->QuestionContext = request; + // If the query starts as a single label e.g., somehost, and we have search domains with .local, + // queryrecord_result_callback calls this function when .local is appended to "somehost". + // At that time, the name in "q" is pointing at somehost.local and its qnameOrig pointing at + // "somehost". We need to copy that information so that when we retry with a different search + // domain e.g., mycompany.local, we get "somehost.mycompany.local". + if (q->qnameOrig) + { + (*question2)->qnameOrig = mallocL("SendAdditionalQuery", DomainNameLength(q->qnameOrig)); + if (!(*question2)->qnameOrig) { LogMsg("SendAdditionalQuery: ERROR!! malloc failure"); return mStatus_NoMemoryErr; } + (*question2)->qnameOrig->c[0] = 0; + AssignDomainName((*question2)->qnameOrig, q->qnameOrig); + LogInfo("SendAdditionalQuery: qnameOrig %##s", (*question2)->qnameOrig->c); + } + // For names of the form "<one-or-more-labels>.bar.local." we always do a second unicast query in parallel. + // For names of the form "<one-label>.local." it's less clear whether we should do a unicast query. + // If the name being queried is exactly the same as the name in the DHCP "domain" option (e.g. the DHCP + // "domain" is my-small-company.local, and the user types "my-small-company.local" into their web browser) + // then that's a hint that it's worth doing a unicast query. Otherwise, we first check to see if the + // site's DNS server claims there's an SOA record for "local", and if so, that's also a hint that queries + // for names in the "local" domain will be safely answered privately before they hit the root name servers. + // Note that in the "my-small-company.local" example above there will typically be an SOA record for + // "my-small-company.local" but *not* for "local", which is why the "local SOA" check would fail in that case. + // We need to check against both ActiveDirectoryPrimaryDomain and SearchList. If it matches against either + // of those, we don't want do the SOA check for the local + if (labels == 2 && !SameDomainName(&q->qname, &ActiveDirectoryPrimaryDomain) && !DomainInSearchList(&q->qname, mDNSfalse)) + { + AssignDomainName(&q2->qname, &localdomain); + q2->qtype = kDNSType_SOA; + q2->LongLived = mDNSfalse; + q2->ForceMCast = mDNSfalse; + q2->ReturnIntermed = mDNStrue; + // Don't append search domains for the .local SOA query + q2->AppendSearchDomains = 0; + q2->AppendLocalSearchDomains = 0; + q2->RetryWithSearchDomains = mDNSfalse; + q2->SearchListIndex = 0; + q2->TimeoutQuestion = 0; + q2->AnonInfo = mDNSNULL; + q2->pid = request->process_id; + } + LogOperation("%3d: DNSServiceQueryRecord(%##s, %s) unicast", request->sd, q2->qname.c, DNSTypeName(q2->qtype)); + err = mDNS_StartQuery(&mDNSStorage, q2); + if (err) LogMsg("%3d: ERROR: DNSServiceQueryRecord %##s %s mDNS_StartQuery: %d", request->sd, q2->qname.c, DNSTypeName(q2->qtype), (int)err); + } + return(err); +#else // !UNICAST_DISABLED + (void) q; + (void) request; + (void) err; + + return mStatus_NoError; +#endif // !UNICAST_DISABLED +} +#endif // APPLE_OSX_mDNSResponder + +// This function tries to append a search domain if valid and possible. If so, returns true. +mDNSlocal mDNSBool RetryQuestionWithSearchDomains(mDNS *const m, DNSQuestion *question, request_state *req, QC_result AddRecord) +{ + int result; + // RetryWithSearchDomains tells the core to call us back so that we can retry with search domains if there is no + // answer in the cache or /etc/hosts. In the first call back from the core, we clear RetryWithSearchDomains so + // that we don't get called back repeatedly. If we got an answer from the cache or /etc/hosts, we don't touch + // RetryWithSearchDomains which may or may not be set. + // + // If we get e.g., NXDOMAIN and the query is neither suppressed nor exhausted the domain search list and + // is a valid question for appending search domains, retry by appending domains + + if ((AddRecord != QC_suppressed) && question->SearchListIndex != -1 && question->AppendSearchDomains) + { + question->RetryWithSearchDomains = 0; + result = AppendNewSearchDomain(m, question); + // As long as the result is either zero or 1, we retry the question. If we exahaust the search + // domains (result is zero) we try the original query (as it was before appending the search + // domains) as such on the wire as a last resort if we have not tried them before. For queries + // with more than one label, we have already tried them before appending search domains and + // hence don't retry again + if (result != -1) + { + mStatus err; + err = mDNS_StartQuery(m, question); + if (!err) + { + LogOperation("%3d: RetryQuestionWithSearchDomains(%##s, %s), retrying after appending search domain", req->sd, question->qname.c, DNSTypeName(question->qtype)); + // If the result was zero, it meant that there are no search domains and we just retried the question + // as a single label and we should not retry with search domains anymore. + if (!result) question->SearchListIndex = -1; + return mDNStrue; + } + else + { + LogMsg("%3d: ERROR: RetryQuestionWithSearchDomains %##s %s mDNS_StartQuery: %d, while retrying with search domains", req->sd, question->qname.c, DNSTypeName(question->qtype), (int)err); + // We have already stopped the query and could not restart. Reset the appropriate pointers + // so that we don't call stop again when the question terminates + question->QuestionContext = mDNSNULL; + } + } + } + else + { + LogInfo("%3d: RetryQuestionWithSearchDomains: Not appending search domains - SuppressQuery %d, SearchListIndex %d, AppendSearchDomains %d", req->sd, AddRecord, question->SearchListIndex, question->AppendSearchDomains); + } + return mDNSfalse; +} + +mDNSlocal void queryrecord_result_reply(mDNS *const m, request_state *req, DNSQuestion *question, const ResourceRecord *const answer, QC_result AddRecord, + DNSServiceErrorType error) +{ + char name[MAX_ESCAPED_DOMAIN_NAME]; + size_t len; + DNSServiceFlags flags = 0; + reply_state *rep; + char *data; + + ConvertDomainNameToCString(answer->name, name); + + LogOperation("%3d: %s(%##s, %s) %s %s", req->sd, + req->hdr.op == query_request ? "DNSServiceQueryRecord" : "DNSServiceGetAddrInfo", + question->qname.c, DNSTypeName(question->qtype), AddRecord ? "ADD" : "RMV", RRDisplayString(m, answer)); + + len = sizeof(DNSServiceFlags); // calculate reply data length + len += sizeof(mDNSu32); // interface index + len += sizeof(DNSServiceErrorType); + len += strlen(name) + 1; + len += 3 * sizeof(mDNSu16); // type, class, rdlen + len += answer->rdlength; + len += sizeof(mDNSu32); // TTL + + rep = create_reply(req->hdr.op == query_request ? query_reply_op : addrinfo_reply_op, len, req); + + if (AddRecord) + flags |= kDNSServiceFlagsAdd; + if (question->ValidationStatus != 0) + { + error = kDNSServiceErr_NoError; + if (question->ValidationRequired && question->ValidationState == DNSSECValDone) + { + switch (question->ValidationStatus) //Set the dnssec flags to be passed on to the Apps here + { + case DNSSEC_Secure: + flags |= kDNSServiceFlagsSecure; + break; + case DNSSEC_Insecure: + flags |= kDNSServiceFlagsInsecure; + break; + case DNSSEC_Indeterminate: + flags |= kDNSServiceFlagsIndeterminate; + break; + case DNSSEC_Bogus: + flags |= kDNSServiceFlagsBogus; + break; + default: + LogMsg("queryrecord_result_reply unknown status %d for %##s", question->ValidationStatus, question->qname.c); + } + } + } + + rep->rhdr->flags = dnssd_htonl(flags); + // Call mDNSPlatformInterfaceIndexfromInterfaceID, but suppressNetworkChange (last argument). Otherwise, if the + // InterfaceID is not valid, then it simulates a "NetworkChanged" which in turn makes questions + // to be stopped and started including *this* one. Normally the InterfaceID is valid. But when we + // are using the /etc/hosts entries to answer a question, the InterfaceID may not be known to the + // mDNS core . Eventually, we should remove the calls to "NetworkChanged" in + // mDNSPlatformInterfaceIndexfromInterfaceID when it can't find InterfaceID as ResourceRecords + // should not have existed to answer this question if the corresponding interface is not valid. + rep->rhdr->ifi = dnssd_htonl(mDNSPlatformInterfaceIndexfromInterfaceID(m, answer->InterfaceID, mDNStrue)); + rep->rhdr->error = dnssd_htonl(error); + + data = (char *)&rep->rhdr[1]; + + put_string(name, &data); + put_uint16(answer->rrtype, &data); + put_uint16(answer->rrclass, &data); + put_uint16(answer->rdlength, &data); + // We need to use putRData here instead of the crude put_rdata function, because the crude put_rdata + // function just does a blind memory copy without regard to structures that may have holes in them. + if (answer->rdlength) + if (!putRData(mDNSNULL, (mDNSu8 *)data, (mDNSu8 *)rep->rhdr + len, answer)) + LogMsg("queryrecord_result_reply putRData failed %d", (mDNSu8 *)rep->rhdr + len - (mDNSu8 *)data); + data += answer->rdlength; + put_uint32(AddRecord ? answer->rroriginalttl : 0, &data); + + append_reply(req, rep); + // Stop the question, if we just timed out + if (error == kDNSServiceErr_Timeout) + { + mDNS_StopQuery(m, question); + // Reset the pointers so that we don't call stop on termination + question->QuestionContext = mDNSNULL; + } + else if ((AddRecord == QC_add) && req->hdr.op == addrinfo_request) + { + // Note: We count all answers including LocalOnly e.g., /etc/hosts. If we + // exclude that, v4ans/v6ans will be zero and we would wrongly think that + // we did not answer questions and setup the status to deliver triggers. + if (question->qtype == kDNSType_A) + req->u.addrinfo.v4ans = 1; + if (question->qtype == kDNSType_AAAA) + req->u.addrinfo.v6ans = 1; + } + else if ((AddRecord == QC_add) && req->hdr.op == query_request) + { + if (question->qtype == kDNSType_A || question->qtype == kDNSType_AAAA) + req->u.queryrecord.ans = 1; + } + +#if APPLE_OSX_mDNSResponder +#if !NO_WCF + CHECK_WCF_FUNCTION(WCFIsServerRunning) + { + struct xucred x; + socklen_t xucredlen = sizeof(x); + + if (WCFIsServerRunning((WCFConnection *)m->WCF) && answer->rdlength != 0) + { + if (getsockopt(req->sd, 0, LOCAL_PEERCRED, &x, &xucredlen) >= 0 && + (x.cr_version == XUCRED_VERSION)) + { + struct sockaddr_storage addr; + const RDataBody2 *const rdb = (RDataBody2 *)answer->rdata->u.data; + addr.ss_len = 0; + if (answer->rrtype == kDNSType_A || answer->rrtype == kDNSType_AAAA) + { + if (answer->rrtype == kDNSType_A) + { + struct sockaddr_in *sin = (struct sockaddr_in *)&addr; + sin->sin_port = 0; + if (!putRData(mDNSNULL, (mDNSu8 *)&sin->sin_addr, (mDNSu8 *)(&sin->sin_addr + sizeof(rdb->ipv4)), answer)) + LogMsg("queryrecord_result_reply: WCF AF_INET putRData failed"); + else + { + addr.ss_len = sizeof (struct sockaddr_in); + addr.ss_family = AF_INET; + } + } + else if (answer->rrtype == kDNSType_AAAA) + { + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&addr; + sin6->sin6_port = 0; + if (!putRData(mDNSNULL, (mDNSu8 *)&sin6->sin6_addr, (mDNSu8 *)(&sin6->sin6_addr + sizeof(rdb->ipv6)), answer)) + LogMsg("queryrecord_result_reply: WCF AF_INET6 putRData failed"); + else + { + addr.ss_len = sizeof (struct sockaddr_in6); + addr.ss_family = AF_INET6; + } + } + if (addr.ss_len) + { + debugf("queryrecord_result_reply: Name %s, uid %u, addr length %d", name, x.cr_uid, addr.ss_len); + CHECK_WCF_FUNCTION((WCFConnection *)WCFNameResolvesToAddr) + { + WCFNameResolvesToAddr(m->WCF, name, (struct sockaddr *)&addr, x.cr_uid); + } + } + } + else if (answer->rrtype == kDNSType_CNAME) + { + domainname cname; + char cname_cstr[MAX_ESCAPED_DOMAIN_NAME]; + if (!putRData(mDNSNULL, cname.c, (mDNSu8 *)(cname.c + MAX_DOMAIN_NAME), answer)) + LogMsg("queryrecord_result_reply: WCF CNAME putRData failed"); + else + { + ConvertDomainNameToCString(&cname, cname_cstr); + CHECK_WCF_FUNCTION((WCFConnection *)WCFNameResolvesToAddr) + { + WCFNameResolvesToName(m->WCF, name, cname_cstr, x.cr_uid); + } + } + } + } + else my_perror("queryrecord_result_reply: ERROR: getsockopt LOCAL_PEERCRED"); + } + } +#endif +#endif +} + +mDNSlocal void queryrecord_result_callback(mDNS *const m, DNSQuestion *question, const ResourceRecord *const answer, QC_result AddRecord) +{ + request_state *req = question->QuestionContext; + DNSServiceErrorType error = kDNSServiceErr_NoError; + DNSQuestion *q = mDNSNULL; + +#if APPLE_OSX_mDNSResponder + { + // Sanity check: QuestionContext is set to NULL after we stop the question and hence we should not + // get any callbacks from the core after this. + if (!req) + { + LogMsg("queryrecord_result_callback: ERROR!! QuestionContext NULL for %##s (%s)", question->qname.c, DNSTypeName(question->qtype)); + return; + } + if (req->hdr.op == query_request && question == req->u.queryrecord.q2) + q = &req->u.queryrecord.q; + else if (req->hdr.op == addrinfo_request && question == req->u.addrinfo.q42) + q = &req->u.addrinfo.q4; + else if (req->hdr.op == addrinfo_request && question == req->u.addrinfo.q62) + q = &req->u.addrinfo.q6; + + if (q && question->qtype != q->qtype && !SameDomainName(&question->qname, &q->qname)) + { + mStatus err; + domainname *orig = question->qnameOrig; + + LogInfo("queryrecord_result_callback: Stopping q2 local %##s", question->qname.c); + mDNS_StopQuery(m, question); + question->QuestionContext = mDNSNULL; + + // We got a negative response for the SOA record indicating that .local does not exist. + // But we might have other search domains (that does not end in .local) that can be + // appended to this question. In that case, we want to retry the question. Otherwise, + // we don't want to try this question as unicast. + if (answer->RecordType == kDNSRecordTypePacketNegative && !q->AppendSearchDomains) + { + LogInfo("queryrecord_result_callback: question %##s AppendSearchDomains zero", q->qname.c); + return; + } + + // If we got a non-negative answer for our "local SOA" test query, start an additional parallel unicast query + // + // Note: When we copy the original question, we copy everything including the AppendSearchDomains, + // RetryWithSearchDomains except for qnameOrig which can be non-NULL if the original question is + // e.g., somehost and then we appended e.g., ".local" and retried that question. See comment in + // SendAdditionalQuery as to how qnameOrig gets initialized. + *question = *q; + question->InterfaceID = mDNSInterface_Unicast; + question->ExpectUnique = mDNStrue; + question->qnameOrig = orig; + + LogOperation("%3d: DNSServiceQueryRecord(%##s, %s) unicast, context %p", req->sd, question->qname.c, DNSTypeName(question->qtype), question->QuestionContext); + + // If the original question timed out, its QuestionContext would already be set to NULL and that's what we copied above. + // Hence, we need to set it explicitly here. + question->QuestionContext = req; + err = mDNS_StartQuery(m, question); + if (err) LogMsg("%3d: ERROR: queryrecord_result_callback %##s %s mDNS_StartQuery: %d", req->sd, question->qname.c, DNSTypeName(question->qtype), (int)err); + + // If we got a positive response to local SOA, then try the .local question as unicast + if (answer->RecordType != kDNSRecordTypePacketNegative) return; + + // Fall through and get the next search domain. The question is pointing at .local + // and we don't want to try that. Try the next search domain. Don't try with local + // search domains for the unicast question anymore. + // + // Note: we started the question above which will be stopped immediately (never sent on the wire) + // before we pick the next search domain below. RetryQuestionWithSearchDomains assumes that the + // question has already started. + question->AppendLocalSearchDomains = 0; + } + + if (q && AddRecord && AddRecord != QC_dnssec && (question->InterfaceID == mDNSInterface_Unicast) && !answer->rdlength) + { + // If we get a negative response to the unicast query that we sent above, retry after appending search domains + // Note: We could have appended search domains below (where do it for regular unicast questions) instead of doing it here. + // As we ignore negative unicast answers below, we would never reach the code where the search domains are appended. + // To keep things simple, we handle unicast ".local" separately here. + LogInfo("queryrecord_result_callback: Retrying .local question %##s (%s) as unicast after appending search domains", question->qname.c, DNSTypeName(question->qtype)); + if (RetryQuestionWithSearchDomains(m, question, req, AddRecord)) + return; + if (question->AppendSearchDomains && !question->AppendLocalSearchDomains && IsLocalDomain(&question->qname)) + { + // If "local" is the last search domain, we need to stop the question so that we don't send the "local" + // question on the wire as we got a negative response for the local SOA. But, we can't stop the question + // yet as we may have to timeout the question (done by the "core") for which we need to leave the question + // in the list. We leave it disabled so that it does not hit the wire. + LogInfo("queryrecord_result_callback: Disabling .local question %##s (%s)", question->qname.c, DNSTypeName(question->qtype)); + question->ThisQInterval = 0; + } + } + // If we are here it means that either "question" is not "q2" OR we got a positive response for "q2" OR we have no more search + // domains to append for "q2". In all cases, fall through and deliver the response + } +#endif // APPLE_OSX_mDNSResponder + + // If a query is being suppressed for some reason, we don't have to do any other + // processing. + // + // Note: We don't check for "SuppressQuery" and instead use QC_suppressed because + // the "core" needs to temporarily turn off SuppressQuery to answer this query. + if (AddRecord == QC_suppressed) + { + LogInfo("queryrecord_result_callback: Suppressed question %##s (%s)", question->qname.c, DNSTypeName(question->qtype)); + queryrecord_result_reply(m, req, question, answer, AddRecord, kDNSServiceErr_NoSuchRecord); + return; + } + + if (answer->RecordType == kDNSRecordTypePacketNegative) + { + // If this question needs to be timed out and we have reached the stop time, mark + // the error as timeout. It is possible that we might get a negative response from an + // external DNS server at the same time when this question reaches its stop time. We + // can't tell the difference as there is no indication in the callback. This should + // be okay as we will be timing out this query anyway. + mDNS_Lock(m); + if (question->TimeoutQuestion) + { + if ((m->timenow - question->StopTime) >= 0) + { + LogInfo("queryrecord_result_callback:Question %##s (%s) timing out, InterfaceID %p", question->qname.c, DNSTypeName(question->qtype), question->InterfaceID); + error = kDNSServiceErr_Timeout; + } + } + mDNS_Unlock(m); + // When we're doing parallel unicast and multicast queries for dot-local names (for supporting Microsoft + // Active Directory sites) we need to ignore negative unicast answers. Otherwise we'll generate negative + // answers for just about every single multicast name we ever look up, since the Microsoft Active Directory + // server is going to assert that pretty much every single multicast name doesn't exist. + // + // If we are timing out this query, we need to deliver the negative answer to the application + if (error != kDNSServiceErr_Timeout) + { + if (!answer->InterfaceID && IsLocalDomain(answer->name)) + { + // Sanity check: "q" will be set only if "question" is the .local unicast query. + if (!q) + { + LogMsg("queryrecord_result_callback: ERROR!! answering multicast question %s with unicast cache record", + RRDisplayString(m, answer)); + return; + } +#if APPLE_OSX_mDNSResponder + if (!ShouldDeliverNegativeResponse(m, question)) + { + return; + } +#endif // APPLE_OSX_mDNSResponder + LogInfo("queryrecord_result_callback:Question %##s (%s) answering local with negative unicast response", question->qname.c, + DNSTypeName(question->qtype)); + } + error = kDNSServiceErr_NoSuchRecord; + } + } + // If we get a negative answer, try appending search domains. Don't append search domains + // - if we are timing out this question + // - if the negative response was received as a result of a multicast query + // - if this is an additional query (q2), we already appended search domains above (indicated by "!q" below) + // - if this response is forced e.g., dnssec validation result + if (error != kDNSServiceErr_Timeout) + { + if (!q && !answer->InterfaceID && !answer->rdlength && AddRecord && AddRecord != QC_dnssec) + { + // If the original question did not end in .local, we did not send an SOA query + // to figure out whether we should send an additional unicast query or not. If we just + // appended .local, we need to see if we need to send an additional query. This should + // normally happen just once because after we append .local, we ignore all negative + // responses for .local above. + LogInfo("queryrecord_result_callback: Retrying question %##s (%s) after appending search domains", question->qname.c, DNSTypeName(question->qtype)); + if (RetryQuestionWithSearchDomains(m, question, req, AddRecord)) + { + // Note: We need to call SendAdditionalQuery every time after appending a search domain as .local could + // be anywhere in the search domain list. +#if APPLE_OSX_mDNSResponder + mStatus err = mStatus_NoError; + err = SendAdditionalQuery(question, req, err); + if (err) LogMsg("queryrecord_result_callback: Sending .local SOA query failed, after appending domains"); +#endif // APPLE_OSX_mDNSResponder + return; + } + } + } + queryrecord_result_reply(m, req, question, answer, AddRecord, error); +} + +mDNSlocal void queryrecord_termination_callback(request_state *request) +{ + LogOperation("%3d: DNSServiceQueryRecord(%##s, %s) STOP PID[%d](%s)", + request->sd, request->u.queryrecord.q.qname.c, DNSTypeName(request->u.queryrecord.q.qtype), request->process_id, request->pid_name); + if (request->u.queryrecord.q.QuestionContext) + { + mDNS_StopQuery(&mDNSStorage, &request->u.queryrecord.q); // no need to error check + LogMcastQ(&mDNSStorage, &request->u.queryrecord.q, request, q_stop); + request->u.queryrecord.q.QuestionContext = mDNSNULL; + } + else + { + DNSQuestion *question = &request->u.queryrecord.q; + LogInfo("queryrecord_termination_callback: question %##s (%s) already stopped, InterfaceID %p", question->qname.c, DNSTypeName(question->qtype), question->InterfaceID); + } + + if (request->u.queryrecord.q.qnameOrig) + { + freeL("QueryTermination", request->u.queryrecord.q.qnameOrig); + request->u.queryrecord.q.qnameOrig = mDNSNULL; + } + + if (callExternalHelpers(request->u.queryrecord.q.InterfaceID, &request->u.queryrecord.q.qname, request->flags)) + { + LogInfo("queryrecord_termination_callback: calling external_stop_browsing_for_service()"); + external_stop_browsing_for_service(request->u.queryrecord.q.InterfaceID, &request->u.queryrecord.q.qname, request->u.queryrecord.q.qtype, request->flags); + } + if (request->u.queryrecord.q2) + { + if (request->u.queryrecord.q2->QuestionContext) + { + LogInfo("queryrecord_termination_callback: Stopping q2 %##s", request->u.queryrecord.q2->qname.c); + mDNS_StopQuery(&mDNSStorage, request->u.queryrecord.q2); + LogMcastQ(&mDNSStorage, request->u.queryrecord.q2, request, q_stop); + } + else + { + DNSQuestion *question = request->u.queryrecord.q2; + LogInfo("queryrecord_termination_callback: q2 %##s (%s) already stopped, InterfaceID %p", question->qname.c, DNSTypeName(question->qtype), question->InterfaceID); + } + if (request->u.queryrecord.q2->qnameOrig) + { + LogInfo("queryrecord_termination_callback: freeing q2 qnameOrig %##s", request->u.queryrecord.q2->qnameOrig->c); + freeL("QueryTermination q2", request->u.queryrecord.q2->qnameOrig); + request->u.queryrecord.q2->qnameOrig = mDNSNULL; + } + freeL("queryrecord Q2", request->u.queryrecord.q2); + request->u.queryrecord.q2 = mDNSNULL; + } +#if APPLE_OSX_mDNSResponder + { + if (request->u.queryrecord.ans) + { + DNSQuestion *v4q, *v6q; + // If we are receiving poisitive answers, provide the hint to the + // upper layer. + v4q = v6q = mDNSNULL; + if (request->u.queryrecord.q.qtype == kDNSType_A) + v4q = &request->u.queryrecord.q; + else if (request->u.queryrecord.q.qtype == kDNSType_AAAA) + v6q = &request->u.queryrecord.q; + mDNSPlatformTriggerDNSRetry(&mDNSStorage, v4q, v6q); + } + } +#endif // APPLE_OSX_mDNSResponder +} + +mDNSlocal void SetQuestionPolicy(DNSQuestion *q, request_state *req) +{ + int i; + + // The policy is either based on pid or UUID. Pass a zero pid + // to the "core" if the UUID is valid. If we always pass the pid, + // then the "core" needs to determine whether the uuid is valid + // by examining all the 16 bytes at the time of the policy + // check and also when setting the delegate socket option. Also, it + // requires that we zero out the uuid wherever the question is + // initialized to make sure that it is not interpreted as valid. + // To prevent these intrusive changes, just pass a zero pid to indicate + // that pid is not valid when uuid is valid. In future if we need the + // pid in the question, we will reevaluate this strategy. + if (req->validUUID) + { + for (i = 0; i < UUID_SIZE; i++) + { + q->uuid[i] = req->uuid[i]; + } + q->pid = 0; + } + else + { + q->pid = req->process_id; + } +} + +mDNSlocal mStatus handle_queryrecord_request(request_state *request) +{ + DNSQuestion *const q = &request->u.queryrecord.q; + char name[256]; + mDNSu16 rrtype, rrclass; + mStatus err; + + DNSServiceFlags flags = get_flags(&request->msgptr, request->msgend); + mDNSu32 interfaceIndex = get_uint32(&request->msgptr, request->msgend); + mDNSInterfaceID InterfaceID = mDNSPlatformInterfaceIDfromInterfaceIndex(&mDNSStorage, interfaceIndex); + if (interfaceIndex && !InterfaceID) return(mStatus_BadParamErr); + + if (get_string(&request->msgptr, request->msgend, name, 256) < 0) return(mStatus_BadParamErr); + rrtype = get_uint16(&request->msgptr, request->msgend); + rrclass = get_uint16(&request->msgptr, request->msgend); + + if (!request->msgptr) + { LogMsg("%3d: DNSServiceQueryRecord(unreadable parameters)", request->sd); return(mStatus_BadParamErr); } + + request->flags = flags; + mDNSPlatformMemZero(&request->u.queryrecord, sizeof(request->u.queryrecord)); + + q->InterfaceID = InterfaceID; + q->flags = flags; + q->Target = zeroAddr; + if (!MakeDomainNameFromDNSNameString(&q->qname, name)) return(mStatus_BadParamErr); +#if 0 + if (!AuthorizedDomain(request, &q->qname, AutoBrowseDomains)) return (mStatus_NoError); +#endif + q->qtype = rrtype; + q->qclass = rrclass; + q->LongLived = (flags & kDNSServiceFlagsLongLivedQuery ) != 0; + q->ExpectUnique = mDNSfalse; + q->ForceMCast = (flags & kDNSServiceFlagsForceMulticast ) != 0; + q->ReturnIntermed = (flags & kDNSServiceFlagsReturnIntermediates) != 0; + q->SuppressUnusable = (flags & kDNSServiceFlagsSuppressUnusable ) != 0; + q->TimeoutQuestion = (flags & kDNSServiceFlagsTimeout ) != 0; + q->WakeOnResolve = 0; + q->UseBackgroundTrafficClass = (flags & kDNSServiceFlagsBackgroundTrafficClass) != 0; + if ((flags & kDNSServiceFlagsValidate) != 0) + q->ValidationRequired = DNSSEC_VALIDATION_SECURE; + else if ((flags & kDNSServiceFlagsValidateOptional) != 0) + q->ValidationRequired = DNSSEC_VALIDATION_SECURE_OPTIONAL; + q->ValidatingResponse = 0; + q->ProxyQuestion = 0; + q->AnonInfo = mDNSNULL; + q->QuestionCallback = queryrecord_result_callback; + q->QuestionContext = request; + q->SearchListIndex = 0; + + q->DNSSECAuthInfo = mDNSNULL; + q->DAIFreeCallback = mDNSNULL; + + //Turn off dnssec validation for local domains and Question Types: RRSIG/ANY(ANY Type is not supported yet) + if ((IsLocalDomain(&q->qname)) || (q->qtype == kDNSServiceType_RRSIG) || (q->qtype == kDNSServiceType_ANY)) + q->ValidationRequired = 0; + + // Don't append search domains for fully qualified domain names including queries + // such as e.g., "abc." that has only one label. We convert all names to FQDNs as internally + // we only deal with FQDNs. Hence, we cannot look at qname to figure out whether we should + // append search domains or not. So, we record that information in AppendSearchDomains. + // + // We append search domains only for queries that are a single label. If overriden using command line + // argument "AlwaysAppendSearchDomains", then we do it for any query which is not fully qualified. + // For DNSSEC questions, append search domains only if kDNSServiceFlagsValidateOptional is set. + + if ((!(q->ValidationRequired == DNSSEC_VALIDATION_SECURE)) && (!(q->ValidationRequired == DNSSEC_VALIDATION_INSECURE)) + && (rrtype == kDNSType_A || rrtype == kDNSType_AAAA) && name[strlen(name) - 1] != '.' && + (AlwaysAppendSearchDomains || CountLabels(&q->qname) == 1)) + { + q->AppendSearchDomains = 1; + q->AppendLocalSearchDomains = 1; + } + else + { + q->AppendSearchDomains = 0; + q->AppendLocalSearchDomains = 0; + } + + // For single label queries that are not fully qualified, look at /etc/hosts, cache and try + // search domains before trying them on the wire as a single label query. RetryWithSearchDomains + // tell the core to call back into the UDS layer if there is no valid response in /etc/hosts or + // the cache + q->RetryWithSearchDomains = ApplySearchDomainsFirst(q) ? 1 : 0; + q->qnameOrig = mDNSNULL; + SetQuestionPolicy(q, request); + + LogOperation("%3d: DNSServiceQueryRecord(%X, %d, %##s, %s) START PID[%d](%s)", + request->sd, flags, interfaceIndex, q->qname.c, DNSTypeName(q->qtype), request->process_id, request->pid_name); + err = mDNS_StartQuery(&mDNSStorage, q); + + if (err) + LogMsg("%3d: ERROR: DNSServiceQueryRecord %##s %s mDNS_StartQuery: %d", request->sd, q->qname.c, DNSTypeName(q->qtype), (int)err); + else + { + request->terminate = queryrecord_termination_callback; + LogMcastQ(&mDNSStorage, q, request, q_start); + if (callExternalHelpers(q->InterfaceID, &q->qname, flags)) + { + LogInfo("handle_queryrecord_request: calling external_start_browsing_for_service()"); + external_start_browsing_for_service(q->InterfaceID, &q->qname, q->qtype, flags); + } + } + +#if APPLE_OSX_mDNSResponder + err = SendAdditionalQuery(q, request, err); +#endif // APPLE_OSX_mDNSResponder + + return(err); +} + +// *************************************************************************** +#if COMPILER_LIKES_PRAGMA_MARK +#pragma mark - +#pragma mark - DNSServiceEnumerateDomains +#endif + +mDNSlocal reply_state *format_enumeration_reply(request_state *request, + const char *domain, DNSServiceFlags flags, mDNSu32 ifi, DNSServiceErrorType err) +{ + size_t len; + reply_state *reply; + char *data; + + len = sizeof(DNSServiceFlags); + len += sizeof(mDNSu32); + len += sizeof(DNSServiceErrorType); + len += strlen(domain) + 1; + + reply = create_reply(enumeration_reply_op, len, request); + reply->rhdr->flags = dnssd_htonl(flags); + reply->rhdr->ifi = dnssd_htonl(ifi); + reply->rhdr->error = dnssd_htonl(err); + data = (char *)&reply->rhdr[1]; + put_string(domain, &data); + return reply; +} + +mDNSlocal void enum_termination_callback(request_state *request) +{ + // Stop the domain enumeration queries to discover the WAB Browse/Registration domains + if (request->u.enumeration.flags & kDNSServiceFlagsRegistrationDomains) + { + LogInfo("%3d: DNSServiceEnumeration Cancel WAB Registration PID[%d](%s)", request->sd, request->process_id, request->pid_name); + uDNS_StopWABQueries(&mDNSStorage, UDNS_WAB_REG_QUERY); + } + else + { + LogInfo("%3d: DNSServiceEnumeration Cancel WAB Browse PID[%d](%s)", request->sd, request->process_id, request->pid_name); + uDNS_StopWABQueries(&mDNSStorage, UDNS_WAB_BROWSE_QUERY); + } + mDNS_StopGetDomains(&mDNSStorage, &request->u.enumeration.q_all); + mDNS_StopGetDomains(&mDNSStorage, &request->u.enumeration.q_default); +} + +mDNSlocal void enum_result_callback(mDNS *const m, + DNSQuestion *const question, const ResourceRecord *const answer, QC_result AddRecord) +{ + char domain[MAX_ESCAPED_DOMAIN_NAME]; + request_state *request = question->QuestionContext; + DNSServiceFlags flags = 0; + reply_state *reply; + (void)m; // Unused + + if (answer->rrtype != kDNSType_PTR) return; + +#if 0 + if (!AuthorizedDomain(request, &answer->rdata->u.name, request->u.enumeration.flags ? AutoRegistrationDomains : AutoBrowseDomains)) return; +#endif + + // We only return add/remove events for the browse and registration lists + // For the default browse and registration answers, we only give an "ADD" event + if (question == &request->u.enumeration.q_default && !AddRecord) return; + + if (AddRecord) + { + flags |= kDNSServiceFlagsAdd; + if (question == &request->u.enumeration.q_default) flags |= kDNSServiceFlagsDefault; + } + + ConvertDomainNameToCString(&answer->rdata->u.name, domain); + // Note that we do NOT propagate specific interface indexes to the client - for example, a domain we learn from + // a machine's system preferences may be discovered on the LocalOnly interface, but should be browsed on the + // network, so we just pass kDNSServiceInterfaceIndexAny + reply = format_enumeration_reply(request, domain, flags, kDNSServiceInterfaceIndexAny, kDNSServiceErr_NoError); + if (!reply) { LogMsg("ERROR: enum_result_callback, format_enumeration_reply"); return; } + + LogOperation("%3d: DNSServiceEnumerateDomains(%#2s) RESULT %s: %s", request->sd, question->qname.c, AddRecord ? "Add" : "Rmv", domain); + + append_reply(request, reply); +} + +mDNSlocal mStatus handle_enum_request(request_state *request) +{ + mStatus err; + DNSServiceFlags flags = get_flags(&request->msgptr, request->msgend); + DNSServiceFlags reg = flags & kDNSServiceFlagsRegistrationDomains; + mDNS_DomainType t_all = reg ? mDNS_DomainTypeRegistration : mDNS_DomainTypeBrowse; + mDNS_DomainType t_default = reg ? mDNS_DomainTypeRegistrationDefault : mDNS_DomainTypeBrowseDefault; + mDNSu32 interfaceIndex = get_uint32(&request->msgptr, request->msgend); + mDNSInterfaceID InterfaceID = mDNSPlatformInterfaceIDfromInterfaceIndex(&mDNSStorage, interfaceIndex); + if (interfaceIndex && !InterfaceID) return(mStatus_BadParamErr); + + if (!request->msgptr) + { LogMsg("%3d: DNSServiceEnumerateDomains(unreadable parameters)", request->sd); return(mStatus_BadParamErr); } + + // mark which kind of enumeration we're doing so that we know what domain enumeration queries to stop + request->u.enumeration.flags = reg; + + // enumeration requires multiple questions, so we must link all the context pointers so that + // necessary context can be reached from the callbacks + request->u.enumeration.q_all.QuestionContext = request; + request->u.enumeration.q_default.QuestionContext = request; + + // if the caller hasn't specified an explicit interface, we use local-only to get the system-wide list. + if (!InterfaceID) InterfaceID = mDNSInterface_LocalOnly; + + // make the calls + LogOperation("%3d: DNSServiceEnumerateDomains(%X=%s)", request->sd, flags, + (flags & kDNSServiceFlagsBrowseDomains ) ? "kDNSServiceFlagsBrowseDomains" : + (flags & kDNSServiceFlagsRegistrationDomains) ? "kDNSServiceFlagsRegistrationDomains" : "<<Unknown>>"); + err = mDNS_GetDomains(&mDNSStorage, &request->u.enumeration.q_all, t_all, NULL, InterfaceID, enum_result_callback, request); + if (!err) + { + err = mDNS_GetDomains(&mDNSStorage, &request->u.enumeration.q_default, t_default, NULL, InterfaceID, enum_result_callback, request); + if (err) mDNS_StopGetDomains(&mDNSStorage, &request->u.enumeration.q_all); + else request->terminate = enum_termination_callback; + } + if (!err) + { + // Start the domain enumeration queries to discover the WAB Browse/Registration domains + if (reg) + { + LogInfo("%3d: DNSServiceEnumerateDomains Start WAB Registration PID[%d](%s)", request->sd, request->process_id, request->pid_name); + uDNS_StartWABQueries(&mDNSStorage, UDNS_WAB_REG_QUERY); + } + else + { + LogInfo("%3d: DNSServiceEnumerateDomains Start WAB Browse PID[%d](%s)", request->sd, request->process_id, request->pid_name); + uDNS_StartWABQueries(&mDNSStorage, UDNS_WAB_BROWSE_QUERY); + } + } + + return(err); +} + +// *************************************************************************** +#if COMPILER_LIKES_PRAGMA_MARK +#pragma mark - +#pragma mark - DNSServiceReconfirmRecord & Misc +#endif + +mDNSlocal mStatus handle_reconfirm_request(request_state *request) +{ + mStatus status = mStatus_BadParamErr; + AuthRecord *rr = read_rr_from_ipc_msg(request, 0, 0); + if (rr) + { + status = mDNS_ReconfirmByValue(&mDNSStorage, &rr->resrec); + LogOperation( + (status == mStatus_NoError) ? + "%3d: DNSServiceReconfirmRecord(%s) interface %d initiated" : + "%3d: DNSServiceReconfirmRecord(%s) interface %d failed: %d", + request->sd, RRDisplayString(&mDNSStorage, &rr->resrec), + mDNSPlatformInterfaceIndexfromInterfaceID(&mDNSStorage, rr->resrec.InterfaceID, mDNSfalse), status); + freeL("AuthRecord/handle_reconfirm_request", rr); + } + return(status); +} + +#if APPLE_OSX_mDNSResponder + +mDNSlocal mStatus handle_release_request(request_state *request) +{ + mStatus err = 0; + char name[256], regtype[MAX_ESCAPED_DOMAIN_NAME], domain[MAX_ESCAPED_DOMAIN_NAME]; + domainname instance; + + // extract the data from the message + DNSServiceFlags flags = get_flags(&request->msgptr, request->msgend); + + if (get_string(&request->msgptr, request->msgend, name, 256) < 0 || + get_string(&request->msgptr, request->msgend, regtype, MAX_ESCAPED_DOMAIN_NAME) < 0 || + get_string(&request->msgptr, request->msgend, domain, MAX_ESCAPED_DOMAIN_NAME) < 0) + { + LogMsg("ERROR: handle_release_request - Couldn't read name/regtype/domain"); + return(mStatus_BadParamErr); + } + + if (!request->msgptr) + { + LogMsg("%3d: PeerConnectionRelease(unreadable parameters)", request->sd); + return(mStatus_BadParamErr); + } + + if (build_domainname_from_strings(&instance, name, regtype, domain) < 0) + { + LogMsg("ERROR: handle_release_request bad “%s” “%s” “%s”", name, regtype, domain); + return(mStatus_BadParamErr); + } + + LogOperation("%3d: PeerConnectionRelease(%X %##s) START PID[%d](%s)", + request->sd, flags, instance.c, request->process_id, request->pid_name); + + external_connection_release(&instance); + return(err); +} + +#else // APPLE_OSX_mDNSResponder + +mDNSlocal mStatus handle_release_request(request_state *request) +{ + (void) request; + return mStatus_UnsupportedErr; +} + +#endif // APPLE_OSX_mDNSResponder + +mDNSlocal mStatus handle_setdomain_request(request_state *request) +{ + char domainstr[MAX_ESCAPED_DOMAIN_NAME]; + domainname domain; + DNSServiceFlags flags = get_flags(&request->msgptr, request->msgend); + (void)flags; // Unused + if (get_string(&request->msgptr, request->msgend, domainstr, MAX_ESCAPED_DOMAIN_NAME) < 0 || + !MakeDomainNameFromDNSNameString(&domain, domainstr)) + { LogMsg("%3d: DNSServiceSetDefaultDomainForUser(unreadable parameters)", request->sd); return(mStatus_BadParamErr); } + + LogOperation("%3d: DNSServiceSetDefaultDomainForUser(%##s)", request->sd, domain.c); + return(mStatus_NoError); +} + +typedef packedstruct +{ + mStatus err; + mDNSu32 len; + mDNSu32 vers; +} DaemonVersionReply; + +mDNSlocal void handle_getproperty_request(request_state *request) +{ + const mStatus BadParamErr = dnssd_htonl((mDNSu32)mStatus_BadParamErr); + char prop[256]; + if (get_string(&request->msgptr, request->msgend, prop, sizeof(prop)) >= 0) + { + LogOperation("%3d: DNSServiceGetProperty(%s)", request->sd, prop); + if (!strcmp(prop, kDNSServiceProperty_DaemonVersion)) + { + DaemonVersionReply x = { 0, dnssd_htonl(4), dnssd_htonl(_DNS_SD_H) }; + send_all(request->sd, (const char *)&x, sizeof(x)); + return; + } + } + + // If we didn't recogize the requested property name, return BadParamErr + send_all(request->sd, (const char *)&BadParamErr, sizeof(BadParamErr)); +} + +#ifdef APPLE_OSX_mDNSResponder +// The caller can specify either the pid or the uuid. If the pid is not specified, +// update the effective uuid. Don't overwrite the pid which is used for debugging +// purposes and initialized when the socket is opened. +mDNSlocal void handle_connection_delegate_request(request_state *request) +{ + mDNSs32 pid; + socklen_t len; + + len = 0; + pid = get_uint32(&request->msgptr, request->msgend); +#ifdef LOCAL_PEEREPID + if (pid) + { + len = sizeof(pid); + if (getsockopt(request->sd, SOL_LOCAL, LOCAL_PEEREPID, &request->process_id, &len) != 0) + return; + // to extract the process name from the pid value + if (proc_pidinfo(request->process_id, PROC_PIDT_SHORTBSDINFO, 1, &proc, PROC_PIDT_SHORTBSDINFO_SIZE) == 0) + return; + mDNSPlatformStrCopy(request->pid_name, proc.pbsi_comm); + //LogMsg("handle_connection_delegate_request: process id %d, name %s", request->process_id, request->pid_name); + } +#endif +#ifdef LOCAL_PEEREUUID + if (!pid) + { + len = UUID_SIZE; + if (getsockopt(request->sd, SOL_LOCAL, LOCAL_PEEREUUID, request->uuid, &len) != 0) + return; + request->validUUID = mDNStrue; + } +#endif +} +#else +mDNSlocal void handle_connection_delegate_request(request_state *request) +{ + (void) request; +} +#endif + +typedef packedstruct +{ + mStatus err; + mDNSs32 pid; +} PIDInfo; + +mDNSlocal void handle_getpid_request(request_state *request) +{ + const request_state *req; + mDNSs32 pid = -1; + mDNSu16 srcport = get_uint16(&request->msgptr, request->msgend); + const DNSQuestion *q = NULL; + PIDInfo pi; + + LogOperation("%3d: DNSServiceGetPID START", request->sd); + + for (req = all_requests; req; req=req->next) + { + if (req->hdr.op == query_request) + q = &req->u.queryrecord.q; + else if (req->hdr.op == addrinfo_request) + q = &req->u.addrinfo.q4; + else if (req->hdr.op == addrinfo_request) + q = &req->u.addrinfo.q6; + + if (q && q->LocalSocket != NULL) + { + mDNSu16 port = mDNSPlatformGetUDPPort(q->LocalSocket); + if (port == srcport) + { + pid = req->process_id; + LogInfo("DNSServiceGetPID: srcport %d, pid %d [%s] question %##s", htons(srcport), pid, req->pid_name, q->qname.c); + break; + } + } + } + // If we cannot find in the client requests, look to see if this was + // started by mDNSResponder. + if (pid == -1) + { + for (q = mDNSStorage.Questions; q; q = q->next) + { + if (q && q->LocalSocket != NULL) + { + mDNSu16 port = mDNSPlatformGetUDPPort(q->LocalSocket); + if (port == srcport) + { +#if APPLE_OSX_mDNSResponder + pid = getpid(); +#endif // APPLE_OSX_mDNSResponder + LogInfo("DNSServiceGetPID: srcport %d, pid %d [%s], question %##s", htons(srcport), pid, "_mDNSResponder", q->qname.c); + break; + } + } + } + } + + pi.err = 0; + pi.pid = pid; + send_all(request->sd, (const char *)&pi, sizeof(PIDInfo)); + LogOperation("%3d: DNSServiceGetPID STOP", request->sd); +} + +// *************************************************************************** +#if COMPILER_LIKES_PRAGMA_MARK +#pragma mark - +#pragma mark - DNSServiceNATPortMappingCreate +#endif + +#define DNSServiceProtocol(X) ((X) == NATOp_AddrRequest ? 0 : (X) == NATOp_MapUDP ? kDNSServiceProtocol_UDP : kDNSServiceProtocol_TCP) + +mDNSlocal void port_mapping_termination_callback(request_state *request) +{ + LogOperation("%3d: DNSServiceNATPortMappingCreate(%X, %u, %u, %d) STOP PID[%d](%s)", request->sd, + DNSServiceProtocol(request->u.pm.NATinfo.Protocol), + mDNSVal16(request->u.pm.NATinfo.IntPort), mDNSVal16(request->u.pm.ReqExt), request->u.pm.NATinfo.NATLease, + request->process_id, request->pid_name); + mDNS_StopNATOperation(&mDNSStorage, &request->u.pm.NATinfo); +} + +// Called via function pointer when we get a NAT Traversal (address request or port mapping) response +mDNSlocal void port_mapping_create_request_callback(mDNS *m, NATTraversalInfo *n) +{ + request_state *request = (request_state *)n->clientContext; + reply_state *rep; + int replyLen; + char *data; + + if (!request) { LogMsg("port_mapping_create_request_callback called with unknown request_state object"); return; } + + // calculate reply data length + replyLen = sizeof(DNSServiceFlags); + replyLen += 3 * sizeof(mDNSu32); // if index + addr + ttl + replyLen += sizeof(DNSServiceErrorType); + replyLen += 2 * sizeof(mDNSu16); // Internal Port + External Port + replyLen += sizeof(mDNSu8); // protocol + + rep = create_reply(port_mapping_reply_op, replyLen, request); + + rep->rhdr->flags = dnssd_htonl(0); + rep->rhdr->ifi = dnssd_htonl(mDNSPlatformInterfaceIndexfromInterfaceID(m, n->InterfaceID, mDNSfalse)); + rep->rhdr->error = dnssd_htonl(n->Result); + + data = (char *)&rep->rhdr[1]; + + *data++ = request->u.pm.NATinfo.ExternalAddress.b[0]; + *data++ = request->u.pm.NATinfo.ExternalAddress.b[1]; + *data++ = request->u.pm.NATinfo.ExternalAddress.b[2]; + *data++ = request->u.pm.NATinfo.ExternalAddress.b[3]; + *data++ = DNSServiceProtocol(request->u.pm.NATinfo.Protocol); + *data++ = request->u.pm.NATinfo.IntPort.b[0]; + *data++ = request->u.pm.NATinfo.IntPort.b[1]; + *data++ = request->u.pm.NATinfo.ExternalPort.b[0]; + *data++ = request->u.pm.NATinfo.ExternalPort.b[1]; + put_uint32(request->u.pm.NATinfo.Lifetime, &data); + + LogOperation("%3d: DNSServiceNATPortMappingCreate(%X, %u, %u, %d) RESULT %.4a:%u TTL %u", request->sd, + DNSServiceProtocol(request->u.pm.NATinfo.Protocol), + mDNSVal16(request->u.pm.NATinfo.IntPort), mDNSVal16(request->u.pm.ReqExt), request->u.pm.NATinfo.NATLease, + &request->u.pm.NATinfo.ExternalAddress, mDNSVal16(request->u.pm.NATinfo.ExternalPort), request->u.pm.NATinfo.Lifetime); + + append_reply(request, rep); +} + +mDNSlocal mStatus handle_port_mapping_request(request_state *request) +{ + mDNSu32 ttl = 0; + mStatus err = mStatus_NoError; + + DNSServiceFlags flags = get_flags(&request->msgptr, request->msgend); + mDNSu32 interfaceIndex = get_uint32(&request->msgptr, request->msgend); + mDNSInterfaceID InterfaceID = mDNSPlatformInterfaceIDfromInterfaceIndex(&mDNSStorage, interfaceIndex); + mDNSu8 protocol = (mDNSu8)get_uint32(&request->msgptr, request->msgend); + (void)flags; // Unused + if (interfaceIndex && !InterfaceID) return(mStatus_BadParamErr); + if (request->msgptr + 8 > request->msgend) request->msgptr = NULL; + else + { + request->u.pm.NATinfo.IntPort.b[0] = *request->msgptr++; + request->u.pm.NATinfo.IntPort.b[1] = *request->msgptr++; + request->u.pm.ReqExt.b[0] = *request->msgptr++; + request->u.pm.ReqExt.b[1] = *request->msgptr++; + ttl = get_uint32(&request->msgptr, request->msgend); + } + + if (!request->msgptr) + { LogMsg("%3d: DNSServiceNATPortMappingCreate(unreadable parameters)", request->sd); return(mStatus_BadParamErr); } + + if (protocol == 0) // If protocol == 0 (i.e. just request public address) then IntPort, ExtPort, ttl must be zero too + { + if (!mDNSIPPortIsZero(request->u.pm.NATinfo.IntPort) || !mDNSIPPortIsZero(request->u.pm.ReqExt) || ttl) return(mStatus_BadParamErr); + } + else + { + if (mDNSIPPortIsZero(request->u.pm.NATinfo.IntPort)) return(mStatus_BadParamErr); + if (!(protocol & (kDNSServiceProtocol_UDP | kDNSServiceProtocol_TCP))) return(mStatus_BadParamErr); + } + + request->u.pm.NATinfo.Protocol = !protocol ? NATOp_AddrRequest : (protocol == kDNSServiceProtocol_UDP) ? NATOp_MapUDP : NATOp_MapTCP; + // u.pm.NATinfo.IntPort = already set above + request->u.pm.NATinfo.RequestedPort = request->u.pm.ReqExt; + request->u.pm.NATinfo.NATLease = ttl; + request->u.pm.NATinfo.clientCallback = port_mapping_create_request_callback; + request->u.pm.NATinfo.clientContext = request; + + LogOperation("%3d: DNSServiceNATPortMappingCreate(%X, %u, %u, %d) START PID[%d](%s)", request->sd, + protocol, mDNSVal16(request->u.pm.NATinfo.IntPort), mDNSVal16(request->u.pm.ReqExt), request->u.pm.NATinfo.NATLease, + request->process_id, request->pid_name); + err = mDNS_StartNATOperation(&mDNSStorage, &request->u.pm.NATinfo); + if (err) LogMsg("ERROR: mDNS_StartNATOperation: %d", (int)err); + else request->terminate = port_mapping_termination_callback; + + return(err); +} + +// *************************************************************************** +#if COMPILER_LIKES_PRAGMA_MARK +#pragma mark - +#pragma mark - DNSServiceGetAddrInfo +#endif + +mDNSlocal void addrinfo_termination_callback(request_state *request) +{ + LogOperation("%3d: DNSServiceGetAddrInfo(%##s) STOP PID[%d](%s)", request->sd, request->u.addrinfo.q4.qname.c, + request->process_id, request->pid_name); + + if (request->u.addrinfo.q4.QuestionContext) + { + mDNS_StopQuery(&mDNSStorage, &request->u.addrinfo.q4); + LogMcastQ(&mDNSStorage, &request->u.addrinfo.q4, request, q_stop); + request->u.addrinfo.q4.QuestionContext = mDNSNULL; + } + if (request->u.addrinfo.q4.qnameOrig) + { + freeL("QueryTermination", request->u.addrinfo.q4.qnameOrig); + request->u.addrinfo.q4.qnameOrig = mDNSNULL; + } + if (request->u.addrinfo.q42) + { + if (request->u.addrinfo.q42->QuestionContext) + { + LogInfo("addrinfo_termination_callback: Stopping q42 %##s", request->u.addrinfo.q42->qname.c); + mDNS_StopQuery(&mDNSStorage, request->u.addrinfo.q42); + LogMcastQ(&mDNSStorage, request->u.addrinfo.q42, request, q_stop); + } + if (request->u.addrinfo.q42->qnameOrig) + { + LogInfo("addrinfo_termination_callback: freeing q42 qnameOrig %##s", request->u.addrinfo.q42->qnameOrig->c); + freeL("QueryTermination q42", request->u.addrinfo.q42->qnameOrig); + request->u.addrinfo.q42->qnameOrig = mDNSNULL; + } + freeL("addrinfo Q42", request->u.addrinfo.q42); + request->u.addrinfo.q42 = mDNSNULL; + } + + if (request->u.addrinfo.q6.QuestionContext) + { + mDNS_StopQuery(&mDNSStorage, &request->u.addrinfo.q6); + LogMcastQ(&mDNSStorage, &request->u.addrinfo.q6, request, q_stop); + request->u.addrinfo.q6.QuestionContext = mDNSNULL; + } + if (request->u.addrinfo.q6.qnameOrig) + { + freeL("QueryTermination", request->u.addrinfo.q6.qnameOrig); + request->u.addrinfo.q6.qnameOrig = mDNSNULL; + } + if (request->u.addrinfo.q62) + { + if (request->u.addrinfo.q62->QuestionContext) + { + LogInfo("addrinfo_termination_callback: Stopping q62 %##s", request->u.addrinfo.q62->qname.c); + mDNS_StopQuery(&mDNSStorage, request->u.addrinfo.q62); + LogMcastQ(&mDNSStorage, request->u.addrinfo.q62, request, q_stop); + } + if (request->u.addrinfo.q62->qnameOrig) + { + LogInfo("addrinfo_termination_callback: freeing q62 qnameOrig %##s", request->u.addrinfo.q62->qnameOrig->c); + freeL("QueryTermination q62", request->u.addrinfo.q62->qnameOrig); + request->u.addrinfo.q62->qnameOrig = mDNSNULL; + } + freeL("addrinfo Q62", request->u.addrinfo.q62); + request->u.addrinfo.q62 = mDNSNULL; + } +#if APPLE_OSX_mDNSResponder + { + DNSQuestion *v4q, *v6q; + v4q = v6q = mDNSNULL; + if (request->u.addrinfo.protocol & kDNSServiceProtocol_IPv4) + { + // If we are not delivering answers, we may be timing out prematurely. + // Note down the current state so that we know to retry when we see a + // valid response again. + if (request->u.addrinfo.q4.TimeoutQuestion && !request->u.addrinfo.v4ans) + { + mDNSPlatformUpdateDNSStatus(&mDNSStorage, &request->u.addrinfo.q4); + } + // If we have a v4 answer and if we timed out prematurely before, provide + // a trigger to the upper layer so that it can retry questions if needed. + if (request->u.addrinfo.v4ans) + v4q = &request->u.addrinfo.q4; + } + if (request->u.addrinfo.protocol & kDNSServiceProtocol_IPv6) + { + if (request->u.addrinfo.q6.TimeoutQuestion && !request->u.addrinfo.v6ans) + { + mDNSPlatformUpdateDNSStatus(&mDNSStorage, &request->u.addrinfo.q6); + } + if (request->u.addrinfo.v6ans) + v6q = &request->u.addrinfo.q6; + } + mDNSPlatformTriggerDNSRetry(&mDNSStorage, v4q, v6q); + } +#endif // APPLE_OSX_mDNSResponder +} + +mDNSlocal mStatus handle_addrinfo_request(request_state *request) +{ + char hostname[256]; + domainname d; + mStatus err = 0; + + DNSServiceFlags flags = get_flags(&request->msgptr, request->msgend); + mDNSu32 interfaceIndex = get_uint32(&request->msgptr, request->msgend); + + mDNSPlatformMemZero(&request->u.addrinfo, sizeof(request->u.addrinfo)); + request->u.addrinfo.interface_id = mDNSPlatformInterfaceIDfromInterfaceIndex(&mDNSStorage, interfaceIndex); + request->u.addrinfo.flags = flags; + request->u.addrinfo.protocol = get_uint32(&request->msgptr, request->msgend); + + if (interfaceIndex && !request->u.addrinfo.interface_id) return(mStatus_BadParamErr); + if (request->u.addrinfo.protocol > (kDNSServiceProtocol_IPv4|kDNSServiceProtocol_IPv6)) return(mStatus_BadParamErr); + + if (get_string(&request->msgptr, request->msgend, hostname, 256) < 0) return(mStatus_BadParamErr); + + if (!request->msgptr) { LogMsg("%3d: DNSServiceGetAddrInfo(unreadable parameters)", request->sd); return(mStatus_BadParamErr); } + + if (!MakeDomainNameFromDNSNameString(&d, hostname)) + { LogMsg("ERROR: handle_addrinfo_request: bad hostname: %s", hostname); return(mStatus_BadParamErr); } + +#if 0 + if (!AuthorizedDomain(request, &d, AutoBrowseDomains)) return (mStatus_NoError); +#endif + + if (!request->u.addrinfo.protocol) + { + flags |= kDNSServiceFlagsSuppressUnusable; + request->u.addrinfo.protocol = (kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6); + } + + request->u.addrinfo.q4.InterfaceID = request->u.addrinfo.q6.InterfaceID = request->u.addrinfo.interface_id; + request->u.addrinfo.q4.flags = request->u.addrinfo.q6.flags = flags; + request->u.addrinfo.q4.Target = request->u.addrinfo.q6.Target = zeroAddr; + request->u.addrinfo.q4.qname = request->u.addrinfo.q6.qname = d; + request->u.addrinfo.q4.qclass = request->u.addrinfo.q6.qclass = kDNSServiceClass_IN; + request->u.addrinfo.q4.LongLived = request->u.addrinfo.q6.LongLived = (flags & kDNSServiceFlagsLongLivedQuery ) != 0; + request->u.addrinfo.q4.ExpectUnique = request->u.addrinfo.q6.ExpectUnique = mDNSfalse; + request->u.addrinfo.q4.ForceMCast = request->u.addrinfo.q6.ForceMCast = (flags & kDNSServiceFlagsForceMulticast ) != 0; + request->u.addrinfo.q4.ReturnIntermed = request->u.addrinfo.q6.ReturnIntermed = (flags & kDNSServiceFlagsReturnIntermediates) != 0; + request->u.addrinfo.q4.SuppressUnusable = request->u.addrinfo.q6.SuppressUnusable = (flags & kDNSServiceFlagsSuppressUnusable ) != 0; + request->u.addrinfo.q4.TimeoutQuestion = request->u.addrinfo.q6.TimeoutQuestion = (flags & kDNSServiceFlagsTimeout ) != 0; + request->u.addrinfo.q4.WakeOnResolve = request->u.addrinfo.q6.WakeOnResolve = 0; + request->u.addrinfo.q4.UseBackgroundTrafficClass = request->u.addrinfo.q6.UseBackgroundTrafficClass = (flags & kDNSServiceFlagsBackgroundTrafficClass) != 0; + if ((flags & kDNSServiceFlagsValidate) != 0) + request->u.addrinfo.q4.ValidationRequired = request->u.addrinfo.q6.ValidationRequired = DNSSEC_VALIDATION_SECURE; + else if ((flags & kDNSServiceFlagsValidateOptional) != 0) + request->u.addrinfo.q4.ValidationRequired = request->u.addrinfo.q6.ValidationRequired = DNSSEC_VALIDATION_SECURE_OPTIONAL; + request->u.addrinfo.q4.ValidatingResponse = request->u.addrinfo.q6.ValidatingResponse = 0; + request->u.addrinfo.q4.ProxyQuestion = request->u.addrinfo.q6.ProxyQuestion = 0; + request->u.addrinfo.q4.qnameOrig = request->u.addrinfo.q6.qnameOrig = mDNSNULL; + request->u.addrinfo.q4.AnonInfo = request->u.addrinfo.q6.AnonInfo = mDNSNULL; + + SetQuestionPolicy(&request->u.addrinfo.q4, request); + SetQuestionPolicy(&request->u.addrinfo.q6, request); + + request->u.addrinfo.q4.DNSSECAuthInfo = request->u.addrinfo.q6.DNSSECAuthInfo = mDNSNULL; + request->u.addrinfo.q4.DAIFreeCallback = request->u.addrinfo.q6.DAIFreeCallback = mDNSNULL; + + //Turn off dnssec validation for local domains + if (IsLocalDomain(&d)) + request->u.addrinfo.q4.ValidationRequired = request->u.addrinfo.q6.ValidationRequired = 0; + + if (request->u.addrinfo.protocol & kDNSServiceProtocol_IPv6) + { + request->u.addrinfo.q6.qtype = kDNSServiceType_AAAA; + request->u.addrinfo.q6.SearchListIndex = 0; + // For DNSSEC questions, append search domains only if kDNSServiceFlagsValidateOptional is set + if ((!(request->u.addrinfo.q6.ValidationRequired == DNSSEC_VALIDATION_SECURE)) && (!(request->u.addrinfo.q6.ValidationRequired == DNSSEC_VALIDATION_INSECURE)) + && hostname[strlen(hostname) - 1] != '.' && (AlwaysAppendSearchDomains || CountLabels(&d) == 1)) + { + request->u.addrinfo.q6.AppendSearchDomains = 1; + request->u.addrinfo.q6.AppendLocalSearchDomains = 1; + } + else + { + request->u.addrinfo.q6.AppendSearchDomains = 0; + request->u.addrinfo.q6.AppendLocalSearchDomains = 0; + } + request->u.addrinfo.q6.RetryWithSearchDomains = (ApplySearchDomainsFirst(&request->u.addrinfo.q6) ? 1 : 0); + request->u.addrinfo.q6.QuestionCallback = queryrecord_result_callback; + request->u.addrinfo.q6.QuestionContext = request; + err = mDNS_StartQuery(&mDNSStorage, &request->u.addrinfo.q6); + if (err != mStatus_NoError) + { + LogMsg("ERROR: mDNS_StartQuery: %d", (int)err); + request->u.addrinfo.q6.QuestionContext = mDNSNULL; + } + #if APPLE_OSX_mDNSResponder + err = SendAdditionalQuery(&request->u.addrinfo.q6, request, err); + #endif // APPLE_OSX_mDNSResponder + if (!err) + { + request->terminate = addrinfo_termination_callback; + LogMcastQ(&mDNSStorage, &request->u.addrinfo.q6, request, q_start); + } + } + + if (!err && (request->u.addrinfo.protocol & kDNSServiceProtocol_IPv4)) + { + request->u.addrinfo.q4.qtype = kDNSServiceType_A; + request->u.addrinfo.q4.SearchListIndex = 0; + + // We append search domains only for queries that are a single label. If overriden using cmd line arg + // "AlwaysAppendSearchDomains", then we do it for any query which is not fully qualified. + // For DNSSEC questions, append search domains only if kDNSServiceFlagsValidateOptional is set. + + if ((!(request->u.addrinfo.q4.ValidationRequired == DNSSEC_VALIDATION_SECURE)) && (!(request->u.addrinfo.q4.ValidationRequired == DNSSEC_VALIDATION_INSECURE)) + && hostname[strlen(hostname) - 1] != '.' && (AlwaysAppendSearchDomains || CountLabels(&d) == 1)) + { + request->u.addrinfo.q4.AppendSearchDomains = 1; + request->u.addrinfo.q4.AppendLocalSearchDomains = 1; + } + else + { + request->u.addrinfo.q4.AppendSearchDomains = 0; + request->u.addrinfo.q4.AppendLocalSearchDomains = 0; + } + request->u.addrinfo.q4.RetryWithSearchDomains = (ApplySearchDomainsFirst(&request->u.addrinfo.q4) ? 1 : 0); + request->u.addrinfo.q4.QuestionCallback = queryrecord_result_callback; + request->u.addrinfo.q4.QuestionContext = request; + err = mDNS_StartQuery(&mDNSStorage, &request->u.addrinfo.q4); + if (err != mStatus_NoError) + { + LogMsg("ERROR: mDNS_StartQuery: %d", (int)err); + request->u.addrinfo.q4.QuestionContext = mDNSNULL; + if (request->u.addrinfo.protocol & kDNSServiceProtocol_IPv6) + { + // If we started a query for IPv6, we need to cancel it + mDNS_StopQuery(&mDNSStorage, &request->u.addrinfo.q6); + request->u.addrinfo.q6.QuestionContext = mDNSNULL; + } + } + #if APPLE_OSX_mDNSResponder + err = SendAdditionalQuery(&request->u.addrinfo.q4, request, err); + #endif // APPLE_OSX_mDNSResponder + if (!err) + { + request->terminate = addrinfo_termination_callback; + LogMcastQ(&mDNSStorage, &request->u.addrinfo.q4, request, q_start); + } + } + + LogOperation("%3d: DNSServiceGetAddrInfo(%X, %d, %d, %##s) START PID[%d](%s)", request->sd, flags, interfaceIndex, + request->u.addrinfo.protocol, d.c, request->process_id, request->pid_name); + return(err); +} + +// *************************************************************************** +#if COMPILER_LIKES_PRAGMA_MARK +#pragma mark - +#pragma mark - Main Request Handler etc. +#endif + +mDNSlocal request_state *NewRequest(void) +{ + request_state **p = &all_requests; + while (*p) + p=&(*p)->next; + *p = mallocL("request_state", sizeof(request_state)); + if (!*p) + FatalError("ERROR: malloc"); + mDNSPlatformMemZero(*p, sizeof(request_state)); + return(*p); +} + +// read_msg may be called any time when the transfer state (req->ts) is t_morecoming. +// if there is no data on the socket, the socket will be closed and t_terminated will be returned +mDNSlocal void read_msg(request_state *req) +{ + if (req->ts == t_terminated || req->ts == t_error) + { LogMsg("%3d: ERROR: read_msg called with transfer state terminated or error", req->sd); req->ts = t_error; return; } + + if (req->ts == t_complete) // this must be death or something is wrong + { + char buf[4]; // dummy for death notification + int nread = udsSupportReadFD(req->sd, buf, 4, 0, req->platform_data); + if (!nread) { req->ts = t_terminated; return; } + if (nread < 0) goto rerror; + LogMsg("%3d: ERROR: read data from a completed request", req->sd); + req->ts = t_error; + return; + } + + if (req->ts != t_morecoming) + { LogMsg("%3d: ERROR: read_msg called with invalid transfer state (%d)", req->sd, req->ts); req->ts = t_error; return; } + + if (req->hdr_bytes < sizeof(ipc_msg_hdr)) + { + mDNSu32 nleft = sizeof(ipc_msg_hdr) - req->hdr_bytes; + int nread = udsSupportReadFD(req->sd, (char *)&req->hdr + req->hdr_bytes, nleft, 0, req->platform_data); + if (nread == 0) { req->ts = t_terminated; return; } + if (nread < 0) goto rerror; + req->hdr_bytes += nread; + if (req->hdr_bytes > sizeof(ipc_msg_hdr)) + { LogMsg("%3d: ERROR: read_msg - read too many header bytes", req->sd); req->ts = t_error; return; } + + // only read data if header is complete + if (req->hdr_bytes == sizeof(ipc_msg_hdr)) + { + ConvertHeaderBytes(&req->hdr); + if (req->hdr.version != VERSION) + { LogMsg("%3d: ERROR: client version 0x%08X daemon version 0x%08X", req->sd, req->hdr.version, VERSION); req->ts = t_error; return; } + + // Largest conceivable single request is a DNSServiceRegisterRecord() or DNSServiceAddRecord() + // with 64kB of rdata. Adding 1009 byte for a maximal domain name, plus a safety margin + // for other overhead, this means any message above 70kB is definitely bogus. + if (req->hdr.datalen > 70000) + { LogMsg("%3d: ERROR: read_msg: hdr.datalen %u (0x%X) > 70000", req->sd, req->hdr.datalen, req->hdr.datalen); req->ts = t_error; return; } + req->msgbuf = mallocL("request_state msgbuf", req->hdr.datalen + MSG_PAD_BYTES); + if (!req->msgbuf) { my_perror("ERROR: malloc"); req->ts = t_error; return; } + req->msgptr = req->msgbuf; + req->msgend = req->msgbuf + req->hdr.datalen; + mDNSPlatformMemZero(req->msgbuf, req->hdr.datalen + MSG_PAD_BYTES); + } + } + + // If our header is complete, but we're still needing more body data, then try to read it now + // Note: For cancel_request req->hdr.datalen == 0, but there's no error return socket for cancel_request + // Any time we need to get the error return socket we know we'll have at least one data byte + // (even if only the one-byte empty C string placeholder for the old ctrl_path parameter) + if (req->hdr_bytes == sizeof(ipc_msg_hdr) && req->data_bytes < req->hdr.datalen) + { + mDNSu32 nleft = req->hdr.datalen - req->data_bytes; + int nread; +#if !defined(_WIN32) + struct iovec vec = { req->msgbuf + req->data_bytes, nleft }; // Tell recvmsg where we want the bytes put + struct msghdr msg; + struct cmsghdr *cmsg; + char cbuf[CMSG_SPACE(4 * sizeof(dnssd_sock_t))]; + msg.msg_name = 0; + msg.msg_namelen = 0; + msg.msg_iov = &vec; + msg.msg_iovlen = 1; + msg.msg_control = cbuf; + msg.msg_controllen = sizeof(cbuf); + msg.msg_flags = 0; + nread = recvmsg(req->sd, &msg, 0); +#else + nread = udsSupportReadFD(req->sd, (char *)req->msgbuf + req->data_bytes, nleft, 0, req->platform_data); +#endif + if (nread == 0) { req->ts = t_terminated; return; } + if (nread < 0) goto rerror; + req->data_bytes += nread; + if (req->data_bytes > req->hdr.datalen) + { LogMsg("%3d: ERROR: read_msg - read too many data bytes", req->sd); req->ts = t_error; return; } +#if !defined(_WIN32) + cmsg = CMSG_FIRSTHDR(&msg); +#if DEBUG_64BIT_SCM_RIGHTS + LogMsg("%3d: Expecting %d %d %d %d", req->sd, sizeof(cbuf), sizeof(cbuf), SOL_SOCKET, SCM_RIGHTS); + LogMsg("%3d: Got %d %d %d %d", req->sd, msg.msg_controllen, cmsg->cmsg_len, cmsg->cmsg_level, cmsg->cmsg_type); +#endif // DEBUG_64BIT_SCM_RIGHTS + if (msg.msg_controllen != 0 && + cmsg->cmsg_level == SOL_SOCKET && + cmsg->cmsg_type == SCM_RIGHTS) + { +#if APPLE_OSX_mDNSResponder + // Strictly speaking BPF_fd belongs solely in the platform support layer, but because + // of privilege separation on Mac OS X we need to get BPF_fd from mDNSResponderHelper, + // and it's convenient to repurpose the existing fd-passing code here for that task + if (req->hdr.op == send_bpf) + { + dnssd_sock_t x = *(dnssd_sock_t *)CMSG_DATA(cmsg); + LogOperation("%3d: Got len %d, BPF %d", req->sd, cmsg->cmsg_len, x); + mDNSPlatformReceiveBPF_fd(&mDNSStorage, x); + } + else +#endif // APPLE_OSX_mDNSResponder + req->errsd = *(dnssd_sock_t *)CMSG_DATA(cmsg); +#if DEBUG_64BIT_SCM_RIGHTS + LogMsg("%3d: read req->errsd %d", req->sd, req->errsd); +#endif // DEBUG_64BIT_SCM_RIGHTS + if (req->data_bytes < req->hdr.datalen) + { + LogMsg("%3d: Client(PID [%d](%s)) sent error socket %d via SCM_RIGHTS with req->data_bytes %d < req->hdr.datalen %d", + req->sd, req->process_id, req->pid_name, req->errsd, req->data_bytes, req->hdr.datalen); + req->ts = t_error; + return; + } + } +#endif + } + + // If our header and data are both complete, see if we need to make our separate error return socket + if (req->hdr_bytes == sizeof(ipc_msg_hdr) && req->data_bytes == req->hdr.datalen) + { + if (req->terminate && req->hdr.op != cancel_request) + { + dnssd_sockaddr_t cliaddr; +#if defined(USE_TCP_LOOPBACK) + mDNSOpaque16 port; + u_long opt = 1; + port.b[0] = req->msgptr[0]; + port.b[1] = req->msgptr[1]; + req->msgptr += 2; + cliaddr.sin_family = AF_INET; + cliaddr.sin_port = port.NotAnInteger; + cliaddr.sin_addr.s_addr = inet_addr(MDNS_TCP_SERVERADDR); +#else + char ctrl_path[MAX_CTLPATH]; + get_string(&req->msgptr, req->msgend, ctrl_path, MAX_CTLPATH); // path is first element in message buffer + mDNSPlatformMemZero(&cliaddr, sizeof(cliaddr)); + cliaddr.sun_family = AF_LOCAL; + mDNSPlatformStrCopy(cliaddr.sun_path, ctrl_path); + // If the error return path UDS name is empty string, that tells us + // that this is a new version of the library that's going to pass us + // the error return path socket via sendmsg/recvmsg + if (ctrl_path[0] == 0) + { + if (req->errsd == req->sd) + { LogMsg("%3d: read_msg: ERROR failed to get errsd via SCM_RIGHTS", req->sd); req->ts = t_error; return; } + goto got_errfd; + } +#endif + + req->errsd = socket(AF_DNSSD, SOCK_STREAM, 0); + if (!dnssd_SocketValid(req->errsd)) + { + my_throttled_perror("ERROR: socket"); + req->ts = t_error; + return; + } + + if (connect(req->errsd, (struct sockaddr *)&cliaddr, sizeof(cliaddr)) < 0) + { +#if !defined(USE_TCP_LOOPBACK) + struct stat sb; + LogMsg("%3d: read_msg: Couldn't connect to error return path socket “%s” errno %d (%s)", + req->sd, cliaddr.sun_path, dnssd_errno, dnssd_strerror(dnssd_errno)); + if (stat(cliaddr.sun_path, &sb) < 0) + LogMsg("%3d: read_msg: stat failed “%s” errno %d (%s)", req->sd, cliaddr.sun_path, dnssd_errno, dnssd_strerror(dnssd_errno)); + else + LogMsg("%3d: read_msg: file “%s” mode %o (octal) uid %d gid %d", req->sd, cliaddr.sun_path, sb.st_mode, sb.st_uid, sb.st_gid); +#endif + req->ts = t_error; + return; + } + +#if !defined(USE_TCP_LOOPBACK) +got_errfd: +#endif + LogOperation("%3d: Error socket %d created %08X %08X", req->sd, req->errsd, req->hdr.client_context.u32[1], req->hdr.client_context.u32[0]); +#if defined(_WIN32) + if (ioctlsocket(req->errsd, FIONBIO, &opt) != 0) +#else + if (fcntl(req->errsd, F_SETFL, fcntl(req->errsd, F_GETFL, 0) | O_NONBLOCK) != 0) +#endif + { + LogMsg("%3d: ERROR: could not set control socket to non-blocking mode errno %d (%s)", + req->sd, dnssd_errno, dnssd_strerror(dnssd_errno)); + req->ts = t_error; + return; + } + } + + req->ts = t_complete; + } + + return; + +rerror: + if (dnssd_errno == dnssd_EWOULDBLOCK || dnssd_errno == dnssd_EINTR) return; + LogMsg("%3d: ERROR: read_msg errno %d (%s)", req->sd, dnssd_errno, dnssd_strerror(dnssd_errno)); + req->ts = t_error; +} + +#define RecordOrientedOp(X) \ + ((X) == reg_record_request || (X) == add_record_request || (X) == update_record_request || (X) == remove_record_request) + +// The lightweight operations are the ones that don't need a dedicated request_state structure allocated for them +#define LightweightOp(X) (RecordOrientedOp(X) || (X) == cancel_request) + +mDNSlocal void request_callback(int fd, short filter, void *info) +{ + mStatus err = 0; + request_state *req = info; + mDNSs32 min_size = sizeof(DNSServiceFlags); + (void)fd; // Unused + (void)filter; // Unused + + for (;;) + { + read_msg(req); + if (req->ts == t_morecoming) + return; + if (req->ts == t_terminated || req->ts == t_error) + { + AbortUnlinkAndFree(req); + return; + } + if (req->ts != t_complete) + { + LogMsg("request_callback: req->ts %d != t_complete PID[%d][%s]", req->ts, req->process_id, req->pid_name); + AbortUnlinkAndFree(req); + return; + } + if (req->hdr.version != VERSION) + { + LogMsg("request_callback: ERROR: client IPC version %d incompatible with daemon IPC version %d PID[%d][%s]", + req->hdr.version, VERSION, req->process_id, req->pid_name); + AbortUnlinkAndFree(req); + return; + } + + switch(req->hdr.op) // Interface + other data + { + case connection_request: min_size = 0; break; + case connection_delegate_request: min_size = 4; /* pid */ break; + case reg_service_request: min_size += sizeof(mDNSu32) + 4 /* name, type, domain, host */ + 4 /* port, textlen */; break; + case add_record_request: min_size += 4 /* type, rdlen */ + 4 /* ttl */; break; + case update_record_request: min_size += 2 /* rdlen */ + 4 /* ttl */; break; + case remove_record_request: break; + case browse_request: min_size += sizeof(mDNSu32) + 2 /* type, domain */; break; + case resolve_request: min_size += sizeof(mDNSu32) + 3 /* type, type, domain */; break; + case query_request: min_size += sizeof(mDNSu32) + 1 /* name */ + 4 /* type, class*/; break; + case enumeration_request: min_size += sizeof(mDNSu32); break; + case reg_record_request: min_size += sizeof(mDNSu32) + 1 /* name */ + 6 /* type, class, rdlen */ + 4 /* ttl */; break; + case reconfirm_record_request: min_size += sizeof(mDNSu32) + 1 /* name */ + 6 /* type, class, rdlen */; break; + case setdomain_request: min_size += 1 /* domain */; break; + case getproperty_request: min_size = 2; break; + case getpid_request: min_size = 2; break; + case port_mapping_request: min_size += sizeof(mDNSu32) + 4 /* udp/tcp */ + 4 /* int/ext port */ + 4 /* ttl */; break; + case addrinfo_request: min_size += sizeof(mDNSu32) + 4 /* v4/v6 */ + 1 /* hostname */; break; + case send_bpf: // Same as cancel_request below + case cancel_request: min_size = 0; break; + case release_request: min_size += sizeof(mDNSu32) + 3 /* type, type, domain */; break; + default: LogMsg("request_callback: ERROR: validate_message - unsupported req type: %d PID[%d][%s]", + req->hdr.op, req->process_id, req->pid_name); + min_size = -1; break; + } + + if ((mDNSs32)req->data_bytes < min_size) + { + LogMsg("request_callback: Invalid message %d bytes; min for %d is %d PID[%d][%s]", + req->data_bytes, req->hdr.op, min_size, req->process_id, req->pid_name); + AbortUnlinkAndFree(req); + return; + } + if (LightweightOp(req->hdr.op) && !req->terminate) + { + LogMsg("request_callback: Reg/Add/Update/Remove %d require existing connection PID[%d][%s]", + req->hdr.op, req->process_id, req->pid_name); + AbortUnlinkAndFree(req); + return; + } + + // check if client wants silent operation + if (req->hdr.ipc_flags & IPC_FLAGS_NOREPLY) req->no_reply = 1; + + // If req->terminate is already set, this means this operation is sharing an existing connection + if (req->terminate && !LightweightOp(req->hdr.op)) + { + request_state *newreq = NewRequest(); + newreq->primary = req; + newreq->sd = req->sd; + newreq->errsd = req->errsd; + newreq->uid = req->uid; + newreq->hdr = req->hdr; + newreq->msgbuf = req->msgbuf; + newreq->msgptr = req->msgptr; + newreq->msgend = req->msgend; + // if the parent request is a delegate connection, copy the + // relevant bits + if (req->validUUID) + { + int i; + newreq->validUUID = mDNStrue; + for (i = 0; i < UUID_SIZE; i++) + { + newreq->uuid[i] = req->uuid[i]; + } + } + else + { + if (req->process_id) + { + newreq->process_id = req->process_id; + } + else + { + set_peer_pid(newreq); + } + } + req = newreq; + } + + // If we're shutting down, don't allow new client requests + // We do allow "cancel" and "getproperty" during shutdown + if (mDNSStorage.ShutdownTime && req->hdr.op != cancel_request && req->hdr.op != getproperty_request) + { + err = mStatus_ServiceNotRunning; + } + else + { + switch(req->hdr.op) + { + // These are all operations that have their own first-class request_state object + case connection_request: + LogOperation("%3d: DNSServiceCreateConnection START PID[%d](%s)", + req->sd, req->process_id, req->pid_name); + req->terminate = connection_termination; + break; + case connection_delegate_request: + LogOperation("%3d: DNSServiceCreateDelegateConnection START PID[%d](%s)", + req->sd, req->process_id, req->pid_name); + req->terminate = connection_termination; + handle_connection_delegate_request(req); + break; + case resolve_request: err = handle_resolve_request (req); break; + case query_request: err = handle_queryrecord_request (req); break; + case browse_request: err = handle_browse_request (req); break; + case reg_service_request: err = handle_regservice_request (req); break; + case enumeration_request: err = handle_enum_request (req); break; + case reconfirm_record_request: err = handle_reconfirm_request (req); break; + case setdomain_request: err = handle_setdomain_request (req); break; + case getproperty_request: handle_getproperty_request (req); break; + case getpid_request: handle_getpid_request (req); break; + case port_mapping_request: err = handle_port_mapping_request(req); break; + case addrinfo_request: err = handle_addrinfo_request (req); break; + case send_bpf: /* Do nothing for send_bpf */ break; + + // These are all operations that work with an existing request_state object + case reg_record_request: err = handle_regrecord_request (req); break; + case add_record_request: err = handle_add_request (req); break; + case update_record_request: err = handle_update_request (req); break; + case remove_record_request: err = handle_removerecord_request(req); break; + case cancel_request: handle_cancel_request (req); break; + case release_request: err = handle_release_request (req); break; + default: LogMsg("request_callback: %3d:ERROR: Unsupported UDS req:%d PID[%d][%s]", + req->sd, req->hdr.op, req->process_id, req->pid_name); break; + } + } + // req->msgbuf may be NULL, e.g. for connection_request or remove_record_request + if (req->msgbuf) freeL("request_state msgbuf", req->msgbuf); + + // There's no return data for a cancel request (DNSServiceRefDeallocate returns no result) + // For a DNSServiceGetProperty call, the handler already generated the response, so no need to do it again here + if (req->hdr.op != cancel_request && req->hdr.op != getproperty_request && req->hdr.op != send_bpf && req->hdr.op != getpid_request) + { + const mStatus err_netorder = dnssd_htonl(err); + send_all(req->errsd, (const char *)&err_netorder, sizeof(err_netorder)); + if (req->errsd != req->sd) + { + LogOperation("%3d: Error socket %d closed %08X %08X (%d)", + req->sd, req->errsd, req->hdr.client_context.u32[1], req->hdr.client_context.u32[0], err); + dnssd_close(req->errsd); + req->errsd = req->sd; + // Also need to reset the parent's errsd, if this is a subordinate operation + if (req->primary) req->primary->errsd = req->primary->sd; + } + } + + // Reset ready to accept the next req on this pipe + if (req->primary) req = req->primary; + req->ts = t_morecoming; + req->hdr_bytes = 0; + req->data_bytes = 0; + req->msgbuf = mDNSNULL; + req->msgptr = mDNSNULL; + req->msgend = 0; + } +} + +mDNSlocal void connect_callback(int fd, short filter, void *info) +{ + dnssd_sockaddr_t cliaddr; + dnssd_socklen_t len = (dnssd_socklen_t) sizeof(cliaddr); + dnssd_sock_t sd = accept(fd, (struct sockaddr*) &cliaddr, &len); +#if defined(SO_NOSIGPIPE) || defined(_WIN32) + unsigned long optval = 1; +#endif + + (void)filter; // Unused + (void)info; // Unused + + if (!dnssd_SocketValid(sd)) + { + if (dnssd_errno != dnssd_EWOULDBLOCK) + my_throttled_perror("ERROR: accept"); + return; + } + +#ifdef SO_NOSIGPIPE + // Some environments (e.g. OS X) support turning off SIGPIPE for a socket + if (setsockopt(sd, SOL_SOCKET, SO_NOSIGPIPE, &optval, sizeof(optval)) < 0) + LogMsg("%3d: WARNING: setsockopt - SO_NOSIGPIPE %d (%s)", sd, dnssd_errno, dnssd_strerror(dnssd_errno)); +#endif + +#if defined(_WIN32) + if (ioctlsocket(sd, FIONBIO, &optval) != 0) +#else + if (fcntl(sd, F_SETFL, fcntl(sd, F_GETFL, 0) | O_NONBLOCK) != 0) +#endif + { + my_perror("ERROR: fcntl(sd, F_SETFL, O_NONBLOCK) - aborting client"); + dnssd_close(sd); + return; + } + else + { + request_state *request = NewRequest(); + request->ts = t_morecoming; + request->sd = sd; + request->errsd = sd; + set_peer_pid(request); +#if APPLE_OSX_mDNSResponder + struct xucred x; + socklen_t xucredlen = sizeof(x); + if (getsockopt(sd, 0, LOCAL_PEERCRED, &x, &xucredlen) >= 0 && x.cr_version == XUCRED_VERSION) request->uid = x.cr_uid; + else my_perror("ERROR: getsockopt, LOCAL_PEERCRED"); + debugf("LOCAL_PEERCRED %d %u %u %d", xucredlen, x.cr_version, x.cr_uid, x.cr_ngroups); +#endif // APPLE_OSX_mDNSResponder + LogOperation("%3d: Adding FD for uid %u", request->sd, request->uid); + udsSupportAddFDToEventLoop(sd, request_callback, request, &request->platform_data); + } +} + +mDNSlocal mDNSBool uds_socket_setup(dnssd_sock_t skt) +{ +#if defined(SO_NP_EXTENSIONS) + struct so_np_extensions sonpx; + socklen_t optlen = sizeof(struct so_np_extensions); + sonpx.npx_flags = SONPX_SETOPTSHUT; + sonpx.npx_mask = SONPX_SETOPTSHUT; + if (setsockopt(skt, SOL_SOCKET, SO_NP_EXTENSIONS, &sonpx, optlen) < 0) + my_perror("WARNING: could not set sockopt - SO_NP_EXTENSIONS"); +#endif +#if defined(_WIN32) + // SEH: do we even need to do this on windows? + // This socket will be given to WSAEventSelect which will automatically set it to non-blocking + u_long opt = 1; + if (ioctlsocket(skt, FIONBIO, &opt) != 0) +#else + if (fcntl(skt, F_SETFL, fcntl(skt, F_GETFL, 0) | O_NONBLOCK) != 0) +#endif + { + my_perror("ERROR: could not set listen socket to non-blocking mode"); + return mDNSfalse; + } + + if (listen(skt, LISTENQ) != 0) + { + my_perror("ERROR: could not listen on listen socket"); + return mDNSfalse; + } + + if (mStatus_NoError != udsSupportAddFDToEventLoop(skt, connect_callback, (void *) NULL, (void **) NULL)) + { + my_perror("ERROR: could not add listen socket to event loop"); + return mDNSfalse; + } + else + { + LogMsg("%3d: Listening for incoming Unix Domain Socket client requests", skt); + mDNSStorage.uds_listener_skt = skt; + } + return mDNStrue; +} + +mDNSexport int udsserver_init(dnssd_sock_t skts[], mDNSu32 count) +{ + dnssd_sockaddr_t laddr; + int ret; + mDNSu32 i = 0; + + LogInfo("udsserver_init: %d %d", _DNS_SD_H, mDNSStorage.mDNS_plat); + + // If a particular platform wants to opt out of having a PID file, define PID_FILE to be "" + if (PID_FILE[0]) + { + FILE *fp = fopen(PID_FILE, "w"); + if (fp != NULL) + { + fprintf(fp, "%d\n", getpid()); + fclose(fp); + } + } + + if (skts) + { + for (i = 0; i < count; i++) + if (dnssd_SocketValid(skts[i]) && !uds_socket_setup(skts[i])) + goto error; + } + else + { + listenfd = socket(AF_DNSSD, SOCK_STREAM, 0); + if (!dnssd_SocketValid(listenfd)) + { + my_perror("ERROR: socket(AF_DNSSD, SOCK_STREAM, 0); failed"); + goto error; + } + + mDNSPlatformMemZero(&laddr, sizeof(laddr)); + + #if defined(USE_TCP_LOOPBACK) + { + laddr.sin_family = AF_INET; + laddr.sin_port = htons(MDNS_TCP_SERVERPORT); + laddr.sin_addr.s_addr = inet_addr(MDNS_TCP_SERVERADDR); + ret = bind(listenfd, (struct sockaddr *) &laddr, sizeof(laddr)); + if (ret < 0) + { + my_perror("ERROR: bind(listenfd, (struct sockaddr *) &laddr, sizeof(laddr)); failed"); + goto error; + } + } + #else + { + mode_t mask = umask(0); + unlink(MDNS_UDS_SERVERPATH); // OK if this fails + laddr.sun_family = AF_LOCAL; + #ifndef NOT_HAVE_SA_LEN + // According to Stevens (section 3.2), there is no portable way to + // determine whether sa_len is defined on a particular platform. + laddr.sun_len = sizeof(struct sockaddr_un); + #endif + if (strlen(MDNS_UDS_SERVERPATH) >= sizeof(laddr.sun_path)) + { + LogMsg("ERROR: MDNS_UDS_SERVERPATH must be < %d characters", (int)sizeof(laddr.sun_path)); + goto error; + } + mDNSPlatformStrCopy(laddr.sun_path, MDNS_UDS_SERVERPATH); + ret = bind(listenfd, (struct sockaddr *) &laddr, sizeof(laddr)); + umask(mask); + if (ret < 0) + { + my_perror("ERROR: bind(listenfd, (struct sockaddr *) &laddr, sizeof(laddr)); failed"); + goto error; + } + } + #endif + + if (!uds_socket_setup(listenfd)) goto error; + } + +#if !defined(PLATFORM_NO_RLIMIT) + { + // Set maximum number of open file descriptors + #define MIN_OPENFILES 10240 + struct rlimit maxfds, newfds; + + // Due to bugs in OS X (<rdar://problem/2941095>, <rdar://problem/3342704>, <rdar://problem/3839173>) + // you have to get and set rlimits once before getrlimit will return sensible values + if (getrlimit(RLIMIT_NOFILE, &maxfds) < 0) { my_perror("ERROR: Unable to get file descriptor limit"); return 0; } + if (setrlimit(RLIMIT_NOFILE, &maxfds) < 0) my_perror("ERROR: Unable to set maximum file descriptor limit"); + + if (getrlimit(RLIMIT_NOFILE, &maxfds) < 0) { my_perror("ERROR: Unable to get file descriptor limit"); return 0; } + newfds.rlim_max = (maxfds.rlim_max > MIN_OPENFILES) ? maxfds.rlim_max : MIN_OPENFILES; + newfds.rlim_cur = (maxfds.rlim_cur > MIN_OPENFILES) ? maxfds.rlim_cur : MIN_OPENFILES; + if (newfds.rlim_max != maxfds.rlim_max || newfds.rlim_cur != maxfds.rlim_cur) + if (setrlimit(RLIMIT_NOFILE, &newfds) < 0) my_perror("ERROR: Unable to set maximum file descriptor limit"); + + if (getrlimit(RLIMIT_NOFILE, &maxfds) < 0) { my_perror("ERROR: Unable to get file descriptor limit"); return 0; } + debugf("maxfds.rlim_max %d", (long)maxfds.rlim_max); + debugf("maxfds.rlim_cur %d", (long)maxfds.rlim_cur); + } +#endif + + // We start a "LocalOnly" query looking for Automatic Browse Domain records. + // When Domain Enumeration in uDNS.c finds an "lb" record from the network, its "FoundDomain" routine + // creates a "LocalOnly" record, which results in our AutomaticBrowseDomainChange callback being invoked + mDNS_GetDomains(&mDNSStorage, &mDNSStorage.AutomaticBrowseDomainQ, mDNS_DomainTypeBrowseAutomatic, + mDNSNULL, mDNSInterface_LocalOnly, AutomaticBrowseDomainChange, mDNSNULL); + + // Add "local" as recommended registration domain ("dns-sd -E"), recommended browsing domain ("dns-sd -F"), and automatic browsing domain + RegisterLocalOnlyDomainEnumPTR(&mDNSStorage, &localdomain, mDNS_DomainTypeRegistration); + RegisterLocalOnlyDomainEnumPTR(&mDNSStorage, &localdomain, mDNS_DomainTypeBrowse); + AddAutoBrowseDomain(0, &localdomain); + + udsserver_handle_configchange(&mDNSStorage); + return 0; + +error: + + my_perror("ERROR: udsserver_init"); + return -1; +} + +mDNSexport int udsserver_exit(void) +{ + // Cancel all outstanding client requests + while (all_requests) AbortUnlinkAndFree(all_requests); + + // Clean up any special mDNSInterface_LocalOnly records we created, both the entries for "local" we + // created in udsserver_init, and others we created as a result of reading local configuration data + while (LocalDomainEnumRecords) + { + ARListElem *rem = LocalDomainEnumRecords; + LocalDomainEnumRecords = LocalDomainEnumRecords->next; + mDNS_Deregister(&mDNSStorage, &rem->ar); + } + + // If the launching environment created no listening socket, + // that means we created it ourselves, so we should clean it up on exit + if (dnssd_SocketValid(listenfd)) + { + dnssd_close(listenfd); +#if !defined(USE_TCP_LOOPBACK) + // Currently, we're unable to remove /var/run/mdnsd because we've changed to userid "nobody" + // to give up unnecessary privilege, but we need to be root to remove this Unix Domain Socket. + // It would be nice if we could find a solution to this problem + if (unlink(MDNS_UDS_SERVERPATH)) + debugf("Unable to remove %s", MDNS_UDS_SERVERPATH); +#endif + } + + if (PID_FILE[0]) unlink(PID_FILE); + + return 0; +} + +mDNSlocal void LogClientInfo(mDNS *const m, request_state *req) +{ + char prefix[16]; + if (req->primary) + mDNS_snprintf(prefix, sizeof(prefix), " -> "); + else + mDNS_snprintf(prefix, sizeof(prefix), "%3d:", req->sd); + + if (!req->terminate) + LogMsgNoIdent("%s No operation yet on this socket", prefix); + else if (req->terminate == connection_termination) + { + int num_records = 0, num_ops = 0; + const registered_record_entry *p; + request_state *r; + for (p = req->u.reg_recs; p; p=p->next) num_records++; + for (r = req->next; r; r=r->next) if (r->primary == req) num_ops++; + LogMsgNoIdent("%s DNSServiceCreateConnection: %d registered record%s, %d kDNSServiceFlagsShareConnection operation%s PID[%d](%s)", + prefix, num_records, num_records != 1 ? "s" : "", num_ops, num_ops != 1 ? "s" : "", + req->process_id, req->pid_name); + for (p = req->u.reg_recs; p; p=p->next) + LogMsgNoIdent(" -> DNSServiceRegisterRecord %3d %s PID[%d](%s)", p->key, ARDisplayString(m, p->rr), + req->process_id, req->pid_name); + for (r = req->next; r; r=r->next) if (r->primary == req) LogClientInfo(m, r); + } + else if (req->terminate == regservice_termination_callback) + { + service_instance *ptr; + char anonstr[256]; + for (ptr = req->u.servicereg.instances; ptr; ptr = ptr->next) + LogMsgNoIdent("%s DNSServiceRegister %##s%s %u/%u PID[%d](%s)", + (ptr == req->u.servicereg.instances) ? prefix : " ", ptr->srs.RR_SRV.resrec.name->c, + AnonDataToString(ptr->srs.AnonData, 0, anonstr, sizeof(anonstr)), mDNSVal16(req->u.servicereg.port), + SRS_PORT(&ptr->srs), req->process_id, req->pid_name); + } + else if (req->terminate == browse_termination_callback) + { + browser_t *blist; + char anonstr[256]; + for (blist = req->u.browser.browsers; blist; blist = blist->next) + LogMsgNoIdent("%s DNSServiceBrowse %##s%s PID[%d](%s)", + (blist == req->u.browser.browsers) ? prefix : " ",blist->q.qname.c, + AnonDataToString(req->u.browser.AnonData, 0, anonstr, sizeof(anonstr)), req->process_id, req->pid_name); + } + else if (req->terminate == resolve_termination_callback) + LogMsgNoIdent("%s DNSServiceResolve %##s PID[%d](%s)", + prefix, req->u.resolve.qsrv.qname.c, req->process_id, req->pid_name); + else if (req->terminate == queryrecord_termination_callback) + LogMsgNoIdent("%s DNSServiceQueryRecord %##s (%s) PID[%d](%s)", + prefix, req->u.queryrecord.q.qname.c, DNSTypeName(req->u.queryrecord.q.qtype), req->process_id, req->pid_name); + else if (req->terminate == enum_termination_callback) + LogMsgNoIdent("%s DNSServiceEnumerateDomains %##s PID[%d](%s)", prefix, req->u.enumeration.q_all.qname.c, + req->process_id, req->pid_name); + else if (req->terminate == port_mapping_termination_callback) + LogMsgNoIdent("%s DNSServiceNATPortMapping %s%s Int %5d Req %5d Ext %.4a:%5d Req TTL %5d Granted TTL %5d PID[%d](%s)", + prefix, + req->u.pm.NATinfo.Protocol & NATOp_MapTCP ? "TCP" : " ", + req->u.pm.NATinfo.Protocol & NATOp_MapUDP ? "UDP" : " ", + mDNSVal16(req->u.pm.NATinfo.IntPort), + mDNSVal16(req->u.pm.ReqExt), + &req->u.pm.NATinfo.ExternalAddress, + mDNSVal16(req->u.pm.NATinfo.ExternalPort), + req->u.pm.NATinfo.NATLease, + req->u.pm.NATinfo.Lifetime, + req->process_id, req->pid_name); + else if (req->terminate == addrinfo_termination_callback) + LogMsgNoIdent("%s DNSServiceGetAddrInfo %s%s %##s PID[%d](%s)", prefix, + req->u.addrinfo.protocol & kDNSServiceProtocol_IPv4 ? "v4" : " ", + req->u.addrinfo.protocol & kDNSServiceProtocol_IPv6 ? "v6" : " ", + req->u.addrinfo.q4.qname.c, req->process_id, req->pid_name); + else + LogMsgNoIdent("%s Unrecognized operation %p", prefix, req->terminate); +} + +mDNSlocal void GetMcastClients(request_state *req) +{ + if (req->terminate == connection_termination) + { + int num_records = 0, num_ops = 0; + const registered_record_entry *p; + request_state *r; + for (p = req->u.reg_recs; p; p=p->next) + num_records++; + for (r = req->next; r; r=r->next) + if (r->primary == req) + num_ops++; + for (p = req->u.reg_recs; p; p=p->next) + { + if (!AuthRecord_uDNS(p->rr)) + n_mrecords++; + } + for (r = req->next; r; r=r->next) + if (r->primary == req) + GetMcastClients(r); + } + else if (req->terminate == regservice_termination_callback) + { + service_instance *ptr; + for (ptr = req->u.servicereg.instances; ptr; ptr = ptr->next) + { + if (!AuthRecord_uDNS(&ptr->srs.RR_SRV)) + n_mrecords++; + } + } + else if (req->terminate == browse_termination_callback) + { + browser_t *blist; + for (blist = req->u.browser.browsers; blist; blist = blist->next) + { + if (mDNSOpaque16IsZero(blist->q.TargetQID)) + n_mquests++; + } + } + else if (req->terminate == resolve_termination_callback) + { + if ((mDNSOpaque16IsZero(req->u.resolve.qsrv.TargetQID)) && (req->u.resolve.qsrv.ThisQInterval > 0)) + n_mquests++; + } + else if (req->terminate == queryrecord_termination_callback) + { + if ((mDNSOpaque16IsZero(req->u.queryrecord.q.TargetQID)) && (req->u.queryrecord.q.ThisQInterval > 0)) + n_mquests++; + } + else if (req->terminate == addrinfo_termination_callback) + { + if ((mDNSOpaque16IsZero(req->u.addrinfo.q4.TargetQID)) && (req->u.addrinfo.q4.ThisQInterval > 0)) + n_mquests++; + } + else + { + return; + } +} + + +mDNSlocal void LogMcastClientInfo(request_state *req) +{ + if (!req->terminate) + LogMcastNoIdent("No operation yet on this socket"); + else if (req->terminate == connection_termination) + { + int num_records = 0, num_ops = 0; + const registered_record_entry *p; + request_state *r; + for (p = req->u.reg_recs; p; p=p->next) + num_records++; + for (r = req->next; r; r=r->next) + if (r->primary == req) + num_ops++; + for (p = req->u.reg_recs; p; p=p->next) + { + if (!AuthRecord_uDNS(p->rr)) + LogMcastNoIdent("R: -> DNSServiceRegisterRecord: %##s %s PID[%d](%s)", p->rr->resrec.name->c, + DNSTypeName(p->rr->resrec.rrtype), req->process_id, req->pid_name, i_mcount++); + } + for (r = req->next; r; r=r->next) + if (r->primary == req) + LogMcastClientInfo(r); + } + else if (req->terminate == regservice_termination_callback) + { + service_instance *ptr; + for (ptr = req->u.servicereg.instances; ptr; ptr = ptr->next) + { + if (!AuthRecord_uDNS(&ptr->srs.RR_SRV)) + LogMcastNoIdent("R: DNSServiceRegister: %##s %u/%u PID[%d](%s)", ptr->srs.RR_SRV.resrec.name->c, mDNSVal16(req->u.servicereg.port), + SRS_PORT(&ptr->srs), req->process_id, req->pid_name, i_mcount++); + } + } + else if (req->terminate == browse_termination_callback) + { + browser_t *blist; + for (blist = req->u.browser.browsers; blist; blist = blist->next) + { + if (mDNSOpaque16IsZero(blist->q.TargetQID)) + LogMcastNoIdent("Q: DNSServiceBrowse %##s %s PID[%d](%s)", blist->q.qname.c, DNSTypeName(blist->q.qtype), + req->process_id, req->pid_name, i_mcount++); + } + } + else if (req->terminate == resolve_termination_callback) + { + if ((mDNSOpaque16IsZero(req->u.resolve.qsrv.TargetQID)) && (req->u.resolve.qsrv.ThisQInterval > 0)) + LogMcastNoIdent("Q: DNSServiceResolve %##s %s PID[%d](%s)", req->u.resolve.qsrv.qname.c, DNSTypeName(req->u.resolve.qsrv.qtype), + req->process_id, req->pid_name, i_mcount++); + } + else if (req->terminate == queryrecord_termination_callback) + { + if ((mDNSOpaque16IsZero(req->u.queryrecord.q.TargetQID)) && (req->u.queryrecord.q.ThisQInterval > 0)) + LogMcastNoIdent("Q: DNSServiceQueryRecord %##s %s PID[%d](%s)", req->u.queryrecord.q.qname.c, DNSTypeName(req->u.queryrecord.q.qtype), + req->process_id, req->pid_name, i_mcount++); + } + else if (req->terminate == addrinfo_termination_callback) + { + if ((mDNSOpaque16IsZero(req->u.addrinfo.q4.TargetQID)) && (req->u.addrinfo.q4.ThisQInterval > 0)) + LogMcastNoIdent("Q: DNSServiceGetAddrInfo %s%s %##s PID[%d](%s)", + req->u.addrinfo.protocol & kDNSServiceProtocol_IPv4 ? "v4" : " ", + req->u.addrinfo.protocol & kDNSServiceProtocol_IPv6 ? "v6" : " ", + req->u.addrinfo.q4.qname.c, req->process_id, req->pid_name, i_mcount++); + } + else + { + return; + } + +} + +mDNSlocal char *RecordTypeName(mDNSu8 rtype) +{ + switch (rtype) + { + case kDNSRecordTypeUnregistered: return ("Unregistered "); + case kDNSRecordTypeDeregistering: return ("Deregistering"); + case kDNSRecordTypeUnique: return ("Unique "); + case kDNSRecordTypeAdvisory: return ("Advisory "); + case kDNSRecordTypeShared: return ("Shared "); + case kDNSRecordTypeVerified: return ("Verified "); + case kDNSRecordTypeKnownUnique: return ("KnownUnique "); + default: return("Unknown"); + } +} + +mDNSlocal void LogEtcHosts(mDNS *const m) +{ + mDNSBool showheader = mDNStrue; + const AuthRecord *ar; + mDNSu32 slot; + AuthGroup *ag; + int count = 0; + int authslot = 0; + mDNSBool truncated = 0; + + for (slot = 0; slot < AUTH_HASH_SLOTS; slot++) + { + if (m->rrauth.rrauth_hash[slot]) authslot++; + for (ag = m->rrauth.rrauth_hash[slot]; ag; ag = ag->next) + for (ar = ag->members; ar; ar = ar->next) + { + if (ar->RecordCallback != FreeEtcHosts) continue; + if (showheader) { showheader = mDNSfalse; LogMsgNoIdent(" State Interface"); } + + // Print a maximum of 50 records + if (count++ >= 50) { truncated = mDNStrue; continue; } + if (ar->ARType == AuthRecordLocalOnly) + { + if (ar->resrec.InterfaceID == mDNSInterface_LocalOnly) + LogMsgNoIdent(" %s LO %s", RecordTypeName(ar->resrec.RecordType), ARDisplayString(m, ar)); + else + { + mDNSu32 scopeid = (mDNSu32)(uintptr_t)ar->resrec.InterfaceID; + LogMsgNoIdent(" %s %u %s", RecordTypeName(ar->resrec.RecordType), scopeid, ARDisplayString(m, ar)); + } + } + } + } + + if (showheader) LogMsgNoIdent("<None>"); + else if (truncated) LogMsgNoIdent("<Truncated: to 50 records, Total records %d, Total Auth Groups %d, Auth Slots %d>", count, m->rrauth.rrauth_totalused, authslot); +} + +mDNSlocal void LogLocalOnlyAuthRecords(mDNS *const m) +{ + mDNSBool showheader = mDNStrue; + const AuthRecord *ar; + mDNSu32 slot; + AuthGroup *ag; + + for (slot = 0; slot < AUTH_HASH_SLOTS; slot++) + { + for (ag = m->rrauth.rrauth_hash[slot]; ag; ag = ag->next) + for (ar = ag->members; ar; ar = ar->next) + { + if (ar->RecordCallback == FreeEtcHosts) continue; + if (showheader) { showheader = mDNSfalse; LogMsgNoIdent(" State Interface"); } + + // Print a maximum of 400 records + if (ar->ARType == AuthRecordLocalOnly) + LogMsgNoIdent(" %s LO %s", RecordTypeName(ar->resrec.RecordType), ARDisplayString(m, ar)); + else if (ar->ARType == AuthRecordP2P) + LogMsgNoIdent(" %s PP %s", RecordTypeName(ar->resrec.RecordType), ARDisplayString(m, ar)); + } + } + + if (showheader) LogMsgNoIdent("<None>"); +} + +mDNSlocal char *AnonInfoToString(AnonymousInfo *ai, char *anonstr, int anstrlen) +{ + anonstr[0] = 0; + if (ai && ai->AnonData) + { + return (AnonDataToString(ai->AnonData, ai->AnonDataLen, anonstr, anstrlen)); + } + return anonstr; +} + +mDNSlocal void LogOneAuthRecord(mDNS *const m, const AuthRecord *ar, mDNSs32 now, const char *const ifname) +{ + char anstr[256]; + if (AuthRecord_uDNS(ar)) + { + LogMsgNoIdent("%7d %7d %7d %7d %s", + ar->ThisAPInterval / mDNSPlatformOneSecond, + (ar->LastAPTime + ar->ThisAPInterval - now) / mDNSPlatformOneSecond, + ar->expire ? (ar->expire - now) / mDNSPlatformOneSecond : 0, + ar->state, ARDisplayString(m, ar)); + } + else + { + LogMsgNoIdent("%7d %7d %7d %7s %s%s", + ar->ThisAPInterval / mDNSPlatformOneSecond, + ar->AnnounceCount ? (ar->LastAPTime + ar->ThisAPInterval - now) / mDNSPlatformOneSecond : 0, + ar->TimeExpire ? (ar->TimeExpire - now) / mDNSPlatformOneSecond : 0, + ifname ? ifname : "ALL", + ARDisplayString(m, ar), AnonInfoToString(ar->resrec.AnonInfo, anstr, sizeof(anstr))); + } +} + +mDNSlocal void LogAuthRecords(mDNS *const m, const mDNSs32 now, AuthRecord *ResourceRecords, int *proxy) +{ + mDNSBool showheader = mDNStrue; + const AuthRecord *ar; + OwnerOptData owner = zeroOwner; + for (ar = ResourceRecords; ar; ar=ar->next) + { + const char *const ifname = InterfaceNameForID(m, ar->resrec.InterfaceID); + if ((ar->WakeUp.HMAC.l[0] != 0) == (proxy != mDNSNULL)) + { + if (showheader) { showheader = mDNSfalse; LogMsgNoIdent(" Int Next Expire State"); } + if (proxy) (*proxy)++; + if (!mDNSPlatformMemSame(&owner, &ar->WakeUp, sizeof(owner))) + { + owner = ar->WakeUp; + if (owner.password.l[0]) + LogMsgNoIdent("Proxying for H-MAC %.6a I-MAC %.6a Password %.6a seq %d", &owner.HMAC, &owner.IMAC, &owner.password, owner.seq); + else if (!mDNSSameEthAddress(&owner.HMAC, &owner.IMAC)) + LogMsgNoIdent("Proxying for H-MAC %.6a I-MAC %.6a seq %d", &owner.HMAC, &owner.IMAC, owner.seq); + else + LogMsgNoIdent("Proxying for %.6a seq %d", &owner.HMAC, owner.seq); + } + if (AuthRecord_uDNS(ar)) + { + LogOneAuthRecord(m, ar, now, ifname); + } + else if (ar->ARType == AuthRecordLocalOnly) + { + LogMsgNoIdent(" LO %s", ARDisplayString(m, ar)); + } + else if (ar->ARType == AuthRecordP2P) + { + LogMsgNoIdent(" PP %s", ARDisplayString(m, ar)); + } + else + { + LogOneAuthRecord(m, ar, now, ifname); + if (ar->resrec.AnonInfo) + { + ResourceRecord *nsec3 = ar->resrec.AnonInfo->nsec3RR; + // We just print the values from the AuthRecord to keep it nicely aligned though + // all we want here is the nsec3 information. + LogMsgNoIdent("%7d %7d %7d %7s %s", + ar->ThisAPInterval / mDNSPlatformOneSecond, + ar->AnnounceCount ? (ar->LastAPTime + ar->ThisAPInterval - now) / mDNSPlatformOneSecond : 0, + ar->TimeExpire ? (ar->TimeExpire - now) / mDNSPlatformOneSecond : 0, + ifname ? ifname : "ALL", + RRDisplayString(m, nsec3)); + } + } + } + } + if (showheader) LogMsgNoIdent("<None>"); +} + +mDNSlocal void PrintOneCacheRecord(mDNS *const m, const CacheRecord *cr, mDNSu32 slot, const mDNSu32 remain, const char *ifname, mDNSu32 *CacheUsed) +{ + LogMsgNoIdent("%3d %s%8d %-7s%s %-6s%s", + slot, + cr->CRActiveQuestion ? "*" : " ", + remain, + ifname ? ifname : "-U-", + (cr->resrec.RecordType == kDNSRecordTypePacketNegative) ? "-" : + (cr->resrec.RecordType & kDNSRecordTypePacketUniqueMask) ? " " : "+", + DNSTypeName(cr->resrec.rrtype), + CRDisplayString(m, cr)); + (*CacheUsed)++; +} + +mDNSlocal void PrintCachedRecords(mDNS *const m, const CacheRecord *cr, mDNSu32 slot, const mDNSu32 remain, const char *ifname, mDNSu32 *CacheUsed) +{ + CacheRecord *nsec; + CacheRecord *soa; + nsec = cr->nsec; + + // The records that are cached under the main cache record like nsec, soa don't have + // their own lifetime. If the main cache record expires, they also expire. + while (nsec) + { + PrintOneCacheRecord(m, nsec, slot, remain, ifname, CacheUsed); + nsec = nsec->next; + } + soa = cr->soa; + if (soa) + { + PrintOneCacheRecord(m, soa, slot, remain, ifname, CacheUsed); + } + if (cr->resrec.AnonInfo) + { + ResourceRecord *nsec3 = cr->resrec.AnonInfo->nsec3RR; + // Even though it is a resource record, we print the sameway + // as a cache record so that it aligns properly. + if (nsec3) + { + LogMsgNoIdent("%3d %s%8d %-7s%s %-6s%s", + slot, + " ", + remain, + ifname ? ifname : "-U-", + (nsec3->RecordType == kDNSRecordTypePacketNegative) ? "-" : + (nsec3->RecordType & kDNSRecordTypePacketUniqueMask) ? " " : "+", + DNSTypeName(nsec3->rrtype), + RRDisplayString(m, nsec3)); + } + } +} + +mDNSlocal char *AnonDataToString(const mDNSu8 *ad, int adlen, char *adstr, int adstrlen) +{ + adstr[0] = 0; + if (ad) + { + int len; + char *orig = adstr; + + // If the caller is lazy to compute the length, we do it for them. + if (!adlen) + len = strlen((const char *)ad); + else + len = adlen; + + // Print the anondata within brackets. Hence, we need space for two + // brackets and a NULL byte. + if (len > (adstrlen - 3)) + len = adstrlen - 3; + + *adstr++ = '('; + mDNSPlatformMemCopy(adstr, ad, len); + adstr[len] = ')'; + adstr[len+1] = 0; + return orig; + } + return adstr; +} + +mDNSexport void LogMDNSStatistics(mDNS *const m) +{ + LogMsgNoIdent("--- MDNS Statistics ---"); + + LogMsgNoIdent("Name Conflicts %u", m->mDNSStats.NameConflicts); + LogMsgNoIdent("KnownUnique Name Conflicts %u", m->mDNSStats.KnownUniqueNameConflicts); + LogMsgNoIdent("Duplicate Query Suppressions %u", m->mDNSStats.DupQuerySuppressions); + LogMsgNoIdent("KA Suppressions %u", m->mDNSStats.KnownAnswerSuppressions); + LogMsgNoIdent("KA Multiple Packets %u", m->mDNSStats.KnownAnswerMultiplePkts); + LogMsgNoIdent("Poof Cache Deletions %u", m->mDNSStats.PoofCacheDeletions); + LogMsgNoIdent("--------------------------------"); + + LogMsgNoIdent("Multicast packets Sent %u", m->MulticastPacketsSent); + LogMsgNoIdent("Multicast packets Received %u", m->MPktNum); + LogMsgNoIdent("Remote Subnet packets %u", m->RemoteSubnet); + LogMsgNoIdent("QU questions received %u", m->mDNSStats.UnicastBitInQueries); + LogMsgNoIdent("Normal multicast questions %u", m->mDNSStats.NormalQueries); + LogMsgNoIdent("Answers for questions %u", m->mDNSStats.MatchingAnswersForQueries); + LogMsgNoIdent("Unicast responses %u", m->mDNSStats.UnicastResponses); + LogMsgNoIdent("Multicast responses %u", m->mDNSStats.MulticastResponses); + LogMsgNoIdent("Unicast response Demotions %u", m->mDNSStats.UnicastDemotedToMulticast); + LogMsgNoIdent("--------------------------------"); + + LogMsgNoIdent("Sleeps %u", m->mDNSStats.Sleeps); + LogMsgNoIdent("Wakeups %u", m->mDNSStats.Wakes); + LogMsgNoIdent("Interface UP events %u", m->mDNSStats.InterfaceUp); + LogMsgNoIdent("Interface UP Flap events %u", m->mDNSStats.InterfaceUpFlap); + LogMsgNoIdent("Interface Down events %u", m->mDNSStats.InterfaceDown); + LogMsgNoIdent("Interface DownFlap events %u", m->mDNSStats.InterfaceDownFlap); + LogMsgNoIdent("Cache refresh queries %u", m->mDNSStats.CacheRefreshQueries); + LogMsgNoIdent("Cache refreshed %u", m->mDNSStats.CacheRefreshed); + LogMsgNoIdent("Wakeup on Resolves %u", m->mDNSStats.WakeOnResolves); +} + +mDNSexport void udsserver_info(mDNS *const m) +{ + const mDNSs32 now = mDNS_TimeNow(m); + mDNSu32 CacheUsed = 0, CacheActive = 0, slot; + int ProxyA = 0, ProxyD = 0; + const CacheGroup *cg; + const CacheRecord *cr; + const DNSQuestion *q; + const DNameListElem *d; + const SearchListElem *s; + + LogMsgNoIdent("Timenow 0x%08lX (%d)", (mDNSu32)now, now); + + LogMsgNoIdent("------------ Cache -------------"); + LogMsgNoIdent("Slt Q TTL if U Type rdlen"); + for (slot = 0; slot < CACHE_HASH_SLOTS; slot++) + { + for (cg = m->rrcache_hash[slot]; cg; cg=cg->next) + { + CacheUsed++; // Count one cache entity for the CacheGroup object + for (cr = cg->members; cr; cr=cr->next) + { + const mDNSs32 remain = cr->resrec.rroriginalttl - (now - cr->TimeRcvd) / mDNSPlatformOneSecond; + const char *ifname; + mDNSInterfaceID InterfaceID = cr->resrec.InterfaceID; + if (!InterfaceID && cr->resrec.rDNSServer && cr->resrec.rDNSServer->scoped) + InterfaceID = cr->resrec.rDNSServer->interface; + ifname = InterfaceNameForID(m, InterfaceID); + if (cr->CRActiveQuestion) CacheActive++; + PrintOneCacheRecord(m, cr, slot, remain, ifname, &CacheUsed); + PrintCachedRecords(m, cr, slot, remain, ifname, &CacheUsed); + } + } + } + + if (m->rrcache_totalused != CacheUsed) + LogMsgNoIdent("Cache use mismatch: rrcache_totalused is %lu, true count %lu", m->rrcache_totalused, CacheUsed); + if (m->rrcache_active != CacheActive) + LogMsgNoIdent("Cache use mismatch: rrcache_active is %lu, true count %lu", m->rrcache_active, CacheActive); + LogMsgNoIdent("Cache currently contains %lu entities; %lu referenced by active questions", CacheUsed, CacheActive); + + LogMsgNoIdent("--------- Auth Records ---------"); + LogAuthRecords(m, now, m->ResourceRecords, mDNSNULL); + + LogMsgNoIdent("--------- LocalOnly, P2P Auth Records ---------"); + LogLocalOnlyAuthRecords(m); + + LogMsgNoIdent("--------- /etc/hosts ---------"); + LogEtcHosts(m); + + LogMsgNoIdent("------ Duplicate Records -------"); + LogAuthRecords(m, now, m->DuplicateRecords, mDNSNULL); + + LogMsgNoIdent("----- Auth Records Proxied -----"); + LogAuthRecords(m, now, m->ResourceRecords, &ProxyA); + + LogMsgNoIdent("-- Duplicate Records Proxied ---"); + LogAuthRecords(m, now, m->DuplicateRecords, &ProxyD); + + LogMsgNoIdent("---------- Questions -----------"); + if (!m->Questions) LogMsgNoIdent("<None>"); + else + { + char anonstr[256]; + CacheUsed = 0; + CacheActive = 0; + LogMsgNoIdent(" Int Next if T NumAns VDNS Qptr DupOf SU SQ Type Name"); + for (q = m->Questions; q; q=q->next) + { + mDNSs32 i = q->ThisQInterval / mDNSPlatformOneSecond; + mDNSs32 n = (NextQSendTime(q) - now) / mDNSPlatformOneSecond; + char *ifname = InterfaceNameForID(m, q->InterfaceID); + CacheUsed++; + if (q->ThisQInterval) CacheActive++; + LogMsgNoIdent("%6d%6d %-7s%s%s %5d 0x%x%x 0x%p 0x%p %1d %2d %-5s%##s%s%s", + i, n, + ifname ? ifname : mDNSOpaque16IsZero(q->TargetQID) ? "" : "-U-", + mDNSOpaque16IsZero(q->TargetQID) ? (q->LongLived ? "l" : " ") : (q->LongLived ? "L" : "O"), + PrivateQuery(q) ? "P" : q->ValidationRequired ? "V" : q->ValidatingResponse ? "R" : " ", + q->CurrentAnswers, q->validDNSServers.l[1], q->validDNSServers.l[0], q, q->DuplicateOf, + q->SuppressUnusable, q->SuppressQuery, DNSTypeName(q->qtype), q->qname.c, + AnonInfoToString(q->AnonInfo, anonstr, sizeof(anonstr)), + q->DuplicateOf ? " (dup)" : ""); + } + LogMsgNoIdent("%lu question%s; %lu active", CacheUsed, CacheUsed > 1 ? "s" : "", CacheActive); + } + + LogMsgNoIdent("----- Local-Only Questions -----"); + if (!m->LocalOnlyQuestions) LogMsgNoIdent("<None>"); + else for (q = m->LocalOnlyQuestions; q; q=q->next) + LogMsgNoIdent(" %5d %-6s%##s%s", + q->CurrentAnswers, DNSTypeName(q->qtype), q->qname.c, q->DuplicateOf ? " (dup)" : ""); + + LogMsgNoIdent("---- Active UDS Client Requests ----"); + if (!all_requests) LogMsgNoIdent("<None>"); + else + { + request_state *req, *r; + for (req = all_requests; req; req=req->next) + { + if (req->primary) // If this is a subbordinate operation, check that the parent is in the list + { + for (r = all_requests; r && r != req; r=r->next) if (r == req->primary) goto foundparent; + LogMsgNoIdent("%3d: Orhpan operation %p; parent %p not found in request list", req->sd); + } + // For non-subbordinate operations, and subbordinate operations that have lost their parent, write out their info + LogClientInfo(m, req); +foundparent:; + } + } + + LogMsgNoIdent("-------- NAT Traversals --------"); + LogMsgNoIdent("ExtAddress %.4a Retry %d Interval %d", + &m->ExtAddress, + m->retryGetAddr ? (m->retryGetAddr - now) / mDNSPlatformOneSecond : 0, + m->retryIntervalGetAddr / mDNSPlatformOneSecond); + if (m->NATTraversals) + { + const NATTraversalInfo *nat; + for (nat = m->NATTraversals; nat; nat=nat->next) + { + LogMsgNoIdent("%p %s Int %5d %s Err %d Retry %5d Interval %5d Expire %5d Req %.4a:%d Ext %.4a:%d", + nat, + nat->Protocol ? (nat->Protocol == NATOp_MapTCP ? "TCP" : "UDP") : "ADD", + mDNSVal16(nat->IntPort), + (nat->lastSuccessfulProtocol == NATTProtocolNone ? "None " : + nat->lastSuccessfulProtocol == NATTProtocolNATPMP ? "NAT-PMP " : + nat->lastSuccessfulProtocol == NATTProtocolUPNPIGD ? "UPnP/IGD" : + nat->lastSuccessfulProtocol == NATTProtocolPCP ? "PCP " : + /* else */ "Unknown " ), + nat->Result, + nat->retryPortMap ? (nat->retryPortMap - now) / mDNSPlatformOneSecond : 0, + nat->retryInterval / mDNSPlatformOneSecond, + nat->ExpiryTime ? (nat->ExpiryTime - now) / mDNSPlatformOneSecond : 0, + &nat->NewAddress, mDNSVal16(nat->RequestedPort), + &nat->ExternalAddress, mDNSVal16(nat->ExternalPort)); + } + } + + LogMsgNoIdent("--------- AuthInfoList ---------"); + if (!m->AuthInfoList) LogMsgNoIdent("<None>"); + else + { + const DomainAuthInfo *a; + for (a = m->AuthInfoList; a; a = a->next) + { + LogMsgNoIdent("%##s %##s %##s %d %d %.16a%s", + a->domain.c, a->keyname.c, + a->hostname.c, (a->port.b[0] << 8 | a->port.b[1]), + (a->deltime ? (a->deltime - now) : 0), + &a->AutoTunnelInnerAddress, a->AutoTunnel ? " AutoTunnel" : ""); + } + } + + #if APPLE_OSX_mDNSResponder + LogMsgNoIdent("--------- TunnelClients --------"); + if (!m->TunnelClients) LogMsgNoIdent("<None>"); + else + { + const ClientTunnel *c; + for (c = m->TunnelClients; c; c = c->next) + LogMsgNoIdent("%##s local %.16a %.4a %.16a remote %.16a %.4a %5d %.16a interval %d", + c->dstname.c, &c->loc_inner, &c->loc_outer, &c->loc_outer6, &c->rmt_inner, &c->rmt_outer, mDNSVal16(c->rmt_outer_port), &c->rmt_outer6, c->q.ThisQInterval); + } + #endif // APPLE_OSX_mDNSResponder + + LogMsgNoIdent("---------- Misc State ----------"); + + LogMsgNoIdent("PrimaryMAC: %.6a", &m->PrimaryMAC); + + LogMsgNoIdent("m->SleepState %d (%s) seq %d", + m->SleepState, + m->SleepState == SleepState_Awake ? "Awake" : + m->SleepState == SleepState_Transferring ? "Transferring" : + m->SleepState == SleepState_Sleeping ? "Sleeping" : "?", + m->SleepSeqNum); + + if (!m->SPSSocket) LogMsgNoIdent("Not offering Sleep Proxy Service"); +#ifndef SPC_DISABLED + else LogMsgNoIdent("Offering Sleep Proxy Service: %#s", m->SPSRecords.RR_SRV.resrec.name->c); +#endif + if (m->ProxyRecords == ProxyA + ProxyD) LogMsgNoIdent("ProxyRecords: %d + %d = %d", ProxyA, ProxyD, ProxyA + ProxyD); + else LogMsgNoIdent("ProxyRecords: MISMATCH %d + %d = %d ≠ %d", ProxyA, ProxyD, ProxyA + ProxyD, m->ProxyRecords); + + LogMsgNoIdent("------ Auto Browse Domains -----"); + if (!AutoBrowseDomains) LogMsgNoIdent("<None>"); + else for (d=AutoBrowseDomains; d; d=d->next) LogMsgNoIdent("%##s", d->name.c); + + LogMsgNoIdent("--- Auto Registration Domains --"); + if (!AutoRegistrationDomains) LogMsgNoIdent("<None>"); + else for (d=AutoRegistrationDomains; d; d=d->next) LogMsgNoIdent("%##s", d->name.c); + + LogMsgNoIdent("--- Search Domains --"); + if (!SearchList) LogMsgNoIdent("<None>"); + else + { + for (s=SearchList; s; s=s->next) + { + char *ifname = InterfaceNameForID(m, s->InterfaceID); + LogMsgNoIdent("%##s %s", s->domain.c, ifname ? ifname : ""); + } + } + LogInfo("--- Trust Anchors ---"); + if (!m->TrustAnchors) + { + LogInfo("<None>"); + } + else + { + TrustAnchor *ta; + mDNSu8 fromTimeBuf[64]; + mDNSu8 untilTimeBuf[64]; + + for (ta=m->TrustAnchors; ta; ta=ta->next) + { + mDNSPlatformFormatTime((unsigned long)ta->validFrom, fromTimeBuf, sizeof(fromTimeBuf)); + mDNSPlatformFormatTime((unsigned long)ta->validUntil, untilTimeBuf, sizeof(untilTimeBuf)); + LogInfo("%##s %d %d %d %d %s %s", ta->zone.c, ta->rds.keyTag, + ta->rds.alg, ta->rds.digestType, ta->digestLen, fromTimeBuf, untilTimeBuf); + } + } + + LogInfo("--- DNSSEC Statistics ---"); + + LogInfo("Next Stats Time %u", m->NextStatLogTime - mDNSPlatformUTC()); + LogMsgNoIdent("Unicast Cache size %u", m->rrcache_totalused_unicast); + LogInfo("DNSSEC Cache size %u", m->DNSSECStats.TotalMemUsed); + if (m->rrcache_totalused_unicast) + LogInfo("DNSSEC usage percentage %u", ((unsigned long)(m->DNSSECStats.TotalMemUsed * 100))/m->rrcache_totalused_unicast); + LogInfo("DNSSEC Extra Packets (0 to 2) %u", m->DNSSECStats.ExtraPackets0); + LogInfo("DNSSEC Extra Packets (3 to 6) %u", m->DNSSECStats.ExtraPackets3); + LogInfo("DNSSEC Extra Packets (7 to 9) %u", m->DNSSECStats.ExtraPackets7); + LogInfo("DNSSEC Extra Packets ( >= 10) %u", m->DNSSECStats.ExtraPackets10); + + LogInfo("DNSSEC Latency (0 to 4ms) %u", m->DNSSECStats.Latency0); + LogInfo("DNSSEC Latency (4 to 9ms) %u", m->DNSSECStats.Latency5); + LogInfo("DNSSEC Latency (10 to 19ms) %u", m->DNSSECStats.Latency10); + LogInfo("DNSSEC Latency (20 to 49ms) %u", m->DNSSECStats.Latency20); + LogInfo("DNSSEC Latency (50 to 99ms) %u", m->DNSSECStats.Latency50); + LogInfo("DNSSEC Latency ( >=100ms) %u", m->DNSSECStats.Latency100); + + LogInfo("DNSSEC Secure Status %u", m->DNSSECStats.SecureStatus); + LogInfo("DNSSEC Insecure Status %u", m->DNSSECStats.InsecureStatus); + LogInfo("DNSSEC Indeterminate Status %u", m->DNSSECStats.IndeterminateStatus); + LogInfo("DNSSEC Bogus Status %u", m->DNSSECStats.BogusStatus); + LogInfo("DNSSEC NoResponse Status %u", m->DNSSECStats.NoResponseStatus); + LogInfo("DNSSEC Probes sent %u", m->DNSSECStats.NumProbesSent); + LogInfo("DNSSEC Msg Size (<=1024) %u", m->DNSSECStats.MsgSize0); + LogInfo("DNSSEC Msg Size (<=2048) %u", m->DNSSECStats.MsgSize1); + LogInfo("DNSSEC Msg Size (> 2048) %u", m->DNSSECStats.MsgSize2); + + LogMDNSStatistics(m); + + LogMsgNoIdent("---- Task Scheduling Timers ----"); + + if (!m->NewQuestions) + LogMsgNoIdent("NewQuestion <NONE>"); + else + LogMsgNoIdent("NewQuestion DelayAnswering %d %d %##s (%s)", + m->NewQuestions->DelayAnswering, m->NewQuestions->DelayAnswering-now, + m->NewQuestions->qname.c, DNSTypeName(m->NewQuestions->qtype)); + + if (!m->NewLocalOnlyQuestions) + LogMsgNoIdent("NewLocalOnlyQuestions <NONE>"); + else + LogMsgNoIdent("NewLocalOnlyQuestions %##s (%s)", + m->NewLocalOnlyQuestions->qname.c, DNSTypeName(m->NewLocalOnlyQuestions->qtype)); + + if (!m->NewLocalRecords) + LogMsgNoIdent("NewLocalRecords <NONE>"); + else + LogMsgNoIdent("NewLocalRecords %02X %s", m->NewLocalRecords->resrec.RecordType, ARDisplayString(m, m->NewLocalRecords)); + + LogMsgNoIdent("SPSProxyListChanged%s", m->SPSProxyListChanged ? "" : " <NONE>"); + LogMsgNoIdent("LocalRemoveEvents%s", m->LocalRemoveEvents ? "" : " <NONE>"); + LogMsgNoIdent("m->AutoTunnelRelayAddr %.16a", &m->AutoTunnelRelayAddr); + LogMsgNoIdent("m->WABBrowseQueriesCount %d", m->WABBrowseQueriesCount); + LogMsgNoIdent("m->WABLBrowseQueriesCount %d", m->WABLBrowseQueriesCount); + LogMsgNoIdent("m->WABRegQueriesCount %d", m->WABRegQueriesCount); + LogMsgNoIdent("m->mDNSOppCaching %d", m->mDNSOppCaching); + LogMsgNoIdent("m->AutoTargetServices %d", m->AutoTargetServices); + +#define LogTimer(MSG,T) LogMsgNoIdent( MSG " %08X %11d %08X %11d", (T), (T), (T)-now, (T)-now) + + LogMsgNoIdent(" ABS (hex) ABS (dec) REL (hex) REL (dec)"); + LogMsgNoIdent("m->timenow %08X %11d", now, now); + LogMsgNoIdent("m->timenow_adjust %08X %11d", m->timenow_adjust, m->timenow_adjust); + LogTimer("m->NextScheduledEvent ", m->NextScheduledEvent); + +#ifndef UNICAST_DISABLED + LogTimer("m->NextuDNSEvent ", m->NextuDNSEvent); + LogTimer("m->NextSRVUpdate ", m->NextSRVUpdate); + LogTimer("m->NextScheduledNATOp ", m->NextScheduledNATOp); + LogTimer("m->retryGetAddr ", m->retryGetAddr); +#endif + + LogTimer("m->NextCacheCheck ", m->NextCacheCheck); + LogTimer("m->NextScheduledSPS ", m->NextScheduledSPS); + LogTimer("m->NextScheduledKA ", m->NextScheduledKA); + LogTimer("m->NextScheduledSPRetry ", m->NextScheduledSPRetry); + LogTimer("m->DelaySleep ", m->DelaySleep); + + LogTimer("m->NextScheduledQuery ", m->NextScheduledQuery); + LogTimer("m->NextScheduledProbe ", m->NextScheduledProbe); + LogTimer("m->NextScheduledResponse", m->NextScheduledResponse); + + LogTimer("m->SuppressSending ", m->SuppressSending); + LogTimer("m->SuppressProbes ", m->SuppressProbes); + LogTimer("m->ProbeFailTime ", m->ProbeFailTime); + LogTimer("m->DelaySleep ", m->DelaySleep); + LogTimer("m->SleepLimit ", m->SleepLimit); + LogTimer("m->NextScheduledStopTime ", m->NextScheduledStopTime); +} + +#if APPLE_OSX_mDNSResponder && MACOSX_MDNS_MALLOC_DEBUGGING +mDNSexport void uds_validatelists(void) +{ + const request_state *req, *p; + for (req = all_requests; req; req=req->next) + { + if (req->next == (request_state *)~0 || (req->sd < 0 && req->sd != -2)) + LogMemCorruption("UDS request list: %p is garbage (%d)", req, req->sd); + + if (req->primary == req) + LogMemCorruption("UDS request list: req->primary should not point to self %p/%d", req, req->sd); + + if (req->primary && req->replies) + LogMemCorruption("UDS request list: Subordinate request %p/%d/%p should not have replies (%p)", + req, req->sd, req->primary && req->replies); + + p = req->primary; + if ((long)p & 3) + LogMemCorruption("UDS request list: req %p primary %p is misaligned (%d)", req, p, req->sd); + else if (p && (p->next == (request_state *)~0 || (p->sd < 0 && p->sd != -2))) + LogMemCorruption("UDS request list: req %p primary %p is garbage (%d)", req, p, p->sd); + + reply_state *rep; + for (rep = req->replies; rep; rep=rep->next) + if (rep->next == (reply_state *)~0) + LogMemCorruption("UDS req->replies: %p is garbage", rep); + + if (req->terminate == connection_termination) + { + registered_record_entry *r; + for (r = req->u.reg_recs; r; r=r->next) + if (r->next == (registered_record_entry *)~0) + LogMemCorruption("UDS req->u.reg_recs: %p is garbage", r); + } + else if (req->terminate == regservice_termination_callback) + { + service_instance *s; + for (s = req->u.servicereg.instances; s; s=s->next) + if (s->next == (service_instance *)~0) + LogMemCorruption("UDS req->u.servicereg.instances: %p is garbage", s); + } + else if (req->terminate == browse_termination_callback) + { + browser_t *b; + for (b = req->u.browser.browsers; b; b=b->next) + if (b->next == (browser_t *)~0) + LogMemCorruption("UDS req->u.browser.browsers: %p is garbage", b); + } + } + + DNameListElem *d; + for (d = SCPrefBrowseDomains; d; d=d->next) + if (d->next == (DNameListElem *)~0 || d->name.c[0] > 63) + LogMemCorruption("SCPrefBrowseDomains: %p is garbage (%d)", d, d->name.c[0]); + + ARListElem *b; + for (b = LocalDomainEnumRecords; b; b=b->next) + if (b->next == (ARListElem *)~0 || b->ar.resrec.name->c[0] > 63) + LogMemCorruption("LocalDomainEnumRecords: %p is garbage (%d)", b, b->ar.resrec.name->c[0]); + + for (d = AutoBrowseDomains; d; d=d->next) + if (d->next == (DNameListElem *)~0 || d->name.c[0] > 63) + LogMemCorruption("AutoBrowseDomains: %p is garbage (%d)", d, d->name.c[0]); + + for (d = AutoRegistrationDomains; d; d=d->next) + if (d->next == (DNameListElem *)~0 || d->name.c[0] > 63) + LogMemCorruption("AutoRegistrationDomains: %p is garbage (%d)", d, d->name.c[0]); +} +#endif // APPLE_OSX_mDNSResponder && MACOSX_MDNS_MALLOC_DEBUGGING + +mDNSlocal int send_msg(request_state *const req) +{ + reply_state *const rep = req->replies; // Send the first waiting reply + ssize_t nwriten; + if (req->no_reply) return(t_complete); + + ConvertHeaderBytes(rep->mhdr); + nwriten = send(req->sd, (char *)&rep->mhdr + rep->nwriten, rep->totallen - rep->nwriten, 0); + ConvertHeaderBytes(rep->mhdr); + + if (nwriten < 0) + { + if (dnssd_errno == dnssd_EINTR || dnssd_errno == dnssd_EWOULDBLOCK) nwriten = 0; + else + { +#if !defined(PLATFORM_NO_EPIPE) + if (dnssd_errno == EPIPE) + return(req->ts = t_terminated); + else +#endif + { + LogMsg("send_msg ERROR: failed to write %d of %d bytes to fd %d errno %d (%s)", + rep->totallen - rep->nwriten, rep->totallen, req->sd, dnssd_errno, dnssd_strerror(dnssd_errno)); + return(t_error); + } + } + } + rep->nwriten += nwriten; + return (rep->nwriten == rep->totallen) ? t_complete : t_morecoming; +} + +mDNSexport mDNSs32 udsserver_idle(mDNSs32 nextevent) +{ + mDNSs32 now = mDNS_TimeNow(&mDNSStorage); + request_state **req = &all_requests; + + while (*req) + { + request_state *const r = *req; + + if (r->terminate == resolve_termination_callback) + if (r->u.resolve.ReportTime && now - r->u.resolve.ReportTime >= 0) + { + r->u.resolve.ReportTime = 0; + LogMsgNoIdent("Client application bug PID[%d](%s) : DNSServiceResolve(%##s) active for over two minutes. " + "This places considerable burden on the network.", r->process_id, r->pid_name, r->u.resolve.qsrv.qname.c); + } + + // Note: Only primary req's have reply lists, not subordinate req's. + while (r->replies) // Send queued replies + { + transfer_state result; + if (r->replies->next) + r->replies->rhdr->flags |= dnssd_htonl(kDNSServiceFlagsMoreComing); + result = send_msg(r); // Returns t_morecoming if buffer full because client is not reading + if (result == t_complete) + { + reply_state *fptr = r->replies; + r->replies = r->replies->next; + freeL("reply_state/udsserver_idle", fptr); + r->time_blocked = 0; // reset failure counter after successful send + r->unresponsiveness_reports = 0; + continue; + } + else if (result == t_terminated || result == t_error) + { + LogMsg("%3d: Could not write data to clientPID[%d](%s) because of error - aborting connection", r->sd, r->process_id, r->pid_name); + LogClientInfo(&mDNSStorage, r); + abort_request(r); + } + break; + } + + if (r->replies) // If we failed to send everything, check our time_blocked timer + { + if (nextevent - now > mDNSPlatformOneSecond) + nextevent = now + mDNSPlatformOneSecond; + + if (mDNSStorage.SleepState != SleepState_Awake) + r->time_blocked = 0; + else if (!r->time_blocked) + r->time_blocked = NonZeroTime(now); + else if (now - r->time_blocked >= 10 * mDNSPlatformOneSecond * (r->unresponsiveness_reports+1)) + { + int num = 0; + struct reply_state *x = r->replies; + while (x) + { + num++; + x=x->next; + } + LogMsg("%3d: Could not write data to client PID[%d](%s) after %ld seconds, %d repl%s waiting", + r->sd, r->process_id, r->pid_name, (now - r->time_blocked) / mDNSPlatformOneSecond, num, num == 1 ? "y" : "ies"); + if (++r->unresponsiveness_reports >= 60) + { + LogMsg("%3d: Client PID[%d](%s) unresponsive; aborting connection", r->sd, r->process_id, r->pid_name); + LogClientInfo(&mDNSStorage, r); + abort_request(r); + } + } + } + + if (!dnssd_SocketValid(r->sd)) // If this request is finished, unlink it from the list and free the memory + { + // Since we're already doing a list traversal, we unlink the request directly instead of using AbortUnlinkAndFree() + *req = r->next; + freeL("request_state/udsserver_idle", r); + } + else + req = &r->next; + } + return nextevent; +} + +struct CompileTimeAssertionChecks_uds_daemon +{ + // Check our structures are reasonable sizes. Including overly-large buffers, or embedding + // other overly-large structures instead of having a pointer to them, can inadvertently + // cause structure sizes (and therefore memory usage) to balloon unreasonably. + char sizecheck_request_state [(sizeof(request_state) <= 2000) ? 1 : -1]; + char sizecheck_registered_record_entry[(sizeof(registered_record_entry) <= 60) ? 1 : -1]; + char sizecheck_service_instance [(sizeof(service_instance) <= 6552) ? 1 : -1]; + char sizecheck_browser_t [(sizeof(browser_t) <= 1096) ? 1 : -1]; + char sizecheck_reply_hdr [(sizeof(reply_hdr) <= 12) ? 1 : -1]; + char sizecheck_reply_state [(sizeof(reply_state) <= 64) ? 1 : -1]; +}; diff --git a/mDNSResponder/mDNSShared/uds_daemon.h b/mDNSResponder/mDNSShared/uds_daemon.h new file mode 100644 index 00000000..ca361172 --- /dev/null +++ b/mDNSResponder/mDNSShared/uds_daemon.h @@ -0,0 +1,90 @@ +/* -*- Mode: C; tab-width: 4 -*- + * + * Copyright (c) 2002-2003 Apple Computer, Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + + File: uds_daemon.h + + Contains: Interfaces necessary to talk to uds_daemon.c. + + Version: 1.0 + + */ + +#include "mDNSEmbeddedAPI.h" +#include "dnssd_ipc.h" + +/* Client interface: */ + +#define SRS_PORT(S) mDNSVal16((S)->RR_SRV.resrec.rdata->u.srv.port) + +extern int udsserver_init(dnssd_sock_t skts[], mDNSu32 count); +extern mDNSs32 udsserver_idle(mDNSs32 nextevent); +extern void udsserver_info(mDNS *const m); // print out info about current state +extern void udsserver_handle_configchange(mDNS *const m); +extern int udsserver_exit(void); // should be called prior to app exit +extern void LogMcastStateInfo(mDNS *const m, mDNSBool mflag, mDNSBool start, mDNSBool mstatelog); +#define LogMcastQ (mDNS_McastLoggingEnabled == 0) ? ((void)0) : LogMcastQuestion +#define LogMcastS (mDNS_McastLoggingEnabled == 0) ? ((void)0) : LogMcastService +#define LogMcast (mDNS_McastLoggingEnabled == 0) ? ((void)0) : LogMsg +#define LogMcastNoIdent (mDNS_McastLoggingEnabled == 0) ? ((void)0) : LogMsgNoIdent + +/* Routines that uds_daemon expects to link against: */ + +typedef void (*udsEventCallback)(int fd, short filter, void *context); +extern mStatus udsSupportAddFDToEventLoop(dnssd_sock_t fd, udsEventCallback callback, void *context, void **platform_data); +extern int udsSupportReadFD(dnssd_sock_t fd, char* buf, int len, int flags, void *platform_data); +extern mStatus udsSupportRemoveFDFromEventLoop(dnssd_sock_t fd, void *platform_data); // Note: This also CLOSES the file descriptor as well + +extern void RecordUpdatedNiceLabel(mDNS *const m, mDNSs32 delay); + +// Globals and functions defined in uds_daemon.c and also shared with the old "daemon.c" on OS X + +extern mDNS mDNSStorage; +extern DNameListElem *AutoRegistrationDomains; +extern DNameListElem *AutoBrowseDomains; + +extern mDNSs32 ChopSubTypes(char *regtype, char **AnonData); +extern AuthRecord *AllocateSubTypes(mDNSs32 NumSubTypes, char *p, char **AnonData); +extern int CountExistingRegistrations(domainname *srv, mDNSIPPort port); +extern void FreeExtraRR(mDNS *const m, AuthRecord *const rr, mStatus result); +extern int CountPeerRegistrations(mDNS *const m, ServiceRecordSet *const srs); + +#if APPLE_OSX_mDNSResponder + +extern void machserver_automatic_browse_domain_changed(const domainname *d, mDNSBool add); +extern void machserver_automatic_registration_domain_changed(const domainname *d, mDNSBool add); +// D2D interface support +extern void external_start_browsing_for_service(mDNSInterfaceID InterfaceID, const domainname *const type, DNS_TypeValues qtype, DNSServiceFlags flags); +extern void external_stop_browsing_for_service(mDNSInterfaceID InterfaceID, const domainname *const type, DNS_TypeValues qtype, DNSServiceFlags flags); +extern void external_start_advertising_service(const ResourceRecord *const resourceRecord, DNSServiceFlags flags); +extern void external_stop_advertising_service(const ResourceRecord *const resourceRecord, DNSServiceFlags flags); +extern void external_start_resolving_service(mDNSInterfaceID InterfaceID, const domainname *const fqdn, DNSServiceFlags flags); +extern void external_stop_resolving_service(mDNSInterfaceID InterfaceID, const domainname *const fqdn, DNSServiceFlags flags); +extern void external_connection_release(const domainname *instance); + +#else // APPLE_OSX_mDNSResponder + +#define external_start_browsing_for_service(A,B,C,D) (void)(A) +#define external_stop_browsing_for_service(A,B,C,D) (void)(A) +#define external_start_advertising_service(A,B) (void)(A) +#define external_stop_advertising_service(A,B) (void)(A) +#define external_start_resolving_service(A,B,C) (void)(A) +#define external_stop_resolving_service(A,B,C) (void)(A) +#define external_connection_release(A) (void)(A) + +#endif // APPLE_OSX_mDNSResponder + +extern const char mDNSResponderVersionString_SCCS[]; +#define mDNSResponderVersionString (mDNSResponderVersionString_SCCS+5) |