Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions ios/PacketTunnel/PacketTunnelProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ final class PacketTunnelProvider: NEPacketTunnelProvider {
private var lastPathKey = ""
private var lastPhysicalIP = ""
private var ignorePathChangesUntil = Date.distantPast
private var zeroRTTTicks = 0

override func startTunnel(options: [String: NSObject]?, completionHandler: @escaping (Error?) -> Void) {
// A Packet Tunnel is jetsam-killed around 15 MB. Cap the Go heap before
Expand Down Expand Up @@ -67,6 +68,7 @@ final class PacketTunnelProvider: NEPacketTunnelProvider {
stopping = false
reconnecting = false
everDialed = false
zeroRTTTicks = 0
routesApplied = false
AppGroup.defaults.removeObject(forKey: AppGroup.defaultsLastError)

Expand Down Expand Up @@ -812,6 +814,21 @@ final class PacketTunnelProvider: NEPacketTunnelProvider {
}
}

/// iOS freezes this process and QUIC then dies after three quiet minutes.
/// wake() is the only signal that the process is running again.
override func wake() {
workQueue.async { [weak self] in
guard let self, !self.stopping, self.startCompleted, self.everDialed else { return }
guard Date() > self.ignorePathChangesUntil else { return }
let udpReady = self.udpSession?.state == .ready
let rtt = self.tunnel?.rttMillis() ?? 0
if self.tunnel == nil || !udpReady || rtt == 0 {
self.zeroRTTTicks = 0
self.scheduleReconnect("extension woke")
}
}
}

override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
workQueue.async { [weak self] in
guard let self else {
Expand Down Expand Up @@ -909,6 +926,21 @@ final class PacketTunnelProvider: NEPacketTunnelProvider {
if ms > 0 {
AppGroup.defaults.set(Int(ms), forKey: AppGroup.defaultsPing)
}
self.workQueue.async {
guard !self.stopping, self.everDialed, !self.reconnecting else { return }
guard Date() > self.ignorePathChangesUntil else { return }
if ms > 0 {
self.zeroRTTTicks = 0
return
}
self.zeroRTTTicks += 1
// QUIC keepalive is 15s. A live session keeps a non-zero RTT.
// Zero for half a minute means the socket died while we slept.
if self.zeroRTTTicks >= 15 {
self.zeroRTTTicks = 0
self.scheduleReconnect("QUIC went silent")
}
}
let total = t.trafficBytes()
if total > 0 {
TrafficAccount.note(total: Int(total))
Expand Down
2 changes: 1 addition & 1 deletion ios/fastlane/Fastfile
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ platform :ios do
upload_to_testflight(
api_key: api_key,
skip_waiting_for_build_processing: true,
changelog: "2.1.1: same 2.1.0 UDP tunnel. Opening another app no longer tears the session down."
changelog: "2.1.1: same 2.1.0 UDP tunnel. After the phone sleeps, a dead QUIC session dials again."
)
end
end
Loading