RPRIVATE
RVOID
    processDnsPacket
    (
        KernelAcqDnsPacket* pDns
    )
{
    rSequence notification = NULL;
    RU32 i = 0;
    DnsLabel* pLabel = NULL;
    DnsHeader* dnsHeader = NULL;
    DnsResponseInfo* pResponseInfo = NULL;
    RCHAR domain[ DNS_LABEL_MAX_SIZE ] = { 0 };
    RU16 recordType = 0;
    RU64 timestamp = 0;
    Atom parentAtom = { 0 };

    if( NULL == pDns )
    {
        return;
    }

    dnsHeader = (DnsHeader*)( (RPU8)pDns + sizeof( *pDns ) );
    pLabel = (DnsLabel*)dnsHeader->data;

    // We are parsing DNS packets coming from the kernel. They may:
    // 1- Be requests and not responses, check there are Answers.
    // 2- Be maliciously crafter packets so we need extra checking for sanity.
    if( 0 == dnsHeader->anCount ||
        0 == dnsHeader->qr ||
        DNS_SANITY_MAX_RECORDS < rpal_ntoh16( dnsHeader->qdCount ) ||
        DNS_SANITY_MAX_RECORDS < rpal_ntoh16( dnsHeader->anCount ) )
    {
        return;
    }

    // We need to walk the Questions first to get to the Answers
    // but we don't really care to record them since they'll be repeated
    // in the Answers.
    for( i = 0; i < rpal_ntoh16( dnsHeader->qdCount ); i++ )
    {
        DnsQuestionInfo* pQInfo = NULL;

        pLabel = dnsReadLabels( pLabel, NULL, (RPU8)dnsHeader, pDns->packetSize, 0, 0 );

        pQInfo = (DnsQuestionInfo*)( pLabel );
        if( !IS_WITHIN_BOUNDS( pQInfo, sizeof( *pQInfo ), dnsHeader, pDns->packetSize ) )
        {
            rpal_debug_warning( "error parsing dns packet" );
            break;
        }

        pLabel = (DnsLabel*)( (RPU8)pQInfo + sizeof( *pQInfo ) );
    }

    if( !IS_WITHIN_BOUNDS( pLabel, sizeof( RU16 ), dnsHeader, pDns->packetSize ) )
    {
        rpal_debug_warning( "error parsing dns packet" );
        return;
    }

    // This is what we care about, the Answers (which also point to each Question).
    // We will emit one event per Answer so as to keep the DNS_REQUEST event flat and atomic.
    for( i = 0; i < rpal_ntoh16( dnsHeader->anCount ); i++ )
    {
        pResponseInfo = NULL;
            
        // This was the Question for this answer.
        rpal_memory_zero( domain, sizeof( domain ) );
        pLabel = dnsReadLabels( pLabel, domain, (RPU8)dnsHeader, pDns->packetSize, 0, 0 );

        pResponseInfo = (DnsResponseInfo*)pLabel;
        pLabel = (DnsLabel*)( (RPU8)pResponseInfo + sizeof( *pResponseInfo ) + rpal_ntoh16( pResponseInfo->rDataLength ) );

        if( !IS_WITHIN_BOUNDS( pResponseInfo, sizeof( *pResponseInfo ), dnsHeader, pDns->packetSize ) )
        {
            rpal_debug_warning( "error parsing dns packet" );
            break;
        }

        if( NULL == ( notification = rSequence_new() ) )
        {
            rpal_debug_warning( "error parsing dns packet" );
            break;
        }

        // This is a timestamp coming from the kernel so it is not globally adjusted.
        // We'll adjust it with the global offset.
        timestamp = pDns->ts;
        timestamp += MSEC_FROM_SEC( rpal_time_getGlobalFromLocal( 0 ) );

        // Try to relate the DNS request to the owner process, this only works on OSX
        // at the moment (since the kernel does not expose the PID at the packet capture
        // stage), and even on OSX it's the DNSResolver process. So it's not super useful
        // but regardless we have the mechanism here as it's better than nothing and when
        // we add better resolving in the kernel it will work transparently.
        parentAtom.key.process.pid = pDns->pid;
        parentAtom.key.category = RP_TAGS_NOTIFICATION_NEW_PROCESS;
        if( atoms_query( &parentAtom, timestamp ) )
        {
            HbsSetParentAtom( notification, parentAtom.id );
        }

        rSequence_addTIMESTAMP( notification, RP_TAGS_TIMESTAMP, timestamp );
        rSequence_addSTRINGA( notification, RP_TAGS_DOMAIN_NAME, domain );
        rSequence_addRU32( notification, RP_TAGS_PROCESS_ID, pDns->pid );

        recordType = rpal_ntoh16( pResponseInfo->recordType );

        rSequence_addRU16( notification, RP_TAGS_MESSAGE_ID, rpal_ntoh16( dnsHeader->msgId ) );
        rSequence_addRU16( notification, RP_TAGS_DNS_TYPE, recordType );

        if( DNS_A_RECORD == recordType )
        {
            rSequence_addIPV4( notification, RP_TAGS_IP_ADDRESS, *(RU32*)pResponseInfo->rData );
        }
        else if( DNS_AAAA_RECORD == recordType )
        {
            rSequence_addIPV6( notification, RP_TAGS_IP_ADDRESS, pResponseInfo->rData );
        }
        else if( DNS_CNAME_RECORD == recordType )
        {
            // CNAME records will have another label as a value and not an IP.
            rpal_memory_zero( domain, sizeof( domain ) );
            dnsReadLabels( (DnsLabel*)pResponseInfo->rData, domain, (RPU8)dnsHeader, pDns->packetSize, 0, 0 );
            rSequence_addSTRINGA( notification, RP_TAGS_CNAME, domain );
        }
        else
        {
            // Right now we only care for A, CNAME and AAAA records.
            rSequence_free( notification );
            notification = NULL;
            continue;
        }

        hbs_publish( RP_TAGS_NOTIFICATION_DNS_REQUEST, notification );
        rSequence_free( notification );
        notification = NULL;
    }
}
static 
RPVOID
    networkDiffThread
    (
        rEvent isTimeToStop,
        RPVOID ctx
    )
{
    NetLib_Tcp4Table* currentTcp4Table = NULL;
    NetLib_Tcp4Table* oldTcp4Table = NULL;

    NetLib_UdpTable* currentUdpTable = NULL;
    NetLib_UdpTable* oldUdpTable = NULL;

    RU32 i = 0;
    RU32 j = 0;
    RBOOL isFound = FALSE;

    rSequence notif = NULL;
    rSequence comp = NULL;

    RU32 timeout = 100;
    RU32 nThLoop = 0;

    UNREFERENCED_PARAMETER( ctx );

    while( rpal_memory_isValid( isTimeToStop ) &&
           !rEvent_wait( isTimeToStop, 0 ) )
    {
        if( NULL != oldTcp4Table )
        {
            rpal_memory_free( oldTcp4Table );
            oldTcp4Table = NULL;
        }

        if( NULL != oldUdpTable )
        {
            rpal_memory_free( oldUdpTable );
            oldUdpTable = NULL;
        }

        // Swap the old snapshot for the (prev) new one
        oldTcp4Table = currentTcp4Table;
        oldUdpTable = currentUdpTable;
        currentTcp4Table = NULL;
        currentUdpTable = NULL;

        // Generate new tables
        currentTcp4Table = NetLib_getTcp4Table();
        currentUdpTable = NetLib_getUdpTable();

        // Diff TCP snapshots for new entries
        if( rpal_memory_isValid( currentTcp4Table ) &&
            rpal_memory_isValid( oldTcp4Table ) )
        {
            for( i = 0; i < currentTcp4Table->nRows; i++ )
            {
                isFound = FALSE;

                if( rEvent_wait( isTimeToStop, 0 ) )
                {
                    break;
                }

                for( j = 0; j < oldTcp4Table->nRows; j++ )
                {
                    if( isTcpEqual( &currentTcp4Table->rows[ i ], &oldTcp4Table->rows[ j ] ) )
                    {
                        isFound = TRUE;
                        break;
                    }
                }

                if( !isFound )
                {
                    if( NULL != ( notif = rSequence_new() ) )
                    {
                        if( rSequence_addRU32( notif, 
                                               RP_TAGS_STATE, 
                                               currentTcp4Table->rows[ i ].state ) &&
                            rSequence_addRU32( notif, 
                                               RP_TAGS_PROCESS_ID, 
                                               currentTcp4Table->rows[ i ].pid ) &&
                            rSequence_addTIMESTAMP( notif, 
                                                    RP_TAGS_TIMESTAMP, 
                                                    rpal_time_getGlobal() ) )
                        {
                            if( NULL != ( comp = rSequence_new() ) )
                            {
                                // Add the destination components
                                if( !rSequence_addIPV4( comp, 
                                                        RP_TAGS_IP_ADDRESS, 
                                                        currentTcp4Table->rows[ i ].destIp ) ||
                                    !rSequence_addRU16( comp, 
                                                        RP_TAGS_PORT, 
                                                        rpal_ntoh16( (RU16)currentTcp4Table->rows[ i ].destPort ) ) ||
                                    !rSequence_addSEQUENCE( notif, 
                                                            RP_TAGS_DESTINATION, 
                                                            comp ) )
                                {
                                    rSequence_free( comp );
                                    comp = NULL;
                                }
                            }

                            if( NULL != ( comp = rSequence_new() ) )
                            {
                                // Add the source components
                                if( !rSequence_addIPV4( comp, 
                                                        RP_TAGS_IP_ADDRESS, 
                                                        currentTcp4Table->rows[ i ].sourceIp ) ||
                                    !rSequence_addRU16( comp, 
                                                        RP_TAGS_PORT, 
                                                        rpal_ntoh16( (RU16)currentTcp4Table->rows[ i ].sourcePort ) ) ||
                                    !rSequence_addSEQUENCE( notif, 
                                                            RP_TAGS_SOURCE, 
                                                            comp ) )
                                {
                                    rSequence_free( comp );
                                    comp = NULL;
                                }
                            }

                            rpal_debug_info( "new tcp connection: 0x%08X = 0x%08X:0x%04X ---> 0x%08X:0x%04X -- 0x%08X.",
                                currentTcp4Table->rows[ i ].state,
                                currentTcp4Table->rows[ i ].sourceIp,
                                currentTcp4Table->rows[ i ].sourcePort,
                                currentTcp4Table->rows[ i ].destIp,
                                currentTcp4Table->rows[ i ].destPort,
                                currentTcp4Table->rows[ i ].pid );
                            notifications_publish( RP_TAGS_NOTIFICATION_NEW_TCP4_CONNECTION, notif );
                        }

                        rSequence_free( notif );
                    }
                }
            }
        }
        else if( 0 != nThLoop )
        {
            rpal_debug_warning( "could not get tcp connections table." );
        }


        // Diff TCP snapshots for new entries
        if( NULL != currentUdpTable &&
            NULL != oldUdpTable )
        {
            for( i = 0; i < currentUdpTable->nRows; i++ )
            {
                isFound = FALSE;

                if( rEvent_wait( isTimeToStop, 0 ) )
                {
                    break;
                }

                for( j = 0; j < oldUdpTable->nRows; j++ )
                {
                    if( isUdpEqual( &currentUdpTable->rows[ i ], &oldUdpTable->rows[ j ] ) )
                    {
                        isFound = TRUE;
                        break;
                    }
                }

                if( !isFound )
                {
                    if( NULL != ( notif = rSequence_new() ) )
                    {
                        if( !rSequence_addIPV4( notif, 
                                                RP_TAGS_IP_ADDRESS, 
                                                currentUdpTable->rows[ i ].localIp ) ||
                            !rSequence_addRU16( notif, 
                                                RP_TAGS_PORT, 
                                                rpal_ntoh16( (RU16)currentUdpTable->rows[ i ].localPort ) ) ||
                            !rSequence_addRU32( notif, 
                                                RP_TAGS_PROCESS_ID, 
                                                currentUdpTable->rows[ i ].pid ) ||
                            !rSequence_addTIMESTAMP( notif, 
                                                     RP_TAGS_TIMESTAMP, 
                                                     rpal_time_getGlobal() ) )
                        {
                            notif = NULL;
                        }
                        else
                        {
                            rpal_debug_info( "new udp connection: 0x%08X:0x%04X -- 0x%08X.",
                                currentUdpTable->rows[ i ].localIp,
                                currentUdpTable->rows[ i ].localPort,
                                currentUdpTable->rows[ i ].pid );
                            notifications_publish( RP_TAGS_NOTIFICATION_NEW_UDP4_CONNECTION, notif );
                        }

                        rSequence_free( notif );
                    }
                }
            }
        }
        else if( 0 != nThLoop )
        {
            rpal_debug_warning( "could not get udp connections table." );
        }

        nThLoop++;
        if( 0 == nThLoop % 10 )
        {
            timeout = libOs_getUsageProportionalTimeout( 1000 ) + 500;
        }

        rpal_thread_sleep( timeout );
    }

    rpal_memory_free( currentTcp4Table );
    rpal_memory_free( currentUdpTable );
    rpal_memory_free( oldTcp4Table );
    rpal_memory_free( oldUdpTable );

    return NULL;
}
RPRIVATE
RVOID
    dnsUmDiffThread
    (
        rEvent isTimeToStop
    )
{
    rSequence notif = NULL;
    rBlob snapCur = NULL;
    rBlob snapPrev = NULL;
    _dnsRecord rec = { 0 };
    _dnsRecord* pCurRec = NULL;
    RU32 i = 0;
    LibOsPerformanceProfile perfProfile = { 0 };
    
#ifdef RPAL_PLATFORM_WINDOWS
    PDNSCACHEENTRY pDnsEntry = NULL;
    PDNSCACHEENTRY pPrevDnsEntry = NULL;
#endif

    perfProfile.enforceOnceIn = 1;
    perfProfile.sanityCeiling = MSEC_FROM_SEC( 10 );
    perfProfile.lastTimeoutValue = 100;
    perfProfile.targetCpuPerformance = 0;
    perfProfile.globalTargetCpuPerformance = GLOBAL_CPU_USAGE_TARGET;
    perfProfile.timeoutIncrementPerSec = 1;

    while( !rEvent_wait( isTimeToStop, 0 ) )
    {
        if( kAcq_isAvailable() )
        {
            // If kernel acquisition becomes available, try kernel again.
            return;
        }

        libOs_timeoutWithProfile( &perfProfile, FALSE, isTimeToStop );

        if( NULL != ( snapCur = rpal_blob_create( 0, 10 * sizeof( rec ) ) ) )
        {
#ifdef RPAL_PLATFORM_WINDOWS
            if( TRUE == getCache( &pDnsEntry ) )
            {
                while( NULL != pDnsEntry )
                {
                    rec.flags = pDnsEntry->dwFlags;
                    rec.type = pDnsEntry->wType;
                    if( NULL != ( rec.name = rpal_string_strdup( pDnsEntry->pszName ) ) )
                    {
                        rpal_blob_add( snapCur, &rec, sizeof( rec ) );
                    }

                    pPrevDnsEntry = pDnsEntry;
                    pDnsEntry = pDnsEntry->pNext;

                    freeCacheEntry( pPrevDnsEntry->pszName, DnsFreeFlat );
                    freeCacheEntry( pPrevDnsEntry, DnsFreeFlat );
                }

                rpal_sort_array( rpal_blob_getBuffer( snapCur ), 
                                 rpal_blob_getSize( snapCur ) / sizeof( rec ), 
                                 sizeof( rec ), 
                                 _cmpDns );
            }
#elif defined( RPAL_PLATFORM_MACOSX )
            rpal_thread_sleep( MSEC_FROM_SEC( 2 ) );
#endif

            // Do a general diff of the snapshots to find new entries.
            if( NULL != snapPrev )
            {
                i = 0;
                while( !rEvent_wait( isTimeToStop, 0 ) &&
                       NULL != ( pCurRec = rpal_blob_arrElem( snapCur, sizeof( rec ), i++ ) ) )
                {
                    if( -1 == rpal_binsearch_array( rpal_blob_getBuffer( snapPrev ), 
                                                    rpal_blob_getSize( snapPrev ) / sizeof( rec ), 
                                                    sizeof( rec ), 
                                                    pCurRec,
                                                    (rpal_ordering_func)_cmpDns ) )
                    {
                        if( NULL != ( notif = rSequence_new() ) )
                        {
                            rSequence_addSTRINGN( notif, RP_TAGS_DOMAIN_NAME, pCurRec->name );
                            rSequence_addRU16( notif, RP_TAGS_DNS_TYPE, pCurRec->type );
                            rSequence_addRU32( notif, RP_TAGS_DNS_FLAGS, pCurRec->flags );
                            hbs_timestampEvent( notif, 0 );

                            hbs_publish( RP_TAGS_NOTIFICATION_DNS_REQUEST, notif );

                            rSequence_free( notif );
                        }
                    }
                }
            }
        }

        if( NULL != snapPrev )
        {
            _freeRecords( snapPrev );
            rpal_blob_free( snapPrev );
            snapPrev = NULL;
        }

        snapPrev = snapCur;
        snapCur = NULL;

        libOs_timeoutWithProfile( &perfProfile, TRUE, isTimeToStop );
    }

    if( NULL != snapPrev )
    {
        _freeRecords( snapPrev );
        rpal_blob_free( snapPrev );
        snapPrev = NULL;
    }
}
static 
RPVOID
    dnsDiffThread
    (
        rEvent isTimeToStop,
        RPVOID ctx
    )
{
    RU32 nThLoop = 0;
    RU32 currentTimeout = 0;
    rSequence notif = NULL;
    rBlob snapCur = NULL;
    rBlob snapPrev = NULL;
    _dnsRecord rec = { 0 };
    _dnsRecord* pCurRec = NULL;
    _dnsRecord* pPrevRec = NULL;
    RU32 i = 0;
    RU32 j = 0;
    RBOOL isNew = FALSE;
    
#ifdef RPAL_PLATFORM_WINDOWS
    PDNSCACHEENTRY pDnsEntry = NULL;
    PDNSCACHEENTRY pPrevDnsEntry = NULL;
#endif

    UNREFERENCED_PARAMETER( ctx );

    while( !rEvent_wait( isTimeToStop, currentTimeout ) )
    {
        if( NULL != ( snapCur = rpal_blob_create( 0, 10 * sizeof( rec ) ) ) )
        {
#ifdef RPAL_PLATFORM_WINDOWS
            if( TRUE == getCache( &pDnsEntry ) )
            {
                while( NULL != pDnsEntry )
                {
                    rec.flags = pDnsEntry->dwFlags;
                    rec.type = pDnsEntry->wType;
                    if( NULL != ( rec.name = rpal_string_strdupw( pDnsEntry->pszName ) ) )
                    {
                        rpal_blob_add( snapCur, &rec, sizeof( rec ) );
                    }

                    pPrevDnsEntry = pDnsEntry;
                    pDnsEntry = pDnsEntry->pNext;

                    freeCacheEntry( pPrevDnsEntry->pszName, DnsFreeFlat );
                    freeCacheEntry( pPrevDnsEntry, DnsFreeFlat );
                }
            }
#endif

            // Do a general diff of the snapshots to find new entries.
            if( NULL != snapPrev )
            {
                i = 0;
                while( NULL != ( pCurRec = rpal_blob_arrElem( snapCur, sizeof( rec ), i++ ) ) )
                {
                    isNew = TRUE;
                    j = 0;
                    while( NULL != ( pPrevRec = rpal_blob_arrElem( snapPrev, sizeof( rec ), j++ ) ) )
                    {
                        if( pCurRec->flags == pPrevRec->flags &&
                            pCurRec->type == pPrevRec->type &&
                            0 == rpal_string_strcmpw( pCurRec->name, pPrevRec->name ) )
                        {
                            isNew = FALSE;
                            break;
                        }
                    }

                    if( isNew &&
                        !rEvent_wait( isTimeToStop, 0 ) )
                    {
                        if( NULL != ( notif = rSequence_new() ) )
                        {
                            rSequence_addSTRINGW( notif, RP_TAGS_DOMAIN_NAME, pCurRec->name );
                            rSequence_addRU16( notif, RP_TAGS_DNS_TYPE, pCurRec->type );
                            rSequence_addRU32( notif, RP_TAGS_DNS_FLAGS, pCurRec->flags );
                            rSequence_addTIMESTAMP( notif, RP_TAGS_TIMESTAMP, rpal_time_getGlobal() );

                            notifications_publish( RP_TAGS_NOTIFICATION_DNS_REQUEST, notif );

                            rSequence_free( notif );
                        }
                    }
                }
            }
        }

        if( NULL != snapPrev )
        {
            _freeRecords( snapPrev );
            rpal_blob_free( snapPrev );
            snapPrev = NULL;
        }

        snapPrev = snapCur;
        snapCur = NULL;

        nThLoop++;
        if( 0 == nThLoop % 20 )
        {
            currentTimeout = libOs_getUsageProportionalTimeout( MSEC_FROM_SEC( 10 ) ) + MSEC_FROM_SEC( 5 );
        }

        rpal_thread_sleep( currentTimeout );
    }

    if( NULL != snapPrev )
    {
        _freeRecords( snapPrev );
        rpal_blob_free( snapPrev );
        snapPrev = NULL;
    }

    return NULL;
}