- Use Linux C Socket API in Windows
- How do we do it?
- 1. Installing WSL in Windows
- 2. Setting Up VSCode
- 3. Compiling and Running Socket Programming Code
- Windows/WSL Interop with AF_UNIX
- How To Code
- Requirements & Limitations:
- Binding Sample 1: No Windows Permissions
- Binding Sample 2: Linux Permissions, No Windows Permissions
- Binding Sample 3: No Linux Permissions, Windows Permissions
- Sample Code
- Windows Server code
- Linux code
Use Linux C Socket API in Windows
Whenever we want to do socket programming in c, we got two choices primarily; one is to use socket API in Linux and another one is Winsock which is for Windows, but sometimes we have some constraints due to which we want to use linux socket api in windows. We will see how to do the same.
How do we do it?
We will install wsl in windows to use linux packages in windows, further to reduce tedious tasks of starting wsl, creating folders, compiling files, etc. We will use vscode to make our task easy.
1. Installing WSL in Windows
- For detailed instructions, refer here.
- RightClick on the windows start menu icon and click on Windows PowerShell(Admin).
- In the opened window, write following command, which will install wsl with ubuntu distro. You can change it according to your preference:
sudo apt update && sudo apt upgrade
2. Setting Up VSCode
- You can use any development ide or text editor for socket programming but vscode provides a straightforward interface to manage wsl using extensions so we will use vscode.
- Open vscode for particular folder.
- Then open extension tab from left side and search for «ms-vscode-remote.remote-wsl» and install it.
- After installing it, on the bottom left side, you will see remote wsl button, click on it, which will open the following box from that select «Reopen folder in WSL» and hit enter.
3. Compiling and Running Socket Programming Code
- From the menu bar, open the Terminal tab and click on New Terminal.
- For the first time, we need to install gcc and gdb using the following command:
sudo apt install gcc && sudo apt install gdb
#include #include #include #include #include #include #define PORT 8080 int main(int argc, char *argv[]) < int server_fd, new_socket, valread; struct sockaddr_in address; int opt = 1; int addr_len = sizeof(address); char buffer[1024], response[1024]; char *msgToSendBack = response; if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) < perror("Socket Creation Failed"); exit(EXIT_FAILURE); > if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT,&opt, sizeof(opt))) < perror("Set Can't be attached"); exit(EXIT_FAILURE); > address.sin_family = AF_INET; address.sin_addr.s_addr = INADDR_ANY; address.sin_port = htons(PORT); if (bind(server_fd, (struct sockaddr *) &address, sizeof(address)) < 0) < perror("Can't Bind"); exit(EXIT_FAILURE); > if (listen(server_fd, 3) < 0) < perror("Can't Listen"); exit(EXIT_FAILURE); > if ((new_socket = accept(server_fd, (struct sockaddr*)&address, (socklen_t*)&addr_len)) < 0) < perror("Connection can't be accpeted"); exit(EXIT_FAILURE); > while(1) < valread = read(new_socket, buffer, 1024); buffer[valread] = '\0'; printf("Incoming Message: %s\n", buffer); if (strncasecmp("How are you?",buffer, strlen("How are you?")) == 0) < send(new_socket, "I am fine", strlen("I am fine"), 0); > else < strcpy(msgToSendBack, buffer); strcat(msgToSendBack, " Message Received"); send(new_socket, msgToSendBack, strlen(msgToSendBack), 0); > printf("Response Message Sent\n"); fflush(stdout); memset(buffer, 0, 1024); memset(response, 0, 1024); > return 0; >
#include #include #include #include #include #define PORT 8080 int main(int argc, char *argv[]) < int sock = 0, valread; struct sockaddr_in serv_addr; char *exit_msg = "exit", *msg; char buffer[1024] = 0>; if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) < printf("\n Socket Connection Error"); return -1; > serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(PORT); if (inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) 0) < printf("\n Invalid address"); return -1; > if (connect(sock, (struct sockaddr *)&serv_addr,sizeof(serv_addr)) < 0) < printf("\n Connection Failed\n"); return -1; > while (1) < printf("Enter Message for server:"); scanf("%[^\n]%*c", msg); if(!strcmp(msg, exit_msg)) < close(sock); return 0; > send(sock, msg, strlen(msg), 0); valread = read(sock, buffer, 1024); printf("From Server: %s\n", buffer); > return 0; >
- Now, using a terminal window that we opened earlier, write the following commands to compile the files
gcc server.c -o server gcc client.c -o client
Windows/WSL Interop with AF_UNIX
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:
- 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.
- 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.
- 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
#undef UNICODE #include #include #include #include #include #include #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
#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; >
#include #include #include #include #include #define SERVER_SOCKET "server.sock" #define CLIENT_SOCKET "client.sock" #define BIND_SOCKET "bind.sock"
#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; >
#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)