Redirect DNS traffic using WFP

Hi!
I am new to WFP and driver development in general and OSR have been so helpful through this learning journey. Right now I am facing a similar problem described on this post: Cisco AnyConnect VPN compatibility woes. I have tried to apply the solution posted there, but I can’t get it to work, so I was hoping to the get some pointer of what could be happening and how could I get this to work. Let me described what I am trying to do:


I am trying to implement the same scenario that Jason_Stephenson described here , a local DNS proxy and a callout driver that redirects the DNS queries to this 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 get the “de-proxy” of 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_DISCARD 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, //This injection handle was created using the FWPS_INJECTION_TYPE_NETWORK flag 
         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
  10. 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 the msdn 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

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_DISCARD 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?

Are you actually sending the response back to the “source application”? (The application that made the DNS request)
Have you tried a filter with a condition of “protocol == UDP”?

The only layer where i can get the packet is FWPM_LAYER_INBOUND_IPPACKET_V4 layer
You can definitely get this packet at the transport layer. (Assuming correct implementation)

// Block-adsorb the original NET_BUFFER_LIST and queued it to be processed by a worker thread
I would suggest you trim your solution down. Focus on getting inline packet modification working before worry about worker threads.

To recalculate the Ip Header checksum I am using this function: …
I’d use FwpsConstructIpHeaderForTransportPacket for this

As before the injection function returns STATUS_SUCCESS, but the completion function returns STATUS_DATA_NOT_ACCEPTED.
Stick a breakpoint in your completion function. What does !ndiskd.nbl <your_net_buffer_list_address> display? Anything interesting?

Also, I have seen some posts on the msdn 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
It should. Indeed a few years ago I worked with MS to get a few bugs fixed in this area. Redirection at this layer is different than other layers though, you’re not injecting on a per packet basis. https://docs.microsoft.com/en-us/windows-hardware/drivers/network/using-bind-or-connect-redirection is a good doc for getting started.

Hope this helps,
J

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_DISCARD 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?
Are you actually sending the response back to the “source application”? (The application that made the DNS request)
Have you tried a filter with a condition of “protocol == UDP”?

The only layer where i can get the packet is FWPM_LAYER_INBOUND_IPPACKET_V4 layer
You can definitely get this packet at the transport layer. (Assuming correct implementation)

// Block-adsorb the original NET_BUFFER_LIST and queued it to be processed by a worker thread
I would suggest you trim your solution down. Focus on getting inline packet modification working before worry about worker threads.

To recalculate the Ip Header checksum I am using this function: …
I’d use FwpsConstructIpHeaderForTransportPacket for this

As before the injection function returns STATUS_SUCCESS, but the completion function returns STATUS_DATA_NOT_ACCEPTED.
Stick a breakpoint in your completion function. What does !ndiskd.nbl your_net_buffer_list_address display? Anything interesting?

Also, I have seen some posts on the msdn 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

It should. Indeed a few years ago I worked with MS to get a few bugs fixed in this area. Redirection at this layer is different than other layers though, you’re not injecting on a per packet basis. https://docs.microsoft.com/en-us/windows-hardware/drivers/network/using-bind-or-connect-redirection is a good doc for getting started.

Hope this helps,
J

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_DISCARD 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?
Are you actually sending the response back to the “source application”? (The application that made the DNS request)
Have you tried a filter with a condition of “protocol == UDP”?

The only layer where i can get the packet is FWPM_LAYER_INBOUND_IPPACKET_V4 layer
You can definitely get this packet at the transport layer. (Assuming correct implementation)

// Block-adsorb the original NET_BUFFER_LIST and queued it to be processed by a worker thread
I would suggest you trim your solution down. Focus on getting inline packet modification working before worry about worker threads.

To recalculate the Ip Header checksum I am using this function: …
I’d use FwpsConstructIpHeaderForTransportPacket for this

As before the injection function returns STATUS_SUCCESS, but the completion function returns STATUS_DATA_NOT_ACCEPTED.
Stick a breakpoint in your completion function. What does !ndiskd.nbl your_net_buffer_list_address display? Anything interesting?

Also, I have seen some posts on the msdn 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

It should. Indeed a few years ago I worked with MS to get a few bugs fixed in this area. Redirection at this layer is different than other layers though, you’re not injecting on a per packet basis. https://docs.microsoft.com/en-us/windows-hardware/drivers/network/using-bind-or-connect-redirection is a good doc for getting started.

Hope this helps,
J

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_DISCARD 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?
Are you actually sending the response back to the “source application”? (The application that made the DNS request)
Have you tried a filter with a condition of “protocol == UDP”?

The only layer where i can get the packet is FWPM_LAYER_INBOUND_IPPACKET_V4 layer
You can definitely get this packet at the transport layer. (Assuming correct implementation)

// Block-adsorb the original NET_BUFFER_LIST and queued it to be processed by a worker thread
I would suggest you trim your solution down. Focus on getting inline packet modification working before worry about worker threads.

To recalculate the Ip Header checksum I am using this function: …
I’d use FwpsConstructIpHeaderForTransportPacket for this

As before the injection function returns STATUS_SUCCESS, but the completion function returns STATUS_DATA_NOT_ACCEPTED.
Stick a breakpoint in your completion function. What does !ndiskd.nbl your_net_buffer_list_address display? Anything interesting?

Also, I have seen some posts on the msdn 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

It should. Indeed a few years ago I worked with MS to get a few bugs fixed in this area. Redirection at this layer is different than other layers though, you’re not injecting on a per packet basis. https://docs.microsoft.com/en-us/windows-hardware/drivers/network/using-bind-or-connect-redirection is a good doc for getting started.

Hope this helps,
J

Did you solve this issue? I am having the same problem. I also tried using ALE_CONNECT_REDIRECT_V4 - checked for UDP and port 53 and rewrote the destination IP to (in my case) a different public DNS server (like 1.1.1.1) - but it didn’t work… a packet sniffer does show the DNS packets going to 1.1.1.1 and even getting a response, but the application didn’t notice the DNS request somehow and the DNS just failed. Do i also have to rewrite the incoming packet to restore the original DNS server (from 1.1.1.1) ?

@FunGuy77 said:
Did you solve this issue? I am having the same problem. I also tried using ALE_CONNECT_REDIRECT_V4 - checked for UDP and port 53 and rewrote the destination IP to (in my case) a different public DNS server (like 1.1.1.1) - but it didn’t work… a packet sniffer does show the DNS packets going to 1.1.1.1 and even getting a response, but the application didn’t notice the DNS request somehow and the DNS just failed. Do i also have to rewrite the incoming packet to restore the original DNS server (from 1.1.1.1) ?

Yes.The whole proceudre should be like this:1.In the ALE_CONNECT_REDIRECT,redirect the dst ip(filter condition is:udp+port53),like 1.1.1.1。 2.In the DATAGRAM DATA layer,use NdisAdvanceNetBufferDataStart to get the whole udp flow,and redirect the src ip(1.1.1.1) to original ip,and then don’t forget to update IpHeaderChecksum(this is important,if you don’t do it,the app will not recognize this response) ,at last,use FwpsInjectTransportReceiveAsync.