Skip to content

Commit 7bf24c1

Browse files
committed
vmnet_test.go: Add TestVmnetNetworkShareModeSharingOverXpc
- Add `TestVmnetNetworkShareModeSharingOverXpc` to `vmnet_test.go` `TestVmnetNetworkShareModeSharingOverXpc` tests sharing `VmnetNetwork` in `SharedMode` over XPC communication. This test registers test executable as an Mach service and launches it using `launchctl`. The launched Mach service provides `VmnetNetwork` serialization to clients upon request, after booting a VM using the provided `VmnetNetwork` to ensure the network is functional on the server side. The client boots VM using the provided `VmnetNetwork` serialization. This test uses `pkg/xpc` package to implement XPC communication. - Add `pkg/xpc` package that providing `<xpc/xpc.h>` APIs to support implementing Mach service server and client Signed-off-by: Norio Nomura <norio.nomura@gmail.com>
1 parent d5496fc commit 7bf24c1

File tree

9 files changed

+1202
-0
lines changed

9 files changed

+1202
-0
lines changed

pkg/xpc/error.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package xpc
2+
3+
/*
4+
#cgo darwin CFLAGS: -mmacosx-version-min=11 -x objective-c -fno-objc-arc
5+
# include "xpc.h"
6+
*/
7+
import "C"
8+
import (
9+
"runtime/cgo"
10+
"unsafe"
11+
)
12+
13+
// Error represents an XPC rich error.
14+
// see: https://developer.apple.com/documentation/xpc/xpc_rich_error_t?language=objc
15+
type Error struct {
16+
Object
17+
}
18+
19+
var _ error = Error{}
20+
21+
// newError creates a new Error from an existing xpc_rich_error_t.
22+
// internal use only.
23+
func newError(richErr unsafe.Pointer) *Error {
24+
if richErr == nil {
25+
return nil
26+
}
27+
return &Error{
28+
Object: Object{XpcObject: richErr},
29+
}
30+
}
31+
32+
// wrapError wraps an existing xpc_rich_error_t into an Error and returns a handle.
33+
// intended to be called from C.
34+
//
35+
//export wrapError
36+
func wrapError(richErr unsafe.Pointer) uintptr {
37+
obj := newError(richErr)
38+
if obj == nil {
39+
return 0
40+
}
41+
obj.handle = cgo.NewHandle(obj)
42+
return uintptr(obj.handle)
43+
}
44+
45+
// unwrapError unwraps a handle into an *Error.
46+
func unwrapError(handle uintptr) *Error {
47+
if handle == 0 {
48+
return nil
49+
}
50+
return cgo.Handle(handle).Value().(*Error)
51+
}
52+
53+
// Error implements the error interface.
54+
func (e Error) Error() string {
55+
desc := C.xpcRichErrorCopyDescription(e.XpcObject)
56+
defer C.free(unsafe.Pointer(desc))
57+
return C.GoString(desc)
58+
}

pkg/xpc/listener.go

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package xpc
2+
3+
/*
4+
#cgo darwin CFLAGS: -mmacosx-version-min=11 -x objective-c -fno-objc-arc
5+
#cgo darwin LDFLAGS: -lobjc -framework Foundation
6+
# include "xpc.h"
7+
*/
8+
import "C"
9+
import (
10+
"runtime/cgo"
11+
"unsafe"
12+
)
13+
14+
// Listener represents an XPC listener.
15+
type Listener struct {
16+
// Exported for use in other packages since unimplemented XPC API may require direct access to xpc_listener_t.
17+
XpcListener unsafe.Pointer
18+
queue unsafe.Pointer
19+
sessionHandler cgo.Handle
20+
}
21+
22+
// SessionHandler is a function that handles incoming sessions.
23+
type SessionHandler func(session *Session)
24+
25+
type listenerOption struct {
26+
sessionHandler SessionHandler
27+
messageHandler MessageHandler
28+
cancellationHandler CancellationHandler
29+
peerRequirement *PeerRequirement
30+
}
31+
32+
// NewListener creates a new XPC listener for the given service name.
33+
// You need to call [Listener.Activate] to start accepting incoming connections.
34+
func NewListener(service string, handler SessionHandler) (*Listener, error) {
35+
cname := C.CString(service)
36+
defer C.free(unsafe.Pointer(cname))
37+
// Use a serial dispatch queue for the listener,
38+
// because the vmnet framework API does not seem to work well with concurrent queues.
39+
// For example, vmnet_network_create fails when using a concurrent queue.
40+
queue := C.dispatchQueueCreateSerial(cname)
41+
cgoSessionHandler := cgo.NewHandle(handler)
42+
43+
var err_out unsafe.Pointer
44+
ptr := C.xpcListenerCreate(
45+
cname,
46+
queue,
47+
C.XPC_LISTENER_CREATE_INACTIVE,
48+
C.uintptr_t(cgoSessionHandler),
49+
&err_out,
50+
)
51+
if err_out != nil {
52+
C.dispatchRelease(queue)
53+
cgoSessionHandler.Delete()
54+
return nil, newError(err_out)
55+
}
56+
return &Listener{
57+
XpcListener: ptr,
58+
queue: queue,
59+
sessionHandler: cgoSessionHandler,
60+
}, nil
61+
}
62+
63+
// Activate starts the listener to accept incoming connections.
64+
// see: https://developer.apple.com/documentation/xpc/xpc_listener_activate
65+
func (l *Listener) Activate() error {
66+
var err_out unsafe.Pointer
67+
C.xpcListenerActivate(l.XpcListener, &err_out)
68+
if err_out != nil {
69+
return newError(err_out)
70+
}
71+
return nil
72+
}
73+
74+
// Close stops the listener from accepting incoming connections.
75+
// see: https://developer.apple.com/documentation/xpc/xpc_listener_cancel
76+
func (l *Listener) Close() error {
77+
C.xpcListenerCancel(l.XpcListener)
78+
79+
C.xpcRelease(l.XpcListener)
80+
if l.sessionHandler != 0 {
81+
l.sessionHandler.Delete()
82+
l.sessionHandler = 0
83+
}
84+
C.dispatchRelease(l.queue)
85+
return nil
86+
}
87+
88+
// SetPeerRequirement sets the peer requirement for the listener.
89+
// see: https://developer.apple.com/documentation/xpc/xpc_listener_set_peer_requirement
90+
func (l *Listener) SetPeerRequirement(req *PeerRequirement) {
91+
C.xpcListenerSetPeerRequirement(l.XpcListener, req.XpcObject)
92+
}
93+
94+
// String returns a description of the listener.
95+
// see: https://developer.apple.com/documentation/xpc/xpc_listener_copy_description
96+
func (l *Listener) String() string {
97+
desc := C.xpcListenerCopyDescription(l.XpcListener)
98+
defer C.free(unsafe.Pointer(desc))
99+
return C.GoString(desc)
100+
}

pkg/xpc/object.go

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
package xpc
2+
3+
/*
4+
#cgo darwin CFLAGS: -mmacosx-version-min=11 -x objective-c -fno-objc-arc
5+
# include "xpc.h"
6+
*/
7+
import "C"
8+
import (
9+
"runtime"
10+
"runtime/cgo"
11+
"unsafe"
12+
)
13+
14+
// Object represents a generic XPC object.
15+
// see: https://developer.apple.com/documentation/xpc/xpc_object_t?language=objc
16+
type Object struct {
17+
// Exported for use in other packages since unimplemented XPC API may require direct access to xpc_object_t.
18+
XpcObject unsafe.Pointer
19+
handle cgo.Handle
20+
}
21+
22+
// NewObject creates a new Object from an existing xpc_object_t.
23+
func NewObject(xpcObj unsafe.Pointer) *Object {
24+
if xpcObj == nil {
25+
return nil
26+
}
27+
return &Object{XpcObject: xpcObj}
28+
}
29+
30+
// wrapObject wraps an existing xpc_object_t into an Object and returns a handle.
31+
// intended to be called from C.
32+
//
33+
//export wrapObject
34+
func wrapObject(o unsafe.Pointer) uintptr {
35+
obj := NewObject(o)
36+
obj.handle = cgo.NewHandle(obj)
37+
return uintptr(obj.handle)
38+
}
39+
40+
// unwrapObject unwraps a handle into an *Object.
41+
func unwrapObject(handle uintptr) *Object {
42+
if handle == 0 {
43+
return nil
44+
}
45+
return cgo.Handle(handle).Value().(*Object)
46+
}
47+
48+
// NewDictionary creates a new empty XPC dictionary object.
49+
// You can customize the dictionary object using DictionaryEntry.
50+
func NewDictionary(entries ...DictionaryEntry) *Object {
51+
obj := NewObject(C.xpcDictionaryCreateEmpty())
52+
for _, e := range entries {
53+
e(obj)
54+
}
55+
return obj
56+
}
57+
58+
// Retain retains the XPC object and returns itself.
59+
// see: https://developer.apple.com/documentation/xpc/xpc_retain?language=objc
60+
func (o *Object) Retain() *Object {
61+
C.xpcRetain(o.XpcObject)
62+
runtime.SetFinalizer(o, func(o *Object) {
63+
o.release()
64+
})
65+
return o
66+
}
67+
68+
// release releases the XPC object.
69+
// see: https://developer.apple.com/documentation/xpc/xpc_release?language=objc
70+
func (o *Object) release() {
71+
if o.handle != 0 {
72+
o.handle.Delete()
73+
o.handle = 0
74+
}
75+
C.xpcRelease(o.XpcObject)
76+
}
77+
78+
// DictionaryGetString retrieves a string value from the XPC dictionary object by key.
79+
// Returns an empty string if the key does not exist.
80+
// see: https://developer.apple.com/documentation/xpc/xpc_dictionary_get_string(_:_:)?language=objc
81+
func (o *Object) DictionaryGetString(key string) string {
82+
cKey := C.CString(key)
83+
defer C.free(unsafe.Pointer(cKey))
84+
val := C.xpcDictionaryGetString(o.XpcObject, cKey)
85+
return C.GoString(val)
86+
}
87+
88+
// DictionaryGetValue retrieves a value from the XPC dictionary object by key.
89+
// Returns nil if the key does not exist.
90+
// see: https://developer.apple.com/documentation/xpc/xpc_dictionary_get_value(_:_:)?language=objc
91+
func (o *Object) DictionaryGetValue(key string) *Object {
92+
cKey := C.CString(key)
93+
defer C.free(unsafe.Pointer(cKey))
94+
val := C.xpcDictionaryGetValue(o.XpcObject, cKey)
95+
if val == nil {
96+
return nil
97+
}
98+
return NewObject(val)
99+
}
100+
101+
// DictionaryEntry defines a function type for customizing reply objects.
102+
type DictionaryEntry func(*Object)
103+
104+
// WithString sets a string value in the reply dictionary.
105+
func WithString(key, value string) DictionaryEntry {
106+
return func(o *Object) {
107+
cKey := C.CString(key)
108+
defer C.free(unsafe.Pointer(cKey))
109+
cValue := C.CString(value)
110+
defer C.free(unsafe.Pointer(cValue))
111+
C.xpcDictionarySetString(o.XpcObject, cKey, cValue)
112+
}
113+
}
114+
115+
// WithValue sets a value in the reply dictionary.
116+
func WithValue(key string, val *Object) DictionaryEntry {
117+
return func(o *Object) {
118+
cKey := C.CString(key)
119+
defer C.free(unsafe.Pointer(cKey))
120+
C.xpcDictionarySetValue(o.XpcObject, cKey, val.XpcObject)
121+
}
122+
}
123+
124+
// DictionaryCreateReply creates a new reply XPC dictionary object based on the current object.
125+
// see: https://developer.apple.com/documentation/xpc/xpc_dictionary_create_reply(_:)?language=objc
126+
// You can customize the reply object using ReplyOption.
127+
func (o *Object) DictionaryCreateReply(entries ...DictionaryEntry) *Object {
128+
reply := C.xpcDictionaryCreateReply(o.XpcObject)
129+
obj := NewObject(unsafe.Pointer(reply))
130+
for _, entry := range entries {
131+
entry(obj)
132+
}
133+
return obj
134+
}
135+
136+
// String returns the description of the XPC object.
137+
// see: https://developer.apple.com/documentation/xpc/xpc_copy_description(_:)?language=objc
138+
func (o *Object) String() string {
139+
cs := C.xpcCopyDescription(o.XpcObject)
140+
defer C.free(unsafe.Pointer(cs))
141+
return C.GoString(cs)
142+
}

pkg/xpc/peer_requirement.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package xpc
2+
3+
/*
4+
#cgo darwin CFLAGS: -mmacosx-version-min=11 -x objective-c -fno-objc-arc
5+
#cgo darwin LDFLAGS: -lobjc -framework Foundation
6+
# include "xpc.h"
7+
*/
8+
import "C"
9+
import "unsafe"
10+
11+
// PeerRequirement represents an xpc_peer_requirement_t.
12+
// see: https://developer.apple.com/documentation/xpc/xpc_peer_requirement_t?language=objc
13+
type PeerRequirement struct {
14+
Object
15+
}
16+
17+
// CreatePeerRequirementLwcr creates a peer requirement from a LWCR object.
18+
// see: https://developer.apple.com/documentation/xpc/xpc_peer_requirement_create_lwcr
19+
// see: https://developer.apple.com/documentation/security/defining-launch-environment-and-library-constraints?language=objc
20+
func CreatePeerRequirementLwcr(lwcr *Object) (*PeerRequirement, error) {
21+
var err_out unsafe.Pointer
22+
ptr := C.xpcPeerRequirementCreateLwcr(
23+
lwcr.XpcObject,
24+
&err_out,
25+
)
26+
if err_out != nil {
27+
return nil, newError(err_out)
28+
}
29+
return &PeerRequirement{
30+
Object: Object{XpcObject: ptr},
31+
}, nil
32+
}

0 commit comments

Comments
 (0)