none
Redirect DNS traffic using WFP RRS feed

  • Question

  • Hi!

    I am trying to implement a callout driver that redirects DNS queries to a local DNS proxy. I managed to get the redirection to the DNS proxy working by registering a callout filter at the FWPM_LAYER_OUTBOUND_TRANSPORT_V4 layer, and then re-inject the packet using FwpsInjectTransportSendAsync. My problem now is that I can't "de-proxy" the response from the DNS proxy to work, as I understand for the Windows DNS client to accept the response, the packet must come from the original IP and port, so the DNS proxy response's source address and port should be restored to the original DNS query destination address and port.

    Supposedly the "de-proxy" should be done on the FWPM_LAYER_INBOUND_TRANSPORT_V4, I have register a filter without conditions and the response packet is not hitting it (I can identify the packet by the remote ip address and port). Neither the packet is present on the FWPM_LAYER_INBOUND_TRANSPORT_V4_DISCARD nor on the FWPM_LAYER_DATAGRAM_DATA_V4/FWPM_LAYER_DATAGRAM_V4_DISCAR or FWPM_LAYER_INBOUND_IPPACKET_V4_DISCARD layers.  Can another callout be absorbing the packet? or Is the TCP/IP stack dropping the packet?, but if it is dropping the packet, how come I can't get the packet on any of the discard layers?

    The only layer where i can get the packet is FWPM_LAYER_INBOUND_IPPACKET_V4 layer, so I am modifying the packet on this layer. My code looks like this:

    void classifyFunc(...)
    {
      NET_BUFFER_LIST * netBufferList = (NET_BUFFER_LIST *)layerData;
      // Verify that the packet wasn't injected before
      FWPS_PACKET_INJECTION_STATE packetState = FwpsQueryPacketInjectionState(injectionNetworkHandle, netBufferList, NULL);
      if (packetState == FWPS_PACKET_INJECTED_BY_SELF || packetState == FWPS_PACKET_PREVIOUSLY_INJECTED_BY_SELF) 
      {
    	classifyOut->actionType = FWP_ACTION_PERMIT;
    	if (filter->flags & FWPS_FILTER_FLAG_CLEAR_ACTION_RIGHT)
    	{
    		classifyOut->rights &= ~FWPS_RIGHT_ACTION_WRITE;
    	}
    	return;
      }
    
      MetadataValues* metadataValues = ExAllocatePoolWithTag(NonPagedPool, sizeof(MetadataValues), SOME_TAG);
     // Get all information needed for redirection (compartmentId, interfaceIndex, subInterfaceIndex, IpHeaderSize, etc) and stored it on metadataValues
    
     // Block-adsorb the original NET_BUFFER_LIST and queued it to be processed by a worker thread
      FwpsReferenceNetBufferList(netBufferList, TRUE);
      classifyOut->actionType = FWP_ACTION_BLOCK;
      classifyOut->rights &= ~FWPS_RIGHT_ACTION_WRITE;
      classifyOut->flags |= FWPS_CLASSIFY_OUT_FLAG_ABSORB;
    
     // Queue the packet to be modified out of bounds by a worker thread.
      addPacketToWorkerQueue(netBufferList, metadataValues); 
    }
    
    void workerThreadFunc(NET_BUFFER_LIST * netBufferList, MetadataValues * metadataValues) //this Is the function that the worker thread executes in order to modify and re-inject the packet
    {
       //Retread the NET_BUFFER_LIST by the amount specified in inMetadataValues->ipHeaderSize
       // Cloned it and advance the original NET_BUFFER_LIST to its original position.
       NET_BUFFER_LIST * clonedBufferList;
       NDIS_STATUS ndis_status = NdisRetreatNetBufferListDataStart(netBufferList, metadataValues->ipHeaderSize, 0, NULL, NULL);
       status = FwpsAllocateCloneNetBufferList(netBufferList, NULL, NULL, 0, &clonedBufferList);
       NdisAdvanceNetBufferListDataStart(netBufferList, metadataValues->ipHeaderSize, FALSE, NULL);
    
      // Advance the cloned NET_BUFFER_LIST to the transport header
      NET_BUFFER * netBuffer = NET_BUFFER_LIST_FIRST_NB(clonedBufferList);
      NdisAdvanceNetBufferDataStart(netBuffer, metadataValues->ipHeaderSize, FALSE, NULL); 
      // Get the UDP header and modify the source port
      UDP_HEADER * udpHeader = (UDP_HEADER *)NdisGetDataBuffer(netBuffer, sizeof(UDP_HEADER), NULL, sizeof(UINT16), 0);
      udpHeader->srcPort = RtlUshortByteSwap(53);
       // Go back to the beginning of the IP Header
      ndis_status = NdisRetreatNetBufferDataStart(netBuffer, metadataValues->ipHeaderSize, 0, NULL); 
    
      // Get the IP header and modify the source and destination ip addresses
      UINT32 source = RtlUlongByteSwap(originalDNS);
      UINT32 destiny = RtlUlongByteSwap(nicIpAddress);
      IP_HEADER_V4 * ipHeader = (IP_HEADER_V4 *)NdisGetDataBuffer(netBuffer, metadataValues->ipHeaderSize, NULL, sizeof(UINT16), 0);
      RtlCopyMemory(ipHeader->pSourceAddress, &source, sizeof(UINT32));
      RtlCopyMemory(ipHeader->pDestinationAddress, &destiny, sizeof(UINT32));
    
      // Finally fix the IpHeader Checksum
      fixIpChecksum(ipHeader, metadataValues->ipHeaderSize); 
    
      //Inject the modified NET_BUFFER_LIST
      FwpsInjectNetworkReceiveAsync(
             injectionNetworkHandle, 
             NULL,
             0,
             metadataValues->compartmentId,
             metadataValues->interfaceIndex, 
             metadataValues->subInterfaceIndex, 
             clonedBuffer, 
             InjectToDNSClientCompleteFunc, 
             NULL); 
    }

    To recalculate the Ip Header checksum I am using this function:

    void fixIpChecksum(IP_HEADER_V4* ipHeader, UINT32 size)
    {
    	UINT32            sum = 0;
    	UINT32            words = size / 2;
    	UINT16 UNALIGNED* pStart = (UINT16*)ipHeader;
    	ipHeader->checksum = 0;
    	for (UINT8 i = 0; i < words; i++)
    	{
              sum += pStart[i];
    	}
    	sum = (sum & 0x0000ffff) + (sum >> 16);
    	sum += (sum >> 16);
    	ipHeader->checksum = (UINT16)~sum;
    }


    The injection function returns STATUS_SUCCESS and on the completion function the status of the injected NET_BUFFER_LIST is STATUS_SUCCESS. But the DNS client is timing out and the packet is not hitting any of the filters that I previously placed on the TRANSPORT_V4/TRANSPORT_V4_DISCARD, IPPACKET_V4/IPPACKET_V4_DISCARD nor DATAGRAMA_DATA_V4/DATAGRAM_DATA_V4_DISCARD layers.

    I also tried to modify and re inject the DNS proxy response packet on the FWPM_LAYER_OUTBOUND_TRANSPORT_V4 layer using the FwpsInjectTransportSendAsync using the following steps:
    1. Verify that the packet hasn't being re injected before:
    2. Retread the NET_BUFFER_LIST by the amount specified on inMetadataValues->ipHeaderSize + inMetadataValues->ipHeaderSize
    3. Clone the NET_BUFFER_LIST
    4. Restore the original NET_BUFFER_LIST (advance the packet by the amount specified on inMetadataValues->ipHeaderSize + inMetadataValues->ipHeaderSize)
    5. Block-adsorb the original NET_BUFFER_LIST
    6. Advance the cloned NET_BUFFER_LIST to the transport
    7. Get UDP header and modify the source port
    8. Retread the cloned NET_BUFFER_LIST to the ip header
    9. Use FwpsConstructIpHeaderForTransportPacket to create the new Ip Header, using the original DNS ip as source and the machines NIC's ip as the destination ip 
    11. Re-inject the cloned NET_BUFFER_LIST using FwpsInjectTransportSendAsyc function

    As before the injection function returns STATUS_SUCCESS, but the completion function returns STATUS_DATA_NOT_ACCEPTED.

    Also, I have seen some posts on these forums recommending to use the ALE_CONNECT_REDIRECT_V4 layer but as far as I have tested this layer doesn't seem work with DNS Queries, my test filter did hit the packet and performed the redirection successfully, but the packet didn't made it to the DNS proxy, It was again swallowed by the TCP/IP stack.

    Do you have any idea of what could I be doing wrong? or any pointers on what should I do to get this to work?  or Should I use another type of driver to do this? 

    Carlos   
    Friday, November 22, 2019 5:03 PM