Skip to content

Commit dd032cd

Browse files
committed
vmnet: Add Interface related APIs and *FileAdaptorForInterfaces
Signed-off-by: Norio Nomura <norio.nomura@gmail.com>
1 parent 09f940b commit dd032cd

15 files changed

+1471
-43
lines changed

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

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"

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

0 commit comments

Comments
 (0)