Merge branch 'refactor-control-channel'

This commit is contained in:
Davide De Rosa 2018-09-20 09:01:23 +02:00
commit 9d6c7c846f
19 changed files with 936 additions and 347 deletions

View File

@ -17,8 +17,13 @@ custom_categories:
- IOInterface
- LinkInterface
- TunnelInterface
- PacketStream
- BidirectionalState
- SessionProxy
- SessionProxyDelegate
- SessionReply
- IPv4Settings
- IPv6Settings
- SessionError
- name: AppExtension
children:

View File

@ -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 */,

View File

@ -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)
}

View File

@ -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:

View 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
}
}

View 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
}
}

View 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
}
}
}

View 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

View 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

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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"
}
}
}

View File

@ -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

View File

@ -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")

View File

@ -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?

View File

@ -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)")
}
}

View File

@ -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"

View File

@ -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)

View 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)
}
}