createTCPConnectionThroughTunnel

In Low Power Mode or after the iPhone has been idle, On-Demand rules fire and StartTunnel runs. Subsequent calls to createTCPConnectionThroughTunnel intermittently fail with an error. Is NetworkExtension known to behave differently under Low Power Mode that might cause this?

Answered by DTS Engineer in 854743022

Yowsers! That’s so not valid code. createTCPConnectionThroughTunnel(…) is meant to be used asynchronously. Polling for completion like this a really bad idea.

It’s possible that this polling is cause the problem you’re seeing. One way that iOS reduces power in Low Power Mode is to reduce the resources it uses to run Dispatch queues [1]. Polling locks up resources, so it’s easy to imagine how you could get into trouble in this way.

However, I don’t think that’s the full story here. Your Task.sleep(…) should be sufficient to avoid such problems.

OTOH, polling is a really bad idea in general, so I’m gonna advise you to stop doing that first. That might fix the problem but, even if it doesn’t, it’s a step in the right direction.

There are two ways to avoid polling here. The first is to use a checked continuation, driving the continuation from a KVO observed on the state property of the NWTCPConnection.

The second is to drop NWTCPConnection completely. It’s part of a suite of APIs we call in-provider networking, and those are all deprecated in favour of Network framework. See TN3151 Choosing the right networking API for more on that.

Implementing the equivalent of createTCPConnectionThroughTunnel(…) using Network framework is a bit tricky. The thing you need, the virtualInterface property on NEPacketTunnelProvider, is only available on iOS 18 and later. So, if you’re planning to planning to raise your deployment target to iOS 18 in the near future, you can go down this path.h If not, you’ll have to stick with the Network Extension in-app networking APIs for the moment.

Note I have a lot more backstory about this in NWEndpoint History and Advice.

Share and Enjoy

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

[1] One extreme case of this is that it won’t run background queues at all in Low Power Mode. Or at least that’s how things worked the last time I looked at this.

Subsequent calls to createTCPConnectionThroughTunnel intermittently fail with an error.

What error?

Share and Enjoy

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

  func getTCPConnection(host: String, port: String, enableTLS: Bool) async throws -> NWTCPConnection? {
    let endpoint = NWHostEndpoint(hostname: host, port: port)
    // let enableTLS = true
    let connection = createTCPConnectionThroughTunnel(to: endpoint, enableTLS: enableTLS, tlsParameters: nil, delegate: self)
    
    var count = 0
    while connection.state != .connected {
      count += 1
      
      try await Task.sleep(nanoseconds: 0_500_000_000)
      
      if connection.state == .connected {
        return connection
      }
      
      if connection.state == .disconnected {
        self.flushLog("Connection state => disconnected. throw error")
        throw StateMonitoringError.connectionDisconnected
      }
      if count > 30 {
        self.flushLog("Connection time out")
        if let error = connection.error {
          self.flushLog("\(error)")
        }
        return nil
      }
    }
    return nil
  }

Previously, this function worked perfectly. However, when OnDemand Rules are set to true and the device is left idle for a long time (such as in Low Power Mode or with no activity), executing this function during the automatic reconnection process results in a connection timeout, because the connection takes too long to be established.

Yowsers! That’s so not valid code. createTCPConnectionThroughTunnel(…) is meant to be used asynchronously. Polling for completion like this a really bad idea.

It’s possible that this polling is cause the problem you’re seeing. One way that iOS reduces power in Low Power Mode is to reduce the resources it uses to run Dispatch queues [1]. Polling locks up resources, so it’s easy to imagine how you could get into trouble in this way.

However, I don’t think that’s the full story here. Your Task.sleep(…) should be sufficient to avoid such problems.

OTOH, polling is a really bad idea in general, so I’m gonna advise you to stop doing that first. That might fix the problem but, even if it doesn’t, it’s a step in the right direction.

There are two ways to avoid polling here. The first is to use a checked continuation, driving the continuation from a KVO observed on the state property of the NWTCPConnection.

The second is to drop NWTCPConnection completely. It’s part of a suite of APIs we call in-provider networking, and those are all deprecated in favour of Network framework. See TN3151 Choosing the right networking API for more on that.

Implementing the equivalent of createTCPConnectionThroughTunnel(…) using Network framework is a bit tricky. The thing you need, the virtualInterface property on NEPacketTunnelProvider, is only available on iOS 18 and later. So, if you’re planning to planning to raise your deployment target to iOS 18 in the near future, you can go down this path.h If not, you’ll have to stick with the Network Extension in-app networking APIs for the moment.

Note I have a lot more backstory about this in NWEndpoint History and Advice.

Share and Enjoy

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

[1] One extreme case of this is that it won’t run background queues at all in Low Power Mode. Or at least that’s how things worked the last time I looked at this.

createTCPConnectionThroughTunnel
 
 
Q