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