Using Bind or Connect Redirection

The connect/bind redirection feature of the Windows Filtering Platform (WFP) enables application layer enforcement (ALE) callout drivers to inspect and, if desired, redirect connections.

This feature is available in Windows 7 and later.

Note  The ClassifyFunctions_ProxyCallouts.cpp module in the WFP driver sample includes code that demonstrates connect/bind redirection.

A WFP connection redirection callout redirects an application's connection request so that the application connects to a proxy service instead of the original destination. The proxy service has two sockets: one for the redirected original connection and one for the new proxied outbound connection.

A WFP redirect record is a buffer of opaque data that WFP must set on an outbound proxy connection at the FWPM_LAYER_ALE_AUTH_CONNECT_REDIRECT_V4 and FWPM_LAYER_ALE_AUTH_CONNECT_REDIRECT_V6 layers, so that the redirected connection and the original connection are logically related.

Changing the local address and port of a flow is only supported in the bind-redirect layer. This functionality isn't supported in the connect-redirect layer.

Layers Used for Redirection

Callout drivers can preform redirection at the following layers, which are called "redirect layers":

  • FWPM_LAYER_ALE_BIND_REDIRECT_V4 (FWPS_LAYER_ALE_BIND_REDIRECT_V4)

  • FWPM_LAYER_ALE_BIND_REDIRECT_V6 (FWPS_LAYER_ALE_BIND_REDIRECT_V6)

  • FWPM_LAYER_ALE_CONNECT_REDIRECT_V4 (FWPS_LAYER_ALE_CONNECT_REDIRECT_V4)

  • FWPM_LAYER_ALE_CONNECT_REDIRECT_V6 (FWPS_LAYER_ALE_CONNECT_REDIRECT_V6)

The layer at which redirection is performed determines the effect of the change. Changes at connect layers affect only the flow being connected. Changes at bind layers affect all connections that are using that socket.

The redirect layers are only available for Windows 7 and later versions of Windows. Callout drivers that support classification at these layers must register using FwpsCalloutRegister1 or higher, not the older FwpsCalloutRegister0 function.

Important

 Redirection is not available for use with all types of network traffic. The types of packets that are supported for redirection are shown in the following list:

  • TCP
  • UDP
  • Raw UDPv4 without the header include option
  • Raw ICMP

Performing Redirection

To redirect a connection, the callout driver must obtain a writable copy of the TCP 4-tuple information, make changes to it as needed, and apply the changes. A set of new functions are provided to obtain writable layer data and to apply it through the engine. Callout drivers have the option of making changes either inline in their classifyFn functions, or asynchronously in another function.

Callout drivers that implement redirection must use classifyFn1 or later instead of classifyFn0 as their classification callout function. To use classifyFn1 or later, the callout must be registered by calling FwpsCalloutRegister1 or later, not the older FwpsCalloutRegister0.

To perform redirection inline a callout driver must perform the following steps in its implementation of classifyFn:

  1. Call FwpsRedirectHandleCreate0 to obtain a handle that can be used to redirect TCP connections. This handle should be cached and used for all redirections. (This step is omitted for Windows 7 and earlier.)

  2. In Windows 8 and later, you must query the redirection state of the connection by using the FwpsQueryConnectionRedirectState0 function in your callout driver. This must be done to prevent infinite redirecting.

  3. Call FwpsAcquireClassifyHandle0 to obtain a handle that will be used for subsequent function calls.

  4. Call FwpsAcquireWritableLayerDataPointer0 to get the writable data structure for the layer in which classifyFn was called. Cast the writableLayerData out parameter to the structure corresponding to the layer, either FWPS_BIND_REQUEST0 or FWPS_CONNECT_REQUEST0.

    Starting with Windows 8, if your callout driver is redirecting to a local service, you must call FwpsRedirectHandleCreate0 to fill in the localRedirectHandle member of the FWPS_CONNECT_REQUEST0 structure in order to make local proxying work.

  5. Make changes to the layer data as needed:

    1. Save the original destination in the local redirect context as shown in the following example:

      FWPS_CONNECT_REQUEST* connectRequest = redirectContext->connectRequest;
      // Replace "..." with your own redirect context size
      connectRequest->localRedirectContextSize = ...;
      // Store original destination IP/Port information in the localRedirectContext member
      connectRequest->localRedirectContext =    ExAllocatePoolWithTag(…);
      
    2. Modify the remote address as shown in the following example:

      // Ensure we don't need to worry about crossing any of the TCP/IP stack's zones
      if(INETADDR_ISANY((PSOCKADDR)&(connectRequest->localAddressAndPort)))
      {
         INETADDR_SETLOOPBACK((PSOCKADDR)&(connectRequest->remoteAddressAndPort));
      }
      else
      {
         INETADDR_SET_ADDRESS((PSOCKADDR)&(connectRequest->remoteAddressAndPort),
                               INETADDR_ADDRESS((PSOCKADDR)&(connectRequest->localAddressAndPort)));
      }
      INETADDR_SET_PORT((PSOCKADDR)&connectRequest->remoteAddressAndPort,
                        RtlUshortByteSwap(params->proxyPort));
      
    3. If your callout driver is redirecting to a local service, it should set the local proxy PID in the localRedirectTargetPID member of the FWPS_CONNECT_REQUEST0 structure.

    4. If your callout driver is redirecting to a local service, it should set the redirect handle returned by FwpsRedirectHandleCreate0 in the localRedirectHandle member of the FWPS_CONNECT_REQUEST0 structure.

  6. Call FwpsApplyModifiedLayerData0 to apply the changes made to the data.

  7. In your proxy service (which could be in user mode or kernel mode), you must query redirect records and contexts as shown in the following example:

    BYTE* redirectRecords;
    BYTE redirectContext[CONTEXT_SIZE];
    listenSock = WSASocket(…);
    result = bind(listenSock, …);
    result = listen(listenSock, …);
    clientSock = WSAAccept(listenSock, …);
    // opaque data to be set on proxy connection
    result = WSAIoctl(clientSock,
                      SIO_QUERY_WFP_CONNECTION_REDIRECT_RECORDS,
                      redirectRecords, …);
    // callout allocated data, contains original destination information
    result = WSAIoctl(clientSock,
                      SIO_QUERY_WFP_CONNECTION_REDIRECT_CONTEXT,
                      redirectContext, …);
    // extract original destination IP and port from above context
    
  8. In your proxy service (which could be in user mode or kernel mode), you must set redirect records on the proxy connection socket as shown in the following example to create a new outbound socket:

    proxySock = WSASocket(…);
    result = WSAIoctl(
                 proxySock,
                 SIO_SET_WFP_CONNECTION_REDIRECT_RECORDS,
                 redirectRecords, …);
    
  9. Call FwpsReleaseClassifyHandle0 to release the classification handle obtained in step 2.

  10. Call FwpsRedirectHandleDestroy0 to destroy the handle that was obtained in step 1.

To perform redirection asynchronously a callout driver must perform the following steps:

  1. Call FwpsRedirectHandleCreate0 to obtain a handle that can be used to redirect TCP connections. (This step is omitted for Windows 7 and earlier.)

  2. In Windows 8 and later, you must query the redirection state of the connection by using the FwpsQueryConnectionRedirectState0 function in your callout driver.

  3. Call FwpsAcquireClassifyHandle0 to obtain a handle that will be used for subsequent function calls. This step and steps 2 and 3 are performed in the callout driver's classifyFn callout function.

  4. Call FwpsPendClassify0 to put the classification in a pending state as shown in the following example:

    FwpsPendClassify(
            redirectContext->classifyHandle,
            0,
            &redirectContext->classifyOut);
    classifyOut->actionType = FWP_ACTION_BLOCK;
    classifyOut->rights &= ~FWPS_RIGHT_ACTION_WRITE;
    

Note

If you are targeting Windows 7, you must perform the following steps in a separate worker function. If you are targeting Windows 8 or later, you can perform all steps for asynchronous redirection from within the classifyFn and ignore Step 5.

  1. Send the classification handle and the writable layer data to another function for asynchronous processing. The remaining steps are performed in that function, not in the callout driver's implementation of classifyFn.

  2. Call FwpsAcquireWritableLayerDataPointer0 to get the writable data structure for the layer in which classifyFn was called. Cast the writableLayerData out parameter to the structure corresponding to the layer, either FWPS_BIND_REQUEST0 or FWPS_CONNECT_REQUEST0.

    Starting with Windows 8, if your callout driver is redirecting locally, you must call FwpsRedirectHandleCreate0 to fill in the localRedirectHandle member of the FWPS_CONNECT_REQUEST0 structure in order to make proxying work.

  3. Store any callout-specific context information in a private context structure as shown in the following example:

    redirectContext->classifyHandle = classifyHandle;
    redirectContext->connectRequest = connectRequest;
    redirectContext->classifyOut = *classifyOut; // deep copy
    // store original destination IP, port
    
  4. Make changes to the layer data as needed.

  5. Call FwpsApplyModifiedLayerData0 to apply the changes made to the data. Set the FWPS_CLASSIFY_FLAG_REAUTHORIZE_IF_MODIFIED_BY_OTHERS flag if you wish to be re-authorized in the event that another callout modifies the data further.

  6. Call FwpsCompleteClassify0 to complete the classify operation asynchronously as shown in the following example:

    FwpsCompleteClassify(
            redirectContext->classifyHandle,
            0,
            &redirectContext->classifyOut);
    classifyOut->actionType = FWP_ACTION_PERMIT;
    classifyOut->rights |= FWPS_RIGHT_ACTION_WRITE;
    
  7. Call FwpsReleaseClassifyHandle0 to release the classification handle obtained in step 1.

Handling Connect Redirection from Multiple Callouts

It's possible that more than one callout driver will initiate connect redirection for the same flow. Callouts that perform connect redirection should be aware of other requests and respond appropriately.

The FWPS_RIGHT_ACTION_WRITE flag should be set whenever a callout pends a classification. Your callout should test for the FWPS_RIGHT_ACTION_WRITE flag to check the rights for your callout to return an action. If this flag isn't set, your callout can still return a FWP_ACTION_BLOCK action in order to veto a FWP_ACTION_PERMIT action that was returned by a previous callout.

In Windows 8 and later, your callout driver must query the redirection state of the connection (to see if your callout driver or another callout driver has modified it) by using the FwpsQueryConnectionRedirectState0 function. If the connection is redirected by your callout driver, or if it was previously redirected by your callout driver, the callout driver should do nothing. Otherwise, it should also check for local redirection as shown in the following example:

FwpsAcquireWritableLayerDataPointer(...,(PVOID*)&connectRequest), ...);
if(connectRequest->previousVersion->modifierFilterId != filterId)
{
    if(connectRequest->previousVersion->localRedirectHandle)
    {
        classifyOut->actionType = FWP_ACTION_PERMIT;
        classifyOut->rights &= FWPS_RIGHT_ACTION_WRITE;
        FwpsApplyModifiedLayerData(
                classifyHandle,
                (PVOID)connectRequest,
                FWPS_CLASSIFY_FLAG_REAUTHORIZE_IF_MODIFIED_BY_OTHERS);
    }
}

If the connection is to a local proxy, your callout driver shouldn't attempt to redirect it.

Callout drivers that use connect redirection should register at the ALE authorization connect layer (FWPS_LAYER_ALE_AUTH_CONNECT_V4 or FWPS_LAYER_ALE_AUTH_CONNECT_V6) and check the following two metadata values for indications where the FWP_CONDITION_FLAG_IS_CONNECTION_REDIRECTED flag is set:

  • FWPS_METADATA_FIELD_LOCAL_REDIRECT_TARGET_PID contains the process identifier for the process that is responsible for the redirected flow.

  • FWPS_METADATA_FIELD_ORIGINAL_DESTINATION contains the address of the original destination for the flow.

The FWPS_CONNECT_REQUEST0 structure contains a member called localRedirectTargetPID. For any loopback connect redirection to be valid, this field must be populated with the PID of the process that will be responsible for the redirected flow. This is the same data that the engine passes at the ALE authorization connect layers as FWPS_METADATA_FIELD_LOCAL_REDIRECT_TARGET_ID.

Starting with Windows 8, the proxy service needs to issue the SIO_QUERY_WFP_CONNECTION_REDIRECT_RECORDS and SIO_QUERY_WFP_CONNECTION_REDIRECT_CONTEXT IOCTLs, using WSAIoctl, against the original endpoint of the proxy service. Additionally, the SIO_SET_WFP_CONNECTION_REDIRECT_RECORDS IOCTL must be issued, using WSAIoctl, on the new (proxied) socket.

WFP Version-Independent Names and Targeting Specific Versions of Windows