Network Extension. Per-App VPN gets no traffic from Apps.

I have setup a Network Extension as Per-App NEAppProxyProviderManager. I use a .mobileconfig setup (See bellow)

I do get Safari traffic but not other Apps traffic, at this moment I'm trying Chrome (com.google.Chrome)

I do get Safari traffic in the
  • (BOOL)handleNewFlow:(NEAppProxyFlow *)flow {}

I have set some Safari domains like .com .net .org, and these domains are redirected successfully to the NE.

I want extend the proxy to get Chrome traffic as well. For that I create the section "AppLayerVPNMapping" with the bundle IDs and DesignatedRequirement of Chrome.

The interesting thing is that I get the UDP traffic redirected to
  • (BOOL)handleNewUDPFlow:(NEAppProxyUDPFlow *)flow initialRemoteEndpoint:(NWEndpoint *)remoteEndpoint {}

which is promising. I do reject it by returning NO, because I'm interesting in the TCP protocol. Returning YES is not possible in my test scenario.

It is known that if rejected, Chrome passes to use TCP instead of UDP, I have tested it with another type of Proxy (transparent Proxy).

The result is that the TCP flow is not redirected to the NE and Chrome does not navigate.

Chrome is based in many processes so I have added the "helper" it uses to the list of apps.

I have used "NETestAppMapping" in info.plist but doesn't help.

This is the mobileconfig I'm using.
Anyone has succeeded to get complex apps traffic ?

Thanks !

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE plist PUBLIC "-...">

<plist version="1">

  <dict>

    <key>PayloadUUID</key>

    <string>DF2B6E99-2857-474B-B98A-3FF2E71E90C6</string>

    <key>PayloadType</key>

    <string>Configuration</string>

    <key>PayloadOrganization</key>

    <string>myorg</string>

    <key>PayloadIdentifier</key>

    <string>DF2B6E99-2857-474B-B98A-3FF2E71E90C6</string>

    <key>PayloadDisplayName</key>

    <string>NC1</string>

    <key>PayloadDescription</key>

    <string/>

    <key>PayloadVersion</key>

    <integer>1</integer>

    <key>PayloadEnabled</key>

    <true/>

    <key>PayloadRemovalDisallowed</key>

    <true/>

    <key>PayloadScope</key>

    <string>System</string>

    <key>PayloadContent</key>

    <array>

      <dict>

        <key>PayloadUUID</key>

        <string>A7F5A3B5-7E12-4E9D-8F9A-0355E9338F97</string>

        <key>PayloadType</key>

        <string>com.apple.vpn.managed.applayer</string>

        <key>PayloadOrganization</key>

        <string>myorg</string>

        <key>PayloadIdentifier</key>

        <string>A7F5A3B5-7E12-4E9D-8F9A-0355E9338F97</string>

        <key>PayloadDisplayName</key>

        <string>VPN</string>

        <key>PayloadDescription</key>

        <string/>

        <key>PayloadVersion</key>

        <integer>1</integer>

        <key>PayloadEnabled</key>

        <true/>

        <key>IPSec</key>

        <dict>

          <key>OnDemandEnabled</key>

          <integer>0</integer>

          <key>PromptForVPNPIN</key>

          <false/>

        </dict>

        <key>IPv4</key>

        <dict>

          <key>OverridePrimary</key>

          <integer>1</integer>

        </dict>

        <key>Proxies</key>

        <dict/>

        <key>UserDefinedName</key>

        <string>TestNC Jon</string>

        <key>VPN</key>

        <dict>

          <key>RemoteAddress</key>

          <string>myprod</string>

          <key>OnDemandUserOverrideDisabled</key>

          <integer>1</integer>

          <key>ExcludeLocalNetworks</key>

          <integer>0</integer>

          <key>AuthName</key>

          <string>user</string>

          <key>ProviderDesignatedRequirement</key>

          <string>identifier "com.myprod.ne" and anchor apple generic and certificate leaf[subject.CN] = "Mac Developer: me (xxxxxxxx)" and certificate 1[field.1.2.840.113635.100.6.2.1] /* exists */</string>

          <key>ProviderBundleIdentifier</key>

          <string>com.myprod.ne</string>

          <key>AuthenticationMethod</key>

          <string>Password</string>

          <key>ProviderType</key>

          <string>app-proxy</string>

          <key>IncludeAllNetworks</key>

          <integer>0</integer>

        </dict>

        <key>VPNType</key>

        <string>VPN</string>

        <key>VPNSubType</key>

        <string>com.myprod.ne-app</string>

        <key>VendorConfig</key>

        <dict/>

        <key>VPNUUID</key>

        <string>825886EA-BB00-4805-ADD6-1674C531669E</string>

        <key>OnDemandMatchAppEnabled</key>

        <true/>

        <key>SafariDomains</key>

        <array>

          <string>.com</string>

          <string>.net</string>

          <string>.org</string>

</array>

        <key>OnDemandUserOverrideDisabled</key>

        <integer>1</integer>

      </dict>

      <dict>

<key>PayloadUUID</key>

<string>A959CFCB-BABF-4819-B2A6-41F95926AF78</string>

<key>PayloadType</key>

<string>com.apple.vpn.managed.appmapping</string>

<key>PayloadIdentifier</key>

<string>com.apple.vpn.managed.appmapping.663DE2E8-0B7D-46D7-B1AE-331985F4082B</string>

<key>PayloadDescription</key>

<string>Configures TestApp-macOS to use the per-app VPN connection.</string>

<key>PayloadDisplayName</key>

<string>Per-App VPN App Mappings</string>

<key>PayloadVersion</key>

<integer>1</integer>

<key>AppLayerVPNMapping</key>

<array>

        <dict>

          <key>VPNUUID</key>

          <string>825886EA-BB00-4805-ADD6-1674C531669E</string>

          <key>Identifier</key>

          <string>com.google.Chrome</string>

          <key>SigningIdentifier</key>

          <string>com.google.Chrome</string>

          <key>DesignatedRequirement</key>

          <string>(identifier "com.google.Chrome" or identifier "com.google.Chrome.beta" or identifier "com.google.Chrome.dev" or identifier "com.google.Chrome.canary") and (certificate leaf = H"85cee8254216185620ddc8851c7a9fc4dfe120ef" or certificate leaf = H"c9a99324ca3fcb23dbcc36bd5fd4f9753305130a")</string>

        </dict>

        <dict>

          <key>VPNUUID</key>

          <string>825886EA-BB00-4805-ADD6-1674C531669E</string>

          <key>Identifier</key>

          <string>com.google.Chrome.helper</string>

          <key>SigningIdentifier</key>

          <string>com.google.Chrome.helper</string>

          <key>DesignatedRequirement</key>

          <string>(identifier "com.google.Chrome" or identifier "com.google.Chrome.beta" or identifier "com.google.Chrome.dev" or identifier "com.google.Chrome.canary") and (certificate leaf = H"85cee8254216185620ddc8851c7a9fc4dfe120ef" or certificate leaf = H"c9a99324ca3fcb23dbcc36bd5fd4f9753305130a")</string>

        </dict>

<dict>

  <key>Identifier</key>

  <string>org.mozilla.firefox</string>

  <key>VPNUUID</key>

  <string>825886EA-BB00-4805-ADD6-1674C531669E</string>

  <key>SigningIdentifier</key>

  <string>org.mozilla.firefox</string>

  <key>DesignatedRequirement</key>

  <string>anchor apple generic and certificate leaf[field.1.2.840.113635.100.6.1.9] /* exists */ or anchor apple generic and certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = "43AQ936H96"</string>

  </dict>

</array>

</dict>

    </array>

  </dict>

</plist>




 

Answered by JonOsoOndo in 668487022
Thanks Matt !

I see that Chrome is trying to reach port 53 (DNS)

0x232948           20:07:18.106075+0300 com.xxxx (2436633849): New flow: NEFlow type = datagram, app = com.google.Chrome, name = , address = 192.168.68.1, port = 53, filter_id = , interface = en0
0x232948           20:07:18.106439+0300 com.*** (2746240844): New flow: NEFlow type = datagram, app = com.google.Chrome, name = , address = 192.168.68.1, port = 53, filter_id = , interface = en0
0x232948           20:07:18.106847+0300 com.*** (1652494299): New flow: NEFlow type = datagram, app = com.google.Chrome, name = , address = 10.0.0.10, port = 53, filter_id = , interface = en0

I return non flow accepted. I do the same for 443 (in App-proxy, not per-app-proxy) and it falls back to TCP. But it seems it is not doing it for the 53 case (in Per-App).

I do have a NENetworkRule that accepts 443 to then reject it to force a TCP fallback.
I'm not sure why I get the 53 flow. I don't have any rule for it, and it seems it can be set in any rule.

According to .h, include exclude do not accept 53 (bellow) So I can't really enforce anything on 53.
In App-Proxy I do get only 443 UDP via a rule(includedNetworkRules). Do you think it is possible in Per-App-Proxy to do the same. Could it be that in Per-App sends everything to the NE and does not really use the NENetworkRules ?

/*!
  • @property includedNetworkRules
  • @discussion An array of NENetworkRule objects that collectively specify the traffic that will be routed through the transparent proxy. The following restrictions
  • apply to each NENetworkRule in this list:
  • Restrictions for rules with an address endpoint:
  • If the port string of the endpoint is "0" or is the empty string, then the address of the endpoint must be a non-wildcard address (i.e. "0.0.0.0" or "::").
  • If the address is a wildcard address (i.e. "0.0.0.0" or "::"), then the port string of the endpoint must be non-empty and must not be "0".
  • A port string of "53" is not allowed. Destination Domain-based rules must be used to match DNS traffic.
  • The matchLocalNetwork property must be nil.
  • The matchDirection property must be NETrafficDirectionOutbound.

 */

@property (copy, nullable) NSArray<NENetworkRule *> *includedNetworkRules API_AVAILABLE(macos(10.15)) API_UNAVAILABLE(ios, tvos) __WATCHOS_PROHIBITED;



/*!
  • @property excludedNetworkRules
  • @discussion An array of NENetworkRule objects that collectively specify the traffic that will not be routed through the transparent proxy. The following restrictions
  • apply to each NENetworkRule in this list:
  • Restrictions for rules with an address endpoint:
  • If the port string of the endpoint is "0" or is the empty string, then the address of the endpoint must be a non-wildcard address (i.e. "0.0.0.0" or "::").
  • If the address is a wildcard address (i.e. "0.0.0.0" or "::"), then the port string of the endpoint must be non-empty and must not be "0".
  • A port string of "53" is not allowed. Destination Domain-based rules must be used to match DNS traffic.
  • The matchLocalNetwork property must be nil.
  • The matchDirection property must be NETrafficDirectionOutbound.

 */

@property (copy, nullable) NSArray<NENetworkRule *> *excludedNetworkRules API_AVAILABLE(macos(10.15)) API_UNAVAILABLE(ios, tvos) __WATCHOS_PROHIBITED;





The good news here is that you are seeing UDP flows from Chrome then your traffic is getting to your NEAppProxyProvider.

Regarding:

The interesting thing is that I get the UDP traffic redirected to
(BOOL)handleNewUDPFlow:(NEAppProxyUDPFlow *)flow initialRemoteEndpoint:(NWEndpoint

  • )remoteEndpoint {}

which is promising. I do reject it by returning NO, because I'm interesting in the TCP
protocol. Returning YES is not possible in my test scenario.
It is known that if rejected, Chrome passes to use TCP instead of UDP, I have tested it with another type of Proxy (transparent Proxy).
If you are returning NO here then I would triple check that Chrome does fall back to TCP every time because this seems to me why you would not be getting TCP flows, but I have no idea how Chrome works under the hood. Now, if you can verify that Chrome is resorting to TCP every time then I would double check the TCP NENetworkRule's being set in NETransparentProxyNetworkSettings.


Matt Eaton
DTS Engineering, CoreOS
meaton3@apple.com
Accepted Answer
Thanks Matt !

I see that Chrome is trying to reach port 53 (DNS)

0x232948           20:07:18.106075+0300 com.xxxx (2436633849): New flow: NEFlow type = datagram, app = com.google.Chrome, name = , address = 192.168.68.1, port = 53, filter_id = , interface = en0
0x232948           20:07:18.106439+0300 com.*** (2746240844): New flow: NEFlow type = datagram, app = com.google.Chrome, name = , address = 192.168.68.1, port = 53, filter_id = , interface = en0
0x232948           20:07:18.106847+0300 com.*** (1652494299): New flow: NEFlow type = datagram, app = com.google.Chrome, name = , address = 10.0.0.10, port = 53, filter_id = , interface = en0

I return non flow accepted. I do the same for 443 (in App-proxy, not per-app-proxy) and it falls back to TCP. But it seems it is not doing it for the 53 case (in Per-App).

I do have a NENetworkRule that accepts 443 to then reject it to force a TCP fallback.
I'm not sure why I get the 53 flow. I don't have any rule for it, and it seems it can be set in any rule.

According to .h, include exclude do not accept 53 (bellow) So I can't really enforce anything on 53.
In App-Proxy I do get only 443 UDP via a rule(includedNetworkRules). Do you think it is possible in Per-App-Proxy to do the same. Could it be that in Per-App sends everything to the NE and does not really use the NENetworkRules ?

/*!
  • @property includedNetworkRules
  • @discussion An array of NENetworkRule objects that collectively specify the traffic that will be routed through the transparent proxy. The following restrictions
  • apply to each NENetworkRule in this list:
  • Restrictions for rules with an address endpoint:
  • If the port string of the endpoint is "0" or is the empty string, then the address of the endpoint must be a non-wildcard address (i.e. "0.0.0.0" or "::").
  • If the address is a wildcard address (i.e. "0.0.0.0" or "::"), then the port string of the endpoint must be non-empty and must not be "0".
  • A port string of "53" is not allowed. Destination Domain-based rules must be used to match DNS traffic.
  • The matchLocalNetwork property must be nil.
  • The matchDirection property must be NETrafficDirectionOutbound.

 */

@property (copy, nullable) NSArray<NENetworkRule *> *includedNetworkRules API_AVAILABLE(macos(10.15)) API_UNAVAILABLE(ios, tvos) __WATCHOS_PROHIBITED;



/*!
  • @property excludedNetworkRules
  • @discussion An array of NENetworkRule objects that collectively specify the traffic that will not be routed through the transparent proxy. The following restrictions
  • apply to each NENetworkRule in this list:
  • Restrictions for rules with an address endpoint:
  • If the port string of the endpoint is "0" or is the empty string, then the address of the endpoint must be a non-wildcard address (i.e. "0.0.0.0" or "::").
  • If the address is a wildcard address (i.e. "0.0.0.0" or "::"), then the port string of the endpoint must be non-empty and must not be "0".
  • A port string of "53" is not allowed. Destination Domain-based rules must be used to match DNS traffic.
  • The matchLocalNetwork property must be nil.
  • The matchDirection property must be NETrafficDirectionOutbound.

 */

@property (copy, nullable) NSArray<NENetworkRule *> *excludedNetworkRules API_AVAILABLE(macos(10.15)) API_UNAVAILABLE(ios, tvos) __WATCHOS_PROHIBITED;





It looks like you set this answer to accepted so I am not sure if you have resolved your issue or what the status here is. If you want to get port 53 traffic then try setting the remoteNetwork to nil in your NENetworkRule, that should get port 53 traffic.


Matt Eaton
DTS Engineering, CoreOS
meaton3@apple.com
Sorry, I set it as resolved by mistake, don't know how to undo it.

What I'm after is that the UDP 53 will not be redirected to the NE, or some way to tell the system to get the 53 requests directly out without sending them to the NE.

Getting the 53 traffic and rejecting it causes Chrome to drop the connection, it seems.

Is there any way to set network rules at the mobileleconfig level, there were we set the AppLayerVPNMapping ?

I am now researching this issue in another context.


Matt Eaton
DTS Engineering, CoreOS
meaton3@apple.com
Network Extension. Per-App VPN gets no traffic from Apps.
 
 
Q