github.com/containers/podman/v4@v4.9.4/pkg/machine/hyperv/vsock.go (about)

     1  //go:build windows
     2  // +build windows
     3  
     4  package hyperv
     5  
     6  import (
     7  	"errors"
     8  	"fmt"
     9  	"net"
    10  	"strings"
    11  
    12  	"github.com/Microsoft/go-winio"
    13  	"github.com/containers/podman/v4/pkg/machine"
    14  	"github.com/containers/podman/v4/utils"
    15  	"github.com/sirupsen/logrus"
    16  	"golang.org/x/sys/windows/registry"
    17  )
    18  
    19  var ErrVSockRegistryEntryExists = errors.New("registry entry already exists")
    20  
    21  const (
    22  	// HvsockMachineName is the string identifier for the machine name in a registry entry
    23  	HvsockMachineName = "MachineName"
    24  	// HvsockPurpose is the string identifier for the sock purpose in a registry entry
    25  	HvsockPurpose = "Purpose"
    26  	// VsockRegistryPath describes the registry path to where the hvsock registry entries live
    27  	VsockRegistryPath = `SOFTWARE\Microsoft\Windows NT\CurrentVersion\Virtualization\GuestCommunicationServices`
    28  	// LinuxVm is the default guid for a Linux VM on Windows
    29  	LinuxVm = "FACB-11E6-BD58-64006A7986D3"
    30  )
    31  
    32  // HVSockPurpose describes what the hvsock is needed for
    33  type HVSockPurpose int
    34  
    35  const (
    36  	// Network implies the sock is used for user-mode networking
    37  	Network HVSockPurpose = iota
    38  	// Events implies the sock is used for notification (like "Ready")
    39  	Events
    40  	// Fileserver implies that the sock is used for serving files from host to VM
    41  	Fileserver
    42  )
    43  
    44  func (hv HVSockPurpose) string() string {
    45  	switch hv {
    46  	case Network:
    47  		return "Network"
    48  	case Events:
    49  		return "Events"
    50  	case Fileserver:
    51  		return "Fileserver"
    52  	}
    53  	return ""
    54  }
    55  
    56  func (hv HVSockPurpose) Equal(purpose string) bool {
    57  	return hv.string() == purpose
    58  }
    59  
    60  func toHVSockPurpose(p string) (HVSockPurpose, error) {
    61  	switch p {
    62  	case "Network":
    63  		return Network, nil
    64  	case "Events":
    65  		return Events, nil
    66  	case "Fileserver":
    67  		return Fileserver, nil
    68  	}
    69  	return 0, fmt.Errorf("unknown hvsockpurpose: %s", p)
    70  }
    71  
    72  func openVSockRegistryEntry(entry string) (registry.Key, error) {
    73  	return registry.OpenKey(registry.LOCAL_MACHINE, entry, registry.QUERY_VALUE)
    74  }
    75  
    76  // HVSockRegistryEntry describes a registry entry used in Windows for HVSOCK implementations
    77  type HVSockRegistryEntry struct {
    78  	KeyName     string        `json:"key_name"`
    79  	Purpose     HVSockPurpose `json:"purpose"`
    80  	Port        uint64        `json:"port"`
    81  	MachineName string        `json:"machineName"`
    82  	Key         registry.Key  `json:"key,omitempty"`
    83  }
    84  
    85  // Add creates a new Windows registry entry with string values from the
    86  // HVSockRegistryEntry.
    87  func (hv *HVSockRegistryEntry) Add() error {
    88  	if err := hv.validate(); err != nil {
    89  		return err
    90  	}
    91  	exists, err := hv.exists()
    92  	if err != nil {
    93  		return err
    94  	}
    95  	if exists {
    96  		return fmt.Errorf("%q: %s", ErrVSockRegistryEntryExists, hv.KeyName)
    97  	}
    98  	parentKey, err := registry.OpenKey(registry.LOCAL_MACHINE, VsockRegistryPath, registry.QUERY_VALUE)
    99  	defer func() {
   100  		if err := parentKey.Close(); err != nil {
   101  			logrus.Error(err)
   102  		}
   103  	}()
   104  	if err != nil {
   105  		return err
   106  	}
   107  	newKey, _, err := registry.CreateKey(parentKey, hv.KeyName, registry.WRITE)
   108  	defer func() {
   109  		if err := newKey.Close(); err != nil {
   110  			logrus.Error(err)
   111  		}
   112  	}()
   113  	if err != nil {
   114  		return err
   115  	}
   116  
   117  	if err := newKey.SetStringValue(HvsockPurpose, hv.Purpose.string()); err != nil {
   118  		return err
   119  	}
   120  	return newKey.SetStringValue(HvsockMachineName, hv.MachineName)
   121  }
   122  
   123  // Remove deletes the registry key and its string values
   124  func (hv *HVSockRegistryEntry) Remove() error {
   125  	return registry.DeleteKey(registry.LOCAL_MACHINE, hv.fqPath())
   126  }
   127  
   128  func (hv *HVSockRegistryEntry) fqPath() string {
   129  	return fmt.Sprintf("%s\\%s", VsockRegistryPath, hv.KeyName)
   130  }
   131  
   132  func (hv *HVSockRegistryEntry) validate() error {
   133  	if hv.Port < 1 {
   134  		return errors.New("port must be larger than 1")
   135  	}
   136  	if len(hv.Purpose.string()) < 1 {
   137  		return errors.New("required field purpose is empty")
   138  	}
   139  	if len(hv.MachineName) < 1 {
   140  		return errors.New("required field machinename is empty")
   141  	}
   142  	if len(hv.KeyName) < 1 {
   143  		return errors.New("required field keypath is empty")
   144  	}
   145  	//decimal_num, err = strconv.ParseInt(hexadecimal_num, 16, 64)
   146  	return nil
   147  }
   148  
   149  func (hv *HVSockRegistryEntry) exists() (bool, error) {
   150  	foo := hv.fqPath()
   151  	_ = foo
   152  	_, err := openVSockRegistryEntry(hv.fqPath())
   153  	if err == nil {
   154  		return true, err
   155  	}
   156  	if errors.Is(err, registry.ErrNotExist) {
   157  		return false, nil
   158  	}
   159  	return false, err
   160  }
   161  
   162  // findOpenHVSockPort looks for an open random port. it verifies the port is not
   163  // already being used by another hvsock in the Windows registry.
   164  func findOpenHVSockPort() (uint64, error) {
   165  	// If we cannot find a free port in 10 attempts, something is wrong
   166  	for i := 0; i < 10; i++ {
   167  		port, err := utils.GetRandomPort()
   168  		if err != nil {
   169  			return 0, err
   170  		}
   171  		// Try and load registry entries by port to see if they exist
   172  		_, err = LoadHVSockRegistryEntry(uint64(port))
   173  		if err == nil {
   174  			// the port is no good, it is being used; try again
   175  			logrus.Errorf("port %d is already used for hvsock", port)
   176  			continue
   177  		}
   178  		if errors.Is(err, registry.ErrNotExist) {
   179  			// the port is good to go
   180  			return uint64(port), nil
   181  		}
   182  		if err != nil {
   183  			// something went wrong
   184  			return 0, err
   185  		}
   186  	}
   187  	return 0, errors.New("unable to find a free port for hvsock use")
   188  }
   189  
   190  // NewHVSockRegistryEntry is a constructor to make a new registry entry in Windows.  After making the new
   191  // object, you must call the add() method to *actually* add it to the Windows registry.
   192  func NewHVSockRegistryEntry(machineName string, purpose HVSockPurpose) (*HVSockRegistryEntry, error) {
   193  	// a so-called wildcard entry ... everything from FACB -> 6D3 is MS special sauce
   194  	// for a " linux vm".  this first segment is hexi for the hvsock port number
   195  	//00000400-FACB-11E6-BD58-64006A7986D3
   196  	port, err := findOpenHVSockPort()
   197  	if err != nil {
   198  		return nil, err
   199  	}
   200  	r := HVSockRegistryEntry{
   201  		KeyName:     portToKeyName(port),
   202  		Purpose:     purpose,
   203  		Port:        port,
   204  		MachineName: machineName,
   205  	}
   206  	if err := r.Add(); err != nil {
   207  		return nil, err
   208  	}
   209  	return &r, nil
   210  }
   211  
   212  func portToKeyName(port uint64) string {
   213  	// this could be flattened but given the complexity, I thought it might
   214  	// be more difficult to read
   215  	hexi := strings.ToUpper(fmt.Sprintf("%08x", port))
   216  	return fmt.Sprintf("%s-%s", hexi, LinuxVm)
   217  }
   218  
   219  func LoadHVSockRegistryEntry(port uint64) (*HVSockRegistryEntry, error) {
   220  	keyName := portToKeyName(port)
   221  	fqPath := fmt.Sprintf("%s\\%s", VsockRegistryPath, keyName)
   222  	k, err := openVSockRegistryEntry(fqPath)
   223  	if err != nil {
   224  		return nil, err
   225  	}
   226  	p, _, err := k.GetStringValue(HvsockPurpose)
   227  	if err != nil {
   228  		return nil, err
   229  	}
   230  
   231  	purpose, err := toHVSockPurpose(p)
   232  	if err != nil {
   233  		return nil, err
   234  	}
   235  
   236  	machineName, _, err := k.GetStringValue(HvsockMachineName)
   237  	if err != nil {
   238  		return nil, err
   239  	}
   240  	return &HVSockRegistryEntry{
   241  		KeyName:     keyName,
   242  		Purpose:     purpose,
   243  		Port:        port,
   244  		MachineName: machineName,
   245  		Key:         k,
   246  	}, nil
   247  }
   248  
   249  // Listener returns a net.Listener for the given HvSock.
   250  func (hv *HVSockRegistryEntry) Listener() (net.Listener, error) {
   251  	n := winio.HvsockAddr{
   252  		VMID:      winio.HvsockGUIDWildcard(), // When listening on the host side, use equiv of 0.0.0.0
   253  		ServiceID: winio.VsockServiceID(uint32(hv.Port)),
   254  	}
   255  	listener, err := winio.ListenHvsock(&n)
   256  	if err != nil {
   257  		return nil, err
   258  	}
   259  
   260  	return listener, nil
   261  }
   262  
   263  // Listen is used on the windows side to listen for anything to come
   264  // over the hvsock as a signal the vm is booted
   265  func (hv *HVSockRegistryEntry) Listen() error {
   266  	listener, err := hv.Listener()
   267  	if err != nil {
   268  		return err
   269  	}
   270  	defer func() {
   271  		if err := listener.Close(); err != nil {
   272  			logrus.Error(err)
   273  		}
   274  	}()
   275  
   276  	errChan := make(chan error)
   277  	go machine.ListenAndWaitOnSocket(errChan, listener)
   278  
   279  	return <-errChan
   280  }