Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
lima-vm
GitHub Repository: lima-vm/lima
Path: blob/master/pkg/driver/vz/network_darwin.go
2611 views
1
//go:build darwin && !no_vz
2
3
// SPDX-FileCopyrightText: Copyright The Lima Authors
4
// SPDX-License-Identifier: Apache-2.0
5
6
package vz
7
8
import (
9
"context"
10
"encoding/binary"
11
"errors"
12
"io"
13
"net"
14
"os"
15
"sync"
16
"syscall"
17
"time"
18
19
"github.com/balajiv113/fd"
20
"github.com/sirupsen/logrus"
21
)
22
23
func PassFDToUnix(unixSock string) (*os.File, error) {
24
unixAddr, err := net.ResolveUnixAddr("unix", unixSock)
25
if err != nil {
26
return nil, err
27
}
28
unixConn, err := net.DialUnix("unix", nil, unixAddr)
29
if err != nil {
30
return nil, err
31
}
32
33
server, client, err := createSockPair()
34
if err != nil {
35
return nil, err
36
}
37
err = fd.Put(unixConn, server)
38
if err != nil {
39
return nil, err
40
}
41
return client, nil
42
}
43
44
// DialQemu support connecting to QEMU supported network stack via unix socket.
45
// Returns os.File, connected dgram connection to be used for vz.
46
func DialQemu(ctx context.Context, unixSock string) (*os.File, error) {
47
var dialer net.Dialer
48
unixConn, err := dialer.DialContext(ctx, "unix", unixSock)
49
if err != nil {
50
return nil, err
51
}
52
qemuConn := &qemuPacketConn{Conn: unixConn}
53
54
server, client, err := createSockPair()
55
if err != nil {
56
return nil, err
57
}
58
dgramConn, err := net.FileConn(server)
59
if err != nil {
60
return nil, err
61
}
62
vzConn := &packetConn{Conn: dgramConn}
63
64
go forwardPackets(qemuConn, vzConn)
65
66
return client, nil
67
}
68
69
func forwardPackets(qemuConn *qemuPacketConn, vzConn *packetConn) {
70
defer qemuConn.Close()
71
defer vzConn.Close()
72
73
var wg sync.WaitGroup
74
wg.Add(2)
75
76
go func() {
77
defer wg.Done()
78
if _, err := io.Copy(qemuConn, vzConn); err != nil {
79
logrus.Errorf("Failed to forward packets from VZ to VMNET: %s", err)
80
}
81
}()
82
83
go func() {
84
defer wg.Done()
85
if _, err := io.Copy(vzConn, qemuConn); err != nil {
86
logrus.Errorf("Failed to forward packets from VMNET to VZ: %s", err)
87
}
88
}()
89
90
wg.Wait()
91
}
92
93
// qemuPacketConn converts raw network packet to a QEMU supported network packet.
94
type qemuPacketConn struct {
95
net.Conn
96
}
97
98
// Read reads a QEMU packet and returns the contained raw packet. Returns (len,
99
// nil) if a packet was read, and (0, err) on error. Errors means the protocol
100
// is broken and the socket must be closed.
101
func (c *qemuPacketConn) Read(b []byte) (n int, err error) {
102
var size uint32
103
if err := binary.Read(c.Conn, binary.BigEndian, &size); err != nil {
104
// Likely connection closed by peer.
105
return 0, err
106
}
107
return io.ReadFull(c.Conn, b[:size])
108
}
109
110
// Write writes a QEMU packet containing the raw packet. Returns (len(b), nil)
111
// if a packet was written, and (0, err) if a packet was not fully written.
112
// Errors means the protocol is broken and the socket must be closed.
113
func (c *qemuPacketConn) Write(b []byte) (int, error) {
114
size := len(b)
115
header := uint32(size)
116
if err := binary.Write(c.Conn, binary.BigEndian, header); err != nil {
117
return 0, err
118
}
119
120
for len(b) != 0 {
121
n, err := c.Conn.Write(b)
122
if err != nil {
123
return 0, err
124
}
125
b = b[n:]
126
}
127
return size, nil
128
}
129
130
// Testing show that retries are very rare (e.g 24 of 62,499,008 packets) and
131
// requires 1 or 2 retries to complete the write. A 100 microseconds sleep loop
132
// consumes about 4% CPU on M1 Pro.
133
const writeRetryDelay = 100 * time.Microsecond
134
135
// packetConn handles ENOBUFS errors when writing to unixgram socket.
136
type packetConn struct {
137
net.Conn
138
}
139
140
// Write writes a packet retrying on ENOBUFS errors.
141
func (c *packetConn) Write(b []byte) (int, error) {
142
var retries uint64
143
for {
144
n, err := c.Conn.Write(b)
145
if n == 0 && err != nil && errors.Is(err, syscall.ENOBUFS) {
146
// This is an expected condition on BSD based system. The kernel
147
// does not support blocking until buffer space is available.
148
// The only way to recover is to retry the call until it
149
// succeeds, or drop the packet.
150
// Handled in a similar way in gvisor-tap-vsock:
151
// https://github.com/containers/gvisor-tap-vsock/issues/367
152
time.Sleep(writeRetryDelay)
153
retries++
154
continue
155
}
156
if err != nil {
157
return 0, err
158
}
159
if n < len(b) {
160
return n, errors.New("incomplete write to unixgram socket")
161
}
162
if retries > 0 {
163
logrus.Debugf("Write completed after %d retries", retries)
164
}
165
return n, nil
166
}
167
}
168
169