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 }