Skip to content
This repository has been archived by the owner on Sep 29, 2024. It is now read-only.

Handle server-initiated reset #41

Merged
merged 3 commits into from
Oct 24, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- CA file was not closed after MD5 calculation when using PIA patches.
- Mitigated an issue with MTU in TCP mode during negotiation. [#39](https://github.com/keeshux/tunnelkit/issues/39)
- Handle server-initiated renegotiation. [#41](https://github.com/keeshux/tunnelkit/pull/41)

## 1.2.0 (2018-10-20)

Expand Down
2 changes: 1 addition & 1 deletion TunnelKit/Sources/AppExtension/TunnelKitProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -652,7 +652,7 @@ extension TunnelKitProvider {
}
} else if let se = error as? SessionError {
switch se {
case .negotiationTimeout, .pingTimeout:
case .negotiationTimeout, .pingTimeout, .staleSession:
return .timeout

case .badCredentials:
Expand Down
3 changes: 3 additions & 0 deletions TunnelKit/Sources/Core/SessionError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ public enum SessionError: String, Error {

/// The server couldn't ping back before timeout.
case pingTimeout

/// The session reached a stale state and can't be recovered.
case staleSession
}

extension Error {
Expand Down
40 changes: 26 additions & 14 deletions TunnelKit/Sources/Core/SessionProxy.swift
Original file line number Diff line number Diff line change
Expand Up @@ -326,12 +326,6 @@ public class SessionProxy {
private func start() {
loopLink()
hardReset()

guard !keys.isEmpty else {
fatalError("Main loop must follow hard reset, keys are empty!")
}

loopNegotiation()
}

private func loopNegotiation() {
Expand Down Expand Up @@ -466,6 +460,12 @@ public class SessionProxy {
// deferStop(.shutdown, e)
// return
}
if (code == .hardResetServerV2) && (negotiationKey.state != .hardReset) {
deferStop(.shutdown, SessionError.staleSession)
return
} else if (code == .softResetV1) && (negotiationKey.state != .softReset) {
softReset(isServerInitiated: true)
}

sendAck(for: controlPacket)

Expand Down Expand Up @@ -557,6 +557,10 @@ public class SessionProxy {

let payload = hardResetPayload() ?? Data()
negotiationKey.state = .hardReset
guard !keys.isEmpty else {
fatalError("Main loop must follow hard reset, keys are empty!")
}
loopNegotiation()
enqueueControlPackets(code: .hardResetClientV2, key: UInt8(negotiationKeyIdx), payload: payload)
}

Expand All @@ -574,8 +578,12 @@ public class SessionProxy {
}

// Ruby: soft_reset
private func softReset() {
log.debug("Send soft reset")
private func softReset(isServerInitiated: Bool) {
if isServerInitiated {
log.debug("Handle soft reset")
} else {
log.debug("Send soft reset")
}

resetControlChannel(forNewSession: false)
negotiationKeyIdx = max(1, (negotiationKeyIdx + 1) % ProtocolMacros.numberOfKeys)
Expand All @@ -586,7 +594,9 @@ public class SessionProxy {
negotiationKey.state = .softReset
negotiationKey.softReset = true
loopNegotiation()
enqueueControlPackets(code: .softResetV1, key: UInt8(negotiationKeyIdx), payload: Data())
if !isServerInitiated {
enqueueControlPackets(code: .softResetV1, key: UInt8(negotiationKeyIdx), payload: Data())
}
}

// Ruby: on_tls_connect
Expand Down Expand Up @@ -667,7 +677,7 @@ public class SessionProxy {
let elapsed = -negotiationKey.startTime.timeIntervalSinceNow
if (elapsed > renegotiatesAfter) {
log.debug("Renegotiating after \(elapsed) seconds")
softReset()
softReset(isServerInitiated: false)
}
}

Expand All @@ -683,15 +693,16 @@ public class SessionProxy {

// Ruby: handle_ctrl_pkt
private func handleControlPacket(_ packet: ControlPacket) {
guard (packet.key == negotiationKey.id) else {
guard packet.key == negotiationKey.id else {
log.error("Bad key in control packet (\(packet.key) != \(negotiationKey.id))")
// deferStop(.shutdown, SessionError.badKey)
return
}

if (((packet.code == .hardResetServerV2) && (negotiationKey.state == .hardReset)) ||
((packet.code == .softResetV1) && (negotiationKey.state == .softReset))) {

// start new TLS handshake
if ((packet.code == .hardResetServerV2) && (negotiationKey.state == .hardReset)) ||
((packet.code == .softResetV1) && (negotiationKey.state == .softReset)) {

if negotiationKey.state == .hardReset {
controlChannel.remoteSessionId = packet.sessionId
}
Expand Down Expand Up @@ -738,6 +749,7 @@ public class SessionProxy {
log.debug("TLS.connect: Pulled ciphertext (\(cipherTextOut.count) bytes)")
enqueueControlPackets(code: .controlV1, key: negotiationKey.id, payload: cipherTextOut)
}
// exchange TLS ciphertext
else if ((packet.code == .controlV1) && (negotiationKey.state == .tls)) {
guard let remoteSessionId = controlChannel.remoteSessionId else {
log.error("No remote sessionId found in packet (control packets before server HARD_RESET)")
Expand Down