Skip to content

Commit bbacef9

Browse files
committed
vmnet: Add Interface related APIs and *FileAdaptorForInterfaces
`vmnet`: Fix golangci-lint-v2 violations `vmnet`: if iface.EnableVirtioHeader { packetSize += virtioNetHdrSize } `vmnet`: Remove `object` `vmnet`: Add doc comments to `*FileAdaptorForInterface`s `vmnet`: Refactor `*FileAdaptorForInterface`s - Remove written packet count from result of `WritePacketsTo*` in `PacketForwarder` - Minimize differences between `pktDescsManager` and `msgHdrXArray` `vmnet`: Handle `syscall.ENOBUFS` in `DatagramPacketForwarder.WritePacketsToConn` `syscall.Sendmsg` may return `syscall.ENOBUFS` if there is not enough buffer set to the destination. Signed-off-by: Norio Nomura <norio.nomura@gmail.com>
1 parent b09771a commit bbacef9

18 files changed

Lines changed: 1598 additions & 55 deletions

cgoutil.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package vz
22

33
/*
4-
#cgo darwin CFLAGS: -mmacosx-version-min=11 -x objective-c
4+
#cgo darwin CFLAGS: -mmacosx-version-min=11 -x objective-c -fno-objc-arc
55
#cgo darwin LDFLAGS: -lobjc -framework Foundation
66
#import <Foundation/Foundation.h>
77

example/macOS/go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,5 @@ require github.com/Code-Hex/vz/v3 v3.0.0-00010101000000-000000000000
99
require (
1010
github.com/Code-Hex/go-infinity-channel v1.0.0 // indirect
1111
golang.org/x/mod v0.22.0 // indirect
12+
golang.org/x/sys v0.39.0 // indirect
1213
)

internal/cgohandler/cgohandler.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
package cgohandler
22

3-
/*
4-
# include <stdint.h>
5-
*/
6-
import "C"
73
import (
84
"runtime"
95
"runtime/cgo"

internal/objc/objc.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,9 @@ func Retain(o NSObject) {
119119

120120
// Ptr returns unsafe.Pointer of the NSObject
121121
func Ptr(o NSObject) unsafe.Pointer {
122+
if o == nil {
123+
return nil
124+
}
122125
return o.ptr()
123126
}
124127

internal/osversion/virtualization_helper.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,13 @@ NSDictionary *dumpProcessinfo();
4646
#pragma message("macOS 15 API has been disabled")
4747
#endif
4848

49+
// for macOS 15.4 API
50+
#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 150400
51+
#define INCLUDE_TARGET_OSX_15_4 1
52+
#else
53+
#pragma message("macOS 15.4 API has been disabled")
54+
#endif
55+
4956
// for macOS 26 API
5057
#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 260000
5158
#define INCLUDE_TARGET_OSX_26 1

network.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ import (
1313
"net"
1414
"os"
1515
"syscall"
16-
"unsafe"
1716

1817
"github.com/Code-Hex/vz/v3/internal/objc"
18+
"github.com/Code-Hex/vz/v3/vmnet"
1919
)
2020

2121
// BridgedNetwork defines a network interface that bridges a physical interface with a virtual machine.
@@ -280,8 +280,9 @@ func (*VmnetNetworkDeviceAttachment) String() string {
280280
return "VmnetNetworkDeviceAttachment"
281281
}
282282

283-
func (v *VmnetNetworkDeviceAttachment) Network() unsafe.Pointer {
284-
return C.VZVmnetNetworkDeviceAttachment_network(objc.Ptr(v))
283+
func (v *VmnetNetworkDeviceAttachment) Network() *vmnet.Network {
284+
ptr := C.VZVmnetNetworkDeviceAttachment_network(objc.Ptr(v))
285+
return vmnet.NewNetworkFromPointer(objc.NewPointer(ptr))
285286
}
286287

287288
var _ NetworkDeviceAttachment = (*VmnetNetworkDeviceAttachment)(nil)
@@ -290,14 +291,14 @@ var _ NetworkDeviceAttachment = (*VmnetNetworkDeviceAttachment)(nil)
290291
//
291292
// This is only supported on macOS 26 and newer, error will
292293
// be returned on older versions.
293-
func NewVmnetNetworkDeviceAttachment(network unsafe.Pointer) (*VmnetNetworkDeviceAttachment, error) {
294+
func NewVmnetNetworkDeviceAttachment(network *vmnet.Network) (*VmnetNetworkDeviceAttachment, error) {
294295
if err := macOSAvailable(26); err != nil {
295296
return nil, err
296297
}
297298

298299
attachment := &VmnetNetworkDeviceAttachment{
299300
pointer: objc.NewPointer(
300-
C.newVZVmnetNetworkDeviceAttachment(network),
301+
C.newVZVmnetNetworkDeviceAttachment(objc.Ptr(network)),
301302
),
302303
}
303304
objc.SetFinalizer(attachment, func(self *VmnetNetworkDeviceAttachment) {

vmnet/datagram_darwin.go

Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
package vmnet
2+
3+
/*
4+
#cgo darwin CFLAGS: -mmacosx-version-min=11 -x objective-c -fno-objc-arc
5+
#cgo darwin LDFLAGS: -lobjc -framework Foundation -framework vmnet
6+
# include "vmnet_darwin.h"
7+
*/
8+
import "C"
9+
import (
10+
"errors"
11+
"fmt"
12+
"net"
13+
"os"
14+
"syscall"
15+
)
16+
17+
// MARK: - DatagramFileAdaptorForInterface
18+
19+
// DatagramFileAdaptorForInterface returns a file for the given [Network].
20+
// - Invoke the returned function in a separate goroutine to start packet forwarding between the vmnet interface and the file.
21+
// - The context can be used to stop the goroutines and the interface.
22+
// - The returned error channel can be used to receive errors from the goroutines.
23+
// - The connection closure is reported as [io.EOF] error or [syscall.ECONNRESET] error in the error channel.
24+
//
25+
// The returned file can be used as a datagram file descriptor for QEMU, krunkit, or VZ.
26+
//
27+
// QEMU:
28+
//
29+
// -netdev datagram,id=net0,addr.type=fd,addr.str=<file descriptor>
30+
// -netdev tap,id=net1,fd=<file descriptor>
31+
//
32+
// krunkit:
33+
//
34+
// --device virtio-net,type=unixgram,fd=<file descriptor>,offloading=on // offloading=on is recommended. See krunkit driver in LIMA.
35+
//
36+
// VZ:
37+
//
38+
// file, errCh, err := DatagramFileAdaptorForInterface(ctx, iface)
39+
// attachment := NewFileHandleNetworkDeviceAttachment(file)
40+
var DatagramFileAdaptorForInterface = FileAdaptorForInterface[*DatagramPacketForwarder, net.PacketConn]
41+
42+
// MARK: - DatagramPacketForwarder for datagram file adaptor
43+
44+
// DatagramPacketForwarder implements PacketForwarder for datagram file descriptor.
45+
type DatagramPacketForwarder struct {
46+
readPktDescsManager *pktDescsManager
47+
writePktDescsManager *pktDescsManager
48+
}
49+
50+
var _ PacketForwarder[net.PacketConn] = (*DatagramPacketForwarder)(nil)
51+
52+
// New creates a new DatagramPacketForwarder.
53+
func (f *DatagramPacketForwarder) New() PacketForwarder[net.PacketConn] {
54+
return &DatagramPacketForwarder{}
55+
}
56+
57+
// Sockopts returns socket options for the given Interface and user desired options.
58+
// Default values are based on the following references:
59+
// - https://developer.apple.com/documentation/virtualization/vzfilehandlenetworkdeviceattachment/maximumtransmissionunit?language=objc
60+
func (*DatagramPacketForwarder) Sockopts(iface *Interface, userOpts Sockopts) Sockopts {
61+
return sockoptsForPacketConn(iface, userOpts)
62+
}
63+
64+
// ConnAndFile creates a [net.PacketConn] and *[os.File] pair using [syscall.Socketpair].
65+
func (f *DatagramPacketForwarder) ConnAndFile(opts Sockopts) (net.PacketConn, *os.File, error) {
66+
return packetConnAndFile(opts)
67+
}
68+
69+
// AllocateBuffers allocates packet descriptor buffers for reading and writing packets.
70+
func (f *DatagramPacketForwarder) AllocateBuffers(iface *Interface) error {
71+
maxPacketSize := iface.MaxPacketSize
72+
if iface.EnableVirtioHeader {
73+
// Add virtio header size
74+
maxPacketSize += virtioNetHdrSize
75+
}
76+
f.readPktDescsManager = newPktDescsManager(iface.MaxReadPacketCount, maxPacketSize)
77+
f.writePktDescsManager = newPktDescsManager(iface.MaxWritePacketCount, maxPacketSize)
78+
return nil
79+
}
80+
81+
// ReadPacketsFromInterface reads packets from the vmnet Interface.
82+
func (f *DatagramPacketForwarder) ReadPacketsFromInterface(iface *Interface, estimatedCount int) (int, error) {
83+
f.readPktDescsManager.reset()
84+
return iface.ReadPackets(f.readPktDescsManager.pktDescs, estimatedCount)
85+
}
86+
87+
// WritePacketsToConn writes packets to the connection.
88+
func (f *DatagramPacketForwarder) WritePacketsToConn(conn net.PacketConn, packetCount int) error {
89+
return f.readPktDescsManager.writePacketsToPacketConn(conn, packetCount)
90+
}
91+
92+
// ReadPacketsFromConn reads packets from the connection.
93+
func (f *DatagramPacketForwarder) ReadPacketsFromConn(conn net.PacketConn) (int, error) {
94+
return f.writePktDescsManager.readPacketsFromPacketConn(conn)
95+
}
96+
97+
// WritePacketsToInterface writes packets to the vmnet Interface.
98+
func (f *DatagramPacketForwarder) WritePacketsToInterface(iface *Interface, packetCount int) error {
99+
return iface.WritePackets(f.writePktDescsManager.pktDescs, packetCount)
100+
}
101+
102+
// sockoptsForPacketConn returns socket options for the given [Interface] and user desired options for [net.PacketConn].
103+
// Default values are based on the following references:
104+
// - https://developer.apple.com/documentation/virtualization/vzfilehandlenetworkdeviceattachment/maximumtransmissionunit?language=objc
105+
func sockoptsForPacketConn(iface *Interface, userOpts Sockopts) Sockopts {
106+
// Calculate minimum buffer sizes based on interface configuration
107+
packetSize := int(iface.MaxPacketSize)
108+
if iface.EnableVirtioHeader {
109+
// Add virtio header size
110+
packetSize += virtioNetHdrSize
111+
}
112+
minPacketCount := max(iface.MaxReadPacketCount, iface.MaxWritePacketCount)
113+
minSendBufSize := packetSize
114+
minRecvBufSize := minSendBufSize * minPacketCount
115+
116+
// Default socket options
117+
sockopts := Sockopts{
118+
ReceiveBufferSize: minRecvBufSize * 4 * 10,
119+
SendBufferSize: packetSize,
120+
}
121+
// If user specified options, override with minimums as needed
122+
if userOpts.ReceiveBufferSize > 0 {
123+
sockopts.ReceiveBufferSize = max(userOpts.ReceiveBufferSize, minRecvBufSize)
124+
}
125+
if userOpts.SendBufferSize > 0 {
126+
sockopts.SendBufferSize = max(userOpts.SendBufferSize, minSendBufSize)
127+
}
128+
return sockopts
129+
}
130+
131+
// packetConnAndFile creates a [net.PacketConn] and *[os.File] pair using [syscall.Socketpair].
132+
func packetConnAndFile(opts Sockopts) (net.PacketConn, *os.File, error) {
133+
sendBufSize, recvBufSize := opts.SendBufferSize, opts.ReceiveBufferSize
134+
connFile, file, err := filePair(syscall.SOCK_DGRAM, sendBufSize, recvBufSize)
135+
if err != nil {
136+
return nil, nil, fmt.Errorf("ConnAndFile failed: %w", err)
137+
}
138+
conn, err := net.FilePacketConn(connFile)
139+
if err != nil {
140+
_ = connFile.Close()
141+
_ = file.Close()
142+
return nil, nil, fmt.Errorf("net.FilePacketConn failed: %w", err)
143+
}
144+
if err = connFile.Close(); err != nil {
145+
_ = conn.Close()
146+
_ = file.Close()
147+
return nil, nil, fmt.Errorf("failed to close connFile: %w", err)
148+
}
149+
return conn, file, nil
150+
}
151+
152+
// MARK: - pktDescsManager methods for datagram file adaptor
153+
154+
// buffersForWritingToPacketConn returns [net.Buffers] to write to the [net.PacketConn]
155+
// adjusted their buffer sizes based vm_pkt_size in [VMPktDesc]s read from [Interface].
156+
// The 4-byte header is excluded.
157+
func (v *pktDescsManager) buffersForWritingToPacketConn(packetCount int) (net.Buffers, error) {
158+
for i, vmPktDesc := range v.iter(packetCount) {
159+
if uint64(vmPktDesc.vm_pkt_size) > v.maxPacketSize {
160+
return nil, fmt.Errorf("vm_pkt_size %d exceeds maxPacketSize %d", vmPktDesc.vm_pkt_size, v.maxPacketSize)
161+
}
162+
// Resize buffer to exclude the 4-byte header
163+
v.writingBuffers[i] = v.backingBuffers[i][headerSize : headerSize+uintptr(vmPktDesc.vm_pkt_size)]
164+
}
165+
return v.writingBuffers[:packetCount], nil
166+
}
167+
168+
// writePacketsToPacketConn writes packets from [VMPktDesc]s to the [net.PacketConn].
169+
// - It returns an error if any occurs during sending packets.
170+
func (v *pktDescsManager) writePacketsToPacketConn(conn net.PacketConn, packetCount int) error {
171+
buffers, err := v.buffersForWritingToPacketConn(packetCount)
172+
if err != nil {
173+
return fmt.Errorf("buffersForWritingToPacketConn failed: %w", err)
174+
}
175+
// Get rawConn for syscall.Sendmsg
176+
rawConn, _ := conn.(syscall.Conn).SyscallConn()
177+
var sentCount int
178+
var sendErr error
179+
rawConnWriteErr := rawConn.Write(func(fd uintptr) (done bool) {
180+
for sentCount < packetCount {
181+
// send packet from buffer
182+
if err := syscall.Sendmsg(int(fd), buffers[sentCount], nil, nil, 0); err != nil {
183+
if errors.Is(err, syscall.EAGAIN) || errors.Is(err, syscall.ENOBUFS) {
184+
return false // try again later
185+
}
186+
sendErr = fmt.Errorf("syscall.Sendmsg failed: %w", err)
187+
return true
188+
}
189+
sentCount++
190+
}
191+
return true
192+
})
193+
if rawConnWriteErr != nil {
194+
return fmt.Errorf("rawConn.Write failed: %w", rawConnWriteErr)
195+
}
196+
if sendErr != nil {
197+
return sendErr
198+
}
199+
return nil
200+
}
201+
202+
// readPacketsFromPacketConn reads packets from the [net.PacketConn] into [VMPktDesc]s.
203+
// - It returns the number of packets read.
204+
// - The packets are expected to come one by one.
205+
// - It receives all available packets until no more packets are available, packetCount reaches maxPacketCount, or an error occurs.
206+
// - It waits for the connection to be ready for initial packet.
207+
func (v *pktDescsManager) readPacketsFromPacketConn(conn net.PacketConn) (int, error) {
208+
var packetCount int
209+
// Read the first packet (blocking)
210+
n, _, err := conn.ReadFrom(v.backingBuffers[packetCount][headerSize:])
211+
if n == 0 {
212+
// normal closure. Will this happen in datagram socket?
213+
return 0, errors.New("conn.ReadFrom: use of closed network connection")
214+
}
215+
if err != nil {
216+
return 0, fmt.Errorf("conn.ReadFrom failed: %w", err)
217+
}
218+
v.at(packetCount).SetPacketSize(n)
219+
packetCount++
220+
// Get rawConn for syscall.Recvfrom
221+
rawConn, _ := conn.(syscall.Conn).SyscallConn()
222+
var recvErr error
223+
rawConnReadErr := rawConn.Read(func(fd uintptr) (done bool) {
224+
// Read available packets until no more packets are available or packetCount reaches maxPacketCount
225+
for packetCount < v.maxPacketCount {
226+
// receive packet into buffer
227+
n, _, err := syscall.Recvfrom(int(fd), v.backingBuffers[packetCount][headerSize:], 0)
228+
if err != nil {
229+
if !errors.Is(err, syscall.EAGAIN) {
230+
recvErr = fmt.Errorf("syscall.Recvfrom failed: %w", err)
231+
}
232+
return true // Do not retry on error
233+
}
234+
v.at(packetCount).SetPacketSize(n)
235+
packetCount++
236+
}
237+
return true
238+
})
239+
if rawConnReadErr != nil {
240+
return 0, fmt.Errorf("rawConn.Read failed: %w", rawConnReadErr)
241+
}
242+
if recvErr != nil {
243+
return 0, recvErr
244+
}
245+
return packetCount, nil
246+
}

0 commit comments

Comments
 (0)