diff --git a/TunnelKit/Sources/Core/SessionProxy+PushReply.swift b/TunnelKit/Sources/Core/SessionProxy+PushReply.swift index 9a88a05..4fc87a6 100644 --- a/TunnelKit/Sources/Core/SessionProxy+PushReply.swift +++ b/TunnelKit/Sources/Core/SessionProxy+PushReply.swift @@ -150,6 +150,9 @@ public protocol SessionReply { /// The optional compression framing. var compressionFraming: SessionProxy.CompressionFraming? { get } + /// The optional keep-alive interval. + var ping: Int? { get } + /// The optional authentication token. var authToken: String? { get } @@ -191,6 +194,8 @@ extension SessionProxy { private static let compRegexp = try! NSRegularExpression(pattern: "comp(ress|-lzo)", options: []) + private static let pingRegexp = try! NSRegularExpression(pattern: "ping \\d+", options: []) + private static let authTokenRegexp = try! NSRegularExpression(pattern: "auth-token [a-zA-Z0-9/=+]+", options: []) private static let peerIdRegexp = try! NSRegularExpression(pattern: "peer-id [0-9]+", options: []) @@ -207,6 +212,8 @@ extension SessionProxy { let compressionFraming: SessionProxy.CompressionFraming? + let ping: Int? + let authToken: String? let peerId: UInt32? @@ -232,6 +239,7 @@ extension SessionProxy { var dnsServers: [String] = [] var compressionFraming: SessionProxy.CompressionFraming? + var ping: Int? var authToken: String? var peerId: UInt32? var cipher: SessionProxy.Cipher? @@ -388,6 +396,12 @@ extension SessionProxy { } } + // MARK: Keep-alive + + PushReply.pingRegexp.enumerateArguments(in: message) { + ping = Int($0[0]) + } + // MARK: Authentication PushReply.authTokenRegexp.enumerateArguments(in: message) { @@ -406,6 +420,7 @@ extension SessionProxy { self.dnsServers = dnsServers self.compressionFraming = compressionFraming + self.ping = ping self.authToken = authToken self.peerId = peerId self.cipher = cipher diff --git a/TunnelKit/Sources/Core/SessionProxy.swift b/TunnelKit/Sources/Core/SessionProxy.swift index 3e5096a..a8120ab 100644 --- a/TunnelKit/Sources/Core/SessionProxy.swift +++ b/TunnelKit/Sources/Core/SessionProxy.swift @@ -85,6 +85,18 @@ public class SessionProxy { private let configuration: Configuration + private var keepAliveInterval: TimeInterval? { + let interval: TimeInterval? + if let negInterval = pushReply?.ping, negInterval > 0 { + interval = TimeInterval(negInterval) + } else if let cfgInterval = configuration.keepAliveInterval, cfgInterval > 0.0 { + interval = cfgInterval + } else { + return nil + } + return interval + } + /// An optional `SessionProxyDelegate` for receiving session events. public weak var delegate: SessionProxyDelegate? @@ -583,13 +595,11 @@ public class SessionProxy { return } - if let interval = configuration.keepAliveInterval, interval > 0 { + // postpone ping if elapsed less than keep-alive + if let interval = keepAliveInterval { let elapsed = now.timeIntervalSince(lastPingOut) guard (elapsed >= interval) else { - let remaining = min(interval, interval - elapsed) - queue.asyncAfter(deadline: .now() + remaining) { [weak self] in - self?.ping() - } + scheduleNextPing(elapsed: elapsed) return } } @@ -598,10 +608,16 @@ public class SessionProxy { sendDataPackets([DataPacket.pingString]) lastPingOut = Date() - if let interval = configuration.keepAliveInterval, interval > 0 { - queue.asyncAfter(deadline: .now() + interval) { [weak self] in - self?.ping() - } + scheduleNextPing() + } + + private func scheduleNextPing(elapsed: TimeInterval = 0.0) { + guard let interval = keepAliveInterval else { + return + } + let remaining = min(interval, interval - elapsed) + queue.asyncAfter(deadline: .now() + remaining) { [weak self] in + self?.ping() } } @@ -904,11 +920,7 @@ public class SessionProxy { } delegate?.sessionDidStart(self, remoteAddress: remoteAddress, reply: reply) - if let interval = configuration.keepAliveInterval, interval > 0 { - queue.asyncAfter(deadline: .now() + interval) { [weak self] in - self?.ping() - } - } + scheduleNextPing() } // Ruby: transition_keys @@ -1040,6 +1052,9 @@ public class SessionProxy { if let negFraming = pushedFraming { log.debug("Negotiated compression framing: \(negFraming.rawValue)") } + if let negPing = pushReply.ping { + log.debug("Negotiated keep-alive: \(negPing) seconds") + } let pushedCipher = pushReply.cipher if let negCipher = pushedCipher { log.debug("Negotiated cipher: \(negCipher.rawValue)") diff --git a/TunnelKitTests/PushTests.swift b/TunnelKitTests/PushTests.swift index 189712b..e60fab2 100644 --- a/TunnelKitTests/PushTests.swift +++ b/TunnelKitTests/PushTests.swift @@ -115,4 +115,12 @@ class PushTests: XCTestCase { XCTAssertEqual(reply.cipher, .aes256gcm) } + + func testPing() { + let msg = "PUSH_REPLY,route 192.168.1.0 255.255.255.0,route 10.0.2.0 255.255.255.0,dhcp-option DNS 192.168.1.99,dhcp-option DNS 176.103.130.130,route 10.0.2.1,topology net30,ping 10,ping-restart 60,ifconfig 10.0.2.14 10.0.2.13" + let reply = try! SessionProxy.PushReply(message: msg)! + reply.debug() + + XCTAssertEqual(reply.ping, 10) + } }