在 Windows 主机和 Linux 来宾之间使用 Hyper-V 套接字
Using the Hyper-V sockets between Windows host and Linux guest
我想编写简单的应用程序,使用 Hyper-V 套接字(netcat over vsock)在 Hyper-V 主机及其虚拟机之间进行通信。在 Internet 上有一些文档描述了如何做到这一点:Make your own integration services, Practical Hyper-V socket communication。然而,他们中的任何一个都帮助我实现了我的目标。
首先,我确定可以使用 Hyper-V 套接字进行连接。在来宾 Linux 上,我加载了 hv_sock
模块和 运行 nc-vsock 能够监听 vsocks 的应用程序:
$ sudo modprobe hv_sock
$ nc-vsock -l 1234
在 PowerShell 中的 Windows 我 运行 hvc,它利用 Hyper-V 套接字并且能够模拟 netcat:
hvc nc -t vsock little-helper 1234
并且有效。我可以看到从服务器发送到客户端的数据,反之亦然。
然后我在1 and 2的基础上写了一个简单的应用程序,稍作改动。
我按照 1 中所述在 Hyper-V 主机的注册表中注册了我的应用程序,并且我 运行 我的应用程序。连接未建立,connect
函数返回错误 10049。
我尝试以管理员身份 运行 我的应用程序并在源代码和 Hyper-V 主机的注册表中操作 GUID。但是,没有任何帮助,应用程序总是报告错误 10049。
我认为文档中有些歧义。例如。据说服务 ID 应该是 运行dom GUID。但后来有一个注意事项,前四个八位字节 t运行slate to port in AF_VSOCK address family,并为此提供了特定的 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;
}
你那里的 ServerVsockTemplate
GUID 是什么?我很确定这应该是 运行 VM 的 GUID,例如 (Get-VM -Name $VMName).Id
,因此很难硬编码到您的源代码中。如果这是您根据 'Register a new application' 生成的 GUID,那就是问题所在。
文档不清楚,但我强烈怀疑 'Register a new application' 部分仅适用于当您在 Windows 上侦听来自其他 Windows 虚拟机的传入连接时,或者在编写与主机上的服务对话的 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 错误。我不知道那是从哪里来的,但是 <hvsocket.h>
中有一个常量 HV_GUID_VSOCK_TEMPLATE
,它与 Microsoft Docs 站点上的那个相匹配:00000000-facb-11e6-bd58-64006a7986d3
- 事实证明,您需要将
SOCKADDR_HV
的 Reserved
成员归零,否则它将失败。传统上,人们会使用 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
,并确保 ServerVsockTemplate
是 运行 nc-vsock 所在的 VM 或 Micro-VM 的 GUID。
我实际上使用 WSL2 微型虚拟机对此进行了测试,其虚拟机 ID 来自 hcsdiag list
而不是 Get-VM
,但我能够连接到我内部的 nc-vsock
WSL2 会话使用此处的源,已按照我的描述进行了修改。
对于那些对 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")
您可以为 Windows 主机和 [=41 找到一些简单的代码示例 here、win_server.c =] 代表 Linux 客人。
我想编写简单的应用程序,使用 Hyper-V 套接字(netcat over vsock)在 Hyper-V 主机及其虚拟机之间进行通信。在 Internet 上有一些文档描述了如何做到这一点:Make your own integration services, Practical Hyper-V socket communication。然而,他们中的任何一个都帮助我实现了我的目标。
首先,我确定可以使用 Hyper-V 套接字进行连接。在来宾 Linux 上,我加载了 hv_sock
模块和 运行 nc-vsock 能够监听 vsocks 的应用程序:
$ sudo modprobe hv_sock
$ nc-vsock -l 1234
在 PowerShell 中的 Windows 我 运行 hvc,它利用 Hyper-V 套接字并且能够模拟 netcat:
hvc nc -t vsock little-helper 1234
并且有效。我可以看到从服务器发送到客户端的数据,反之亦然。
然后我在1 and 2的基础上写了一个简单的应用程序,稍作改动。
我按照 1 中所述在 Hyper-V 主机的注册表中注册了我的应用程序,并且我 运行 我的应用程序。连接未建立,connect
函数返回错误 10049。
我尝试以管理员身份 运行 我的应用程序并在源代码和 Hyper-V 主机的注册表中操作 GUID。但是,没有任何帮助,应用程序总是报告错误 10049。
我认为文档中有些歧义。例如。据说服务 ID 应该是 运行dom GUID。但后来有一个注意事项,前四个八位字节 t运行slate to port in AF_VSOCK address family,并为此提供了特定的 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;
}
你那里的 ServerVsockTemplate
GUID 是什么?我很确定这应该是 运行 VM 的 GUID,例如 (Get-VM -Name $VMName).Id
,因此很难硬编码到您的源代码中。如果这是您根据 'Register a new application' 生成的 GUID,那就是问题所在。
文档不清楚,但我强烈怀疑 'Register a new application' 部分仅适用于当您在 Windows 上侦听来自其他 Windows 虚拟机的传入连接时,或者在编写与主机上的服务对话的 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 错误。我不知道那是从哪里来的,但是<hvsocket.h>
中有一个常量HV_GUID_VSOCK_TEMPLATE
,它与 Microsoft Docs 站点上的那个相匹配:00000000-facb-11e6-bd58-64006a7986d3
- 事实证明,您需要将
SOCKADDR_HV
的Reserved
成员归零,否则它将失败。传统上,人们会使用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
,并确保 ServerVsockTemplate
是 运行 nc-vsock 所在的 VM 或 Micro-VM 的 GUID。
我实际上使用 WSL2 微型虚拟机对此进行了测试,其虚拟机 ID 来自 hcsdiag list
而不是 Get-VM
,但我能够连接到我内部的 nc-vsock
WSL2 会话使用此处的源,已按照我的描述进行了修改。
对于那些对 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 部分
$service = New-Item -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Virtualization\GuestCommunicationServices" -Name "00001389-facb-11e6-bd58-64006a7986d3"
$service.SetValue("ElementName", "HV Socket Demo")
您可以为 Windows 主机和 [=41 找到一些简单的代码示例 here、win_server.c =] 代表 Linux 客人。