diff --git a/.jazzy.yaml b/.jazzy.yaml index d8abcdb..c96fb76 100644 --- a/.jazzy.yaml +++ b/.jazzy.yaml @@ -18,6 +18,7 @@ custom_categories: - LinkInterface - TunnelInterface - PacketStream + - BidirectionalState - SessionProxy - SessionProxyDelegate - SessionReply diff --git a/TunnelKit.xcodeproj/project.pbxproj b/TunnelKit.xcodeproj/project.pbxproj index 72fc35c..3289944 100644 --- a/TunnelKit.xcodeproj/project.pbxproj +++ b/TunnelKit.xcodeproj/project.pbxproj @@ -30,6 +30,8 @@ 0E1108B31F77B9F900A92462 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0E1108B21F77B9F900A92462 /* Assets.xcassets */; }; 0E1108B61F77B9F900A92462 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0E1108B41F77B9F900A92462 /* LaunchScreen.storyboard */; }; 0E12B2A32145341B00B4BAE9 /* PacketTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E12B2A22145341B00B4BAE9 /* PacketTests.swift */; }; + 0E12B2A521454F7F00B4BAE9 /* BidirectionalState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E12B2A421454F7F00B4BAE9 /* BidirectionalState.swift */; }; + 0E12B2A621454F7F00B4BAE9 /* BidirectionalState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E12B2A421454F7F00B4BAE9 /* BidirectionalState.swift */; }; 0E245D692135972800B012A2 /* PushTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E245D682135972800B012A2 /* PushTests.swift */; }; 0E245D6C2137F73600B012A2 /* CompressionFramingNative.h in Headers */ = {isa = PBXBuildFile; fileRef = 0E245D6B2137F73600B012A2 /* CompressionFramingNative.h */; }; 0E39BCE8214B2AB60035E9DE /* ControlPacket.h in Headers */ = {isa = PBXBuildFile; fileRef = 0E39BCE6214B2AB60035E9DE /* ControlPacket.h */; }; @@ -195,6 +197,7 @@ 0E1108B51F77B9F900A92462 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 0E1108B71F77B9F900A92462 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 0E12B2A22145341B00B4BAE9 /* PacketTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PacketTests.swift; sourceTree = ""; }; + 0E12B2A421454F7F00B4BAE9 /* BidirectionalState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BidirectionalState.swift; sourceTree = ""; }; 0E17D7F91F730D9F009EE129 /* TunnelKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = TunnelKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 0E245D682135972800B012A2 /* PushTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PushTests.swift; sourceTree = ""; }; 0E245D6B2137F73600B012A2 /* CompressionFramingNative.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CompressionFramingNative.h; sourceTree = ""; }; @@ -424,6 +427,7 @@ children = ( 0EFEB42E2006D3C800F81029 /* Allocation.h */, 0EFEB4462006D3C800F81029 /* Allocation.m */, + 0E12B2A421454F7F00B4BAE9 /* BidirectionalState.swift */, 0E245D6B2137F73600B012A2 /* CompressionFramingNative.h */, 0E39BCE6214B2AB60035E9DE /* ControlPacket.h */, 0E39BCE7214B2AB60035E9DE /* ControlPacket.m */, @@ -884,6 +888,7 @@ 0EFEB4782006D3C800F81029 /* TunnelKitProvider+Configuration.swift in Sources */, 0E3E0F212108A8CC00B371C1 /* SessionProxy+PushReply.swift in Sources */, 0EFEB4752006D3C800F81029 /* Errors.m in Sources */, + 0E12B2A521454F7F00B4BAE9 /* BidirectionalState.swift in Sources */, 0EBBF2E52084FE6F00E36B40 /* GenericSocket.swift in Sources */, 0EFEB4762006D3C800F81029 /* DataPath.m in Sources */, 0E0C2127212ED29D008AB282 /* SessionProxy+Configuration.swift in Sources */, @@ -936,6 +941,7 @@ 0EFEB4A42006D7F300F81029 /* DataPath.m in Sources */, 0EBBF2E62084FE6F00E36B40 /* GenericSocket.swift in Sources */, 0E3E0F222108A8CC00B371C1 /* SessionProxy+PushReply.swift in Sources */, + 0E12B2A621454F7F00B4BAE9 /* BidirectionalState.swift in Sources */, 0EFEB4912006D7F300F81029 /* TLSBox.m in Sources */, 0EFEB49D2006D7F300F81029 /* IOInterface.swift in Sources */, 0E0C2128212ED29D008AB282 /* SessionProxy+Configuration.swift in Sources */, diff --git a/TunnelKit/Sources/AppExtension/TunnelKitProvider.swift b/TunnelKit/Sources/AppExtension/TunnelKitProvider.swift index 932b1d2..5bc5af2 100644 --- a/TunnelKit/Sources/AppExtension/TunnelKitProvider.swift +++ b/TunnelKit/Sources/AppExtension/TunnelKitProvider.swift @@ -281,8 +281,8 @@ open class TunnelKitProvider: NEPacketTunnelProvider { case .dataCount: if let proxy = proxy { response = Data() - response?.append(UInt64(proxy.bytesIn)) - response?.append(UInt64(proxy.bytesOut)) + response?.append(UInt64(proxy.bytesCount.inbound)) + response?.append(UInt64(proxy.bytesCount.outbound)) } default: diff --git a/TunnelKit/Sources/Core/BidirectionalState.swift b/TunnelKit/Sources/Core/BidirectionalState.swift new file mode 100644 index 0000000..a5da822 --- /dev/null +++ b/TunnelKit/Sources/Core/BidirectionalState.swift @@ -0,0 +1,65 @@ +// +// BidirectionalState.swift +// TunnelKit +// +// Created by Davide De Rosa on 9/9/18. +// Copyright (c) 2018 Davide De Rosa. All rights reserved. +// +// https://github.com/keeshux +// +// This file is part of TunnelKit. +// +// TunnelKit is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// TunnelKit is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with TunnelKit. If not, see . +// + +import Foundation + +/// A generic structure holding a pair of inbound/outbound states. +public class BidirectionalState { + private let resetValue: T + + /// The inbound state. + public var inbound: T + + /// The outbound state. + public var outbound: T + + /** + Returns current state as a pair. + + - Returns: Current state as a pair, inbound first. + */ + public var pair: (T, T) { + return (inbound, outbound) + } + + /** + Inits state with a value that will later be reused by `reset()`. + + - Parameter value: The value to initialize with and reset to. + */ + public init(withResetValue value: T) { + inbound = value + outbound = value + resetValue = value + } + + /** + Resets state to the value provided with `init(withResetValue:)`. + */ + public func reset() { + inbound = resetValue + outbound = resetValue + } +} diff --git a/TunnelKit/Sources/Core/SessionProxy.swift b/TunnelKit/Sources/Core/SessionProxy.swift index a90e742..5b9916c 100644 --- a/TunnelKit/Sources/Core/SessionProxy.swift +++ b/TunnelKit/Sources/Core/SessionProxy.swift @@ -146,9 +146,7 @@ public class SessionProxy { private var connectedDate: Date? - private var lastPingOut: Date - - private var lastPingIn: Date + private var lastPing: BidirectionalState private var isStopping: Bool @@ -159,23 +157,17 @@ public class SessionProxy { private let controlPlainBuffer: ZeroingData - private var controlQueueOut: [ControlPacket] - - private var controlQueueIn: [ControlPacket] + private var controlQueue: BidirectionalState<[ControlPacket]> private var controlPendingAcks: Set - private var controlPacketIdOut: UInt32 - - private var controlPacketIdIn: UInt32 + private var controlPacketId: BidirectionalState private var authenticator: Authenticator? // MARK: Data - private(set) var bytesIn: Int - - private(set) var bytesOut: Int + private(set) var bytesCount: BidirectionalState // MARK: Init @@ -192,18 +184,14 @@ public class SessionProxy { keys = [:] oldKeys = [] negotiationKeyIdx = 0 - lastPingOut = Date.distantPast - lastPingIn = Date.distantPast + lastPing = BidirectionalState(withResetValue: Date.distantPast) isStopping = false controlPlainBuffer = Z(count: TLSBoxMaxBufferLength) - controlQueueOut = [] - controlQueueIn = [] + controlQueue = BidirectionalState(withResetValue: []) controlPendingAcks = [] - controlPacketIdOut = 0 - controlPacketIdIn = 0 - bytesIn = 0 - bytesOut = 0 + controlPacketId = BidirectionalState(withResetValue: 0) + bytesCount = BidirectionalState(withResetValue: 0) } deinit { @@ -433,7 +421,7 @@ public class SessionProxy { return } - lastPingIn = Date() + lastPing.inbound = Date() var dataPacketsByKey = [UInt8: [Data]]() @@ -545,22 +533,22 @@ public class SessionProxy { } let controlPacket = ControlPacket(code: code, key: key, sessionId: sessionId, packetId: packetId, payload: payload) - controlQueueIn.append(controlPacket) - controlQueueIn.sort { $0.packetId < $1.packetId } + controlQueue.inbound.append(controlPacket) + controlQueue.inbound.sort { $0.packetId < $1.packetId } - for queuedControlPacket in controlQueueIn { - if (queuedControlPacket.packetId < controlPacketIdIn) { - controlQueueIn.removeFirst() + for queuedControlPacket in controlQueue.inbound { + if (queuedControlPacket.packetId < controlPacketId.inbound) { + controlQueue.inbound.removeFirst() continue } - if (queuedControlPacket.packetId != controlPacketIdIn) { + if (queuedControlPacket.packetId != controlPacketId.inbound) { continue } handleControlPacket(queuedControlPacket) - controlPacketIdIn += 1 - controlQueueIn.removeFirst() + controlPacketId.inbound += 1 + controlQueue.inbound.removeFirst() } } @@ -580,7 +568,7 @@ public class SessionProxy { return } sendDataPackets(packets) - lastPingOut = Date() + lastPing.outbound = Date() } // Ruby: ping @@ -590,14 +578,14 @@ public class SessionProxy { } let now = Date() - guard (now.timeIntervalSince(lastPingIn) <= CoreConfiguration.pingTimeout) else { + guard (now.timeIntervalSince(lastPing.inbound) <= CoreConfiguration.pingTimeout) else { deferStop(.shutdown, SessionError.pingTimeout) return } // postpone ping if elapsed less than keep-alive if let interval = keepAliveInterval { - let elapsed = now.timeIntervalSince(lastPingOut) + let elapsed = now.timeIntervalSince(lastPing.outbound) guard (elapsed >= interval) else { scheduleNextPing(elapsed: elapsed) return @@ -606,7 +594,7 @@ public class SessionProxy { log.debug("Send ping") sendDataPackets([DataPacket.pingString]) - lastPingOut = Date() + lastPing.outbound = Date() scheduleNextPing() } @@ -626,14 +614,11 @@ public class SessionProxy { // Ruby: reset_ctrl private func resetControlChannel() { controlPlainBuffer.zero() - controlQueueOut.removeAll() - controlQueueIn.removeAll() + controlQueue.reset() controlPendingAcks.removeAll() - controlPacketIdOut = 0 - controlPacketIdIn = 0 + controlPacketId.reset() authenticator = nil - bytesIn = 0 - bytesOut = 0 + bytesCount.reset() } // Ruby: hard_reset @@ -942,7 +927,7 @@ public class SessionProxy { fatalError("Missing sessionId, do hardReset() first") } - let oldIdOut = controlPacketIdOut + let oldIdOut = controlPacketId.outbound let maxCount = link.mtu var queuedCount = 0 var offset = 0 @@ -950,19 +935,19 @@ public class SessionProxy { repeat { let subPayloadLength = min(maxCount, payload.count - offset) let subPayloadData = payload.subdata(offset: offset, count: subPayloadLength) - let packet = ControlPacket(code: code, key: key, sessionId: sessionId, packetId: controlPacketIdOut, payload: subPayloadData) + let packet = ControlPacket(code: code, key: key, sessionId: sessionId, packetId: controlPacketId.outbound, payload: subPayloadData) - controlQueueOut.append(packet) - controlPacketIdOut += 1 + controlQueue.outbound.append(packet) + controlPacketId.outbound += 1 offset += maxCount queuedCount += subPayloadLength } while (offset < payload.count) assert(queuedCount == payload.count) - let packetCount = controlPacketIdOut - oldIdOut + let packetCount = controlPacketId.outbound - oldIdOut if (packetCount > 1) { - log.debug("Enqueued \(packetCount) control packets [\(oldIdOut)-\(controlPacketIdOut - 1)]") + log.debug("Enqueued \(packetCount) control packets [\(oldIdOut)-\(controlPacketId.outbound - 1)]") } else { log.debug("Enqueued 1 control packet [\(oldIdOut)]") } @@ -972,7 +957,7 @@ public class SessionProxy { // Ruby: flush_ctrl_q_out private func flushControlQueue() { - for controlPacket in controlQueueOut { + for controlPacket in controlQueue.outbound { if let sentDate = controlPacket.sentDate { let timeAgo = -sentDate.timeIntervalSinceNow guard (timeAgo >= CoreConfiguration.retransmissionLimit) else { @@ -1083,7 +1068,7 @@ public class SessionProxy { // Ruby: handle_data_pkt private func handleDataPackets(_ packets: [Data], key: SessionKey) { - bytesIn += packets.flatCount + bytesCount.inbound += packets.flatCount do { guard let decryptedPackets = try key.decrypt(packets: packets) else { log.warning("Could not decrypt packets, is SessionKey properly configured (dataPath, peerId)?") @@ -1118,7 +1103,7 @@ public class SessionProxy { } // WARNING: runs in Network.framework queue - bytesOut += encryptedPackets.flatCount + bytesCount.outbound += encryptedPackets.flatCount link?.writePackets(encryptedPackets) { [weak self] (error) in if let error = error { self?.queue.sync { @@ -1151,9 +1136,9 @@ public class SessionProxy { } // drop queued out packets if ack-ed - for (i, controlPacket) in controlQueueOut.enumerated() { + for (i, controlPacket) in controlQueue.outbound.enumerated() { if packetIds.contains(controlPacket.packetId) { - controlQueueOut.remove(at: i) + controlQueue.outbound.remove(at: i) } }