在Windows主机和Linux来宾之间使用Hyper-V套接字

时间:2020-03-15 18:17:08

标签: linux windows sockets hyper-v

我想编写一个简单的应用程序,该应用程序使用Hyper-V套接字(vsock上的netcat)在Hyper-V主机与其虚拟机之间进行通信。互联网上有一些文档描述了如何执行此操作:Make your own integration servicesPractical Hyper-V socket communication。但是,它们中的任何一个都可以帮助我实现自己的目标。

首先,我确保可以使用Hyper-V套接字进行连接。在来宾Linux上,我加载了hv_sock模块并运行nc-vsock应用程序,该应用程序可以在vsocks上进行监听:

$ sudo modprobe hv_sock
$ nc-vsock -l 1234

在Windows中的PowerShell中,我运行了hvc,它利用Hyper-V套接字并能够模拟netcat:

hvc nc -t vsock little-helper 1234

,并且有效。我可以看到从服务器发送到客户端的数据,反之亦然。

然后,我基于12编写了一个简单的应用程序,并做了些微改动。 如1中所述,我已在Hyper-V Host的注册表中注册了我的应用程序,然后运行了我的应用程序。未建立连接,connect函数返回错误10049。

我试图以管理员身份运行应用程序,并在源代码和Hyper-V Host的注册表中操纵GUID。但是,没有任何帮助,应用程序总是报告错误10049。

我认为文档中有些含糊。例如。据说服务ID应该是随机的GUID。但是稍后需要注意的是,前四个八位字节转换为AF_VSOCK地址族中的端口,并且为此目的提供了特定的GUID。

问题很简单:我做错了或被误解了。是否可以在Windows和Linux之间利用vsock编写netcat?

完整代码:

#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 个答案:

答案 0 :(得分:2)

对于Hyper-V不是很熟悉又不想像我一样花2个小时调试的人,有几点:

确保在来宾上启用 hv_sock 内核模块,我使用的是 Ubuntu Server 20.04,默认情况下没有启用此功能。

lsmod | grep hv_sock

如果它不存在,您需要添加它并重新启动:

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

需要向 Hyper-V 主机的注册表注册一个新应用程序,但文档具有误导性,因为只有 Windows 客户机才需要随机 GUID,对于 Linux 客户机,GUID 需要是以非常具体的格式,如 HV_GUID_VSOCK_TEMPLATE 所述,意思是 <port>-facb-11e6-bd58-64006a7986d3

所以对于端口 5001,注册表项应该是 00001389-facb-11e6-bd58-64006a7986d3(1389 是 5001 十六进制) 您可以按照 Register a new application 部分

中的说明从 powershell 轻松完成此操作
$service = New-Item -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Virtualization\GuestCommunicationServices" -Name "00001389-facb-11e6-bd58-64006a7986d3"
$service.SetValue("ElementName", "HV Socket Demo")

您可以找到一些简单的代码示例 here,用于 Windows 主机的 win_server.c 和用于 Linux 客户机的 wsl_client.c

答案 1 :(得分:1)

您在那里的ServerVsockTemplate GUID是什么?我非常确定这应该是正在运行的VM的GUID,例如(Get-VM -Name $VMName).Id,因此很难将其硬编码到您的源代码中。如果那是您根据'Register a new application'生成的GUID,那就是问题。

文档尚不清楚,但是我强烈怀疑“注册新应用”部分仅适用于当您在Windows上侦听来自其他Windows VM的传入连接时,或者编写了以下内容的Linux设备驱动程序时:与主机上的服务对话。也许也可以允许一个VM向其他VM提供服务,但我认为不会。

编辑:Quick Testing VSOCK (Hyper-V) Support in X410说您还需要为vsock GUID设置注册表项,以接收来自虚拟机的连接。

在Linux用户空间中,您只能访问vsock。 other services由Linux下的驱动程序管理。

Hyper-V vsock implementation的Linux源代码中,对vsock工作流程有更清晰的解释。

当然,我认为也可以在Windows VM和主机之间使用VSock。


编辑,因为我实际上已经去测试了。

在代码上有两个错误,ServerVsockTemplate GUID上必须是目标VM的GUID。

  • VSockTemplate GUID错误。我不知道它是从哪里来的,但无论如何,HV_GUID_VSOCK_TEMPLATE中有一个常量<hvsocket.h>,它与Microsoft Docs网站上的常量00000000-facb-11e6-bd58-64006a7986d3
  • 相匹配
  • 事实证明,您需要将Reserved的{​​{1}}成员清零,否则它将失败。传统上,人们会使用SOCKADDR_HV将新的memset结构归零,但在这种情况下,我们可以采用简单的方法。

为使此工作有效,请将sockaddr_*创建代码更改为以下代码:

SOCKADDR_HV

然后删除 // 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; ,并确保VSockTemplate是运行nc-vsock的VM或Micro-VM的GUID。

我实际上是使用WSL2微型VM测试的,其VM ID来自ServerVsockTemplate而不是hcsdiag list,但是我能够在WSL2会话中连接到Get-VM使用这里的源代码,按照我的描述进行修改。