Thanks for getting back (particularly at the weekend!)
My service shows up as: XXXXXXXXX.com.example.endpoint-test.Extension with status 0 (where XXXXXXXXX is my team identifier). In addition
- I see the corresponding process running as root using
ps
: /Library/SystemExtensions/1049F8B0-4BEC-44AD-B0D0-AE8EBCBC431A/com.example.endpoint-test.Extension.systemextension/Contents/MacOS/com.example.endpoint-test.Extension. systemextensionsctl list
shows my extension as [activated enabled]
I have also looked at the output from sudo launchctl procinfo $PID
and I see: XPC_SERVICE_NAME => XXXXXXXXX.com.example.endpoint-test.Extension
(not sure if this is relevant).
In order to help debug this, I'll give you a dump of any key information I can think of which might be useful:
- I have given the extension permission to run and Full Disk Access via System Preferences->Security & Privacy
- My container app Bundle ID is
com.example.endpoint-test
- My Extension Bundle ID is
com.example.endpoint-test.Extension
- I have distinct provisioning profiles for both the app and the extension. Each has the System Extension entitlement. N.B. My developer account has been granted the right to use Endpoint Security for development only.
- Both bundles have App Groups capabilities enabled with group
$(TeamIdentifierPrefix)com.example.mygroup
- In the container app entitlements plist I have:
com.apple.security.temporary-exception.mach-lookup.global-name = com.example.endpoint-test.Extension
- In the extension entitlements plist I have:
com.apple.developer.endpoint-security.client=1
- In the extension Info.plist I have:
NSEndpointSecurityMachServiceName= com.example.endpoint-test.Extension
- I am debugging the app from the Xcode DerivedData directory. The documentation says I need to copy the app to /Applications in order for System Extensions to work, however, I've not found any behavioural changes if I do that. The only downside is that if I delete the app from DerivedData then the system doesn't uninstall the extension for me and it gets "stuck". In order to delete I just rebuild the app, copy to /Applications and delete from there.
My code roughly looks like the following
- Container app requests user interaction to install the extension
- System extension creates and
resume
s an NSXPCListener
on startup using initWithMachServiceName
with "com.example.endpoint-test.Extension"
- Container app tries to connect a few seconds after the system extension API callback returns success
- Container app creates an
NSXPCListener
on startup using initWithMachServiceName
with "com.example.endpoint-test.Extension"
, sets the remoteObjectInterface
and resume
s the connection. - Container app calls
remoteObjectProxyWithErrorHandler
to get the remote interface - Container app calls a method on the remote interface. N.B. This call then causes the error handler passed to
remoteObjectProxyWithErrorHandler
to fire. Without this call the error handler doesn't fire. - Note that the
shouldAcceptNewConnection
method in the extension's listener delegate never fires during this.
Actual code
(Sorry about the objC - don't ask)
Server listen:
NSXPCListener *listener;
void extensionListen(void) {
NSLog(@"Extension Service Started");
ServiceDelegate *delegate = [ServiceDelegate new];
listener = [[NSXPCListener alloc] initWithMachServiceName: @"com.example.endpoint-test.Extension"];
listener.delegate = delegate;
[listener resume];
}
App Connect
void appConnect(void) {
NSLog(@"appConnect");
_connectionToService = [[NSXPCConnection alloc] initWithMachServiceName:@"com.example.endpoint-test.Extension" options:NSXPCConnectionPrivileged];
_connectionToService.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:@protocol(ServiceProtocol)];
[_connectionToService resume];
id rop = [_connectionToService remoteObjectProxyWithErrorHandler:^(NSError* error) {
NSLog(@"Error: %@", error);
}];
[rop upperCaseString:@"hello" withReply:^(NSString *aString) {
NSLog(@"Result string was: %@", aString);
}];
// [_connectionToService invalidate];
}
One particular area where I lack confidence that what I'm doing is correct is around the various strings I've put into plists and initWithMachServiceName
. There are so many possible combinations, but none seem to work. Quite possibly there's a bug there.
It's entirely possible I'm missing the point of what XPC is for. Doc imply maybe it's only for (single client)-(XPC)server model. It sounded to me like XPC was designed to be a MITM, not hold business logic. I want to use it for a (multiple client)-server model. Also, it's unclear to me whether XPC can be used for any 2 apps to communicate or just ones int the same bundle.
Note furthermore that I've found the Simple Firewall example. Interestingly this is designed differently to what the docs tell you to do. Specifically, docs tell you to make your XPC service a separate binary and put it in a specific location in the bundle. However, this example seems to just embed the XPC service inside the Sys Ext. 🤷