Thursday, February 23, 2017

Hyper-V sockets (without internals)

This is small article, which shows how convert simple network socket application to application, which uses Hyper-V socket for communication.
    Using software
  • Windows Server 2016 as Host;
  • Windows Server 2016 as Guest;
  • Visual Studio 2015 and Windows 10 as development environment.

  1. Go to msdn and read about Hyper-V Sockets (1). Read that you need Windows SDK build 10.0.14393.795 (recently QFE for 10.0.14393.033)
  2. Run PowerShell script on Host Server

$friendlyName = "HV Socket Application"

# Create a new random GUID and add it to the services list then add the name as a value

$service = New-Item -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Virtualization\GuestCommunicationServices" -Name ((New-Guid).Guid)

$service.SetValue("ElementName", $friendlyName)

# Copy GUID to clipboard for later use
Write-Host "Service GUID: " $service.PSChildName

  1. Remember GUID
  2. Google (or wrote) application which works with Hyper-V Sockets. I get example from (2)
We open project in Visual Studio 2015 and convert it. We see two projects in solution: Client Example and Server Example. For every project we need change Target Platform Version to 10.0.14393.0
MSDN article gives GUID definitions
Name
GUID
Description
HV_GUID_ZERO, HV_GUID_WILDCARD
00000000-0000-0000-0000-000000000000
Listeners should bind to this VmId to accept connection from all partitions.
HV_GUID_BROADCAST
FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF

HV_GUID_CHILDREN
90db8b89-0d35-4f79-8ce9-49ea0ac8b7cd
Wildcard address for children. Listeners should bind to this VmId to accept connection from its children.
HV_GUID_LOOPBACK
e0e16197-dd56-4a10-9195-5ee7a155a838
Loopback address. Using this VmId connects to the same partition as the connector.
HV_GUID_PARENT
a42e7cda-d03f-480c-9cc2-a4de20abb878
Parent address. Using this VmId connects to the parent partition of the connector.*


But not define HV_PROTOCOL_RAW constant. Value HV_PROTOCOL_RAW is 1.
Now we change code in client:
1. Add definitions:
#define HV_PROTOCOL_RAW 1
struct SOCKADDR_HV
{
    ADDRESS_FAMILY Family;
    USHORT Reserved;
    GUID VmId;
    GUID ServiceId;
};
  1. Define GUIDs
#include <combaseapi.h>
………………………
wchar_t* clsid_str = L"{a42e7cda-d03f-480c-9cc2-a4de20abb878}"; // HV_PARENT_GUID
CLSIDFromString(clsid_str, &VmID);
    clsid_str = L"{b1d00d3e-fe10-4570-ad62-7648779d7a1b}";
CLSIDFromString(clsid_str, &ServiceID); //GUID of Service, generated by powershell
        //configure protocol
        ZeroMemory(&clientService, sizeof(clientService));
        clientService.Family = AF_HYPERV;
        clientService.VmId = *vmId;
        clientService.ServiceId = *serviceId;

  1. Change protocol parameters:

        hints.ai_family = AF_HYPERV;
        hints.ai_socktype = SOCK_STREAM;   
        hints.ai_protocol = HV_PROTOCOL_RAW;
  1. Comment getaddrinfo block.
  2. Replace socket connection string
//ConnectSocket = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol); old code
ConnectSocket = socket(hints.ai_family, hints.ai_socktype, hints.ai_protocol);
  1. Comment Stop() function and its calls

Server code:
  1. Add GUIDS
DEFINE_GUID(HV_GUID_PARENT,0xa42e7cda,0xd03f,0x480c, 0x9c, 0xc2, 0xa, 0x4, 0xde, 0x20, 0xab, 0xb8, 0x78);
DEFINE_GUID(HV_GUID_ZERO,0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0);
  1. Initialize GUIDS

    CLSID ServiceID;
    wchar_t* clsid_str = L"{b1d00d3e-fe10-4570-ad62-7648779d7a1b}";
    CLSIDFromString(clsid_str, &ServiceID); //GUID of Service, generated by powershell
  1. Change protocol parameters
ZeroMemory(&clientService, sizeof(clientService));
    clientService.Family = AF_HYPERV;
    clientService.VmId = HV_GUID_ZERO;
    clientService.ServiceId = *serviceId;

    ZeroMemory(&hints, sizeof(hints));
    hints.ai_family = AF_HYPERV;        // Internet address family is unspecified so that either an IPv6 or IPv4 address can be returned
hints.ai_socktype = SOCK_STREAM;    // Requests the socket type to be a stream socket for the TCP protocol
hints.ai_protocol = HV_PROTOCOL_RAW;

hints.ai_addrlen = sizeof(SOCKADDR_HV);
    hints.ai_addr = reinterpret_cast<SOCKADDR *>(&clientService);
  1. Replace code
//ListenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol); - orig.
ListenSocket = socket(hints.ai_family, hints.ai_socktype, hints.ai_protocol);

  1. Replace code
//iResult = bind(ListenSocket, result->ai_addr, (int)result->ai_addrlen); - orig.
iResult = bind(ListenSocket, hints.ai_addr, (int)hints.ai_addrlen);

Compile solution. You get ClientExample.exe and ServerExample.exe. Run ServerExample on host server and ClientExample in guest virtual machine. Enter “test” in ClientExample.exe and see it as output of ServerExample.

            After you register service you can run applications with simple user privileges on host server or virtual machine. It works like usual network application and doesn't need any special permissions for work.
            P.S. If you use one of existring GUID as ServiceID:
            7FDFD0EA-CEA8-4576-92D6-E072DDD2C422 - Machine Provisioning Service
            ACEF5661-84A1-4E44-856B-6245E69F4620 - Host Compute Service
            999E53D4-3D5C-4C3E-8779-BED06EC056E1 - VM Session Service 1
            A5201C21-2770-4C11-A68E-F182EDB29220 - VM Session Service 2
            you don't need administrative privileges for using Hyper-V sockets

Sources
  1. https://w4mhi.wordpress.com/complete-hyper-v-socket-client-code/ - very good article. But code compiles with errors.
  2. https://github.com/gerhart01/HyperV-sockets - source code from article.