-
Notifications
You must be signed in to change notification settings - Fork 65
/
integration_linux_test.go
550 lines (454 loc) · 13.3 KB
/
integration_linux_test.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
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
//go:build linux
// +build linux
package vsock_test
import (
"bytes"
"errors"
"fmt"
"io"
"math"
"net"
"os"
"regexp"
"strconv"
"sync"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/mdlayher/vsock"
"github.com/mdlayher/vsock/internal/vsutil"
"golang.org/x/net/nettest"
"golang.org/x/sync/errgroup"
"golang.org/x/sys/unix"
)
func TestIntegrationContextIDGuest(t *testing.T) {
if vsutil.IsHypervisor(t) {
t.Skip("skipping, machine is not a guest")
}
cid, err := vsock.ContextID()
if err != nil {
t.Fatalf("failed to retrieve guest's context ID: %v", err)
}
t.Logf("guest context ID: %d", cid)
// Guests should always have a context ID of 3 or more, since
// 0-2 are invalid or reserved.
if cid < 3 {
t.Fatalf("unexpected guest context ID: %d", cid)
}
}
func TestIntegrationContextIDHost(t *testing.T) {
if !vsutil.IsHypervisor(t) {
t.Skip("skipping, machine is not a hypervisor")
}
cid, err := vsock.ContextID()
if err != nil {
t.Fatalf("failed to retrieve host's context ID: %v", err)
}
t.Logf("host context ID: %d", cid)
if want, got := uint32(vsock.Host), cid; want != got {
t.Fatalf("unexpected host context ID:\n- want: %d\n- got: %d",
want, got)
}
}
func TestIntegrationListenerUnblockAcceptTimeout(t *testing.T) {
l, done := newListener(t)
defer done()
if err := l.SetDeadline(time.Now().Add(100 * time.Millisecond)); err != nil {
t.Fatalf("failed to set listener deadline: %v", err)
}
_, err := l.Accept()
if err == nil {
t.Fatal("expected an error, but none occurred")
}
if nerr, ok := err.(net.Error); !ok || (ok && !nerr.Timeout()) {
t.Errorf("expected timeout network error, but got: %#v", err)
}
}
func TestIntegrationConnShutdown(t *testing.T) {
vsutil.SkipHostIntegration(t)
// This functionality is proposed for inclusion in x/net/nettest, and should
// be removed from here if the proposal is accepted. See:
// https://github.com/golang/go/issues/31033.
timer := time.AfterFunc(10*time.Second, func() {
panic("test took too long")
})
defer timer.Stop()
mp := makeVSockPipe()
c1, c2, stop, err := mp()
if err != nil {
t.Fatalf("failed to make pipe: %v", err)
}
defer stop()
vc1, vc2 := c1.(*vsock.Conn), c2.(*vsock.Conn)
var wg sync.WaitGroup
wg.Add(1)
defer wg.Wait()
// Perform CloseRead/CloseWrite in lock-step.
var (
readClosed = make(chan struct{})
writeClosed = make(chan struct{})
)
go func() {
defer wg.Done()
b := make([]byte, 8)
if _, err := io.ReadFull(vc2, b); err != nil {
panicf("failed to vc2.Read: %v", err)
}
if err := vc2.CloseRead(); err != nil {
panicf("failed to vc2.CloseRead: %v", err)
}
close(readClosed)
// Any further read should return io.EOF.
<-writeClosed
if _, err := io.ReadFull(vc2, b); err != io.EOF {
panicf("expected vc2.Read EOF, but got: %v", err)
}
if err := vc2.CloseWrite(); err != nil {
panicf("failed to vc2.CloseWrite: %v", err)
}
}()
b := make([]byte, 8)
if _, err := vc1.Write(b); err != nil {
t.Fatalf("failed to vc1.Write: %v", err)
}
// Any write to a read-closed connection should return EPIPE.
//
// TODO(mdlayher): this test was flappy until err != nil check was added;
// sometimes it returns EPIPE and sometimes it does not. Check this.
<-readClosed
if _, err := vc1.Write(b); err != nil && !isBrokenPipe(err) {
t.Fatalf("expected vc1.Write broken pipe, but got: %v", err)
}
if err := vc1.CloseWrite(); err != nil {
t.Fatalf("failed to vc1.CloseWrite: %v", err)
}
close(writeClosed)
// Any further read should return io.EOF after the other end write-closes.
if _, err := io.ReadFull(vc1, b); err != io.EOF {
panicf("expected vc1.Read EOF, but got: %v", err)
}
}
func TestIntegrationConnSyscallConn(t *testing.T) {
vsutil.SkipHostIntegration(t)
mp := makeVSockPipe()
c, _, stop, err := mp()
if err != nil {
t.Fatalf("failed to make pipe: %v", err)
}
defer stop()
rc, err := c.(*vsock.Conn).SyscallConn()
if err != nil {
t.Fatalf("failed to syscallconn: %v", err)
}
// Greatly reduce the size of the socket buffer.
const (
size uint64 = 64
name = unix.SO_VM_SOCKETS_BUFFER_MAX_SIZE
)
err = rc.Control(func(fd uintptr) {
err := unix.SetsockoptUint64(int(fd), unix.AF_VSOCK, name, size)
if err != nil {
t.Fatalf("failed to setsockopt: %v", err)
}
out, err := unix.GetsockoptUint64(int(fd), unix.AF_VSOCK, name)
if err != nil {
t.Fatalf("failed to getsockopt: %v", err)
}
if diff := cmp.Diff(size, out); diff != "" {
t.Fatalf("unexpected socket buffer size (-want +got):\n%s", diff)
}
})
if err != nil {
t.Fatalf("failed to control: %v", err)
}
}
func TestIntegrationConnDialNoListener(t *testing.T) {
// Dial out to vsock listeners which do not exist, and expect an immediate
// error rather than hanging. This mostly relies on changes to the
// underlying socket library, but we test it anyway to lock things in.
//
// See: https://github.com/mdlayher/vsock/issues/47.
const max = math.MaxUint32
for _, port := range []uint32{max - 2, max - 1, max} {
_, err := vsock.Dial(vsock.Local, port, nil)
if err == nil {
t.Fatal("dial succeeded, but should not have")
}
got, ok := err.(*net.OpError)
if !ok {
t.Fatalf("expected *net.OpError, but got %T", err)
}
// Expect one of ECONNRESET, ENODEV, ENETUNREACH, ETIMEDOUT depending on the kernel.
switch {
case errors.Is(got.Err, unix.ECONNRESET), errors.Is(got.Err, unix.ENODEV),
errors.Is(got.Err, unix.ENETUNREACH), errors.Is(got.Err, unix.ETIMEDOUT):
// OK.
default:
t.Fatalf("unexpected syscall error: %v", got.Err)
}
// Zero out the error comparison.
got.Err = nil
want := &net.OpError{
Op: "dial",
Net: "vsock",
Addr: &vsock.Addr{ContextID: vsock.Local, Port: port},
}
if diff := cmp.Diff(want, err); diff != "" {
t.Errorf("unexpected error (-want +got):\n%s", diff)
}
}
}
func TestIntegrationFileListenerOK(t *testing.T) {
// Use raw system calls to set up the socket for FileListener. Although the
// socket library does the heavy lifting, we want to verify that this also
// works specifically for AF_VSOCK.
fd, err := unix.Socket(unix.AF_VSOCK, unix.SOCK_STREAM, 0)
if err != nil {
t.Fatalf("failed to open socket: %v", err)
}
// Bind to local, any available port.
err = unix.Bind(fd, &unix.SockaddrVM{
CID: unix.VMADDR_CID_LOCAL,
Port: unix.VMADDR_PORT_ANY,
})
if err != nil {
// Same problem with GitHub actions kernel, investigate.
switch err {
case unix.EADDRNOTAVAIL:
skipOldKernel(t)
default:
t.Fatalf("failed to bind: %v", err)
}
}
if err := unix.Listen(fd, unix.SOMAXCONN); err != nil {
t.Fatalf("failed to listen: %v", err)
}
// The socket should be ready, create a blocking file which is ready to be
// passed into FileListener.
f := os.NewFile(uintptr(fd), "vsock-listener")
defer f.Close()
l, err := vsock.FileListener(f)
if err != nil {
t.Fatalf("failed to open file listener: %v", err)
}
defer l.Close()
// To exercise the listener, attempt to accept and then immediately close a
// single vsock connection. Dial to the listener from the main goroutine and
// wait for everything to finish.
var eg errgroup.Group
eg.Go(func() error {
c, err := l.Accept()
if err != nil {
return fmt.Errorf("failed to accept: %v", err)
}
_ = c.Close()
return nil
})
addr := l.Addr().(*vsock.Addr)
c, err := vsock.Dial(addr.ContextID, addr.Port, nil)
if err != nil {
t.Fatalf("failed to dial listener: %v", err)
}
_ = c.Close()
if err := eg.Wait(); err != nil {
t.Fatalf("failed to wait for listener goroutine: %v", err)
}
}
func TestIntegrationFileListenerInvalid(t *testing.T) {
// Same idea as the previous test, but intentionally create a TCP socket
// instead of a vsock so we can verify the library rejects the socket.
fd, err := unix.Socket(unix.AF_INET6, unix.SOCK_STREAM, 0)
if err != nil {
t.Fatalf("failed to open socket: %v", err)
}
// Bind to any address.
if err := unix.Bind(fd, &unix.SockaddrInet6{}); err != nil {
t.Fatalf("failed to bind: %v", err)
}
if err := unix.Listen(fd, unix.SOMAXCONN); err != nil {
t.Fatalf("failed to listen: %v", err)
}
// The library should reject this file for having the wrong address family.
f := os.NewFile(uintptr(fd), "tcpv6-listener")
defer f.Close()
_, got := vsock.FileListener(f)
want := &net.OpError{
Op: "listen",
Net: "vsock",
Err: os.NewSyscallError("listen", unix.EINVAL),
}
if diff := cmp.Diff(want, got); diff != "" {
t.Fatalf("unexpected error (-want +got):\n%s", diff)
}
}
func isBrokenPipe(err error) bool {
if err == nil {
return false
}
nerr, ok := err.(*net.OpError)
if !ok {
return false
}
return nerr.Err == unix.EPIPE
}
func TestIntegrationNettestTestConn(t *testing.T) {
vsutil.SkipHostIntegration(t)
nettest.TestConn(t, makeVSockPipe())
}
var cidRe = regexp.MustCompile(`\S+\((\d+)\)`)
func TestIntegrationNettestTestListener(t *testing.T) {
vsutil.SkipHostIntegration(t)
// This test uses the nettest.TestListener API which is being built in:
// https://go-review.googlesource.com/c/net/+/123056.
//
// TODO(mdlayher): stop skipping this test once that CL lands.
mos := func() (ln net.Listener, dial func(string, string) (net.Conn, error), stop func(), err error) {
l, err := vsock.ListenContextID(vsock.Local, 0, nil)
if err != nil {
return nil, nil, nil, err
}
stop = func() {
// TODO(mdlayher): cancel context if we use vsock.DialContext.
_ = l.Close()
}
dial = func(_, addr string) (net.Conn, error) {
host, sport, err := net.SplitHostPort(addr)
if err != nil {
return nil, err
}
// Extract the CID value from the surrounding text.
scid := cidRe.FindStringSubmatch(host)
cid, err := strconv.Atoi(scid[1])
if err != nil {
return nil, err
}
port, err := strconv.Atoi(sport)
if err != nil {
return nil, err
}
return vsock.Dial(uint32(cid), uint32(port), nil)
}
return l, dial, stop, nil
}
_ = mos
t.Skip("skipping, enable once https://go-review.googlesource.com/c/net/+/123056 is merged")
// nettest.TestListener(t, mos)
}
func newListener(t *testing.T) (*vsock.Listener, func()) {
t.Helper()
timer := time.AfterFunc(10*time.Second, func() {
panic("test took too long")
})
// Bind to Local for all integration tests to avoid the need to run a
// hypervisor and VM setup.
l, err := vsock.ListenContextID(vsock.Local, 0, nil)
if err != nil {
vsutil.SkipDeviceError(t, err)
// Unwrap net.OpError + os.SyscallError if needed.
// TODO(mdlayher): errors.Unwrap in Go 1.13.
nerr, ok := err.(*net.OpError)
if !ok {
t.Fatalf("failed to create vsock listener: %v", err)
}
serr, ok := nerr.Err.(*os.SyscallError)
if !ok {
t.Fatalf("unexpected inner error for *net.OpError: %#v", nerr.Err)
}
switch serr.Err {
case unix.EADDRNOTAVAIL:
skipOldKernel(t)
default:
t.Fatalf("unexpected vsock listener system call error: %v", err)
}
}
return l, func() {
// Clean up the timer and this listener.
timer.Stop()
_ = l.Close()
}
}
func skipOldKernel(t *testing.T) {
t.Helper()
// The kernel in use is to old to support Local binds, so this
// test must be skipped. Print an informative message.
var utsname unix.Utsname
if err := unix.Uname(&utsname); err != nil {
t.Fatalf("failed to get uname: %v", err)
}
t.Skipf("skipping, kernel %s is too old to support AF_VSOCK local binds",
string(bytes.TrimRight(utsname.Release[:], "\x00")))
}
func makeVSockPipe() nettest.MakePipe {
return makeLocalPipe(
func() (net.Listener, error) { return vsock.ListenContextID(vsock.Local, 0, nil) },
func(addr net.Addr) (net.Conn, error) {
// ContextID will always be Local.
a := addr.(*vsock.Addr)
return vsock.Dial(a.ContextID, a.Port, nil)
},
)
}
// makeLocalPipe produces a nettest.MakePipe function using the input functions
// to configure a net.Listener and point a net.Conn at the listener.
//
// This function is proposed for inclusion in x/net/nettest, and should be
// removed from here if the proposal is accepted. See:
// https://github.com/golang/go/issues/30984.
func makeLocalPipe(
listen func() (net.Listener, error),
dial func(addr net.Addr) (net.Conn, error),
) nettest.MakePipe {
if listen == nil {
panic("nil listen function passed to makeLocalPipe")
}
if dial == nil {
dial = func(addr net.Addr) (net.Conn, error) {
return net.Dial(addr.Network(), addr.String())
}
}
// The majority of this code is taken from golang.org/x/net/nettest:
// https://go.googlesource.com/net/+/9e4ed9723b84cb6661bb04e4104f7bfb3ff5d016/nettest/conntest_test.go.
//
// Copyright 2016 The Go Authors. All rights reserved.
return func() (c1, c2 net.Conn, stop func(), err error) {
ln, err := listen()
if err != nil {
return nil, nil, nil, fmt.Errorf("failed to create local listener: %v", err)
}
// Start a connection between two endpoints.
var err1, err2 error
done := make(chan bool)
go func() {
c2, err2 = ln.Accept()
close(done)
}()
c1, err1 = dial(ln.Addr())
<-done
stop = func() {
if err1 == nil {
c1.Close()
}
if err2 == nil {
c2.Close()
}
ln.Close()
switch ln.Addr().Network() {
case "unix", "unixpacket":
os.Remove(ln.Addr().String())
}
}
switch {
case err1 != nil:
stop()
return nil, nil, nil, err1
case err2 != nil:
stop()
return nil, nil, nil, err2
default:
return c1, c2, stop, nil
}
}
}
func panicf(format string, a ...interface{}) {
panic(fmt.Sprintf(format, a...))
}