Using the Hyper-V sockets between Windows host and Linux guest

Viewed 3459

I want to write simple application that communicates between the Hyper-V host and its virtual machine using Hyper-V sockets (netcat over vsock). In the Internet there are a few documents describing how to do it: Make your own integration services, Practical Hyper-V socket communication. However, any of them helps me to achieve my goal.

First of all, I've made sure that the connection using Hyper-V sockets is possible. On the guest Linux I loaded hv_sock module and run nc-vsock application which is able to listen on vsocks:

$ sudo modprobe hv_sock
$ nc-vsock -l 1234

On Windows in PowerShell I ran hvc, which utilises Hyper-V sockets and is able to emulate netcat:

hvc nc -t vsock little-helper 1234

and it works. I can see data sent from server to client and vice versa.

Then I wrote a simple application basing on 1 and 2 with slight changes. I registered my application with the Hyper-V Host's registry as said in 1 and I ran my application. The connection was not established and the connect function returned error 10049.

I've tried to run my application as a administrator and manipulate GUIDs in the source code and on the Hyper-V Host's registry as well. However, nothing helps and application always reports error 10049.

In my opinion in the document are some ambiguity. E.g. it's said that the service id shall be a random GUID. But later on there is a note that the first four octets translate to port in AF_VSOCK address family, and the specific GUID is presented for this purpose.

Question is rather simple: what I did wrong or misunderstood. Is it possible to write netcat utilising vsock between Windows and Linux?

Full code:

#include <iostream>

#include <winsock2.h>
#include <ws2tcpip.h>
#include <hvsocket.h>

#include <combaseapi.h>

int main()
{
    struct __declspec(uuid("00000000-185c-4e04-985a-4c2eee3e03cc")) VSockTemplate {};
    struct __declspec(uuid("2a9fa68e-4add-45cb-85c8-de97fc66d388")) ServerVsockTemplate {};

    //----------------------
    // Initialize Winsock
    WSADATA wsaData;
    int iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
    if (iResult != NO_ERROR) {
        wprintf(L"WSAStartup function failed with error: %d\n", iResult);
        return 1;
    }
    //----------------------
    // Create a SOCKET for connecting to server
    SOCKET ConnectSocket;
    ConnectSocket = socket(AF_HYPERV, SOCK_STREAM, HV_PROTOCOL_RAW);
    if (ConnectSocket == INVALID_SOCKET) {
        wprintf(L"socket function failed with error: %ld\n", WSAGetLastError());
        WSACleanup();
        return 1;
    }
    //----------------------
    // The sockaddr_in structure specifies the address family,
    // IP address, and port of the server to be connected to.
    SOCKADDR_HV clientService;
    clientService.Family = AF_HYPERV;
    clientService.VmId = __uuidof(ServerVsockTemplate);
    clientService.ServiceId = __uuidof(VSockTemplate);
    clientService.ServiceId.Data1 = 1234;

    //----------------------
    // Connect to server.
    iResult = connect(ConnectSocket, (SOCKADDR*)&clientService, sizeof(clientService));
    if (iResult == SOCKET_ERROR) {
        wprintf(L"connect function failed with error: %ld\n", WSAGetLastError());
        iResult = closesocket(ConnectSocket);
        if (iResult == SOCKET_ERROR)
            wprintf(L"closesocket function failed with error: %ld\n", WSAGetLastError());
        WSACleanup();
        return 1;
    }

    wprintf(L"Connected to server.\n");

    iResult = closesocket(ConnectSocket);
    if (iResult == SOCKET_ERROR) {
        wprintf(L"closesocket function failed with error: %ld\n", WSAGetLastError());
        WSACleanup();
        return 1;
    }

    WSACleanup();
    return 0;
}
2 Answers

For whoever is not very familiar with Hyper-V and does not want to spend 2 hours debugging, like I did, a few points:

Ensure that the hv_sock kernel module is enabled on the guest, I used Ubuntu Server 20.04, which does not have this enabled by default.

lsmod | grep hv_sock

If it's not there you need to add it and reboot:

sudo sh -c 'echo "hv_sock" > /etc/modules-load.d/hv_sock.conf'
sudo reboot

You need to register a new application with Hyper-V Host's registry, but the docs are misleading, as the random GUID is only needed with a Windows guest, for Linux guests the GUID needs to be in a very specific format, as described by HV_GUID_VSOCK_TEMPLATE, meaning <port>-facb-11e6-bd58-64006a7986d3

So for port 5001 the registry key should be 00001389-facb-11e6-bd58-64006a7986d3 (1389 is 5001 in hex) You can do that easily from powershell as described in the Register a new application section

$service = New-Item -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Virtualization\GuestCommunicationServices" -Name "00001389-facb-11e6-bd58-64006a7986d3"
$service.SetValue("ElementName", "HV Socket Demo")

You can find some simple code samples here, win_server.c for the Windows host and wsl_client.c for the Linux guest.

What is the ServerVsockTemplate GUID you have there? I'm pretty sure that's supposed to be the GUID of a running VM, e.g., (Get-VM -Name $VMName).Id, so it'd be hard to hard-code into your source. If that's the GUID you generated per 'Register a new application', that'll be the problem.

The docs aren't clear, but I have the strong suspicion that 'Register a new application' part is only for when you're listening on Windows for incoming connections from other Windows VMs, or when writing a Linux device driver that talks to a service on the host. It might also be to allow a VM to offer services to other VMs, but I would assume not.

Edit: Quick Testing VSOCK (Hyper-V) Support in X410 says you need to make the registry keys for the vsock GUIDs as well, to receive connections from the vm.

In Linux userspace, you only have access to vsock; the other services are managed by drivers under Linux.

There's a clearer explanation of the vsock workflow in the Linux source for the Hyper-V vsock implementation.

I assume it's possible to use VSock between a Windows VM and the host as well, of course.


Edit, because I actually went and tested this.

Two mistakes in the code, on top of the ServerVsockTemplate GUID needing to be the GUID of the target VM.

  • The VSockTemplate GUID is wrong. I don't know where that came from, but there's a constant HV_GUID_VSOCK_TEMPLATE in <hvsocket.h> anyway, which matches the one on the Microsoft Docs site: 00000000-facb-11e6-bd58-64006a7986d3
  • It turns out, you need to zero the Reserved member of the SOCKADDR_HV, or it'll fail. Traditionally, one would use memset to zero a new sockaddr_* structure, but in this case, we can take the easy path.

So to make this work, change the SOCKADDR_HV creation code to the following:

    // The sockaddr_in structure specifies the address family,
    // IP address, and port of the server to be connected to.
    SOCKADDR_HV clientService;
    clientService.Family = AF_HYPERV;
    clientService.Reserved = 0;
    clientService.VmId = __uuidof(ServerVsockTemplate);
    clientService.ServiceId = HV_GUID_VSOCK_TEMPLATE;
    clientService.ServiceId.Data1 = 1234;

Then you delete VSockTemplate, and make sure ServerVsockTemplate is the GUID of the VM or Micro-VM where you're running nc-vsock.

I actually tested this with the WSL2 micro-VM, for which the VM ID comes from hcsdiag list rather than Get-VM, but I was able to connect to nc-vsock inside my WSL2 session using the source here, modified as I have described.

Related