Merge branch 'refactor-control-channel'
This commit is contained in:
commit
9d6c7c846f
@ -17,8 +17,13 @@ custom_categories:
|
||||
- IOInterface
|
||||
- LinkInterface
|
||||
- TunnelInterface
|
||||
- PacketStream
|
||||
- BidirectionalState
|
||||
- SessionProxy
|
||||
- SessionProxyDelegate
|
||||
- SessionReply
|
||||
- IPv4Settings
|
||||
- IPv6Settings
|
||||
- SessionError
|
||||
- name: AppExtension
|
||||
children:
|
||||
|
@ -29,8 +29,19 @@
|
||||
0E1108B11F77B9F900A92462 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0E1108AF1F77B9F900A92462 /* Main.storyboard */; };
|
||||
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 */; };
|
||||
0E12B2A821456C0200B4BAE9 /* ControlChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E12B2A721456C0200B4BAE9 /* ControlChannel.swift */; };
|
||||
0E12B2A921456C0200B4BAE9 /* ControlChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E12B2A721456C0200B4BAE9 /* ControlChannel.swift */; };
|
||||
0E12B2AB2145E01700B4BAE9 /* ControlChannelSerializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E12B2AA2145E01700B4BAE9 /* ControlChannelSerializer.swift */; };
|
||||
0E12B2AC2145E01700B4BAE9 /* ControlChannelSerializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E12B2AA2145E01700B4BAE9 /* ControlChannelSerializer.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 */; };
|
||||
0E39BCE9214B2AB60035E9DE /* ControlPacket.h in Headers */ = {isa = PBXBuildFile; fileRef = 0E39BCE6214B2AB60035E9DE /* ControlPacket.h */; };
|
||||
0E39BCEA214B2AB60035E9DE /* ControlPacket.m in Sources */ = {isa = PBXBuildFile; fileRef = 0E39BCE7214B2AB60035E9DE /* ControlPacket.m */; };
|
||||
0E39BCEB214B2AB60035E9DE /* ControlPacket.m in Sources */ = {isa = PBXBuildFile; fileRef = 0E39BCE7214B2AB60035E9DE /* ControlPacket.m */; };
|
||||
0E3E0F212108A8CC00B371C1 /* SessionProxy+PushReply.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3E0F202108A8CC00B371C1 /* SessionProxy+PushReply.swift */; };
|
||||
0E3E0F222108A8CC00B371C1 /* SessionProxy+PushReply.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3E0F202108A8CC00B371C1 /* SessionProxy+PushReply.swift */; };
|
||||
0E58F1302138AC2F00A49F27 /* DNSTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E58F12F2138AC2F00A49F27 /* DNSTests.swift */; };
|
||||
@ -189,10 +200,16 @@
|
||||
0E1108B21F77B9F900A92462 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
0E1108B51F77B9F900A92462 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
0E1108B71F77B9F900A92462 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
0E12B2A22145341B00B4BAE9 /* PacketTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PacketTests.swift; sourceTree = "<group>"; };
|
||||
0E12B2A421454F7F00B4BAE9 /* BidirectionalState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BidirectionalState.swift; sourceTree = "<group>"; };
|
||||
0E12B2A721456C0200B4BAE9 /* ControlChannel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ControlChannel.swift; sourceTree = "<group>"; };
|
||||
0E12B2AA2145E01700B4BAE9 /* ControlChannelSerializer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ControlChannelSerializer.swift; sourceTree = "<group>"; };
|
||||
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 = "<group>"; };
|
||||
0E245D6B2137F73600B012A2 /* CompressionFramingNative.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CompressionFramingNative.h; sourceTree = "<group>"; };
|
||||
0E3251C51F95770D00C108D9 /* TunnelKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = TunnelKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
0E39BCE6214B2AB60035E9DE /* ControlPacket.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ControlPacket.h; sourceTree = "<group>"; };
|
||||
0E39BCE7214B2AB60035E9DE /* ControlPacket.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ControlPacket.m; sourceTree = "<group>"; };
|
||||
0E3E0F202108A8CC00B371C1 /* SessionProxy+PushReply.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionProxy+PushReply.swift"; sourceTree = "<group>"; };
|
||||
0E58F12F2138AC2F00A49F27 /* DNSTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DNSTests.swift; sourceTree = "<group>"; };
|
||||
0E6479DD212EAC96008E6888 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
@ -318,6 +335,7 @@
|
||||
0EB2B45E20F0C098004233D7 /* EncryptionPerformanceTests.swift */,
|
||||
0EB2B45220F0BB44004233D7 /* EncryptionTests.swift */,
|
||||
0EB2B45820F0BD9A004233D7 /* LinkTests.swift */,
|
||||
0E12B2A22145341B00B4BAE9 /* PacketTests.swift */,
|
||||
0E245D682135972800B012A2 /* PushTests.swift */,
|
||||
0EB2B45620F0BD16004233D7 /* RandomTests.swift */,
|
||||
0EB2B45C20F0BF41004233D7 /* RawPerformanceTests.swift */,
|
||||
@ -415,7 +433,12 @@
|
||||
children = (
|
||||
0EFEB42E2006D3C800F81029 /* Allocation.h */,
|
||||
0EFEB4462006D3C800F81029 /* Allocation.m */,
|
||||
0E12B2A421454F7F00B4BAE9 /* BidirectionalState.swift */,
|
||||
0E245D6B2137F73600B012A2 /* CompressionFramingNative.h */,
|
||||
0E39BCE6214B2AB60035E9DE /* ControlPacket.h */,
|
||||
0E39BCE7214B2AB60035E9DE /* ControlPacket.m */,
|
||||
0E12B2A721456C0200B4BAE9 /* ControlChannel.swift */,
|
||||
0E12B2AA2145E01700B4BAE9 /* ControlChannelSerializer.swift */,
|
||||
0EFEB44A2006D3C800F81029 /* CoreConfiguration.swift */,
|
||||
0E07597C20F0060E00F38FD8 /* CryptoAEAD.h */,
|
||||
0E07597D20F0060E00F38FD8 /* CryptoAEAD.m */,
|
||||
@ -522,6 +545,7 @@
|
||||
0EFEB4582006D3C800F81029 /* MSS.h in Headers */,
|
||||
0E245D6C2137F73600B012A2 /* CompressionFramingNative.h in Headers */,
|
||||
0EFEB4602006D3C800F81029 /* DataPath.h in Headers */,
|
||||
0E39BCE8214B2AB60035E9DE /* ControlPacket.h in Headers */,
|
||||
0E07597E20F0060E00F38FD8 /* CryptoAEAD.h in Headers */,
|
||||
0EFEB46C2006D3C800F81029 /* ZeroingData.h in Headers */,
|
||||
);
|
||||
@ -543,6 +567,7 @@
|
||||
0EF5CF282141E183004FF1BD /* CompressionFramingNative.h in Headers */,
|
||||
0EEC49E820B5F7F6008FEB91 /* ReplayProtector.h in Headers */,
|
||||
0EEC49E920B5F7F6008FEB91 /* TLSBox.h in Headers */,
|
||||
0E39BCE9214B2AB60035E9DE /* ControlPacket.h in Headers */,
|
||||
0E07597F20F0060E00F38FD8 /* CryptoAEAD.h in Headers */,
|
||||
0EEC49E220B5F7F6008FEB91 /* CryptoBox.h in Headers */,
|
||||
);
|
||||
@ -808,6 +833,7 @@
|
||||
0EB2B45320F0BB44004233D7 /* EncryptionTests.swift in Sources */,
|
||||
0EB2B45B20F0BE4C004233D7 /* TestUtils.swift in Sources */,
|
||||
0E58F1302138AC2F00A49F27 /* DNSTests.swift in Sources */,
|
||||
0E12B2A32145341B00B4BAE9 /* PacketTests.swift in Sources */,
|
||||
0E245D692135972800B012A2 /* PushTests.swift in Sources */,
|
||||
0EB2B46120F0C0A4004233D7 /* DataPathPerformanceTests.swift in Sources */,
|
||||
0EB2B45F20F0C098004233D7 /* EncryptionPerformanceTests.swift in Sources */,
|
||||
@ -845,6 +871,7 @@
|
||||
0EFEB45D2006D3C800F81029 /* CryptoBox.m in Sources */,
|
||||
0EBBF2FA2085061600E36B40 /* NETCPInterface.swift in Sources */,
|
||||
0E0C2125212ED29D008AB282 /* SessionError.swift in Sources */,
|
||||
0E12B2A821456C0200B4BAE9 /* ControlChannel.swift in Sources */,
|
||||
0EFEB4552006D3C800F81029 /* SessionProxy+EncryptionBridge.swift in Sources */,
|
||||
0EFEB45C2006D3C800F81029 /* ZeroingData.m in Sources */,
|
||||
0EFEB4632006D3C800F81029 /* ProtocolMacros.swift in Sources */,
|
||||
@ -857,6 +884,8 @@
|
||||
0EC1BBA820D7D803007C4C7B /* ConnectionStrategy.swift in Sources */,
|
||||
0EFEB46F2006D3C800F81029 /* IOInterface.swift in Sources */,
|
||||
0E07598020F0060E00F38FD8 /* CryptoAEAD.m in Sources */,
|
||||
0E39BCEA214B2AB60035E9DE /* ControlPacket.m in Sources */,
|
||||
0E12B2AB2145E01700B4BAE9 /* ControlChannelSerializer.swift in Sources */,
|
||||
0EFEB4662006D3C800F81029 /* ZeroingData.swift in Sources */,
|
||||
0EBBF2F3208505D300E36B40 /* NEUDPInterface.swift in Sources */,
|
||||
0EFEB4682006D3C800F81029 /* MSS.m in Sources */,
|
||||
@ -869,6 +898,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 */,
|
||||
@ -896,6 +926,7 @@
|
||||
0EFEB4962006D7F300F81029 /* ProtocolMacros.swift in Sources */,
|
||||
0EFEB48A2006D7C400F81029 /* TunnelKitProvider.swift in Sources */,
|
||||
0E0C2126212ED29D008AB282 /* SessionError.swift in Sources */,
|
||||
0E12B2A921456C0200B4BAE9 /* ControlChannel.swift in Sources */,
|
||||
0EBBF2FB2085061600E36B40 /* NETCPInterface.swift in Sources */,
|
||||
0EFEB4982006D7F300F81029 /* ZeroingData.swift in Sources */,
|
||||
0EFEB4A32006D7F300F81029 /* Errors.m in Sources */,
|
||||
@ -908,6 +939,8 @@
|
||||
0E07596020EF6D1400F38FD8 /* CryptoCBC.m in Sources */,
|
||||
0EC1BBA920D7D803007C4C7B /* ConnectionStrategy.swift in Sources */,
|
||||
0EFEB4932006D7F300F81029 /* CryptoBox.m in Sources */,
|
||||
0E39BCEB214B2AB60035E9DE /* ControlPacket.m in Sources */,
|
||||
0E12B2AC2145E01700B4BAE9 /* ControlChannelSerializer.swift in Sources */,
|
||||
0E07598120F0060E00F38FD8 /* CryptoAEAD.m in Sources */,
|
||||
0EFEB49C2006D7F300F81029 /* Data+Manipulation.swift in Sources */,
|
||||
0EBBF2F4208505D400E36B40 /* NEUDPInterface.swift in Sources */,
|
||||
@ -920,6 +953,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 */,
|
||||
|
@ -224,7 +224,7 @@ class NETCPLink: LinkInterface {
|
||||
|
||||
var newBuffer = buffer
|
||||
newBuffer.append(contentsOf: data)
|
||||
let (until, packets) = CommonPacket.parsed(newBuffer)
|
||||
let (until, packets) = PacketStream.packets(from: newBuffer)
|
||||
newBuffer = newBuffer.subdata(in: until..<newBuffer.count)
|
||||
self?.loopReadPackets(queue, newBuffer, handler)
|
||||
|
||||
@ -234,14 +234,14 @@ class NETCPLink: LinkInterface {
|
||||
}
|
||||
|
||||
func writePacket(_ packet: Data, completionHandler: ((Error?) -> Void)?) {
|
||||
let stream = CommonPacket.stream(packet)
|
||||
let stream = PacketStream.stream(from: packet)
|
||||
impl.write(stream) { (error) in
|
||||
completionHandler?(error)
|
||||
}
|
||||
}
|
||||
|
||||
func writePackets(_ packets: [Data], completionHandler: ((Error?) -> Void)?) {
|
||||
let stream = CommonPacket.stream(packets)
|
||||
let stream = PacketStream.stream(from: packets)
|
||||
impl.write(stream) { (error) in
|
||||
completionHandler?(error)
|
||||
}
|
||||
|
@ -280,9 +280,10 @@ open class TunnelKitProvider: NEPacketTunnelProvider {
|
||||
|
||||
case .dataCount:
|
||||
if let proxy = proxy {
|
||||
let dataCount = proxy.dataCount()
|
||||
response = Data()
|
||||
response?.append(UInt64(proxy.bytesIn))
|
||||
response?.append(UInt64(proxy.bytesOut))
|
||||
response?.append(UInt64(dataCount.0)) // inbound
|
||||
response?.append(UInt64(dataCount.1)) // outbound
|
||||
}
|
||||
|
||||
default:
|
||||
|
65
TunnelKit/Sources/Core/BidirectionalState.swift
Normal file
65
TunnelKit/Sources/Core/BidirectionalState.swift
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// A generic structure holding a pair of inbound/outbound states.
|
||||
public class BidirectionalState<T> {
|
||||
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
|
||||
}
|
||||
}
|
230
TunnelKit/Sources/Core/ControlChannel.swift
Normal file
230
TunnelKit/Sources/Core/ControlChannel.swift
Normal file
@ -0,0 +1,230 @@
|
||||
//
|
||||
// ControlChannel.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 <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import __TunnelKitNative
|
||||
import SwiftyBeaver
|
||||
|
||||
private let log = SwiftyBeaver.self
|
||||
|
||||
class ControlChannelError: Error, CustomStringConvertible {
|
||||
let description: String
|
||||
|
||||
init(_ message: String) {
|
||||
description = "\(String(describing: ControlChannelError.self))(\(message))"
|
||||
}
|
||||
}
|
||||
|
||||
class ControlChannel {
|
||||
private let serializer: ControlChannelSerializer
|
||||
|
||||
private(set) var sessionId: Data?
|
||||
|
||||
var remoteSessionId: Data? {
|
||||
didSet {
|
||||
if let id = remoteSessionId {
|
||||
log.debug("Control: Remote sessionId is \(id.toHex())")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var queue: BidirectionalState<[ControlPacket]>
|
||||
|
||||
private var currentPacketId: BidirectionalState<UInt32>
|
||||
|
||||
private var pendingAcks: Set<UInt32>
|
||||
|
||||
private var plainBuffer: ZeroingData
|
||||
|
||||
private var dataCount: BidirectionalState<Int>
|
||||
|
||||
convenience init() {
|
||||
self.init(serializer: PlainSerializer())
|
||||
}
|
||||
|
||||
private init(serializer: ControlChannelSerializer) {
|
||||
self.serializer = serializer
|
||||
sessionId = nil
|
||||
remoteSessionId = nil
|
||||
queue = BidirectionalState(withResetValue: [])
|
||||
currentPacketId = BidirectionalState(withResetValue: 0)
|
||||
pendingAcks = []
|
||||
plainBuffer = Z(count: TLSBoxMaxBufferLength)
|
||||
dataCount = BidirectionalState(withResetValue: 0)
|
||||
}
|
||||
|
||||
func reset(forNewSession: Bool) throws {
|
||||
if forNewSession {
|
||||
try sessionId = SecureRandom.data(length: PacketSessionIdLength)
|
||||
remoteSessionId = nil
|
||||
}
|
||||
queue.reset()
|
||||
currentPacketId.reset()
|
||||
pendingAcks.removeAll()
|
||||
plainBuffer.zero()
|
||||
dataCount.reset()
|
||||
serializer.reset()
|
||||
}
|
||||
|
||||
func readInboundPacket(withData data: Data, offset: Int) throws -> ControlPacket {
|
||||
let packet = try serializer.deserialize(data: data, start: offset, end: nil)
|
||||
log.debug("Control: Read packet \(packet)")
|
||||
if let ackIds = packet.ackIds as? [UInt32], let ackRemoteSessionId = packet.ackRemoteSessionId {
|
||||
try readAcks(ackIds, acksRemoteSessionId: ackRemoteSessionId)
|
||||
}
|
||||
return packet
|
||||
}
|
||||
|
||||
func enqueueInboundPacket(packet: ControlPacket) -> [ControlPacket] {
|
||||
queue.inbound.append(packet)
|
||||
queue.inbound.sort { $0.packetId < $1.packetId }
|
||||
|
||||
var toHandle: [ControlPacket] = []
|
||||
for queuedPacket in queue.inbound {
|
||||
if queuedPacket.packetId < currentPacketId.inbound {
|
||||
queue.inbound.removeFirst()
|
||||
continue
|
||||
}
|
||||
if queuedPacket.packetId != currentPacketId.inbound {
|
||||
continue
|
||||
}
|
||||
|
||||
toHandle.append(queuedPacket)
|
||||
|
||||
currentPacketId.inbound += 1
|
||||
queue.inbound.removeFirst()
|
||||
}
|
||||
return toHandle
|
||||
}
|
||||
|
||||
func enqueueOutboundPackets(withCode code: PacketCode, key: UInt8, payload: Data, maxPacketSize: Int) {
|
||||
guard let sessionId = sessionId else {
|
||||
fatalError("Missing sessionId, do reset(forNewSession: true) first")
|
||||
}
|
||||
|
||||
let oldIdOut = currentPacketId.outbound
|
||||
var queuedCount = 0
|
||||
var offset = 0
|
||||
|
||||
repeat {
|
||||
let subPayloadLength = min(maxPacketSize, payload.count - offset)
|
||||
let subPayloadData = payload.subdata(offset: offset, count: subPayloadLength)
|
||||
let packet = ControlPacket(code: code, key: key, sessionId: sessionId, packetId: currentPacketId.outbound, payload: subPayloadData)
|
||||
|
||||
queue.outbound.append(packet)
|
||||
currentPacketId.outbound += 1
|
||||
offset += maxPacketSize
|
||||
queuedCount += subPayloadLength
|
||||
} while (offset < payload.count)
|
||||
|
||||
assert(queuedCount == payload.count)
|
||||
|
||||
// packet count
|
||||
let packetCount = currentPacketId.outbound - oldIdOut
|
||||
if (packetCount > 1) {
|
||||
log.debug("Control: Enqueued \(packetCount) packets [\(oldIdOut)-\(currentPacketId.outbound - 1)]")
|
||||
} else {
|
||||
log.debug("Control: Enqueued 1 packet [\(oldIdOut)]")
|
||||
}
|
||||
}
|
||||
|
||||
func writeOutboundPackets() throws -> [Data] {
|
||||
var rawList: [Data] = []
|
||||
for packet in queue.outbound {
|
||||
if let sentDate = packet.sentDate {
|
||||
let timeAgo = -sentDate.timeIntervalSinceNow
|
||||
guard (timeAgo >= CoreConfiguration.retransmissionLimit) else {
|
||||
log.debug("Control: Skip writing packet with packetId \(packet.packetId) (sent on \(sentDate), \(timeAgo) seconds ago)")
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
log.debug("Control: Write control packet \(packet)")
|
||||
|
||||
let raw = try serializer.serialize(packet: packet)
|
||||
rawList.append(raw)
|
||||
packet.sentDate = Date()
|
||||
|
||||
// track pending acks for sent packets
|
||||
pendingAcks.insert(packet.packetId)
|
||||
}
|
||||
// log.verbose("Packets now pending ack: \(pendingAcks)")
|
||||
return rawList
|
||||
}
|
||||
|
||||
func hasPendingAcks() -> Bool {
|
||||
return !pendingAcks.isEmpty
|
||||
}
|
||||
|
||||
// Ruby: handle_acks
|
||||
private func readAcks(_ packetIds: [UInt32], acksRemoteSessionId: Data) throws {
|
||||
guard let sessionId = sessionId else {
|
||||
throw SessionError.missingSessionId
|
||||
}
|
||||
guard acksRemoteSessionId == sessionId else {
|
||||
log.error("Control: Ack session mismatch (\(acksRemoteSessionId.toHex()) != \(sessionId.toHex()))")
|
||||
throw SessionError.sessionMismatch
|
||||
}
|
||||
|
||||
// drop queued out packets if ack-ed
|
||||
for (i, packet) in queue.outbound.enumerated() {
|
||||
if packetIds.contains(packet.packetId) {
|
||||
queue.outbound.remove(at: i)
|
||||
}
|
||||
}
|
||||
|
||||
// remove ack-ed packets from pending
|
||||
pendingAcks.subtract(packetIds)
|
||||
|
||||
// log.verbose("Packets still pending ack: \(pendingAcks)")
|
||||
}
|
||||
|
||||
func writeAcks(withKey key: UInt8, ackPacketIds: [UInt32], ackRemoteSessionId: Data) throws -> Data {
|
||||
guard let sessionId = sessionId else {
|
||||
throw SessionError.missingSessionId
|
||||
}
|
||||
let packet = ControlPacket(key: key, sessionId: sessionId, ackIds: ackPacketIds as [NSNumber], ackRemoteSessionId: ackRemoteSessionId)
|
||||
log.debug("Control: Write ack packet \(packet)")
|
||||
return try serializer.serialize(packet: packet)
|
||||
}
|
||||
|
||||
func currentControlData(withTLS tls: TLSBox) throws -> ZeroingData {
|
||||
var length = 0
|
||||
try tls.pullRawPlainText(plainBuffer.mutableBytes, length: &length)
|
||||
return plainBuffer.withOffset(0, count: length)
|
||||
}
|
||||
|
||||
func addReceivedDataCount(_ count: Int) {
|
||||
dataCount.inbound += count
|
||||
}
|
||||
|
||||
func addSentDataCount(_ count: Int) {
|
||||
dataCount.outbound += count
|
||||
}
|
||||
|
||||
func currentDataCount() -> (Int, Int) {
|
||||
return dataCount.pair
|
||||
}
|
||||
}
|
129
TunnelKit/Sources/Core/ControlChannelSerializer.swift
Normal file
129
TunnelKit/Sources/Core/ControlChannelSerializer.swift
Normal file
@ -0,0 +1,129 @@
|
||||
//
|
||||
// ControlChannelSerializer.swift
|
||||
// TunnelKit
|
||||
//
|
||||
// Created by Davide De Rosa on 9/10/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 <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import __TunnelKitNative
|
||||
import SwiftyBeaver
|
||||
|
||||
private let log = SwiftyBeaver.self
|
||||
|
||||
protocol ControlChannelSerializer {
|
||||
func reset()
|
||||
|
||||
func serialize(packet: ControlPacket) throws -> Data
|
||||
|
||||
func deserialize(data: Data, start: Int, end: Int?) throws -> ControlPacket
|
||||
}
|
||||
|
||||
extension ControlChannel {
|
||||
class PlainSerializer: ControlChannelSerializer {
|
||||
func reset() {
|
||||
}
|
||||
|
||||
func serialize(packet: ControlPacket) throws -> Data {
|
||||
return packet.serialized()
|
||||
}
|
||||
|
||||
func deserialize(data packet: Data, start: Int, end: Int?) throws -> ControlPacket {
|
||||
var offset = start
|
||||
let end = end ?? packet.count
|
||||
|
||||
guard end >= offset + PacketHeaderLength else {
|
||||
throw ControlChannelError("Missing header")
|
||||
}
|
||||
let codeValue = packet[offset] >> 3
|
||||
guard let code = PacketCode(rawValue: codeValue) else {
|
||||
throw ControlChannelError("Unknown code: \(codeValue))")
|
||||
}
|
||||
let key = packet[offset] & 0b111
|
||||
offset += PacketHeaderLength
|
||||
|
||||
log.debug("Control: Try read packet with code \(code) and key \(key)")
|
||||
|
||||
guard end >= offset + PacketSessionIdLength else {
|
||||
throw ControlChannelError("Missing sessionId")
|
||||
}
|
||||
let sessionId = packet.subdata(offset: offset, count: PacketSessionIdLength)
|
||||
offset += PacketSessionIdLength
|
||||
|
||||
guard end >= offset + 1 else {
|
||||
throw ControlChannelError("Missing ackSize")
|
||||
}
|
||||
let ackSize = packet[offset]
|
||||
offset += 1
|
||||
|
||||
var ackIds: [UInt32]?
|
||||
var ackRemoteSessionId: Data?
|
||||
if ackSize > 0 {
|
||||
guard end >= (offset + Int(ackSize) * PacketIdLength) else {
|
||||
throw ControlChannelError("Missing acks")
|
||||
}
|
||||
var ids: [UInt32] = []
|
||||
for _ in 0..<ackSize {
|
||||
let id = packet.networkUInt32Value(from: offset)
|
||||
ids.append(id)
|
||||
offset += PacketIdLength
|
||||
}
|
||||
|
||||
guard end >= offset + PacketSessionIdLength else {
|
||||
throw ControlChannelError("Missing remoteSessionId")
|
||||
}
|
||||
let remoteSessionId = packet.subdata(offset: offset, count: PacketSessionIdLength)
|
||||
offset += PacketSessionIdLength
|
||||
|
||||
ackIds = ids
|
||||
ackRemoteSessionId = remoteSessionId
|
||||
}
|
||||
|
||||
if code == .ackV1 {
|
||||
guard let ackIds = ackIds else {
|
||||
throw ControlChannelError("Ack packet without ids")
|
||||
}
|
||||
guard let ackRemoteSessionId = ackRemoteSessionId else {
|
||||
throw ControlChannelError("Ack packet without remoteSessionId")
|
||||
}
|
||||
return ControlPacket(key: key, sessionId: sessionId, ackIds: ackIds as [NSNumber], ackRemoteSessionId: ackRemoteSessionId)
|
||||
}
|
||||
|
||||
guard end >= offset + PacketIdLength else {
|
||||
throw ControlChannelError("Missing packetId")
|
||||
}
|
||||
let packetId = packet.networkUInt32Value(from: offset)
|
||||
offset += PacketIdLength
|
||||
|
||||
var payload: Data?
|
||||
if offset < end {
|
||||
payload = packet.subdata(in: offset..<end)
|
||||
}
|
||||
|
||||
let controlPacket = ControlPacket(code: code, key: key, sessionId: sessionId, packetId: packetId, payload: payload)
|
||||
if let ackIds = ackIds {
|
||||
controlPacket.ackIds = ackIds as [NSNumber]
|
||||
controlPacket.ackRemoteSessionId = ackRemoteSessionId
|
||||
}
|
||||
return controlPacket
|
||||
}
|
||||
}
|
||||
}
|
60
TunnelKit/Sources/Core/ControlPacket.h
Normal file
60
TunnelKit/Sources/Core/ControlPacket.h
Normal file
@ -0,0 +1,60 @@
|
||||
//
|
||||
// ControlPacket.h
|
||||
// TunnelKit
|
||||
//
|
||||
// Created by Davide De Rosa on 9/14/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 <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "PacketMacros.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface ControlPacket : NSObject
|
||||
|
||||
- (instancetype)initWithCode:(PacketCode)code
|
||||
key:(uint8_t)key
|
||||
sessionId:(NSData *)sessionId
|
||||
packetId:(uint32_t)packetId
|
||||
payload:(nullable NSData *)payload;
|
||||
|
||||
- (instancetype)initWithKey:(uint8_t)key
|
||||
sessionId:(NSData *)sessionId
|
||||
ackIds:(NSArray<NSNumber *> *)ackIds
|
||||
ackRemoteSessionId:(NSData *)ackRemoteSessionId;
|
||||
|
||||
@property (nonatomic, assign, readonly) PacketCode code;
|
||||
@property (nonatomic, readonly) BOOL isAck;
|
||||
@property (nonatomic, assign, readonly) uint8_t key;
|
||||
@property (nonatomic, strong, readonly) NSData *sessionId;
|
||||
@property (nonatomic, strong) NSArray<NSNumber *> *_Nullable ackIds; // uint32_t
|
||||
@property (nonatomic, strong) NSData *_Nullable ackRemoteSessionId;
|
||||
@property (nonatomic, assign, readonly) uint32_t packetId;
|
||||
@property (nonatomic, strong, readonly) NSData *_Nullable payload;
|
||||
@property (nonatomic, strong) NSDate *_Nullable sentDate;
|
||||
|
||||
- (NSInteger)serializeTo:(nullable uint8_t *)to;
|
||||
- (NSData *)serialized;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
137
TunnelKit/Sources/Core/ControlPacket.m
Normal file
137
TunnelKit/Sources/Core/ControlPacket.m
Normal file
@ -0,0 +1,137 @@
|
||||
//
|
||||
// ControlPacket.m
|
||||
// TunnelKit
|
||||
//
|
||||
// Created by Davide De Rosa on 9/14/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 <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
#import "ControlPacket.h"
|
||||
|
||||
@implementation ControlPacket
|
||||
|
||||
- (instancetype)initWithCode:(PacketCode)code
|
||||
key:(uint8_t)key
|
||||
sessionId:(NSData *)sessionId
|
||||
packetId:(uint32_t)packetId
|
||||
payload:(nullable NSData *)payload
|
||||
{
|
||||
NSCParameterAssert(sessionId.length == PacketSessionIdLength);
|
||||
|
||||
if (!(self = [super init])) {
|
||||
return nil;
|
||||
}
|
||||
_code = code;
|
||||
_key = key;
|
||||
_sessionId = sessionId;
|
||||
_packetId = packetId;
|
||||
_payload = payload;
|
||||
self.sentDate = nil;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithKey:(uint8_t)key
|
||||
sessionId:(NSData *)sessionId
|
||||
ackIds:(NSArray<NSNumber *> *)ackIds
|
||||
ackRemoteSessionId:(NSData *)ackRemoteSessionId
|
||||
{
|
||||
NSCParameterAssert(sessionId.length == PacketSessionIdLength);
|
||||
NSCParameterAssert(ackRemoteSessionId.length == PacketSessionIdLength);
|
||||
|
||||
if (!(self = [super init])) {
|
||||
return nil;
|
||||
}
|
||||
_packetId = UINT32_MAX; // bogus marker
|
||||
_code = PacketCodeAckV1;
|
||||
_key = key;
|
||||
_sessionId = sessionId;
|
||||
_ackIds = ackIds;
|
||||
_ackRemoteSessionId = ackRemoteSessionId;
|
||||
self.sentDate = nil;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)isAck
|
||||
{
|
||||
return (self.packetId == UINT32_MAX);
|
||||
}
|
||||
|
||||
- (NSInteger)capacity
|
||||
{
|
||||
const BOOL isAck = self.isAck;
|
||||
const NSUInteger ackLength = self.ackIds.count;
|
||||
NSCAssert(!isAck || ackLength > 0, @"Ack packet must provide positive ackLength");
|
||||
NSInteger n = PacketHeaderLength + PacketSessionIdLength;
|
||||
n += PacketAckLengthLength;
|
||||
if (ackLength > 0) {
|
||||
n += ackLength * PacketIdLength + PacketSessionIdLength;
|
||||
}
|
||||
if (!isAck) {
|
||||
n += PacketIdLength;
|
||||
}
|
||||
n += self.payload.length;
|
||||
return n;
|
||||
}
|
||||
|
||||
// Ruby: send_ctrl
|
||||
- (NSInteger)serializeTo:(uint8_t *)to
|
||||
{
|
||||
if (!to) {
|
||||
return [self capacity];
|
||||
}
|
||||
uint8_t *ptr = to;
|
||||
ptr += PacketHeaderSet(ptr, self.code, self.key, self.sessionId.bytes);
|
||||
if (self.ackIds.count > 0) {
|
||||
NSCParameterAssert(self.ackRemoteSessionId.length == PacketSessionIdLength);
|
||||
*ptr = self.ackIds.count;
|
||||
ptr += PacketAckLengthLength;
|
||||
for (NSNumber *n in self.ackIds) {
|
||||
const uint32_t ackId = (uint32_t)n.unsignedIntegerValue;
|
||||
*(uint32_t *)ptr = CFSwapInt32HostToBig(ackId);
|
||||
ptr += PacketIdLength;
|
||||
}
|
||||
memcpy(ptr, self.ackRemoteSessionId.bytes, PacketSessionIdLength);
|
||||
ptr += PacketSessionIdLength;
|
||||
}
|
||||
else {
|
||||
*ptr = 0; // no acks
|
||||
ptr += PacketAckLengthLength;
|
||||
}
|
||||
if (self.code != PacketCodeAckV1) {
|
||||
*(uint32_t *)ptr = CFSwapInt32HostToBig(self.packetId);
|
||||
ptr += PacketIdLength;
|
||||
if (self.payload) {
|
||||
memcpy(ptr, self.payload.bytes, self.payload.length);
|
||||
ptr += self.payload.length;
|
||||
}
|
||||
}
|
||||
return (int)(ptr - to);
|
||||
}
|
||||
|
||||
- (NSData *)serialized
|
||||
{
|
||||
NSMutableData *data = [[NSMutableData alloc] initWithLength:self.capacity];
|
||||
[self serializeTo:data.mutableBytes];
|
||||
return data;
|
||||
}
|
||||
|
||||
@end
|
@ -290,7 +290,7 @@ const NSInteger CryptoAEADTagLength = 16;
|
||||
self.crypto.extraLength = PacketIdLength;
|
||||
self.crypto.extraPacketIdOffset = 0;
|
||||
self.setDataHeader = ^(uint8_t *to, uint8_t key) {
|
||||
PacketHeaderSet(to, PacketCodeDataV1, key);
|
||||
PacketHeaderSet(to, PacketCodeDataV1, key, nil);
|
||||
};
|
||||
self.checkPeerId = NULL;
|
||||
}
|
||||
|
@ -328,7 +328,7 @@ const NSInteger CryptoCBCMaxHMACLength = 100;
|
||||
if (peerId == PacketPeerIdDisabled) {
|
||||
self.headerLength = 1;
|
||||
self.setDataHeader = ^(uint8_t *to, uint8_t key) {
|
||||
PacketHeaderSet(to, PacketCodeDataV1, key);
|
||||
PacketHeaderSet(to, PacketCodeDataV1, key, nil);
|
||||
};
|
||||
self.checkPeerId = NULL;
|
||||
}
|
||||
|
@ -38,20 +38,20 @@
|
||||
import Foundation
|
||||
import __TunnelKitNative
|
||||
|
||||
class CommonPacket {
|
||||
let packetId: UInt32
|
||||
|
||||
let code: PacketCode
|
||||
|
||||
let key: UInt8
|
||||
|
||||
let sessionId: Data?
|
||||
|
||||
let payload: Data?
|
||||
|
||||
var sentDate: Date?
|
||||
// TODO: convert to C for efficiency
|
||||
|
||||
static func parsed(_ stream: Data) -> (Int, [Data]) {
|
||||
/// Reads and writes packets as a stream. Useful for stream-oriented links (e.g TCP/IP).
|
||||
public class PacketStream {
|
||||
|
||||
/**
|
||||
Parses packets from a stream.
|
||||
|
||||
- Parameter stream: The data stream.
|
||||
- Returns: A pair where the first value is the `Int` offset up to which
|
||||
the stream could be parsed, and the second value is an array containing
|
||||
the parsed packets up to such offset.
|
||||
*/
|
||||
public static func packets(from stream: Data) -> (Int, [Data]) {
|
||||
var ni = 0
|
||||
var parsed: [Data] = []
|
||||
while (ni + 2 <= stream.count) {
|
||||
@ -68,14 +68,26 @@ class CommonPacket {
|
||||
return (ni, parsed)
|
||||
}
|
||||
|
||||
static func stream(_ packet: Data) -> Data {
|
||||
var stream = Data(capacity: 2 + packet.count)
|
||||
stream.append(UInt16(packet.count).bigEndian)
|
||||
stream.append(contentsOf: packet)
|
||||
return stream
|
||||
/**
|
||||
Creates a contiguous stream of packets.
|
||||
|
||||
- Parameter packet: The packet.
|
||||
- Returns: A stream made of the packet.
|
||||
*/
|
||||
public static func stream(from packet: Data) -> Data {
|
||||
var raw = Data(capacity: 2 + packet.count)
|
||||
raw.append(UInt16(packet.count).bigEndian)
|
||||
raw.append(contentsOf: packet)
|
||||
return raw
|
||||
}
|
||||
|
||||
static func stream(_ packets: [Data]) -> Data {
|
||||
/**
|
||||
Creates a contiguous stream of packets.
|
||||
|
||||
- Parameter packets: The array of packets.
|
||||
- Returns: A stream made of the array of packets.
|
||||
*/
|
||||
public static func stream(from packets: [Data]) -> Data {
|
||||
var raw = Data()
|
||||
for payload in packets {
|
||||
raw.append(UInt16(payload.count).bigEndian)
|
||||
@ -84,27 +96,50 @@ class CommonPacket {
|
||||
return raw
|
||||
}
|
||||
|
||||
init(_ packetId: UInt32, _ code: PacketCode, _ key: UInt8, _ sessionId: Data?, _ payload: Data?) {
|
||||
self.packetId = packetId
|
||||
self.code = code
|
||||
self.key = key
|
||||
self.sessionId = sessionId
|
||||
self.payload = payload
|
||||
self.sentDate = nil
|
||||
private init() {
|
||||
}
|
||||
}
|
||||
|
||||
// Ruby: send_ctrl
|
||||
func toBuffer() -> Data {
|
||||
var raw = PacketWithHeader(code, key, sessionId)
|
||||
raw.append(UInt8(0))
|
||||
raw.append(UInt32(packetId).bigEndian)
|
||||
if let payload = payload {
|
||||
raw.append(payload)
|
||||
/// :nodoc:
|
||||
extension ControlPacket {
|
||||
|
||||
/// :nodoc:
|
||||
open override var description: String {
|
||||
var msg: [String] = ["\(code) | \(key)"]
|
||||
msg.append("sid: \(sessionId.toHex())")
|
||||
if let ackIds = ackIds, let ackRemoteSessionId = ackRemoteSessionId {
|
||||
msg.append("acks: {\(ackIds), \(ackRemoteSessionId.toHex())}")
|
||||
}
|
||||
return raw
|
||||
if !isAck {
|
||||
msg.append("pid: \(packetId)")
|
||||
}
|
||||
if let payload = payload {
|
||||
if CoreConfiguration.logsSensitiveData {
|
||||
msg.append("[\(payload.count) bytes] -> \(payload.toHex())")
|
||||
} else {
|
||||
msg.append("[\(payload.count) bytes]")
|
||||
}
|
||||
}
|
||||
return "{\(msg.joined(separator: ", "))}"
|
||||
}
|
||||
}
|
||||
|
||||
class DataPacket {
|
||||
static let pingString = Data(hex: "2a187bf3641eb4cb07ed2d0a981fc748")
|
||||
}
|
||||
|
||||
/// :nodoc:
|
||||
extension PacketCode: CustomStringConvertible {
|
||||
public var description: String {
|
||||
switch self {
|
||||
case .softResetV1: return "SOFT_RESET_V1"
|
||||
case .controlV1: return "CONTROL_V1"
|
||||
case .ackV1: return "ACK_V1"
|
||||
case .dataV1: return "DATA_V1"
|
||||
case .hardResetClientV2: return "HARD_RESET_CLIENT_V2"
|
||||
case .hardResetServerV2: return "HARD_RESET_SERVER_V2"
|
||||
case .dataV2: return "DATA_V2"
|
||||
case .unknown: return "UNKNOWN"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -39,8 +39,12 @@
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
#define PacketPeerIdDisabled 0xffffffu
|
||||
#define PacketIdLength 4
|
||||
#define PacketHeaderLength ((NSInteger)1)
|
||||
#define PacketIdLength ((NSInteger)4)
|
||||
#define PacketSessionIdLength ((NSInteger)8)
|
||||
#define PacketAckLengthLength ((NSInteger)1)
|
||||
#define PacketPeerIdLength ((NSInteger)3)
|
||||
#define PacketPeerIdDisabled ((uint32_t)0xffffffu)
|
||||
|
||||
typedef NS_ENUM(uint8_t, PacketCode) {
|
||||
PacketCodeSoftResetV1 = 0x03,
|
||||
@ -59,27 +63,21 @@ typedef NS_ENUM(uint8_t, PacketCode) {
|
||||
extern const uint8_t DataPacketPingData[16];
|
||||
|
||||
// Ruby: header
|
||||
static inline int PacketHeaderSet(uint8_t *to, PacketCode code, uint8_t key)
|
||||
static inline int PacketHeaderSet(uint8_t *to, PacketCode code, uint8_t key, const uint8_t *_Nullable sessionId)
|
||||
{
|
||||
*(uint8_t *)to = (code << 3) | (key & 0b111);
|
||||
return sizeof(uint8_t);
|
||||
}
|
||||
|
||||
// Ruby: header
|
||||
static inline NSData *PacketWithHeader(PacketCode code, uint8_t key, NSData *_Nullable sessionId)
|
||||
{
|
||||
NSMutableData *to = [[NSMutableData alloc] initWithLength:(sizeof(uint8_t) + (sessionId ? sessionId.length : 0))];
|
||||
const int offset = PacketHeaderSet(to.mutableBytes, code, key);
|
||||
int offset = PacketHeaderLength;
|
||||
if (sessionId) {
|
||||
memcpy(to.mutableBytes + offset, sessionId.bytes, sessionId.length);
|
||||
memcpy(to + offset, sessionId, PacketSessionIdLength);
|
||||
offset += PacketSessionIdLength;
|
||||
}
|
||||
return to;
|
||||
return offset;
|
||||
}
|
||||
|
||||
static inline int PacketHeaderSetDataV2(uint8_t *to, uint8_t key, uint32_t peerId)
|
||||
{
|
||||
*(uint32_t *)to = ((PacketCodeDataV2 << 3) | (key & 0b111)) | htonl(peerId & 0xffffff);
|
||||
return sizeof(uint32_t);
|
||||
return PacketHeaderLength + PacketPeerIdLength;
|
||||
}
|
||||
|
||||
static inline int PacketHeaderGetDataV2PeerId(const uint8_t *from)
|
||||
@ -87,14 +85,4 @@ static inline int PacketHeaderGetDataV2PeerId(const uint8_t *from)
|
||||
return ntohl(*(const uint32_t *)from & 0xffffff00);
|
||||
}
|
||||
|
||||
static inline NSData *PacketWithHeaderDataV2(uint8_t key, uint32_t peerId, NSData *sessionId)
|
||||
{
|
||||
NSMutableData *to = [[NSMutableData alloc] initWithLength:(sizeof(uint32_t) + (sessionId ? sessionId.length : 0))];
|
||||
const int offset = PacketHeaderSetDataV2(to.mutableBytes, key, peerId);
|
||||
if (sessionId) {
|
||||
memcpy(to.mutableBytes + offset, sessionId.bytes, sessionId.length);
|
||||
}
|
||||
return to;
|
||||
}
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
@ -36,14 +36,8 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import __TunnelKitNative
|
||||
|
||||
class ProtocolMacros {
|
||||
static let peerIdLength = 3
|
||||
|
||||
static let sessionIdLength = 8
|
||||
|
||||
static let packetIdLength = 4
|
||||
|
||||
// UInt32(0) + UInt8(KeyMethod = 2)
|
||||
static let tlsPrefix = Data(hex: "0000000002")
|
||||
|
@ -127,15 +127,15 @@ extension SessionProxy {
|
||||
/// The path to the optional CA for TLS negotiation (PEM format).
|
||||
public var caPath: String?
|
||||
|
||||
/// Sets compression framing, disabled by default.
|
||||
public var compressionFraming: CompressionFraming
|
||||
|
||||
/// The path to the optional client certificate for TLS negotiation (PEM format).
|
||||
public var clientCertificatePath: String?
|
||||
|
||||
/// The path to the private key for the certificate at `clientCertificatePath` (PEM format).
|
||||
public var clientKeyPath: String?
|
||||
|
||||
/// Sets compression framing, disabled by default.
|
||||
public var compressionFraming: CompressionFraming
|
||||
|
||||
/// Sends periodical keep-alive packets if set.
|
||||
public var keepAliveInterval: TimeInterval?
|
||||
|
||||
|
@ -136,19 +136,13 @@ public class SessionProxy {
|
||||
return link?.isReliable ?? false
|
||||
}
|
||||
|
||||
private var sessionId: Data?
|
||||
|
||||
private var remoteSessionId: Data?
|
||||
|
||||
private var pushReply: SessionReply?
|
||||
|
||||
private var nextPushRequestDate: Date?
|
||||
|
||||
private var connectedDate: Date?
|
||||
|
||||
private var lastPingOut: Date
|
||||
|
||||
private var lastPingIn: Date
|
||||
private var lastPing: BidirectionalState<Date>
|
||||
|
||||
private var isStopping: Bool
|
||||
|
||||
@ -157,26 +151,10 @@ public class SessionProxy {
|
||||
|
||||
// MARK: Control
|
||||
|
||||
private let controlPlainBuffer: ZeroingData
|
||||
|
||||
private var controlQueueOut: [CommonPacket]
|
||||
|
||||
private var controlQueueIn: [CommonPacket]
|
||||
|
||||
private var controlPendingAcks: Set<UInt32>
|
||||
|
||||
private var controlPacketIdOut: UInt32
|
||||
|
||||
private var controlPacketIdIn: UInt32
|
||||
private var controlChannel: ControlChannel
|
||||
|
||||
private var authenticator: Authenticator?
|
||||
|
||||
// MARK: Data
|
||||
|
||||
private(set) var bytesIn: Int
|
||||
|
||||
private(set) var bytesOut: Int
|
||||
|
||||
// MARK: Init
|
||||
|
||||
/**
|
||||
@ -192,18 +170,10 @@ 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 = []
|
||||
controlPendingAcks = []
|
||||
controlPacketIdOut = 0
|
||||
controlPacketIdIn = 0
|
||||
bytesIn = 0
|
||||
bytesOut = 0
|
||||
controlChannel = ControlChannel()
|
||||
}
|
||||
|
||||
deinit {
|
||||
@ -285,6 +255,15 @@ public class SessionProxy {
|
||||
loopTunnel()
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the current data bytes count.
|
||||
|
||||
- Returns: The current data bytes count as a pair, inbound first.
|
||||
*/
|
||||
public func dataCount() -> (Int, Int) {
|
||||
return controlChannel.currentDataCount()
|
||||
}
|
||||
|
||||
/**
|
||||
Shuts down the session with an optional `Error` reason. Does nothing if the session is already stopped or about to stop.
|
||||
|
||||
@ -329,8 +308,6 @@ public class SessionProxy {
|
||||
negotiationKeyIdx = 0
|
||||
currentKeyIdx = nil
|
||||
|
||||
sessionId = nil
|
||||
remoteSessionId = nil
|
||||
nextPushRequestDate = nil
|
||||
connectedDate = nil
|
||||
authenticator = nil
|
||||
@ -433,7 +410,7 @@ public class SessionProxy {
|
||||
return
|
||||
}
|
||||
|
||||
lastPingIn = Date()
|
||||
lastPing.inbound = Date()
|
||||
|
||||
var dataPacketsByKey = [UInt8: [Data]]()
|
||||
|
||||
@ -441,7 +418,7 @@ public class SessionProxy {
|
||||
// log.verbose("Received data from LINK (\(packet.count) bytes): \(packet.toHex())")
|
||||
|
||||
guard let firstByte = packet.first else {
|
||||
log.warning("Dropped malformed packet (missing header)")
|
||||
log.warning("Dropped malformed packet (missing opcode)")
|
||||
continue
|
||||
}
|
||||
let codeValue = firstByte >> 3
|
||||
@ -449,20 +426,19 @@ public class SessionProxy {
|
||||
log.warning("Dropped malformed packet (unknown code: \(codeValue))")
|
||||
continue
|
||||
}
|
||||
let key = firstByte & 0b111
|
||||
// log.verbose("Parsed packet with code \(code)")
|
||||
|
||||
// log.verbose("Parsed packet with (code, key) = (\(code.rawValue), \(key))")
|
||||
|
||||
var offset = 1
|
||||
if (code == .dataV2) {
|
||||
guard packet.count >= offset + ProtocolMacros.peerIdLength else {
|
||||
guard packet.count >= offset + PacketPeerIdLength else {
|
||||
log.warning("Dropped malformed packet (missing peerId)")
|
||||
continue
|
||||
}
|
||||
offset += ProtocolMacros.peerIdLength
|
||||
offset += PacketPeerIdLength
|
||||
}
|
||||
|
||||
if (code == .dataV1) || (code == .dataV2) {
|
||||
let key = firstByte & 0b111
|
||||
guard let _ = keys[key] else {
|
||||
log.error("Key with id \(key) not found")
|
||||
deferStop(.shutdown, SessionError.badKey)
|
||||
@ -476,91 +452,27 @@ public class SessionProxy {
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
guard packet.count >= offset + ProtocolMacros.sessionIdLength else {
|
||||
log.warning("Dropped malformed packet (missing sessionId)")
|
||||
continue
|
||||
}
|
||||
let sessionId = packet.subdata(offset: offset, count: ProtocolMacros.sessionIdLength)
|
||||
offset += ProtocolMacros.sessionIdLength
|
||||
|
||||
guard packet.count >= offset + 1 else {
|
||||
log.warning("Dropped malformed packet (missing ackSize)")
|
||||
continue
|
||||
}
|
||||
let ackSize = packet[offset]
|
||||
offset += 1
|
||||
|
||||
log.debug("Packet has code \(code.rawValue), key \(key), sessionId \(sessionId.toHex()) and \(ackSize) acks entries")
|
||||
|
||||
if (ackSize > 0) {
|
||||
guard packet.count >= (offset + Int(ackSize) * ProtocolMacros.packetIdLength) else {
|
||||
log.warning("Dropped malformed packet (missing acks)")
|
||||
let controlPacket: ControlPacket
|
||||
do {
|
||||
let parsedPacket = try controlChannel.readInboundPacket(withData: packet, offset: 0)
|
||||
handleAcks()
|
||||
if parsedPacket.code == .ackV1 {
|
||||
continue
|
||||
}
|
||||
var ackedPacketIds = [UInt32]()
|
||||
for _ in 0..<ackSize {
|
||||
let ackedPacketId = packet.networkUInt32Value(from: offset)
|
||||
ackedPacketIds.append(ackedPacketId)
|
||||
offset += ProtocolMacros.packetIdLength
|
||||
}
|
||||
|
||||
guard packet.count >= offset + ProtocolMacros.sessionIdLength else {
|
||||
log.warning("Dropped malformed packet (missing remoteSessionId)")
|
||||
continue
|
||||
}
|
||||
let remoteSessionId = packet.subdata(offset: offset, count: ProtocolMacros.sessionIdLength)
|
||||
offset += ProtocolMacros.sessionIdLength
|
||||
|
||||
log.debug("Server acked packetIds \(ackedPacketIds) with remoteSessionId \(remoteSessionId.toHex())")
|
||||
|
||||
handleAcks(ackedPacketIds, remoteSessionId: remoteSessionId)
|
||||
}
|
||||
|
||||
if (code == .ackV1) {
|
||||
controlPacket = parsedPacket
|
||||
} catch let e {
|
||||
log.warning("Dropped malformed packet: \(e)")
|
||||
continue
|
||||
// deferStop(.shutdown, e)
|
||||
// return
|
||||
}
|
||||
|
||||
guard packet.count >= offset + ProtocolMacros.packetIdLength else {
|
||||
log.warning("Dropped malformed packet (missing packetId)")
|
||||
continue
|
||||
}
|
||||
let packetId = packet.networkUInt32Value(from: offset)
|
||||
log.debug("Control packet has packetId \(packetId)")
|
||||
offset += ProtocolMacros.packetIdLength
|
||||
sendAck(for: controlPacket)
|
||||
|
||||
sendAck(key: key, packetId: packetId, remoteSessionId: sessionId)
|
||||
|
||||
var payload: Data?
|
||||
if (offset < packet.count) {
|
||||
payload = packet.subdata(in: offset..<packet.count)
|
||||
|
||||
if let payload = payload {
|
||||
if CoreConfiguration.logsSensitiveData {
|
||||
log.debug("Control packet payload (\(payload.count) bytes): \(payload.toHex())")
|
||||
} else {
|
||||
log.debug("Control packet payload (\(payload.count) bytes)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let controlPacket = CommonPacket(packetId, code, key, sessionId, payload)
|
||||
controlQueueIn.append(controlPacket)
|
||||
controlQueueIn.sort { $0.packetId < $1.packetId }
|
||||
|
||||
for queuedControlPacket in controlQueueIn {
|
||||
if (queuedControlPacket.packetId < controlPacketIdIn) {
|
||||
controlQueueIn.removeFirst()
|
||||
continue
|
||||
}
|
||||
if (queuedControlPacket.packetId != controlPacketIdIn) {
|
||||
continue
|
||||
}
|
||||
|
||||
handleControlPacket(queuedControlPacket)
|
||||
|
||||
controlPacketIdIn += 1
|
||||
controlQueueIn.removeFirst()
|
||||
let pendingInboundQueue = controlChannel.enqueueInboundPacket(packet: controlPacket)
|
||||
for inboundPacket in pendingInboundQueue {
|
||||
handleControlPacket(inboundPacket)
|
||||
}
|
||||
}
|
||||
|
||||
@ -580,7 +492,7 @@ public class SessionProxy {
|
||||
return
|
||||
}
|
||||
sendDataPackets(packets)
|
||||
lastPingOut = Date()
|
||||
lastPing.outbound = Date()
|
||||
}
|
||||
|
||||
// Ruby: ping
|
||||
@ -590,14 +502,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 +518,7 @@ public class SessionProxy {
|
||||
|
||||
log.debug("Send ping")
|
||||
sendDataPackets([DataPacket.pingString])
|
||||
lastPingOut = Date()
|
||||
lastPing.outbound = Date()
|
||||
|
||||
scheduleNextPing()
|
||||
}
|
||||
@ -624,30 +536,21 @@ public class SessionProxy {
|
||||
// MARK: Handshake
|
||||
|
||||
// Ruby: reset_ctrl
|
||||
private func resetControlChannel() {
|
||||
controlPlainBuffer.zero()
|
||||
controlQueueOut.removeAll()
|
||||
controlQueueIn.removeAll()
|
||||
controlPendingAcks.removeAll()
|
||||
controlPacketIdOut = 0
|
||||
controlPacketIdIn = 0
|
||||
private func resetControlChannel(forNewSession: Bool) {
|
||||
authenticator = nil
|
||||
bytesIn = 0
|
||||
bytesOut = 0
|
||||
do {
|
||||
try controlChannel.reset(forNewSession: forNewSession)
|
||||
} catch let e {
|
||||
deferStop(.shutdown, e)
|
||||
}
|
||||
}
|
||||
|
||||
// Ruby: hard_reset
|
||||
private func hardReset() {
|
||||
log.debug("Send hard reset")
|
||||
|
||||
resetControlChannel()
|
||||
resetControlChannel(forNewSession: true)
|
||||
pushReply = nil
|
||||
do {
|
||||
try sessionId = SecureRandom.data(length: ProtocolMacros.sessionIdLength)
|
||||
} catch let e {
|
||||
deferStop(.shutdown, e)
|
||||
return
|
||||
}
|
||||
negotiationKeyIdx = 0
|
||||
let newKey = SessionKey(id: UInt8(negotiationKeyIdx))
|
||||
keys[negotiationKeyIdx] = newKey
|
||||
@ -661,7 +564,7 @@ public class SessionProxy {
|
||||
private func softReset() {
|
||||
log.debug("Send soft reset")
|
||||
|
||||
resetControlChannel()
|
||||
resetControlChannel(forNewSession: false)
|
||||
negotiationKeyIdx = max(1, (negotiationKeyIdx + 1) % ProtocolMacros.numberOfKeys)
|
||||
let newKey = SessionKey(id: UInt8(negotiationKeyIdx))
|
||||
keys[negotiationKeyIdx] = newKey
|
||||
@ -750,41 +653,32 @@ public class SessionProxy {
|
||||
// MARK: Control
|
||||
|
||||
// Ruby: handle_ctrl_pkt
|
||||
private func handleControlPacket(_ packet: CommonPacket) {
|
||||
private func handleControlPacket(_ packet: ControlPacket) {
|
||||
guard (packet.key == negotiationKey.id) else {
|
||||
log.error("Bad key in control packet (\(packet.key) != \(negotiationKey.id))")
|
||||
// deferStop(.shutdown, SessionError.badKey)
|
||||
return
|
||||
}
|
||||
|
||||
log.debug("Handle control packet with code \(packet.code.rawValue) and id \(packet.packetId)")
|
||||
|
||||
if (((packet.code == .hardResetServerV2) && (negotiationKey.state == .hardReset)) ||
|
||||
((packet.code == .softResetV1) && (negotiationKey.state == .softReset))) {
|
||||
|
||||
if (negotiationKey.state == .hardReset) {
|
||||
guard let sessionId = packet.sessionId else {
|
||||
deferStop(.shutdown, SessionError.missingSessionId)
|
||||
return
|
||||
}
|
||||
remoteSessionId = sessionId
|
||||
if negotiationKey.state == .hardReset {
|
||||
controlChannel.remoteSessionId = packet.sessionId
|
||||
}
|
||||
guard let remoteSessionId = remoteSessionId else {
|
||||
log.error("No remote session id")
|
||||
guard let remoteSessionId = controlChannel.remoteSessionId else {
|
||||
log.error("No remote sessionId (never set)")
|
||||
deferStop(.shutdown, SessionError.missingSessionId)
|
||||
return
|
||||
}
|
||||
guard (packet.sessionId == remoteSessionId) else {
|
||||
if let packetSessionId = packet.sessionId {
|
||||
log.error("Packet session mismatch (\(packetSessionId.toHex()) != \(remoteSessionId.toHex()))")
|
||||
}
|
||||
guard packet.sessionId == remoteSessionId else {
|
||||
log.error("Packet session mismatch (\(packet.sessionId.toHex()) != \(remoteSessionId.toHex()))")
|
||||
deferStop(.shutdown, SessionError.sessionMismatch)
|
||||
return
|
||||
}
|
||||
|
||||
negotiationKey.state = .tls
|
||||
|
||||
log.debug("Remote sessionId is \(remoteSessionId.toHex())")
|
||||
log.debug("Start TLS handshake")
|
||||
|
||||
negotiationKey.tlsOptional = TLSBox(
|
||||
@ -808,14 +702,13 @@ public class SessionProxy {
|
||||
enqueueControlPackets(code: .controlV1, key: negotiationKey.id, payload: cipherTextOut)
|
||||
}
|
||||
else if ((packet.code == .controlV1) && (negotiationKey.state == .tls)) {
|
||||
guard let remoteSessionId = remoteSessionId else {
|
||||
guard let remoteSessionId = controlChannel.remoteSessionId else {
|
||||
log.error("No remote sessionId found in packet (control packets before server HARD_RESET)")
|
||||
deferStop(.shutdown, SessionError.missingSessionId)
|
||||
return
|
||||
}
|
||||
guard (packet.sessionId == remoteSessionId) else {
|
||||
if let packetSessionId = packet.sessionId {
|
||||
log.error("Packet session mismatch (\(packetSessionId.toHex()) != \(remoteSessionId.toHex()))")
|
||||
}
|
||||
guard packet.sessionId == remoteSessionId else {
|
||||
log.error("Packet session mismatch (\(packet.sessionId.toHex()) != \(remoteSessionId.toHex()))")
|
||||
deferStop(.shutdown, SessionError.sessionMismatch)
|
||||
return
|
||||
}
|
||||
@ -838,10 +731,7 @@ public class SessionProxy {
|
||||
}
|
||||
|
||||
do {
|
||||
var length = 0
|
||||
try negotiationKey.tls.pullRawPlainText(controlPlainBuffer.mutableBytes, length: &length)
|
||||
|
||||
let controlData = controlPlainBuffer.withOffset(0, count: length)
|
||||
let controlData = try controlChannel.currentControlData(withTLS: negotiationKey.tls)
|
||||
handleControlData(controlData)
|
||||
} catch _ {
|
||||
}
|
||||
@ -850,7 +740,9 @@ public class SessionProxy {
|
||||
|
||||
// Ruby: handle_ctrl_data
|
||||
private func handleControlData(_ data: ZeroingData) {
|
||||
guard let auth = authenticator else { return }
|
||||
guard let auth = authenticator else {
|
||||
return
|
||||
}
|
||||
|
||||
if CoreConfiguration.logsSensitiveData {
|
||||
log.debug("Pulled plain control data (\(data.count) bytes): \(data.toHex())")
|
||||
@ -946,75 +838,35 @@ public class SessionProxy {
|
||||
log.warning("Not writing to LINK, interface is down")
|
||||
return
|
||||
}
|
||||
|
||||
let oldIdOut = controlPacketIdOut
|
||||
let maxCount = link.mtu
|
||||
var queuedCount = 0
|
||||
var offset = 0
|
||||
|
||||
repeat {
|
||||
let subPayloadLength = min(maxCount, payload.count - offset)
|
||||
let subPayloadData = payload.subdata(offset: offset, count: subPayloadLength)
|
||||
let packet = CommonPacket(controlPacketIdOut, code, key, sessionId, subPayloadData)
|
||||
|
||||
controlQueueOut.append(packet)
|
||||
controlPacketIdOut += 1
|
||||
offset += maxCount
|
||||
queuedCount += subPayloadLength
|
||||
} while (offset < payload.count)
|
||||
|
||||
assert(queuedCount == payload.count)
|
||||
|
||||
let packetCount = controlPacketIdOut - oldIdOut
|
||||
if (packetCount > 1) {
|
||||
log.debug("Enqueued \(packetCount) control packets [\(oldIdOut)-\(controlPacketIdOut - 1)]")
|
||||
} else {
|
||||
log.debug("Enqueued 1 control packet [\(oldIdOut)]")
|
||||
}
|
||||
|
||||
|
||||
controlChannel.enqueueOutboundPackets(withCode: code, key: key, payload: payload, maxPacketSize: link.mtu)
|
||||
flushControlQueue()
|
||||
}
|
||||
|
||||
// Ruby: flush_ctrl_q_out
|
||||
private func flushControlQueue() {
|
||||
for controlPacket in controlQueueOut {
|
||||
if let sentDate = controlPacket.sentDate {
|
||||
let timeAgo = -sentDate.timeIntervalSinceNow
|
||||
guard (timeAgo >= CoreConfiguration.retransmissionLimit) else {
|
||||
log.debug("Skip control packet with id \(controlPacket.packetId) (sent on \(sentDate), \(timeAgo) seconds ago)")
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
log.debug("Send control packet with code \(controlPacket.code.rawValue)")
|
||||
|
||||
if let payload = controlPacket.payload {
|
||||
if CoreConfiguration.logsSensitiveData {
|
||||
log.debug("Control packet has payload (\(payload.count) bytes): \(payload.toHex())")
|
||||
} else {
|
||||
log.debug("Control packet has payload (\(payload.count) bytes)")
|
||||
}
|
||||
}
|
||||
|
||||
let raw = controlPacket.toBuffer()
|
||||
log.debug("Send control packet (\(raw.count) bytes): \(raw.toHex())")
|
||||
|
||||
// track pending acks for sent packets
|
||||
controlPendingAcks.insert(controlPacket.packetId)
|
||||
|
||||
// WARNING: runs in Network.framework queue
|
||||
link?.writePacket(raw) { [weak self] (error) in
|
||||
if let error = error {
|
||||
self?.queue.sync {
|
||||
log.error("Failed LINK write during control flush: \(error)")
|
||||
self?.deferStop(.reconnect, SessionError.failedLinkWrite)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
controlPacket.sentDate = Date()
|
||||
let rawList: [Data]
|
||||
do {
|
||||
rawList = try controlChannel.writeOutboundPackets()
|
||||
} catch let e {
|
||||
log.warning("Failed control packet serialization: \(e)")
|
||||
deferStop(.shutdown, e)
|
||||
return
|
||||
}
|
||||
for raw in rawList {
|
||||
log.debug("Send control packet (\(raw.count) bytes): \(raw.toHex())")
|
||||
}
|
||||
|
||||
// WARNING: runs in Network.framework queue
|
||||
link?.writePackets(rawList) { [weak self] (error) in
|
||||
if let error = error {
|
||||
self?.queue.sync {
|
||||
log.error("Failed LINK write during control flush: \(error)")
|
||||
self?.deferStop(.reconnect, SessionError.failedLinkWrite)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
// log.verbose("Packets now pending ack: \(controlPendingAcks)")
|
||||
}
|
||||
|
||||
// Ruby: setup_keys
|
||||
@ -1022,10 +874,10 @@ public class SessionProxy {
|
||||
guard let auth = authenticator else {
|
||||
fatalError("Setting up encryption without having authenticated")
|
||||
}
|
||||
guard let sessionId = sessionId else {
|
||||
guard let sessionId = controlChannel.sessionId else {
|
||||
fatalError("Setting up encryption without a local sessionId")
|
||||
}
|
||||
guard let remoteSessionId = remoteSessionId else {
|
||||
guard let remoteSessionId = controlChannel.remoteSessionId else {
|
||||
fatalError("Setting up encryption without a remote sessionId")
|
||||
}
|
||||
guard let serverRandom1 = auth.serverRandom1, let serverRandom2 = auth.serverRandom2 else {
|
||||
@ -1088,7 +940,7 @@ public class SessionProxy {
|
||||
|
||||
// Ruby: handle_data_pkt
|
||||
private func handleDataPackets(_ packets: [Data], key: SessionKey) {
|
||||
bytesIn += packets.flatCount
|
||||
controlChannel.addReceivedDataCount(packets.flatCount)
|
||||
do {
|
||||
guard let decryptedPackets = try key.decrypt(packets: packets) else {
|
||||
log.warning("Could not decrypt packets, is SessionKey properly configured (dataPath, peerId)?")
|
||||
@ -1123,7 +975,7 @@ public class SessionProxy {
|
||||
}
|
||||
|
||||
// WARNING: runs in Network.framework queue
|
||||
bytesOut += encryptedPackets.flatCount
|
||||
controlChannel.addSentDataCount(encryptedPackets.flatCount)
|
||||
link?.writePackets(encryptedPackets) { [weak self] (error) in
|
||||
if let error = error {
|
||||
self?.queue.sync {
|
||||
@ -1145,52 +997,40 @@ public class SessionProxy {
|
||||
|
||||
// MARK: Acks
|
||||
|
||||
// Ruby: handle_acks
|
||||
private func handleAcks(_ packetIds: [UInt32], remoteSessionId: Data) {
|
||||
guard (remoteSessionId == sessionId) else {
|
||||
if let sessionId = sessionId {
|
||||
log.error("Ack session mismatch (\(remoteSessionId.toHex()) != \(sessionId.toHex()))")
|
||||
}
|
||||
deferStop(.shutdown, SessionError.sessionMismatch)
|
||||
return
|
||||
}
|
||||
|
||||
// drop queued out packets if ack-ed
|
||||
for (i, controlPacket) in controlQueueOut.enumerated() {
|
||||
if packetIds.contains(controlPacket.packetId) {
|
||||
controlQueueOut.remove(at: i)
|
||||
}
|
||||
}
|
||||
|
||||
// remove ack-ed packets from pending
|
||||
controlPendingAcks.subtract(packetIds)
|
||||
// log.verbose("Packets still pending ack: \(controlPendingAcks)")
|
||||
private func handleAcks() {
|
||||
|
||||
// retry PUSH_REQUEST if ack queue is empty (all sent packets were ack'ed)
|
||||
if (isReliableLink && controlPendingAcks.isEmpty) {
|
||||
if isReliableLink && !controlChannel.hasPendingAcks() {
|
||||
pushRequest()
|
||||
}
|
||||
}
|
||||
|
||||
// Ruby: send_ack
|
||||
private func sendAck(key: UInt8, packetId: UInt32, remoteSessionId: Data) {
|
||||
log.debug("Send ack for received packetId \(packetId)")
|
||||
private func sendAck(for controlPacket: ControlPacket) {
|
||||
log.debug("Send ack for received packetId \(controlPacket.packetId)")
|
||||
|
||||
var raw = PacketWithHeader(.ackV1, key, sessionId)
|
||||
raw.append(UInt8(1)) // ackSize
|
||||
raw.append(UInt32(packetId).bigEndian)
|
||||
raw.append(remoteSessionId)
|
||||
let raw: Data
|
||||
do {
|
||||
raw = try controlChannel.writeAcks(
|
||||
withKey: controlPacket.key,
|
||||
ackPacketIds: [controlPacket.packetId],
|
||||
ackRemoteSessionId: controlPacket.sessionId
|
||||
)
|
||||
} catch let e {
|
||||
deferStop(.shutdown, e)
|
||||
return
|
||||
}
|
||||
|
||||
// WARNING: runs in Network.framework queue
|
||||
link?.writePacket(raw) { [weak self] (error) in
|
||||
if let error = error {
|
||||
self?.queue.sync {
|
||||
log.error("Failed LINK write during send ack for packetId \(packetId): \(error)")
|
||||
log.error("Failed LINK write during send ack for packetId \(controlPacket.packetId): \(error)")
|
||||
self?.deferStop(.reconnect, SessionError.failedLinkWrite)
|
||||
return
|
||||
}
|
||||
}
|
||||
log.debug("Ack successfully written to LINK for packetId \(packetId)")
|
||||
log.debug("Ack successfully written to LINK for packetId \(controlPacket.packetId)")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -42,6 +42,7 @@ module __TunnelKitNative {
|
||||
header "Encryption.h"
|
||||
header "MSS.h"
|
||||
header "PacketMacros.h"
|
||||
header "ControlPacket.h"
|
||||
header "ReplayProtector.h"
|
||||
header "CompressionFramingNative.h"
|
||||
header "DataPath.h"
|
||||
|
@ -77,27 +77,27 @@ class LinkTests: XCTestCase {
|
||||
bytes.append(contentsOf: [0xaa])
|
||||
XCTAssertEqual(bytes.count, 21)
|
||||
|
||||
(until, packets) = CommonPacket.parsed(Data(bytes: bytes))
|
||||
(until, packets) = PacketStream.packets(from: Data(bytes: bytes))
|
||||
XCTAssertEqual(until, 18)
|
||||
XCTAssertEqual(packets.count, 3)
|
||||
|
||||
bytes.append(contentsOf: [0xbb, 0xcc])
|
||||
(until, packets) = CommonPacket.parsed(Data(bytes: bytes))
|
||||
(until, packets) = PacketStream.packets(from: Data(bytes: bytes))
|
||||
XCTAssertEqual(until, 23)
|
||||
XCTAssertEqual(packets.count, 4)
|
||||
|
||||
bytes.append(contentsOf: [0x00, 0x05])
|
||||
(until, packets) = CommonPacket.parsed(Data(bytes: bytes))
|
||||
(until, packets) = PacketStream.packets(from: Data(bytes: bytes))
|
||||
XCTAssertEqual(until, 23)
|
||||
XCTAssertEqual(packets.count, 4)
|
||||
|
||||
bytes.append(contentsOf: [0x11, 0x22, 0x33, 0x44])
|
||||
(until, packets) = CommonPacket.parsed(Data(bytes: bytes))
|
||||
(until, packets) = PacketStream.packets(from: Data(bytes: bytes))
|
||||
XCTAssertEqual(until, 23)
|
||||
XCTAssertEqual(packets.count, 4)
|
||||
|
||||
bytes.append(contentsOf: [0x55])
|
||||
(until, packets) = CommonPacket.parsed(Data(bytes: bytes))
|
||||
(until, packets) = PacketStream.packets(from: Data(bytes: bytes))
|
||||
XCTAssertEqual(until, 30)
|
||||
XCTAssertEqual(packets.count, 5)
|
||||
|
||||
@ -108,7 +108,7 @@ class LinkTests: XCTestCase {
|
||||
|
||||
bytes.append(contentsOf: [0x00, 0x04])
|
||||
bytes.append(contentsOf: [0x10, 0x20])
|
||||
(until, packets) = CommonPacket.parsed(Data(bytes: bytes))
|
||||
(until, packets) = PacketStream.packets(from: Data(bytes: bytes))
|
||||
XCTAssertEqual(until, 0)
|
||||
XCTAssertEqual(packets.count, 0)
|
||||
bytes.removeSubrange(0..<until)
|
||||
@ -117,7 +117,7 @@ class LinkTests: XCTestCase {
|
||||
bytes.append(contentsOf: [0x30, 0x40])
|
||||
bytes.append(contentsOf: [0x00, 0x07])
|
||||
bytes.append(contentsOf: [0x10, 0x20, 0x30, 0x40])
|
||||
(until, packets) = CommonPacket.parsed(Data(bytes: bytes))
|
||||
(until, packets) = PacketStream.packets(from: Data(bytes: bytes))
|
||||
XCTAssertEqual(until, 6)
|
||||
XCTAssertEqual(packets.count, 1)
|
||||
bytes.removeSubrange(0..<until)
|
||||
@ -128,14 +128,14 @@ class LinkTests: XCTestCase {
|
||||
bytes.append(contentsOf: [0xff])
|
||||
bytes.append(contentsOf: [0x00, 0x03])
|
||||
bytes.append(contentsOf: [0xaa])
|
||||
(until, packets) = CommonPacket.parsed(Data(bytes: bytes))
|
||||
(until, packets) = PacketStream.packets(from: Data(bytes: bytes))
|
||||
XCTAssertEqual(until, 12)
|
||||
XCTAssertEqual(packets.count, 2)
|
||||
bytes.removeSubrange(0..<until)
|
||||
XCTAssertEqual(bytes.count, 3)
|
||||
|
||||
bytes.append(contentsOf: [0xbb, 0xcc])
|
||||
(until, packets) = CommonPacket.parsed(Data(bytes: bytes))
|
||||
(until, packets) = PacketStream.packets(from: Data(bytes: bytes))
|
||||
XCTAssertEqual(until, 5)
|
||||
XCTAssertEqual(packets.count, 1)
|
||||
bytes.removeSubrange(0..<until)
|
||||
|
70
TunnelKitTests/PacketTests.swift
Normal file
70
TunnelKitTests/PacketTests.swift
Normal file
@ -0,0 +1,70 @@
|
||||
//
|
||||
// PacketTests.swift
|
||||
// TunnelKitTests
|
||||
//
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import TunnelKit
|
||||
import __TunnelKitNative
|
||||
|
||||
class PacketTests: XCTestCase {
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
// Put setup code here. This method is called before the invocation of each test method in the class.
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
// Put teardown code here. This method is called after the invocation of each test method in the class.
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
func testControlPacket() {
|
||||
let id: UInt32 = 0x1456
|
||||
let code: PacketCode = .controlV1
|
||||
let key: UInt8 = 3
|
||||
let sessionId = Data(hex: "1122334455667788")
|
||||
let payload = Data(hex: "932748238742397591704891")
|
||||
|
||||
let serialized = ControlPacket(code: code, key: key, sessionId: sessionId, packetId: id, payload: payload).serialized()
|
||||
let expected = Data(hex: "2311223344556677880000001456932748238742397591704891")
|
||||
print("Serialized: \(serialized.toHex())")
|
||||
print("Expected : \(expected.toHex())")
|
||||
|
||||
XCTAssertEqual(serialized, expected)
|
||||
}
|
||||
|
||||
func testAckPacket() {
|
||||
let acks: [UInt32] = [0xaa, 0xbb, 0xcc, 0xdd, 0xee]
|
||||
let key: UInt8 = 3
|
||||
let sessionId = Data(hex: "1122334455667788")
|
||||
let remoteSessionId = Data(hex: "a639328cbf03490e")
|
||||
|
||||
let serialized = ControlPacket(key: key, sessionId: sessionId, ackIds: acks as [NSNumber], ackRemoteSessionId: remoteSessionId).serialized()
|
||||
let expected = Data(hex: "2b112233445566778805000000aa000000bb000000cc000000dd000000eea639328cbf03490e")
|
||||
print("Serialized: \(serialized.toHex())")
|
||||
print("Expected : \(expected.toHex())")
|
||||
|
||||
XCTAssertEqual(serialized, expected)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user