Network Extension App for MacOS with 3 Extensions

Hi All, I am currently working on a Network Extension App for MacOS using 3 types of extensions provided by Apple's Network Extension Framework.

Content Filter, App Proxy (Want to get/capture/log all HTTP/HTTPS traffic), DNS Proxy (Want to get/capture/log all DNS records). Later parse into human readable format.

  1. Is my selection of network extension types correct for the intended logs I need?

I am able to run with one extension: Main App(Xcode Target1) <-> Content Filter Extension. Here there is a singleton class IPCConnection between App(ViewController.swift) which is working fine with NEMachServiceName from Info.plist of ContentFilter Extension(Xcode Target2)

However, when I add an App Proxy extension as a new Xcode Target3, I think the App and extension's communication getting messed up and App not getting started/Crashing. Here, In the same Main App, I am adding new separate IPCConnection for this extension.

Here is the project organization/folder structure.

MyNetworkExtension
├──MyNetworkExtension(Xcode Target1)
│   ├── AppDelegate.swift
│   ├── Assets.xcassets
│   ├── Info.plist
│   ├── MyNetworkExtension.entitlement
│    | ── Main
│    |-----ViewController.swift
│   └── Base.lproj
│       └── Main.storyboard
├── ContentFilterExtension(Xcode Target2)
│   ├── ContentFilterExtension.entitlement
│   │   ├── FilterDataProvider.swift
│   │   ├── Info.plist
│   │   ├── IPCConnection.swift
│   │   └── main.swift
├── AppProxyProviderExtension(Xcode Target3)
│   ├── AppProxyProviderExtension.entitlement
│   │   ├── AppProxyIPCConnection.swift
│   │   ├── AppProxyProvider.swift
│   │   ├── Info.plist
│   │   └── main.swift
└── Frameworks
    ├── libbsm.tbd
    └── NetworkExtension.framework
  1. Is my Approach for creating a single Network Extension App with Multiple extensions correct or is there any better approach of project organization that will make future modifications/working easier and makes the maintenance better?

I want to keep the logic for each extension separate while having the same, single Main App that manages everything(installing, activating, managing identifiers, extensions, etc).

  1. What's the best approach to establish a Communication from MainApp to each extension separately, without affecting one another? Is it good idea to establish 3 separate IPC Connections(each is a singleton class) for each extension?

  2. Are there any suggestions you can provide that relates to my use case of capturing all the network traffic logs(including HTTP/HTTPS, DNS Records, etc), especially on App to Extension Communication, where my app unable to keep multiple IPC Connections and maintain them separately?

I've been working on it for a while, and still unable to make the Network Extension App work with multiple extensions(each as a new Xcode target).

Main App with single extension is working fine, but if I add new extension, App getting crashed. I suspect it's due to XPC/IPC connection things!

I really appreciate any support on this either directly or by any suggestions/resources that will help me get better understand and make some progress.

Please reach out if in case any clarifications or specific information that's needed to better understand my questions.

Thank you very much

Answered by DTS Engineer in 856333022

We do allow you to embed multiple network extensions in the same app. That’s true on all our platforms, although the mechanics vary based on whether you’re using a system extension (sysex) or an app extension (appex). For more info about what packaging is supported on what platforms, see TN3134 Network Extension provider deployment.

It sounds like you’re using a sysexes, and that presents a decision point:

  • You can have multiple independent sysex, each with a single NE provider.
  • Or you can have a single sysex with multiple NE providers.

The latter is generally easier to wrangle. In that case you have a single sysex target with multiple NE provider subclasses linked into the resulting executable, and then list all of those in the NEProviderClasses dictionary in your sysex’s Info.plist.

IMPORTANT If you set things up this way there’s a single sysex process and all of your NE providers run within that. This has pros (you can share data between them) and cons (if one crashes, they all crash).

Of specific interest is:

  • You have to configure each provider independently using its corresponding NEXxxProviderManager type. So, even though they’re all in the same sysex, you can enable and disable each one at will.
  • If you use XPC to talk to your sysex, there’s only one named endpoint, identified by the NEMachServiceName property.

Share and Enjoy

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

We do allow you to embed multiple network extensions in the same app. That’s true on all our platforms, although the mechanics vary based on whether you’re using a system extension (sysex) or an app extension (appex). For more info about what packaging is supported on what platforms, see TN3134 Network Extension provider deployment.

It sounds like you’re using a sysexes, and that presents a decision point:

  • You can have multiple independent sysex, each with a single NE provider.
  • Or you can have a single sysex with multiple NE providers.

The latter is generally easier to wrangle. In that case you have a single sysex target with multiple NE provider subclasses linked into the resulting executable, and then list all of those in the NEProviderClasses dictionary in your sysex’s Info.plist.

IMPORTANT If you set things up this way there’s a single sysex process and all of your NE providers run within that. This has pros (you can share data between them) and cons (if one crashes, they all crash).

Of specific interest is:

  • You have to configure each provider independently using its corresponding NEXxxProviderManager type. So, even though they’re all in the same sysex, you can enable and disable each one at will.
  • If you use XPC to talk to your sysex, there’s only one named endpoint, identified by the NEMachServiceName property.

Share and Enjoy

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

Hi @DTS Engineer,

Thank you for clarifying. I confirm that I am referring to Network Extensions within the context of System Extensions.

Regarding the first approach I mentioned earlier, I am using separate NEMachServiceNames in the Info.plist files for two different extensions, while using the same App Groups name for all extensions in their respective targets.

For the Second approach you suggested, I understand that in the configuration, we can place multiple extensions under the NEProviderClasses dictionary in the extension's entitlement file, and maintain a single NEMachServiceName in the Info.plist file of the Extension's target (Xcode Target2).

Given that I am new to this framework, I would greatly appreciate it if you could provide more detailed guidance on how to:

  • Utilize multiple NE Providers with a single system extension.
  • Activate these providers one after another.
  • Handle IPC (Inter-Process Communication) connections between the Main App and the single System Extension(apart from Info.plist config with service name)

Any detailed examples or documentation would be extremely helpful.

I am thinking of the following kind of project/folder structure(for Second approach), Correct me if I am wrong in understanding:

MyNetworkExtensionProject/
├── MyNetworkExtensionApp/ (Xcode Target1)
│   ├── AppDelegate.swift
│   ├── ViewController.swift
│   ├── Resources/
│   │   ├── Assets.xcassets
│   │   ├── Info.plist
│   │   └── Main.storyboard
│   └── MyNetworkExtensionApp.entitlements
│
├── MyNetworkSystemExtension/ (Xcode Target2)
│  ├── Providers/
│   │    ├── AppProxyProvider.swift 
│   │    ├── DNSProxyProvider.swift
│   │    ├── ContentFilterProvider.swift 
│   │     |──  IPCConnection.swift
|.  |.    |--- OtherFiles for parsing etc
│   ├── Resources/
│   │   ├── Info.plist
│   │   └── main.swift
│   └── MyNetworkSystemExtension.entitlements
│
└── Frameworks/
    ├── NetworkExtension.framework
    └── libbsm.tbd

Also, To capture HTTP(and HTTPS logs) of all kinds of network traffic, I am confused on which among 3 of the APIs/Classes best suites for this purpose for MacOS:

NEAppProxyProvider/ NETransparentProxyProvider/ Packet Tunnel Provider

And to capture/log DNS Records(all kinds possible), Is the NEDNSProxyProvider right one?

Kindly help me choose the best suitable one for my usecase

Your responses/clarifications are very helpful as I am starting with this project. Thanks a lot for your time and support

I am thinking of the following kind of project/folder structure

The Xcode structure isn’t all that important. What matters is the on-disk structure. And in the second approach the on-disk structure is exactly what you get if you create an app from the macOS > App template and then add a macOS > System Extension > Network Extension target to it, that is:

Test798872.app/
  Contents/
    Library/
      SystemExtensions/
        com.example.apple-samplecode.Test798872.MyNESysex.systemextension/
          … standard sysex stuff …
    … standard app stuff …

From the on-disk structure, the only indication that your sysex hosts multiple providers is:

  • The com.apple.developer.networking.networkextension entitlement, on both the app and the sysex, must list each provider type.
  • The contents of the NEProviderClasses property in the sysex’s Info.plist must list the classes for each provider type.
To capture HTTP … which among 3 of the APIs/Classes best suites for this purpose for MacOS

It depends. If you just want to see the contents of the flows [1], then the best option is a content filter. If you want to modify the contents of the flows, you’ll need a transparent proxy.

Content filters are much easier to implement than transparent proxies, because in a transparent proxy you have to actually proxy any flows that you accept.

However, it’s not that simple. Most HTTP is actually HTTPS, and with HTTPS a content filter only sees the encrypted flows [2]. If you want to see each HTTP request and response, you need to defeat TLS. There are standard techniques for doing that — search the ’net for TLS inspection — but those require you to modify the flow.

So, in summary:

  • If you’re happy with only the metadata and encrypted content of a flow, you can use a content filter.
  • If you want to get the decrypted content of a flow, you need a transparent proxy. This will be more work because a) you have to actually proxy the flow, and b) you’ll need to implement TLS inspection.
And to capture/log DNS Records … Is the NEDNSProxyProvider right one?

Again, it depends.

A DNS proxy is a proxy. That is, DNS flows come to the proxy and the proxy is responsible for resolving any requests that arrive over those flows. This is not a trivial task.

Moreover, and this is different from the transparent proxy case, there’s no mechanism to opt out. If your DNS proxy receives a flow, it must handle it. In contrast, a transparent proxy can return false from its handle-new-flow method and the system will deal with the flow as if the proxy weren’t installed.

Now, if you implement a DNS proxy then, yes, you can see all DNS resolution on the system [3], by virtue of the fact that you’re actually responsible for all DNS resolution on the system (-: However, you have to do a bunch of work to get there.

If you only care about seeing DNS resolution, and don’t want to modify it, there are other options. For example, most DNS resolution is done via UDP and is relatively easy to see in a filter packet provider. However, most is not all. If someone decides to resolve DNS over TCP, handling that in a filter packet provider is deeply un-fun.

You also have to consider encrypted DNS, that is DNS over HTTPS (DoH) and DNS over TLS (DoT). With a proxy provider you can prevent the system from opportunistically switching over to those — see this thread — but if the user has specifically enabled them then you’re back into TLS inspection territory.

Share and Enjoy

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

[1] In this case I’m using flow to mean:

  • For TCP, the connection
  • For UDP, the sequence of datagrams that all share the same local IP / local port / remote IP / remote port tuple

[2] On macOS. On iOS the rules are slightly different.

[3] Well, all DNS resolution done by the system resolver. Some apps include their own resolvers and take steps to do their resolution in a way that’s not seen by a DNS proxy.

Network Extension App for MacOS with 3 Extensions
 
 
Q