Current wisdom on multiple XPC services in a System Extension?

I'm following up on a couple of forum threads from 2020 to get more clarity on the current guidance for supporting multiple XPC services in system extensions. For context, I'm trying to create a system extension that contains both an Endpoint Security client and a Network Extension filter, and I'm seeing indications that the system may not expect this and doesn't handle it smoothly.

First: Previous guidance indicated that the system would automatically provide a Mach service named <TeamID>.<BundleID>.xpc to use for communicating with the system extension. However, the SystemExtension man page currently documents an Info.plist key called NSEndpointSecurityMachServiceName and suggests that the default service name is deprecated; and in fact if this key is not set, I find a message in the Console:

The extension from <app-name> (<bundle-id>) is using the deprecated default mach service name. Please update the extension to set the NSEndpointSecurityMachServiceName key in the Info.plist file.

I have accordingly set this key, but I wanted to confirm that this is the current best practice.

Second, and more interesting: Another user was trying to do something similar and observed that the Mach service for the endpoint security client wasn't available but the NE filter was. Quinn did some research and replied that this was intended behavior, quoting the EndpointSecurity man page:

"If ES extension is combined with a Network Extension, set the NEMachServiceName key in the Info.plist"

(which I have also done), and concluding from this:

... if you have a combined ES and NE system extension then the Mach service provided by the NE side takes precedence.

However, the current man page does not include this quoted text and says nothing about a combined ES and NE system extension.

So I'm wondering about current best practice. If I do combine the ES and NE clients in a single system extension, should they each declare the Mach service name under their respective Info.plist keys? And could there be a single XPC listener for both, using the same service name under each key, or would it be better to have separate XPC listeners?

Alternatively, would it be preferable to have each component in a separate system extension? (This would entail some rearchitecting of the current design.)

Answered by DTS Engineer in 879626022
I wanted to confirm that this is the current best practice.

For an ES sysex, yes.

As to what happens when you combine ES and NE, I did some digging and that text is definitely present in the Xcode 12.5 man page and definitely gone in the Xcode 13 one. Based on that info I was able to track down FB8701548, which was a developer request that we remove that limit. This was resolved in macOS 11 and, as part of that, we removed that text from the man page.

Neat!

With that in mind, let’s return to your other questions:

should they each declare the Mach service name under their respective Info.plist keys?

That’s up to you. I can see arguments either way:

  • If the ES and NE subsystems within your sysex are tightly bound together, it’d make sense to use a single named endpoint.
  • OTOH, if those subsystems act independently, it’d make sense to have two different named endpoints.
And could there be a single XPC listener for both, using the same service name under each key … ?

Don’t do that. If you want two listeners, use two different endpoint names.

would it be preferable to have each component in a separate system extension?

Again, that’s pretty much up to you. Having said that, there are good reasons to use a single sysex:

  • Managing a sysex is a pain, and managing two sysexes is extra pain.
  • With a single sysex, both subsystem run in the same process, which is an advantage if your subsystems are tightly bound. For example, if you have a single rules database then both subsystems can share that database using just a mutex, rather than requiring some complicated shared memory or IPC shenanigans.

OTOH, I can see at least one counter argument:

  • If you put both subsystems in the same sysex then a failure in one subsystem will also take out the other subsystem. Notably, the system kills an ES sysex if it doesn’t respond to events promptly, and in a combined sysex that will also take out your NE providers as well.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Accepted Answer
I wanted to confirm that this is the current best practice.

For an ES sysex, yes.

As to what happens when you combine ES and NE, I did some digging and that text is definitely present in the Xcode 12.5 man page and definitely gone in the Xcode 13 one. Based on that info I was able to track down FB8701548, which was a developer request that we remove that limit. This was resolved in macOS 11 and, as part of that, we removed that text from the man page.

Neat!

With that in mind, let’s return to your other questions:

should they each declare the Mach service name under their respective Info.plist keys?

That’s up to you. I can see arguments either way:

  • If the ES and NE subsystems within your sysex are tightly bound together, it’d make sense to use a single named endpoint.
  • OTOH, if those subsystems act independently, it’d make sense to have two different named endpoints.
And could there be a single XPC listener for both, using the same service name under each key … ?

Don’t do that. If you want two listeners, use two different endpoint names.

would it be preferable to have each component in a separate system extension?

Again, that’s pretty much up to you. Having said that, there are good reasons to use a single sysex:

  • Managing a sysex is a pain, and managing two sysexes is extra pain.
  • With a single sysex, both subsystem run in the same process, which is an advantage if your subsystems are tightly bound. For example, if you have a single rules database then both subsystems can share that database using just a mutex, rather than requiring some complicated shared memory or IPC shenanigans.

OTOH, I can see at least one counter argument:

  • If you put both subsystems in the same sysex then a failure in one subsystem will also take out the other subsystem. Notably, the system kills an ES sysex if it doesn’t respond to events promptly, and in a combined sysex that will also take out your NE providers as well.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Quinn, thank you for another quick and informative answer (and for explaining the change in the man page text). If I were in charge, you would never have to pay for a beverage again. ;-)

A couple of quick clarifications:

  • I haven't implemented any XPC listeners yet; it's more how I'm thinking about the low-level messages I'm seeing that refer to Mach service names and the like.
  • That said, when I wrote "a single XPC listener for both, using the same service name under each key", I meant that I would enter the service name for the single listener under both keys in the Info.plist file. It seems clear to me that multiple listeners need unique service names for each.
  • You've summarized quite well the tradeoffs of single vs. multiple sysexen. In the case where both subsystems feed events to a common module, a single sysex seems like the better choice.

Thanks again for your assistance.

I would enter the service name for the single listener under both keys in the Info.plist file.

That’s not going to help, and it could hurt:

  • It’s not going to help because you can achieve the same effect by putting that name into either of the spots in your Info.plist.
  • It could hurt because… well… it’ll be exercising code paths that are very likely to be untested.
I'm seeing that refer to Mach service names and the like.

XPC is implemented on top of Mach messaging. In some contexts an XPC named endpoint name is synonymous with a Mach service name. For example, when you use XPC in a normal launchd daemon, you put your XPC endpoint name into the MachServices property. However, there are places, like XPC services, where using Mach messaging directly, rather than XPC, is not viable.

Apropos Mach messaging, using it directly is something I strongly recommend against. It’s very tricky to get that right.

I touch on a lot of these ideas in XPC and App-to-App Communication. And there’s a bunch of other links to XPC documentation in XPC Resources.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

I appreciate the additional clarification. Right now I'm focusing on getting the ES and NE extensions working from within the same system extension bundle; I'm not doing any explicit XPC (and I don't intend to use Mach messaging directly — I'm just trying to make sense of the system log messages).

I'll make sure the service name is entered in only one place (under the NetworkExtension section). Thanks again.

Current wisdom on multiple XPC services in a System Extension?
 
 
Q