Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added NetworkDeviceAttachmentWasDisconnected api #169

Merged
merged 7 commits into from
Nov 5, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 3 additions & 9 deletions configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ type VirtualMachineConfiguration struct {
memorySize uint64
*pointer

networkDeviceConfiguration []*VirtioNetworkDeviceConfiguration
storageDeviceConfiguration []StorageDeviceConfiguration
}

Expand Down Expand Up @@ -116,20 +117,13 @@ func (v *VirtualMachineConfiguration) SetNetworkDevicesVirtualMachineConfigurati
}
array := objc.ConvertToNSMutableArray(ptrs)
C.setNetworkDevicesVZVirtualMachineConfiguration(objc.Ptr(v), objc.Ptr(array))
v.networkDeviceConfiguration = cs
}

// NetworkDevices return the list of network device configuration set in this virtual machine configuration.
// Return an empty array if no network device configuration is set.
func (v *VirtualMachineConfiguration) NetworkDevices() []*VirtioNetworkDeviceConfiguration {
nsArray := objc.NewNSArray(
C.networkDevicesVZVirtualMachineConfiguration(objc.Ptr(v)),
)
ptrs := nsArray.ToPointerSlice()
networkDevices := make([]*VirtioNetworkDeviceConfiguration, len(ptrs))
for i, ptr := range ptrs {
networkDevices[i] = newVirtioNetworkDeviceConfiguration(ptr)
}
return networkDevices
return v.networkDeviceConfiguration
Comment on lines -124 to +126
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I decided to take this concise approach because it is difficult to identify the interface NetworkDeviceAttachment class in the Objective-C world and restore it in the Go world.

This also has the advantage that it is identical to the VirtioNetworkDeviceConfiguration pointer at set time.

}

// SetSerialPortsVirtualMachineConfiguration sets list of serial ports. Empty by default.
Expand Down
12 changes: 12 additions & 0 deletions internal/sliceutil/sliceutil.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package sliceutil

// FindValueByIndex returns the value of the index in s,
// or -1 if not present.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't seem returning -1

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I fixed 4be99b6

func FindValueByIndex[S ~[]E, E any](s S, idx int) (v E) {
for i := range s {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this for loop really necessary?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, I will remove it.
Thank you

if i == idx {
return s[i]
}
}
return v
}
50 changes: 50 additions & 0 deletions internal/sliceutil/sliceutil_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package sliceutil_test

import (
"testing"

"github.com/Code-Hex/vz/v3/internal/sliceutil"
)

func TestFindValueByIndex(t *testing.T) {
tests := []struct {
name string
slice []int
index int
expected int
}{
{
name: "Index within range",
slice: []int{1, 2, 3, 4, 5},
index: 2,
expected: 3,
},
{
name: "Index out of range",
slice: []int{1, 2, 3, 4, 5},
index: 10,
expected: 0, // default value of int
},
{
name: "Negative index",
slice: []int{1, 2, 3, 4, 5},
index: -1,
expected: 0, // default value of int
},
{
name: "Empty slice",
slice: []int{},
index: 0,
expected: 0, // default value of int
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := sliceutil.FindValueByIndex(tt.slice, tt.index)
if result != tt.expected {
t.Errorf("FindValueByIndex(%v, %d) = %v; want %v", tt.slice, tt.index, result, tt.expected)
}
})
}
}
35 changes: 26 additions & 9 deletions network.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (
"net"
"os"
"syscall"
"unsafe"

"github.com/Code-Hex/vz/v3/internal/objc"
)
Expand Down Expand Up @@ -91,6 +90,10 @@ type NATNetworkDeviceAttachment struct {
*baseNetworkDeviceAttachment
}

func (*NATNetworkDeviceAttachment) String() string {
return "NATNetworkDeviceAttachment"
}

var _ NetworkDeviceAttachment = (*NATNetworkDeviceAttachment)(nil)

// NewNATNetworkDeviceAttachment creates a new NATNetworkDeviceAttachment.
Expand Down Expand Up @@ -127,6 +130,10 @@ type BridgedNetworkDeviceAttachment struct {
*baseNetworkDeviceAttachment
}

func (*BridgedNetworkDeviceAttachment) String() string {
return "BridgedNetworkDeviceAttachment"
}

var _ NetworkDeviceAttachment = (*BridgedNetworkDeviceAttachment)(nil)

// NewBridgedNetworkDeviceAttachment creates a new BridgedNetworkDeviceAttachment with networkInterface.
Expand Down Expand Up @@ -164,6 +171,10 @@ type FileHandleNetworkDeviceAttachment struct {
mtu int
}

func (*FileHandleNetworkDeviceAttachment) String() string {
return "FileHandleNetworkDeviceAttachment"
}

var _ NetworkDeviceAttachment = (*FileHandleNetworkDeviceAttachment)(nil)

// NewFileHandleNetworkDeviceAttachment initialize the attachment with a file handle.
Expand Down Expand Up @@ -253,7 +264,7 @@ func (f *FileHandleNetworkDeviceAttachment) MaximumTransmissionUnit() int {
// see: https://developer.apple.com/documentation/virtualization/vznetworkdeviceattachment?language=objc
type NetworkDeviceAttachment interface {
objc.NSObject

fmt.Stringer
networkDeviceAttachment()
}

Expand All @@ -271,6 +282,8 @@ func (*baseNetworkDeviceAttachment) networkDeviceAttachment() {}
// see: https://developer.apple.com/documentation/virtualization/vzvirtionetworkdeviceconfiguration?language=objc
type VirtioNetworkDeviceConfiguration struct {
*pointer

attachment NetworkDeviceAttachment
}

// NewVirtioNetworkDeviceConfiguration creates a new VirtioNetworkDeviceConfiguration with NetworkDeviceAttachment.
Expand All @@ -282,27 +295,31 @@ func NewVirtioNetworkDeviceConfiguration(attachment NetworkDeviceAttachment) (*V
return nil, err
}

config := newVirtioNetworkDeviceConfiguration(
C.newVZVirtioNetworkDeviceConfiguration(
objc.Ptr(attachment),
),
)
config := newVirtioNetworkDeviceConfiguration(attachment)
objc.SetFinalizer(config, func(self *VirtioNetworkDeviceConfiguration) {
objc.Release(self)
})
return config, nil
}

func newVirtioNetworkDeviceConfiguration(ptr unsafe.Pointer) *VirtioNetworkDeviceConfiguration {
func newVirtioNetworkDeviceConfiguration(attachment NetworkDeviceAttachment) *VirtioNetworkDeviceConfiguration {
ptr := C.newVZVirtioNetworkDeviceConfiguration(
objc.Ptr(attachment),
)
return &VirtioNetworkDeviceConfiguration{
pointer: objc.NewPointer(ptr),
pointer: objc.NewPointer(ptr),
attachment: attachment,
}
}

func (v *VirtioNetworkDeviceConfiguration) SetMACAddress(macAddress *MACAddress) {
C.setNetworkDevicesVZMACAddress(objc.Ptr(v), objc.Ptr(macAddress))
}

func (v *VirtioNetworkDeviceConfiguration) Attachment() NetworkDeviceAttachment {
return v.attachment
}

// MACAddress represents a media access control address (MAC address), the 48-bit ethernet address.
// see: https://developer.apple.com/documentation/virtualization/vzmacaddress?language=objc
type MACAddress struct {
Expand Down
105 changes: 101 additions & 4 deletions virtualization.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ package vz
*/
import "C"
import (
"fmt"
"runtime/cgo"
"sync"
"unsafe"

infinity "github.com/Code-Hex/go-infinity-channel"
"github.com/Code-Hex/vz/v3/internal/objc"
"github.com/Code-Hex/vz/v3/internal/sliceutil"
)

// VirtualMachineState represents execution state of the virtual machine.
Expand Down Expand Up @@ -91,9 +93,15 @@ type VirtualMachine struct {
dispatchQueue unsafe.Pointer
machineState *machineState

disconnectedIn *infinity.Channel[*disconnected]
disconnectedOut *infinity.Channel[*DisconnectedError]
watchDisconnectedOnce sync.Once

finalizeOnce sync.Once

config *VirtualMachineConfiguration

mu sync.RWMutex
}

type machineState struct {
Expand Down Expand Up @@ -123,20 +131,27 @@ func NewVirtualMachine(config *VirtualMachineConfiguration) (*VirtualMachine, er
state: VirtualMachineState(0),
stateNotify: infinity.NewChannel[VirtualMachineState](),
}

stateHandle := cgo.NewHandle(machineState)

disconnectedIn := infinity.NewChannel[*disconnected]()
disconnectedOut := infinity.NewChannel[*DisconnectedError]()
disconnectedHandle := cgo.NewHandle(disconnectedIn)

v := &VirtualMachine{
id: cs.String(),
pointer: objc.NewPointer(
C.newVZVirtualMachineWithDispatchQueue(
objc.Ptr(config),
dispatchQueue,
C.uintptr_t(stateHandle),
C.uintptr_t(disconnectedHandle),
),
),
dispatchQueue: dispatchQueue,
machineState: machineState,
config: config,
dispatchQueue: dispatchQueue,
machineState: machineState,
disconnectedIn: disconnectedIn,
disconnectedOut: disconnectedOut,
config: config,
}

objc.SetFinalizer(v, func(self *VirtualMachine) {
Expand Down Expand Up @@ -357,3 +372,85 @@ func (v *VirtualMachine) StartGraphicApplication(width, height float64) error {
C.startVirtualMachineWindow(objc.Ptr(v), C.double(width), C.double(height))
return nil
}

// DisconnectedError represents an error that occurs when a VM’s network attachment is disconnected
// due to a network-related issue. This error is triggered by the framework when such a disconnection happens.
type DisconnectedError struct {
// Err is the underlying error that caused the disconnection, triggered by the framework.
// This error provides information on why the network attachment was disconnected.
Err error
// The network device configuration associated with the disconnection event.
// This configuration helps identify which network device experienced the disconnection.
// If Config is nil, the specific configuration details are unavailable.
Config *VirtioNetworkDeviceConfiguration
}

var _ error = (*DisconnectedError)(nil)

func (e *DisconnectedError) Unwrap() error { return e.Err }
func (e *DisconnectedError) Error() string {
if e.Config == nil {
return e.Err.Error()
}
return fmt.Sprintf("%s: %v", e.Config.attachment, e.Err)
}

type disconnected struct {
err error
index int
}

// NetworkDeviceAttachmentWasDisconnected returns a receive channel.
// The channel emits an error message each time the network attachment is disconnected,
// typically triggered by events such as failure to start, initial boot, device reset, or reboot.
// As a result, this method may be invoked multiple times throughout the virtual machine's lifecycle.
//
// This is only supported on macOS 12 and newer, error will be returned on older versions.
func (v *VirtualMachine) NetworkDeviceAttachmentWasDisconnected() (<-chan *DisconnectedError, error) {
if err := macOSAvailable(12); err != nil {
return nil, err
}
v.watchDisconnectedOnce.Do(func() {
go v.watchDisconnected()
})
return v.disconnectedOut.Out(), nil
}

// TODO(codehex): refactoring to leave using machineState's mutex lock.
func (v *VirtualMachine) watchDisconnected() {
for disconnected := range v.disconnectedIn.Out() {
v.mu.RLock()
config := sliceutil.FindValueByIndex(
v.config.networkDeviceConfiguration,
disconnected.index,
)
v.mu.RUnlock()
v.disconnectedOut.In() <- &DisconnectedError{
Err: disconnected.err,
Config: config,
}
}
v.disconnectedOut.Close()
}

//export emitAttachmentWasDisconnected
func emitAttachmentWasDisconnected(index C.int, errPtr unsafe.Pointer, cgoHandleUintptr C.uintptr_t) {
handler := cgo.Handle(cgoHandleUintptr)
err := newNSError(errPtr)
// I expected it will not cause panic.
// if caused panic, that's unexpected behavior.
ch, _ := handler.Value().(*infinity.Channel[*disconnected])
ch.In() <- &disconnected{
err: err,
index: int(index),
}
}

//export closeAttachmentWasDisconnectedChannel
func closeAttachmentWasDisconnectedChannel(cgoHandleUintptr C.uintptr_t) {
handler := cgo.Handle(cgoHandleUintptr)
// I expected it will not cause panic.
// if caused panic, that's unexpected behavior.
ch, _ := handler.Value().(*infinity.Channel[*disconnected])
ch.Close()
}
26 changes: 25 additions & 1 deletion virtualization_11.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,42 @@
void connectionHandler(void *connection, void *err, uintptr_t cgoHandle);
void changeStateOnObserver(int state, uintptr_t cgoHandle);
bool shouldAcceptNewConnectionHandler(uintptr_t cgoHandle, void *connection, void *socketDevice);
void emitAttachmentWasDisconnected(int index, void *err, uintptr_t cgoHandle);
void closeAttachmentWasDisconnectedChannel(uintptr_t cgoHandle);

@interface Observer : NSObject
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context;
@end

@interface VZVirtualMachineDelegateWrapper : NSObject <VZVirtualMachineDelegate>
@property (nonatomic, strong, readonly) NSHashTable<id<VZVirtualMachineDelegate>> *delegates;

- (instancetype)init;
- (void)addDelegate:(id<VZVirtualMachineDelegate>)delegate;
- (void)guestDidStopVirtualMachine:(VZVirtualMachine *)virtualMachine;
- (void)virtualMachine:(VZVirtualMachine *)virtualMachine didStopWithError:(NSError *)error;
- (void)virtualMachine:(VZVirtualMachine *)virtualMachine
networkDevice:(VZNetworkDevice *)networkDevice
attachmentWasDisconnectedWithError:(NSError *)error API_AVAILABLE(macos(12.0));
@end

@interface ObservableVZVirtualMachine : VZVirtualMachine
- (instancetype)initWithConfiguration:(VZVirtualMachineConfiguration *)configuration
queue:(dispatch_queue_t)queue
statusUpdateHandle:(uintptr_t)statusUpdateHandle;
- (void)dealloc;
@end

@interface NetworkDeviceDisconnectedHandler : NSObject <VZVirtualMachineDelegate>
- (instancetype)initWithHandle:(uintptr_t)cgoHandle;
- (void)virtualMachine:(VZVirtualMachine *)virtualMachine
networkDevice:(VZNetworkDevice *)networkDevice
attachmentWasDisconnectedWithError:(NSError *)error API_AVAILABLE(macos(12.0));
- (int)networkDevices:(NSArray<VZNetworkDevice *> *)networkDevices
indexOf:(VZNetworkDevice *)networkDevice API_AVAILABLE(macos(12.0));
- (void)dealloc;
@end

/* VZVirtioSocketListener */
@interface VZVirtioSocketListenerDelegateImpl : NSObject <VZVirtioSocketListenerDelegate>
- (instancetype)initWithHandle:(uintptr_t)cgoHandle;
Expand Down Expand Up @@ -88,7 +112,7 @@ void VZVirtioSocketDevice_removeSocketListenerForPort(void *socketDevice, void *
void VZVirtioSocketDevice_connectToPort(void *socketDevice, void *vmQueue, uint32_t port, uintptr_t cgoHandle);

/* VirtualMachine */
void *newVZVirtualMachineWithDispatchQueue(void *config, void *queue, uintptr_t cgoHandle);
void *newVZVirtualMachineWithDispatchQueue(void *config, void *queue, uintptr_t statusUpdateCgoHandle, uintptr_t disconnectedCgoHandle);
bool requestStopVirtualMachine(void *machine, void *queue, void **error);
void startWithCompletionHandler(void *machine, void *queue, uintptr_t cgoHandle);
void pauseWithCompletionHandler(void *machine, void *queue, uintptr_t cgoHandle);
Expand Down
Loading