1// SPDX-License-Identifier: MIT
2
3package guest
4
5import (
6 "context"
7 "fmt"
8 "io"
9 "net"
10 "strconv"
11 "strings"
12 "time"
13
14 "golang.org/x/sys/unix"
15)
16
17// Target is where the executor connects back to: "unix:/path" (container) or
18// "vsock:PORT" (vm; the host is CID 2).
19type Target struct {
20 Network string // "unix" or "vsock"
21 Addr string // socket path, or the vsock port
22}
23
24// ParseTarget parses "unix:/path" or "vsock:port".
25func ParseTarget(s string) (Target, error) {
26 kind, rest, ok := strings.Cut(s, ":")
27 if !ok || rest == "" {
28 return Target{}, fmt.Errorf("connect target %q: want unix:PATH or vsock:PORT", s)
29 }
30 switch kind {
31 case "unix":
32 return Target{Network: "unix", Addr: rest}, nil
33 case "vsock":
34 if p, err := strconv.ParseUint(rest, 10, 32); err != nil || p == 0 {
35 return Target{}, fmt.Errorf("connect target %q: bad vsock port", s)
36 }
37 return Target{Network: "vsock", Addr: rest}, nil
38 }
39 return Target{}, fmt.Errorf("connect target %q: unknown kind %q", s, kind)
40}
41
42func (t Target) String() string { return t.Network + ":" + t.Addr }
43
44// Dial connects to the host, retrying until ctx ends or timeout elapses: the
45// host listens before the sandbox starts, but a vsock device may take a moment.
46func (t Target) Dial(ctx context.Context, timeout time.Duration) (io.ReadWriteCloser, error) {
47 ctx, cancel := context.WithTimeout(ctx, timeout)
48 defer cancel()
49 var last error
50 for {
51 var c io.ReadWriteCloser
52 var err error
53 if t.Network == "unix" {
54 var d net.Dialer
55 c, err = d.DialContext(ctx, "unix", t.Addr)
56 } else {
57 c, err = dialVsock(t.Addr)
58 }
59 if err == nil {
60 return c, nil
61 }
62 last = err
63 select {
64 case <-ctx.Done():
65 return nil, fmt.Errorf("connect to %s: %w (last error: %v)", t, ctx.Err(), last)
66 case <-time.After(100 * time.Millisecond):
67 }
68 }
69}
70
71// vsockHostCID is the well-known CID of the host.
72const vsockHostCID = 2
73
74func dialVsock(port string) (io.ReadWriteCloser, error) {
75 p, _ := strconv.ParseUint(port, 10, 32)
76 fd, err := unix.Socket(unix.AF_VSOCK, unix.SOCK_STREAM|unix.SOCK_CLOEXEC, 0)
77 if err != nil {
78 return nil, err
79 }
80 // A blocking connect is fine: a Firecracker vsock either connects to the
81 // host listener at once or fails.
82 if err := unix.Connect(fd, &unix.SockaddrVM{CID: vsockHostCID, Port: uint32(p)}); err != nil {
83 _ = unix.Close(fd)
84 return nil, err
85 }
86 return newFDConn(fd)
87}