+ // Set up SIGINT handler.
+
+ signal( SIGINT, SIG_IGN );
+ err = DispatchSignalSourceCreate( SIGINT, Exit, kExitReason_SIGINT, &signalSource );
+ require_noerr( err, exit );
+ dispatch_resume( signalSource );
+
+ // Check command parameters.
+
+ if( gSSDPDiscover_ReceiveSecs < -1 )
+ {
+ FPrintF( stdout, "Invalid receive time: %d seconds.\n", gSSDPDiscover_ReceiveSecs );
+ err = kParamErr;
+ goto exit;
+ }
+
+ // Create context.
+
+ context = (SSDPDiscoverContext *) calloc( 1, sizeof( *context ) );
+ require_action( context, exit, err = kNoMemoryErr );
+
+ context->receiveSecs = gSSDPDiscover_ReceiveSecs;
+ context->useIPv4 = ( gSSDPDiscover_UseIPv4 || !gSSDPDiscover_UseIPv6 ) ? true : false;
+ context->useIPv6 = ( gSSDPDiscover_UseIPv6 || !gSSDPDiscover_UseIPv4 ) ? true : false;
+
+ err = InterfaceIndexFromArgString( gInterface, &context->ifindex );
+ require_noerr_quiet( err, exit );
+
+ // Set up IPv4 socket.
+
+ if( context->useIPv4 )
+ {
+ int port;
+ err = UDPClientSocketOpen( AF_INET, NULL, 0, -1, &port, &sockV4 );
+ require_noerr( err, exit );
+
+ err = SocketSetMulticastInterface( sockV4, NULL, context->ifindex );
+ require_noerr( err, exit );
+
+ err = setsockopt( sockV4, IPPROTO_IP, IP_MULTICAST_LOOP, (char *) &(uint8_t){ 1 }, (socklen_t) sizeof( uint8_t ) );
+ err = map_socket_noerr_errno( sockV4, err );
+ require_noerr( err, exit );
+ }
+
+ // Set up IPv6 socket.
+
+ if( context->useIPv6 )
+ {
+ err = UDPClientSocketOpen( AF_INET6, NULL, 0, -1, NULL, &sockV6 );
+ require_noerr( err, exit );
+
+ err = SocketSetMulticastInterface( sockV6, NULL, context->ifindex );
+ require_noerr( err, exit );
+
+ err = setsockopt( sockV6, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, (char *) &(int){ 1 }, (socklen_t) sizeof( int ) );
+ err = map_socket_noerr_errno( sockV6, err );
+ require_noerr( err, exit );
+ }
+
+ // Print prologue.
+
+ SSDPDiscoverPrintPrologue( context );
+
+ // Send mDNS query message.
+
+ sendCount = 0;
+ if( IsValidSocket( sockV4 ) )
+ {
+ struct sockaddr_in mcastAddr4;
+
+ memset( &mcastAddr4, 0, sizeof( mcastAddr4 ) );
+ SIN_LEN_SET( &mcastAddr4 );
+ mcastAddr4.sin_family = AF_INET;
+ mcastAddr4.sin_port = htons( kSSDPPort );
+ mcastAddr4.sin_addr.s_addr = htonl( 0xEFFFFFFA ); // 239.255.255.250
+
+ err = WriteSSDPSearchRequest( &context->header, &mcastAddr4, gSSDPDiscover_MX, gSSDPDiscover_ST );
+ require_noerr( err, exit );
+
+ n = sendto( sockV4, context->header.buf, context->header.len, 0, (const struct sockaddr *) &mcastAddr4,
+ (socklen_t) sizeof( mcastAddr4 ) );
+ err = map_socket_value_errno( sockV4, n == (ssize_t) context->header.len, n );
+ if( err )
+ {
+ FPrintF( stderr, "*** Failed to send query on IPv4 socket with error %#m\n", err );
+ ForgetSocket( &sockV4 );
+ }
+ else
+ {
+ if( gSSDPDiscover_Verbose )
+ {
+ GetTimestampStr( time );
+ FPrintF( stdout, "---\n" );
+ FPrintF( stdout, "Send time: %s\n", time );
+ FPrintF( stdout, "Source Port: %d\n", SocketToPortNumber( sockV4 ) );
+ FPrintF( stdout, "Destination: %##a\n", &mcastAddr4 );
+ FPrintF( stdout, "Message size: %zu\n", context->header.len );
+ FPrintF( stdout, "HTTP header:\n%1{text}", context->header.buf, context->header.len );
+ }
+ ++sendCount;
+ }
+ }
+
+ if( IsValidSocket( sockV6 ) )
+ {
+ struct sockaddr_in6 mcastAddr6;
+
+ memset( &mcastAddr6, 0, sizeof( mcastAddr6 ) );
+ SIN6_LEN_SET( &mcastAddr6 );
+ mcastAddr6.sin6_family = AF_INET6;
+ mcastAddr6.sin6_port = htons( kSSDPPort );
+ mcastAddr6.sin6_addr.s6_addr[ 0 ] = 0xFF; // SSDP IPv6 link-local multicast address FF02::C
+ mcastAddr6.sin6_addr.s6_addr[ 1 ] = 0x02;
+ mcastAddr6.sin6_addr.s6_addr[ 15 ] = 0x0C;
+
+ err = WriteSSDPSearchRequest( &context->header, &mcastAddr6, gSSDPDiscover_MX, gSSDPDiscover_ST );
+ require_noerr( err, exit );
+
+ n = sendto( sockV6, context->header.buf, context->header.len, 0, (const struct sockaddr *) &mcastAddr6,
+ (socklen_t) sizeof( mcastAddr6 ) );
+ err = map_socket_value_errno( sockV6, n == (ssize_t) context->header.len, n );
+ if( err )
+ {
+ FPrintF( stderr, "*** Failed to send query on IPv6 socket with error %#m\n", err );
+ ForgetSocket( &sockV6 );
+ }
+ else
+ {
+ if( gSSDPDiscover_Verbose )
+ {
+ GetTimestampStr( time );
+ FPrintF( stdout, "---\n" );
+ FPrintF( stdout, "Send time: %s\n", time );
+ FPrintF( stdout, "Source Port: %d\n", SocketToPortNumber( sockV6 ) );
+ FPrintF( stdout, "Destination: %##a\n", &mcastAddr6 );
+ FPrintF( stdout, "Message size: %zu\n", context->header.len );
+ FPrintF( stdout, "HTTP header:\n%1{text}", context->header.buf, context->header.len );
+ }
+ ++sendCount;
+ }
+ }
+ require_action_quiet( sendCount > 0, exit, err = kUnexpectedErr );
+
+ // If there's no wait period after the send, then exit.
+
+ if( context->receiveSecs == 0 ) goto exit;
+
+ // Create dispatch read sources for socket(s).
+
+ if( IsValidSocket( sockV4 ) )
+ {
+ SocketContext * sockContext;
+
+ sockContext = (SocketContext *) calloc( 1, sizeof( *sockContext ) );
+ require_action( sockContext, exit, err = kNoMemoryErr );
+
+ err = DispatchReadSourceCreate( sockV4, SSDPDiscoverReadHandler, SocketContextCancelHandler, sockContext,
+ &context->readSourceV4 );
+ if( err ) ForgetMem( &sockContext );
+ require_noerr( err, exit );
+
+ sockContext->context = context;
+ sockContext->sock = sockV4;
+ sockV4 = kInvalidSocketRef;
+ dispatch_resume( context->readSourceV4 );
+ }
+
+ if( IsValidSocket( sockV6 ) )
+ {
+ SocketContext * sockContext;
+
+ sockContext = (SocketContext *) calloc( 1, sizeof( *sockContext ) );
+ require_action( sockContext, exit, err = kNoMemoryErr );
+
+ err = DispatchReadSourceCreate( sockV6, SSDPDiscoverReadHandler, SocketContextCancelHandler, sockContext,
+ &context->readSourceV6 );
+ if( err ) ForgetMem( &sockContext );
+ require_noerr( err, exit );
+
+ sockContext->context = context;
+ sockContext->sock = sockV6;
+ sockV6 = kInvalidSocketRef;
+ dispatch_resume( context->readSourceV6 );
+ }
+
+ if( context->receiveSecs > 0 )
+ {
+ dispatch_after_f( dispatch_time_seconds( context->receiveSecs ), dispatch_get_main_queue(), kExitReason_Timeout,
+ Exit );
+ }
+ dispatch_main();
+
+exit:
+ ForgetSocket( &sockV4 );
+ ForgetSocket( &sockV6 );
+ dispatch_source_forget( &signalSource );
+ if( err ) exit( 1 );
+}
+
+static int SocketToPortNumber( SocketRef inSock )
+{
+ OSStatus err;
+ sockaddr_ip sip;
+ socklen_t len;
+
+ len = (socklen_t) sizeof( sip );
+ err = getsockname( inSock, &sip.sa, &len );
+ err = map_socket_noerr_errno( inSock, err );
+ check_noerr( err );
+ return( err ? -1 : SockAddrGetPort( &sip ) );
+}
+
+static OSStatus WriteSSDPSearchRequest( HTTPHeader *inHeader, const void *inHostSA, int inMX, const char *inST )
+{
+ OSStatus err;
+
+ err = HTTPHeader_InitRequest( inHeader, "M-SEARCH", "*", "HTTP/1.1" );
+ require_noerr( err, exit );
+
+ err = HTTPHeader_SetField( inHeader, "Host", "%##a", inHostSA );
+ require_noerr( err, exit );
+
+ err = HTTPHeader_SetField( inHeader, "ST", "%s", inST ? inST : "ssdp:all" );
+ require_noerr( err, exit );
+
+ err = HTTPHeader_SetField( inHeader, "Man", "\"ssdp:discover\"" );
+ require_noerr( err, exit );
+
+ err = HTTPHeader_SetField( inHeader, "MX", "%d", inMX );
+ require_noerr( err, exit );
+
+ err = HTTPHeader_Commit( inHeader );
+ require_noerr( err, exit );
+
+exit:
+ return( err );
+}
+
+//===========================================================================================================================
+// SSDPDiscoverPrintPrologue
+//===========================================================================================================================
+
+static void SSDPDiscoverPrintPrologue( const SSDPDiscoverContext *inContext )
+{
+ const int receiveSecs = inContext->receiveSecs;
+ const char * ifName;
+ char ifNameBuf[ IF_NAMESIZE + 1 ];
+ NetTransportType ifType;
+ char time[ kTimestampBufLen ];
+
+ ifName = if_indextoname( inContext->ifindex, ifNameBuf );
+
+ ifType = kNetTransportType_Undefined;
+ if( ifName ) SocketGetInterfaceInfo( kInvalidSocketRef, ifName, NULL, NULL, NULL, NULL, NULL, NULL, NULL, &ifType );
+
+ FPrintF( stdout, "Interface: %s/%d/%s\n",
+ ifName ? ifName : "?", inContext->ifindex, NetTransportTypeToString( ifType ) );
+ FPrintF( stdout, "IP protocols: %?s%?s%?s\n",
+ inContext->useIPv4, "IPv4", ( inContext->useIPv4 && inContext->useIPv6 ), ", ", inContext->useIPv6, "IPv6" );
+ FPrintF( stdout, "Receive duration: " );
+ if( receiveSecs >= 0 ) FPrintF( stdout, "%d second%?c\n", receiveSecs, receiveSecs != 1, 's' );
+ else FPrintF( stdout, "∞\n" );
+ FPrintF( stdout, "Start time: %s\n", GetTimestampStr( time ) );
+}
+
+//===========================================================================================================================
+// SSDPDiscoverReadHandler
+//===========================================================================================================================
+
+static void SSDPDiscoverReadHandler( void *inContext )
+{
+ OSStatus err;
+ SocketContext * const sockContext = (SocketContext *) inContext;
+ SSDPDiscoverContext * const context = (SSDPDiscoverContext *) sockContext->context;
+ HTTPHeader * const header = &context->header;
+ sockaddr_ip fromAddr;
+ size_t msgLen;
+ char time[ kTimestampBufLen ];
+
+ GetTimestampStr( time );
+
+ err = SocketRecvFrom( sockContext->sock, header->buf, sizeof( header->buf ), &msgLen, &fromAddr, sizeof( fromAddr ),
+ NULL, NULL, NULL, NULL );
+ require_noerr( err, exit );
+
+ FPrintF( stdout, "---\n" );
+ FPrintF( stdout, "Receive time: %s\n", time );
+ FPrintF( stdout, "Source: %##a\n", &fromAddr );
+ FPrintF( stdout, "Message size: %zu\n", msgLen );
+ header->len = msgLen;
+ if( HTTPHeader_Validate( header ) )
+ {
+ FPrintF( stdout, "HTTP header:\n%1{text}", header->buf, header->len );
+ if( header->extraDataLen > 0 )
+ {
+ FPrintF( stdout, "HTTP body: %1.1H", header->extraDataPtr, (int) header->extraDataLen, INT_MAX );
+ }
+ }
+ else
+ {
+ FPrintF( stdout, "Invalid HTTP message:\n%1.1H", header->buf, (int) msgLen, INT_MAX );
+ goto exit;
+ }
+
+exit:
+ if( err ) exit( 1 );
+}
+
+//===========================================================================================================================
+// HTTPHeader_Validate
+//
+// Parses for the end of an HTTP header and updates the HTTPHeader structure so it's ready to parse. Returns true if valid.
+// This assumes the "buf" and "len" fields are set. The other fields are set by this function.
+//
+// Note: This was copied from CoreUtils because the HTTPHeader_Validate function is currently not exported in the framework.
+//===========================================================================================================================
+
+Boolean HTTPHeader_Validate( HTTPHeader *inHeader )
+{
+ const char * src;
+ const char * end;
+
+ // Check for interleaved binary data (4 byte header that begins with $). See RFC 2326 section 10.12.
+
+ require( inHeader->len < sizeof( inHeader->buf ), exit );
+ src = inHeader->buf;
+ end = src + inHeader->len;
+ if( ( ( end - src ) >= 4 ) && ( src[ 0 ] == '$' ) )
+ {
+ src += 4;
+ }
+ else
+ {
+ // Search for an empty line (HTTP-style header/body separator). CRLFCRLF, LFCRLF, or LFLF accepted.
+ // $$$ TO DO: Start from the last search location to avoid re-searching the same data over and over.
+
+ for( ;; )
+ {
+ while( ( src < end ) && ( src[ 0 ] != '\n' ) ) ++src;
+ if( src >= end ) goto exit;
+ ++src;
+ if( ( ( end - src ) >= 2 ) && ( src[ 0 ] == '\r' ) && ( src[ 1 ] == '\n' ) ) // CFLFCRLF or LFCRLF
+ {
+ src += 2;
+ break;
+ }
+ else if( ( ( end - src ) >= 1 ) && ( src[ 0 ] == '\n' ) ) // LFLF
+ {
+ src += 1;
+ break;
+ }
+ }
+ }
+ inHeader->extraDataPtr = src;
+ inHeader->extraDataLen = (size_t)( end - src );
+ inHeader->len = (size_t)( src - inHeader->buf );
+ return( true );
+
+exit:
+ return( false );
+}
+
+#if( TARGET_OS_DARWIN )
+//===========================================================================================================================
+// ResQueryCmd
+//===========================================================================================================================
+
+// res_query() from libresolv is actually called res_9_query (see /usr/include/resolv.h).
+
+SOFT_LINK_LIBRARY_EX( "/usr/lib", resolv );
+SOFT_LINK_FUNCTION_EX( resolv, res_9_query,
+ int,
+ ( const char *dname, int class, int type, u_char *answer, int anslen ),
+ ( dname, class, type, answer, anslen ) );
+
+// res_query() from libinfo
+
+SOFT_LINK_LIBRARY_EX( "/usr/lib", info );
+SOFT_LINK_FUNCTION_EX( info, res_query,
+ int,
+ ( const char *dname, int class, int type, u_char *answer, int anslen ),
+ ( dname, class, type, answer, anslen ) );
+
+typedef int ( *res_query_f )( const char *dname, int class, int type, u_char *answer, int anslen );
+
+static void ResQueryCmd( void )
+{
+ OSStatus err;
+ res_query_f res_query_ptr;
+ int n;
+ uint16_t type, class;
+ char time[ kTimestampBufLen ];
+ uint8_t answer[ 1024 ];
+
+ // Get pointer to one of the res_query() functions.
+
+ if( gResQuery_UseLibInfo )
+ {
+ if( !SOFT_LINK_HAS_FUNCTION( info, res_query ) )
+ {
+ FPrintF( stderr, "Failed to soft link res_query from libinfo.\n" );
+ err = kNotFoundErr;
+ goto exit;
+ }
+ res_query_ptr = soft_res_query;
+ }
+ else
+ {
+ if( !SOFT_LINK_HAS_FUNCTION( resolv, res_9_query ) )
+ {
+ FPrintF( stderr, "Failed to soft link res_query from libresolv.\n" );
+ err = kNotFoundErr;
+ goto exit;
+ }
+ res_query_ptr = soft_res_9_query;
+ }
+
+ // Get record type.
+
+ err = RecordTypeFromArgString( gResQuery_Type, &type );
+ require_noerr( err, exit );
+
+ // Get record class.
+
+ if( gResQuery_Class )
+ {
+ err = RecordClassFromArgString( gResQuery_Class, &class );
+ require_noerr( err, exit );
+ }
+ else
+ {
+ class = kDNSServiceClass_IN;
+ }
+
+ // Print prologue.
+
+ FPrintF( stdout, "Name: %s\n", gResQuery_Name );
+ FPrintF( stdout, "Type: %s (%u)\n", RecordTypeToString( type ), type );
+ FPrintF( stdout, "Class: %s (%u)\n", ( class == kDNSServiceClass_IN ) ? "IN" : "???", class );
+ FPrintF( stdout, "Start time: %s\n", GetTimestampStr( time ) );
+ FPrintF( stdout, "---\n" );
+
+ // Call res_query().
+
+ n = res_query_ptr( gResQuery_Name, class, type, (u_char *) answer, (int) sizeof( answer ) );
+ if( n < 0 )
+ {
+ FPrintF( stderr, "res_query() failed with error: %d (%s).\n", h_errno, hstrerror( h_errno ) );
+ err = kUnknownErr;
+ goto exit;
+ }
+
+ // Print result.
+
+ FPrintF( stdout, "Message size: %d\n\n", n );
+ PrintUDNSMessage( answer, (size_t) n, false );
+
+exit:
+ if( err ) exit( 1 );
+}
+
+//===========================================================================================================================
+// ResolvDNSQueryCmd
+//===========================================================================================================================
+
+// dns_handle_t is defined as a pointer to a privately-defined struct in /usr/include/dns.h. It's defined as a void * here to
+// avoid including the header file.
+
+typedef void * dns_handle_t;
+
+SOFT_LINK_FUNCTION_EX( resolv, dns_open, dns_handle_t, ( const char *path ), ( path ) );
+SOFT_LINK_FUNCTION_VOID_RETURN_EX( resolv, dns_free, ( dns_handle_t *dns ), ( dns ) );
+SOFT_LINK_FUNCTION_EX( resolv, dns_query,
+ int32_t, (
+ dns_handle_t dns,
+ const char * name,
+ uint32_t dnsclass,
+ uint32_t dnstype,
+ char * buf,
+ uint32_t len,
+ struct sockaddr * from,
+ uint32_t * fromlen ),
+ ( dns, name, dnsclass, dnstype, buf, len, from, fromlen ) );
+
+static void ResolvDNSQueryCmd( void )
+{
+ OSStatus err;
+ int n;
+ dns_handle_t dns = NULL;
+ uint16_t type, class;
+ sockaddr_ip from;
+ uint32_t fromLen;
+ char time[ kTimestampBufLen ];
+ uint8_t answer[ 1024 ];
+
+ // Make sure that the required symbols are available.
+
+ if( !SOFT_LINK_HAS_FUNCTION( resolv, dns_open ) )
+ {
+ FPrintF( stderr, "Failed to soft link dns_open from libresolv.\n" );
+ err = kNotFoundErr;
+ goto exit;
+ }
+
+ if( !SOFT_LINK_HAS_FUNCTION( resolv, dns_free ) )
+ {
+ FPrintF( stderr, "Failed to soft link dns_free from libresolv.\n" );
+ err = kNotFoundErr;
+ goto exit;
+ }
+
+ if( !SOFT_LINK_HAS_FUNCTION( resolv, dns_query ) )
+ {
+ FPrintF( stderr, "Failed to soft link dns_query from libresolv.\n" );
+ err = kNotFoundErr;
+ goto exit;
+ }
+
+ // Get record type.
+
+ err = RecordTypeFromArgString( gResolvDNSQuery_Type, &type );
+ require_noerr( err, exit );
+
+ // Get record class.
+
+ if( gResolvDNSQuery_Class )
+ {
+ err = RecordClassFromArgString( gResolvDNSQuery_Class, &class );
+ require_noerr( err, exit );
+ }
+ else
+ {
+ class = kDNSServiceClass_IN;
+ }
+
+ // Get dns handle.
+
+ dns = soft_dns_open( gResolvDNSQuery_Path );
+ if( !dns )
+ {
+ FPrintF( stderr, "dns_open( %s ) failed.\n", gResolvDNSQuery_Path );
+ err = kUnknownErr;
+ goto exit;
+ }
+
+ // Print prologue.
+
+ FPrintF( stdout, "Name: %s\n", gResolvDNSQuery_Name );
+ FPrintF( stdout, "Type: %s (%u)\n", RecordTypeToString( type ), type );
+ FPrintF( stdout, "Class: %s (%u)\n", ( class == kDNSServiceClass_IN ) ? "IN" : "???", class );
+ FPrintF( stdout, "Path: %s\n", gResolvDNSQuery_Path ? gResolvDNSQuery_Name : "<NULL>" );
+ FPrintF( stdout, "Start time: %s\n", GetTimestampStr( time ) );
+ FPrintF( stdout, "---\n" );
+
+ // Call dns_query().
+
+ memset( &from, 0, sizeof( from ) );
+ fromLen = (uint32_t) sizeof( from );
+ n = soft_dns_query( dns, gResolvDNSQuery_Name, class, type, (char *) answer, (uint32_t) sizeof( answer ), &from.sa,
+ &fromLen );
+ if( n < 0 )
+ {
+ FPrintF( stderr, "dns_query() failed with error: %d (%s).\n", h_errno, hstrerror( h_errno ) );
+ err = kUnknownErr;
+ goto exit;
+ }
+
+ // Print result.
+
+ FPrintF( stdout, "From: %##a\n", &from );
+ FPrintF( stdout, "Message size: %d\n\n", n );
+ PrintUDNSMessage( answer, (size_t) n, false );
+
+exit:
+ if( dns ) soft_dns_free( dns );
+ if( err ) exit( 1 );
+}
+#endif // TARGET_OS_DARWIN
+
+//===========================================================================================================================
+// DaemonVersionCmd
+//===========================================================================================================================
+
+static void DaemonVersionCmd( void )
+{
+ OSStatus err;
+ uint32_t size, version;
+ char strBuf[ 16 ];
+
+ size = (uint32_t) sizeof( version );
+ err = DNSServiceGetProperty( kDNSServiceProperty_DaemonVersion, &version, &size );
+ require_noerr( err, exit );
+
+ FPrintF( stdout, "Daemon version: %s\n", SourceVersionToCString( version, strBuf ) );
+
+exit:
+ if( err ) exit( 1 );
+}
+
+//===========================================================================================================================
+// Exit
+//===========================================================================================================================
+
+static void Exit( void *inContext )
+{
+ const char * const reason = (const char *) inContext;
+ char time[ kTimestampBufLen ];
+
+ FPrintF( stdout, "---\n" );
+ FPrintF( stdout, "End time: %s\n", GetTimestampStr( time ) );
+ if( reason ) FPrintF( stdout, "End reason: %s\n", reason );
+ exit( gExitCode );
+}
+
+//===========================================================================================================================
+// GetTimestampStr
+//===========================================================================================================================
+
+static char * GetTimestampStr( char inBuffer[ kTimestampBufLen ] )
+{
+ struct timeval now;
+ struct tm * tm;
+ size_t len;
+
+ gettimeofday( &now, NULL );