Windows/WSL Interop with AF_UNIX

Avatar

Craig

Starting in Windows Insider build 17093, a WSL application can communicate with a Windows application over Unix sockets. Back in December, we blogged about bringing AF_UNIX to Windows. Now, we’re building on that functionality. Consider a requirement where you want to run some kind of service as a Windows application. Additionally, you would like to make this service available to both Windows and WSL applications. Now, that’s possible with Unix sockets.

How To Code

Let’s look at how the code for such applications would be written.

The code for this application is at the bottom of this article.

One thing worth noting is the path of the socket. For Windows application, the path is in Win32 format and for WSL applications, the format is as in Linux file system.

Requirements & Limitations:

  1. A WSL Unix socket can only communicate with a Win32 Unix socket OR with a WSL Unix socket, but not both. For instance, a WSL Unix socket server can only accept connections from either WSL Unix socket(s) OR Win32 Unix socket(s). So, how is it determined which one is it? It’s based on the path the socket is bound to or connecting to, as specified in the `bind` or `connect` syscall. If the Unix socket path is a DrvFS path (i.e your system volumes mounted within WSL, ex: /mnt/c, /mnt/d etc.) then it can only communicate to a Windows Unix socket. If the path is a LxFS path (i.e Linux mounted volume within WSL, ex: /home, /var, /usr etc.) then it can only communicate with WSL Unix sockets.
  2. For a WSL Unix socket to establish connection with Windows Unix sockets, the first operation after the socket is created should be either a bind or connect. Any other operation on the socket will render it an exclusive WSL Unix socket that can only communicate with other WSL Unix sockets.
  3. As Windows Unix socket implementation does not currently support passing ancillary data such as `SCM_RIGHTS` etc., the ancillary data will also not be supported for Win32<->WSL interop over Unix sockets.

It is important to note that we enforce both Unix and Windows permissions for Unix interop between WSL/Windows–this is illustrated in the samples below.

NOTE: Turn on DrvFS metadata for these examples.

Binding Sample 1: No Windows Permissions

In this sample, we’ll bind a WSL Unix socket process to a path which it does not have access to. The binding will fail because I don’t have write permissions in Windows.

But grant write access to the path in Windows and it will bind successfully…

Binding Sample 2: Linux Permissions, No Windows Permissions

In this sample, a WSL Unix socket process connects to a Windows Unix socket path which it has access to from WSL, but not from Windows (I removed write permissions from user on this folder in Windows)

But if I run an elevated ubuntu (so that we do have write access on the file), I can connect as expected.

Binding Sample 3: No Linux Permissions, Windows Permissions

In this sample, a WSL Unix socket process connects to a Windows Unix socket path which it has access to from Windows, but not from WSL. You’ll see that I revoke access in WSL, which causes my client to fail to connect. Then, I’ll grant write access in WSL and the client will succeed.

Sample Code

Windows Server code

af_unix_win_server.cpp

#undef UNICODE

#include <winsock2.h>
#include <windows.h>
#include <ws2tcpip.h>
#include <afunix.h>
#include <stdlib.h>
#include <stdio.h>

#define SERVER_SOCKET "server.sock"

int __cdecl main(void)
{
    SOCKET ClientSocket = INVALID_SOCKET;
    SOCKET ListenSocket = INVALID_SOCKET;
    int Result = 0;
    char SendBuffer[] = "af_unix from Windows to WSL!";
    int SendResult = 0;
    SOCKADDR_UN ServerSocket = { 0 };
    WSADATA WsaData = { 0 };

    // Initialize Winsock
    Result = WSAStartup(MAKEWORD(2,2), &WsaData);
    if (Result != 0) {
        printf("WSAStartup failed with error: %d\n", Result);
        goto Exit;
    }

    // Create a AF_UNIX stream server socket.
    ListenSocket = socket(AF_UNIX, SOCK_STREAM, 0);
    if (ListenSocket == INVALID_SOCKET) {
        printf("socket failed with error: %d\n", WSAGetLastError());
        goto Exit;
    }

    ServerSocket.sun_family = AF_UNIX;
    strncpy_s(ServerSocket.sun_path, sizeof ServerSocket.sun_path, SERVER_SOCKET, (sizeof SERVER_SOCKET) - 1);

    // Bind the socket to the path.
    Result = bind(ListenSocket, (struct sockaddr *)&ServerSocket, sizeof(ServerSocket));
    if (Result == SOCKET_ERROR) {
        printf("bind failed with error: %d\n", WSAGetLastError());
        goto Exit;
    }

    // Listen to start accepting connections.
    Result = listen(ListenSocket, SOMAXCONN);
    if (Result == SOCKET_ERROR) {
        printf("listen failed with error: %d\n", WSAGetLastError());
        goto Exit;
    }

    printf("Accepting connections on: '%s'\n", SERVER_SOCKET);
    // Accept a connection.
    ClientSocket = accept(ListenSocket, NULL, NULL);
    if (ClientSocket == INVALID_SOCKET) {
        printf("accept failed with error: %d\n", WSAGetLastError());
        goto Exit;
    }
    printf("Accepted a connection.\n" );

    // Send some data.
    SendResult = send(ClientSocket, SendBuffer, (int)strlen(SendBuffer), 0 );
    if (SendResult == SOCKET_ERROR) {
        printf("send failed with error: %d\n", WSAGetLastError());
        goto Exit;
    }
    printf("Relayed %zu bytes: '%s'\n", strlen(SendBuffer), SendBuffer);

    // shutdown the connection.
    printf("Shutting down\n");
    Result = shutdown(ClientSocket, 0);
    if (Result == SOCKET_ERROR) {
        printf("shutdown failed with error: %d\n", WSAGetLastError());
        goto Exit;
    }

Exit:

    // cleanup
    if (ListenSocket != INVALID_SOCKET) {
        closesocket(ListenSocket);
    }

    if (ClientSocket != INVALID_SOCKET) {
        closesocket(ClientSocket);
    }

    // Analogous to `unlink`
    DeleteFileA(SERVER_SOCKET);
    WSACleanup();
    return 0;
}

Linux code

server.c

#include "header.h"

int main(int argc, char* argv[]) {
    ssize_t BytesSent = 0;
    struct sockaddr_un serverAddr = { 0 };
    struct sockaddr_un tmpAddr = { 0 };
    char buf[100] = "this is a test";
    int serverFd = 0, acceptFd = 0;
    socklen_t addrLen = 0;

    if ((serverFd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
        perror("socket error");
        goto ErrorExit;
    }

    // bind the server.
    memset(&serverAddr, 0, sizeof(serverAddr));
    serverAddr.sun_family = AF_UNIX;
    strncpy(serverAddr.sun_path, SERVER_SOCKET, sizeof(serverAddr.sun_path) - 1);
    if (bind(serverFd, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) == -1) {
        perror("server bind error");
        goto ErrorExit;
    }

    // listen
    if (listen(serverFd, 5) == -1) {
        perror("listen error");
        goto ErrorExit;
    }

    addrLen = sizeof(tmpAddr);
    memset(&tmpAddr, 0, sizeof(tmpAddr));
    if (getsockname(serverFd, (struct sockaddr*)&tmpAddr, &addrLen) == -1) {
        perror("getsockname error");
        goto ErrorExit;
    }

    printf("getsockname on listening socket: %s\n", tmpAddr.sun_path);
    printf("server listening on: %s\n", SERVER_SOCKET);
    fflush(stdout);

    // accept connection
    addrLen = sizeof(tmpAddr);
    memset(&tmpAddr, 0, sizeof(tmpAddr));
    if ((acceptFd = accept(serverFd, (struct sockaddr*)&tmpAddr, &addrLen)) == -1) {
        perror("accept error");
        goto ErrorExit;
    }

    printf("accept returned address: %s, address size: %d\n", tmpAddr.sun_path, addrLen);
    if ((BytesSent = send(acceptFd, buf, strlen(buf), 0)) == -1) {
        perror("send");
        goto ErrorExit;
    }

    printf("sent data successfully, bytes sent: %zd, data: %s\n", BytesSent, buf);

ErrorExit:
    unlink(SERVER_SOCKET);
    return 0;
}

header.h

#include <sys/socket.h>
#include <sys/un.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#define SERVER_SOCKET "server.sock"
#define CLIENT_SOCKET "client.sock"
#define BIND_SOCKET "bind.sock"

client.c

#include "header.h"

int main(int argc, char* argv[]) {
    char Buf[100] = { 0 };
    ssize_t BytesRecvd = 0;
    struct sockaddr_un clientAddr = { 0 };
    struct sockaddr_un serverAddr = { 0 };
    struct sockaddr_un address = { 0 };
    char serverAddress[100] = { 0 };
    int serverFd = 0, rc = 0, clientFd = 0;
    int acceptFd = 0;
    socklen_t addrLen = 0;

    if ((clientFd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
        perror("clientFd socket error");
        goto ErrorExit;
    }

    clientAddr.sun_family = AF_UNIX;
    strncpy(clientAddr.sun_path, CLIENT_SOCKET, sizeof(clientAddr.sun_path) - 1);
    if (bind(clientFd, (struct sockaddr*)&clientAddr, sizeof(clientAddr)) == -1) {
        perror("server bind error");
        goto ErrorExit;
    }

    addrLen = sizeof(clientAddr);
    if (getsockname(clientFd, (struct sockaddr*)&clientAddr, &addrLen) == -1) {
        perror("getsockname(client)");
        goto ErrorExit;
    }

    printf("getsockname returned: %s, addressize: %d\n", clientAddr.sun_path, addrLen);
    serverAddr.sun_family = AF_UNIX;
    strncpy(serverAddr.sun_path, SERVER_SOCKET, sizeof(serverAddr.sun_path));
    printf("client: connecting to %s\n", SERVER_SOCKET);
    if (connect(clientFd, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) == -1) {
        perror("connect error");
        goto ErrorExit;
    }

    printf("client: connected to the server\n");
    addrLen = sizeof(clientAddr);
    if (getpeername(clientFd, (struct sockaddr*)&clientAddr, &addrLen) == -1) {
        perror("getpeername(client)");
        goto ErrorExit;
    }

    printf("getpeername returned: %s, addressize: %d\n", clientAddr.sun_path, addrLen);
    if ((BytesRecvd = recv(clientFd, Buf, sizeof(Buf), 0)) == -1) {
        perror("recv");
        goto ErrorExit;
    }

    printf("received: %zd bytes, %s\n", BytesRecvd, Buf);

ErrorExit:
    unlink(CLIENT_SOCKET);
    return 0;
}

bind.c

#include "header.h"

int main(int argc, char* argv[]) {
    struct sockaddr_un serverAddr = { 0 };
    int serverFd = 0;

    if ((serverFd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
        perror("socket error");
        goto ErrorExit;
    }

    // bind the server.
    serverAddr.sun_family = AF_UNIX;
    strncpy(serverAddr.sun_path, BIND_SOCKET, sizeof(serverAddr.sun_path) - 1);
    printf("binding to: '%s'\n", BIND_SOCKET);
    if (bind(serverFd, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) == -1) {
        perror("server bind error");
        goto ErrorExit;
    }

ErrorExit:
    unlink(BIND_SOCKET);
    return 0;
}

Cheers, from Sunil Muthuswamy and Craig (@CraigWilhite)

1 comment

Comments are closed. Login to edit/delete your existing comments