WFP connect redirect with udp and get original destination

Hi, there~

I am developing WFP in windows 10 21H2, and i want to use WFP FWPS_LAYER_ALE_CONNECT_REDIRECT_V4 to make the origin traffic redirect to my local proxy, and in my local proxy to send to the origin destination.

i.e: client --> server change to: client (connect_redirect)--> proxy (get origin dest)--> server

The problem is: i test TCP by using WFPSample or WFP doc, it works well, but when i turn to UDP, it doesn't work.

the detail problem is i can't get origin destination from udp socket by WSAIoctl with SIO_QUERY_WFP_CONNECTION_REDIRECT_CONTEXT, the fake code may like this:

auto status = WSAIoctl(_socket.native_handle(),
                            SIO_QUERY_WFP_CONNECTION_REDIRECT_CONTEXT,
                            NULL, 0,
                            redirect_records->buf_,
                            sizeof(redirect_records->buf_),
                            &redirect_records->buf_size_,
                            0, 0);

and i get error code is WSAEO PNOTSUPP.

I know the difference between tcp and udp is the socket, because tcp has accept pharse, so i use accepted socket in tcp, it works well.
but udp don't need accept, so i directly use listen udp socket, but i get the error code. i don't know how to fix it because there is no doc in msdn or sample code with UDP.

i find in developing a DNS redirection and interception project making use of Windows Filtering Platform. here, also use udp connect redirect, but didn't show how to get origin destination.

and what's more, redirect udp dns traffic, there is no need to get original destination, because you only need to redirect to your dns server, so can't get original destination is ok.

i also find in Failed to redirect connected UDP traffic - Hardware Developer | Microsoft Learn, but it seems the client behavor, that's if client use sendto, the udp traffic can redirect to my proxy. but if client use connect and send, the udp traffic can't redirect to my proxy. but all can't get original destination.

pelucky

1 Like

Sorry to bother you, I also encountered the same problem, have you solved it?
In the document of that control code, it is said that the redirection context of the UDP socket can be obtained. The following is the original text:
The SIO_QUERY_WFP_CONNECTION_REDIRECT_RECORDS IOCTL is used by a WFP-based redirect service to retrieve the redirect record from the accepted TCP/IP packet connection (the connected socket for a TCP socket or a UDP socket, for example) redirected to it by its companion kernel-mode callout registered at ALE_CONNECT_REDIRECT layers in a kernel-mode driver. The SIO_QUERY_WFP_CONNECTION_REDIRECT_CONTEXT IOCTL is used by a WFP-based redirect service to retrieve the redirect context for a redirect record from the accepted TCP/IP packet connection (the connected socket for a TCP socket or a UDP socket, for example) redirected to it by its companion callout registered at ALE_CONNECT_REDIRECT layers. The redirect context is optional and is used if the current redirection state of a connection is that the connection was redirected by the calling redirect service or the connection was previously redirected by the calling redirect service but later redirected again by a different redirect service. The redirect service transfers the retrieved redirect record to the TCP socket it uses to proxy the original content using the SIO_SET_WFP_CONNECTION_REDIRECT_RECORDS IOCTL.

no, i think udp can't get origin connection...

thanks,i will try this

Here is solution:

#pragma comment(lib, "Ws2_32.lib")

#include <iostream>
#include <winsock2.h>
#include <mswsock.h>
#include <ws2tcpip.h>
#include <mstcpip.h>
#include <stdio.h>

int main() 
{
    WSADATA wsaData;
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) return 1;

    // 1. Create UDP socket
    SOCKET s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    
    // Without this option, we can't get redirect context
    DWORD bOption = 1;
    if (setsockopt(s, IPPROTO_IP, IP_WFP_REDIRECT_CONTEXT, (char*)&bOption, sizeof(bOption)) == SOCKET_ERROR) {
        printf("setsockopt IP_WFP_REDIRECT_CONTEXT failed: %d\n", WSAGetLastError());
    }

    sockaddr_in serverAddr = { 0 };
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(8080);
    serverAddr.sin_addr.s_addr = INADDR_ANY;

    bind(s, (sockaddr*)&serverAddr, sizeof(serverAddr));

    // 2. Get WSARecvMsg function address
    LPFN_WSARECVMSG pWSARecvMsg = NULL;
    GUID guidWSARecvMsg = WSAID_WSARECVMSG;
    DWORD dwBytes;
    WSAIoctl(s, SIO_GET_EXTENSION_FUNCTION_POINTER, &guidWSARecvMsg, sizeof(guidWSARecvMsg),
        &pWSARecvMsg, sizeof(pWSARecvMsg), &dwBytes, NULL, NULL);

    while (true) {
        char packetBuf[11024];
        char controlBuf[11024]; 

        WSABUF wsaBuf = { sizeof(packetBuf), packetBuf };
        WSAMSG msg = { 0 };
        msg.lpBuffers = &wsaBuf;
        msg.dwBufferCount = 1;
        msg.Control.buf = controlBuf;
        msg.Control.len = sizeof(controlBuf);

        DWORD bytesReceived = 0;
        // 3. Read data from socket (including control data)
        if (pWSARecvMsg(s, &msg, &bytesReceived, NULL, NULL) != SOCKET_ERROR) {
            std::cout << "Bytes received: " << bytesReceived << std::endl;
            // 4. Search "Ancillary Data"
            for (WSACMSGHDR* cmsg = WSA_CMSG_FIRSTHDR(&msg);
                cmsg != NULL;
                cmsg = WSA_CMSG_NXTHDR(&msg, cmsg))
            {
                std::cout << "Trying to retrieve WFP context " << std::endl;
                // Get context
                if (cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_WFP_REDIRECT_CONTEXT) {
                    SOCKADDR_IN* ctx = (SOCKADDR_IN*)WSA_CMSG_DATA(cmsg);
                    std::uint16_t port = htons(ctx->sin_port);
                    char ip[INET_ADDRSTRLEN];
                    if (!inet_ntop(AF_INET, &ctx->sin_addr, ip, sizeof(ip))) {
                        printf("inet_ntop: %d", GetLastError());
                    }


                    printf("--- PACKET ACCEPTED! ---\n");
                    printf("PACKET DATA: %d\n", (int)bytesReceived);
                    printf("WFP IP: %s, PORT: %d\n", ip, port);
                    printf("----------------------\n");
                }
            }
        }
        else {
            printf("ERROR: %d\n", WSAGetLastError());
            break;
        }
    }

    closesocket(s);
    WSACleanup();
    return 0;
}
1 Like

Thank you @Muhammad, I am able to get the udp context in userspace using your solution.

I have another question, I want the userspace to also get the flowid from the ioctl, since there is no flowid in ale_connect_redirect layer, I am retrieving the redirect context in ale_auth_connect layer and updating the flowid in the redirect context

    if (inFixedValues->layerId == FWPS_LAYER_ALE_AUTH_CONNECT_V4 || inFixedValues->layerId == FWPS_LAYER_ALE_AUTH_CONNECT_V6) {
        ...
        if (FWPS_IS_METADATA_FIELD_PRESENT(inMetaValues, FWPS_METADATA_FIELD_REDIRECT_RECORD_HANDLE)) {
            ...
            FWPS_CONNECTION_REDIRECT_STATE r = FwpsQueryConnectionRedirectState(inMetaValues->redirectRecords, gDriverCtx.redirectHandle, &redirectCtx);
            ...
            redirectCtx->FlowId = inMetaValues->flowHandle;
        }

This works well for TCP however in case of UDP there is no redirect records present, is there some other way to get the redirect context at the ale_auth_connect layer?

Add the FlowId to a FlowContext at a layer where it exists. You can then read that FlowContext at CONNECT_REDIRECT and add it to your metadata during redirection. Be aware the flow context will need cleaned up. FwpsFlowAssociateContext0 function (fwpsk.h) - Windows drivers | Microsoft Learn

1 Like

Hi @Jason_Stephenson, thanks for your response.

Doesnt ale_connect_redirect happen before ale_auth_connect (where flowid is present)?

I tried associating the flowcontext to the ale_connect_redirect layer at ale_auth_connect thinking ale_connect_redirect will be called again with FWPS_CONNECTION_REDIRECTED_BY_SELF but that did not happen.

Btw I found another way using transportEndpointHandle, it seems to be the same value in both the layers and would be unique according to a-driver-that-listens-to-traffic/53052/25 (im not allowed to use link for some reason), I am maintaining a hashtable by that value and getting the context for the redirect connection in ale_auth_connect, will that work?

Not as I recall, although it has been many years since I’ve done this. Is there not an AUTH_CONNECT before redirection? It should be fairly easy to confirm this if you have a working environment

ALE_CONNECT_REDIRECT happens first according to my tests, the same is mentioned here https: / / learn.microsoft.com/en-us/windows / win32/fwp/tcp-packet-flows

Consider using a tuple of <src.ip,src.port,dst.ip, dst.port,protocol> for uniquness if you find transportEndpoint handle insufficient. I’ve used it in the past and it worked well

Yeah I initially thought of using 5 tuple, but the problem is the ALE_CONNECT_REDIRECT layer that src ip is 0.0.0.0 and then in ALE_AUTH_CONNECT the src ip is chosen interface's ip address, the desination is the redirected ip:port.

ALE_CONNECT_REDIRECT_V4 layer, FlowId=0, PID=5048, TransportEndpointHandle=713, Proto=6, Local=0.0.0.0:35688, Remote=192.168.56.1:8081
... after redirecting to 127.0.0.1:65001
ALE_AUTH_CONNECT_V4 layer, FlowId=857, PID=5048, TransportEndpointHandle=713, Proto=6, Local=127.0.0.1:35688, Remote=127.0.0.1:65001

If two sockets used different ips for same port with SO_REUSEADDR then its ambigious to lookup the context.

I don’t think you need to do anything at CONNECT_REDIRECT. If I understood your ask you want to retrieve the flowHandle in usermode for a redirected flow. You could:

  1. At AUTH_CONNECT for a redirected flow store the 5tuple : flowId mapping
  2. On receipt of a redirected connection in usermode, extract original destination(ip+port) from the redirect context
  3. Use this and the src details (extracted from socket) along with the protocol to query your hashtable using a custom ioctl

I actually though of that too, but what if the connection gets src natted before it comes to application (is this actually possible?)

I also wanted a way to associate the flow in ale_connect_redirect to the flow which comes later in ale_auth_connect, because in ale_connect_redirect since I cant block the connection in this layer, after receiving decision from userspace to proxy or not, I check the action in the flow context created in ale_connect_redirect and block if required in ale_auth_connect.

I could just send the request to userspace again in ale_auth_connect and check if request should be blocked but wanted to do all this once.