Issues with VOIP Calls on iOS Local Proxy Server using NEPacketTunnelProvider

Hi there! I recently used SwiftNIO example - Connect Proxy with NEPacketTunnelProvider to set up a local proxy server which support HTTPS proxy. I also referenced this link to simultaneously support HTTP proxy. It works great when opening web pages in browsers like Safari or Chrome!

However, when I use some instant messenger app to make VOIP calls, I can't make successful calls. What could be the possible reason for this? I want my proxy server to intercept only HTTP/HTTPS requests but not interfere with or block VOIP calls. Is there any configuration parameter that can achieve this?

I'm sorry, but I don't have a strong background in proxy servers and VOIP. I would greatly appreciate any guidance or insights.

Here is how I start Packet Tunnel and Proxy Server:

let tunnelNetworkSettings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: localhostIP)

let proxySettings = NEProxySettings()
proxySettings.httpServer = NEProxyServer(address: localhostIP, port: localhostHttpPort)
proxySettings.httpsServer = NEProxyServer(address: localhostIP, port: localhostHttpsPort)
proxySettings.autoProxyConfigurationEnabled = false
proxySettings.httpEnabled = true
proxySettings.httpsEnabled = true
proxySettings.excludeSimpleHostnames = true
proxySettings.exceptionList = ["192.168.0.0/16", "10.0.0.0/8", "172.16.0.0/12", "127.0.0.1", "localhost", "*.local"]
tunnelNetworkSettings.proxySettings = proxySettings

tunnelNetworkSettings.ipv4Settings = NEIPv4Settings(addresses: ["10.8.0.2"], subnetMasks: ["255.255.255.0"])
tunnelNetworkSettings.mtu = 1500
let dns = NEDNSSettings(servers: ["8.8.8.8"])
dns.matchDomains = [""]
tunnelNetworkSettings.dnsSettings = dns
tunnelNetworkSettings.ipv4Settings?.includedRoutes = [NEIPv4Route.default()]
tunnelNetworkSettings.ipv4Settings?.excludedRoutes = [
    NEIPv4Route(destinationAddress: "192.168.0.0", subnetMask: "255.255.0.0"),
    NEIPv4Route(destinationAddress: "10.0.0.0", subnetMask: "255.0.0.0"),
    NEIPv4Route(destinationAddress: "172.16.0.0", subnetMask: "255.240.0.0")
]

setTunnelNetworkSettings(tunnelNetworkSettings) { error in
    self.pendingCompletion?(error)
    self.pendingCompletion = nil
}

And how I've used SwiftNIO:

let group = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount)
let bootstrap = ServerBootstrap(group: group)
    .serverChannelOption(ChannelOptions.socket(SOL_SOCKET, SO_REUSEADDR), value: 1)
    .childChannelOption(ChannelOptions.socket(SOL_SOCKET, SO_REUSEADDR), value: 1)
    .childChannelInitializer { channel in
        channel.pipeline.addHandler(ByteToMessageHandler(HTTPRequestDecoder(leftOverBytesStrategy: .forwardBytes)))
            .flatMap {
                channel.pipeline
                    .addHandler(HTTPResponseEncoder())
            }
            .flatMap {
                if channel.localAddress?.port == self.localhostHttpPort {
                    channel.pipeline
                        .addHandler(ConnectHttpHandler())
                } else {
                    channel.pipeline
                        .addHandler(ConnectHttpsHandler())
                }
            }
    }
Answered by DTS Engineer in 771394022

Most folks who hit issues like this are trying to twist the NE packet tunnel architecture into doing something that it was not intended to do. DTS sees a lot of this, so much so that my colleague (at the time) wrote TN3120 Expected use cases for Network Extension packet tunnel providers. Packet tunnel providers are intended to… well… tunnel packets. Folks who use it for that purpose rarely run into problems like this.

What could be the possible reason for this?

This is failing because you claim the default route:

tunnelNetworkSettings.ipv4Settings?.includedRoutes = [NEIPv4Route.default()]

When you do that, the system expects you to be able to forward packets to any destination on the Internet. Presumably you don’t do that, and so various networking apps start to fail.

The only reason Safari works is that your VPN also sets up a proxy server. Safari uses URLSession which has built-in proxy support. So, its HTTP[S] requests get routed to your local proxy, which allows them to work.

If the user runs an app that’s not compatible with an HTTP[S] proxy, the app’s traffic will be sent as packets to your packet tunnel provider. You have no way to forward those, and so things fail.

You’ve noticed this with VoIP apps but there are numerous other apps that’ll have the same problem.

How do you fix that? Well, that’s tricky, and it very much depends on your goals. You wrote:

I want my proxy server to intercept only HTTP/HTTPS requests but not interfere with or block VOIP calls.

It sounds like you’re trying to build a debugging HTTP[S] proxy. It’s possible to do that — there are some popular products out there — but I can’t help you with it. It’s not an expected use case for packet tunnel providers and, as per TN3120, that means it’s something that DTS doesn’t support.

IMPORTANT I work for Developer Technical Support (DTS) and so “support” in the above paragraph refers to what DTS supports. I can’t make definitive statements about the policy of other groups at Apple, most notably App Review.

Share and Enjoy

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

Accepted Answer

Most folks who hit issues like this are trying to twist the NE packet tunnel architecture into doing something that it was not intended to do. DTS sees a lot of this, so much so that my colleague (at the time) wrote TN3120 Expected use cases for Network Extension packet tunnel providers. Packet tunnel providers are intended to… well… tunnel packets. Folks who use it for that purpose rarely run into problems like this.

What could be the possible reason for this?

This is failing because you claim the default route:

tunnelNetworkSettings.ipv4Settings?.includedRoutes = [NEIPv4Route.default()]

When you do that, the system expects you to be able to forward packets to any destination on the Internet. Presumably you don’t do that, and so various networking apps start to fail.

The only reason Safari works is that your VPN also sets up a proxy server. Safari uses URLSession which has built-in proxy support. So, its HTTP[S] requests get routed to your local proxy, which allows them to work.

If the user runs an app that’s not compatible with an HTTP[S] proxy, the app’s traffic will be sent as packets to your packet tunnel provider. You have no way to forward those, and so things fail.

You’ve noticed this with VoIP apps but there are numerous other apps that’ll have the same problem.

How do you fix that? Well, that’s tricky, and it very much depends on your goals. You wrote:

I want my proxy server to intercept only HTTP/HTTPS requests but not interfere with or block VOIP calls.

It sounds like you’re trying to build a debugging HTTP[S] proxy. It’s possible to do that — there are some popular products out there — but I can’t help you with it. It’s not an expected use case for packet tunnel providers and, as per TN3120, that means it’s something that DTS doesn’t support.

IMPORTANT I work for Developer Technical Support (DTS) and so “support” in the above paragraph refers to what DTS supports. I can’t make definitive statements about the policy of other groups at Apple, most notably App Review.

Share and Enjoy

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

Thank you very much for your detailed explanation. This gives me a deeper understanding of packet tunnel and proxy server!

Overall, I want to achieve something like the role of NEFilterPacketProvider: helping users block unsafe/scam websites without the need to parse what content users have been browsing, but simply knowing which websites users have visited.

I would like to try using NEFilterPacketProvider and not be limited to supervised devices. After reading the article you provided, there is a section that mentions:

In an unmanaged environment, deploy your content filter as part of a Screen Time app.

Perhaps the Screen Time Framework can meet my requirements?

Hi @eskimo,

I've read the Screen Time API documentation, and it seems that users need to enable the Family Sharing feature to use this API, which doesn't appear to be what I'm looking for. I tried deleting tunnelNetworkSettings.ipv4Settings?.includedRoutes = [NEIPv4Route.default()], and as a result, VoIP worked successfully, but streaming failed.

As you mentioned, using the packet tunnel in this way is not a recommended practice. I would appreciate if you have any alternative suggestions to meet my requirements.

I would like to try using NEFilterPacketProvider and not be limited to supervised devices.

To be clear, filter packet providers are a macOS only thing. On iOS you only have content filter providers.

Perhaps the Screen Time framework can meet my requirements?

Probably not. See this post for info on this exception.

[And a bug number tracking a to-do list item for me to update TN3134 with that info.]

Share and Enjoy

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

This is failing because you claim the default route:

tunnelNetworkSettings.ipv4Settings?.includedRoutes = [NEIPv4Route.default()]

When you do that, the system expects you to be able to forward packets to any destination on the Internet. Presumably you don’t do that, and so various networking apps start to fail.

This is the root cause of my problem!

After deleting this line and fixing the bugs in the HTTP/HTTPS proxy (HTTP redirecting to HTTPS URLs, which was causing streaming videos to fail), it now works like a charm! Thanks again for your help! : )

Issues with VOIP Calls on iOS Local Proxy Server using NEPacketTunnelProvider
 
 
Q