diff --git a/CHANGELOG.md b/CHANGELOG.md index 7fb1f1a..16b21f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Index out of range during negotiation (Grivus). [#143](https://github.com/passepartoutvpn/tunnelkit/pull/143) +- Handle server shutdown/restart (remote `--explicit-exit-notify`). [#131](https://github.com/passepartoutvpn/tunnelkit/issues/131) ## 2.2.1 (2019-12-14) diff --git a/TunnelKit/Sources/Protocols/OpenVPN/AppExtension/OpenVPNTunnelProvider+Interaction.swift b/TunnelKit/Sources/Protocols/OpenVPN/AppExtension/OpenVPNTunnelProvider+Interaction.swift index d39e4ed..00e1891 100644 --- a/TunnelKit/Sources/Protocols/OpenVPN/AppExtension/OpenVPNTunnelProvider+Interaction.swift +++ b/TunnelKit/Sources/Protocols/OpenVPN/AppExtension/OpenVPNTunnelProvider+Interaction.swift @@ -142,6 +142,9 @@ extension OpenVPNTunnelProvider { /// Default gateway could not be attained. case gatewayUnattainable + /// Remove server has shut down. + case serverShutdown + /// The server replied in an unexpected way. case unexpectedReply } diff --git a/TunnelKit/Sources/Protocols/OpenVPN/AppExtension/OpenVPNTunnelProvider.swift b/TunnelKit/Sources/Protocols/OpenVPN/AppExtension/OpenVPNTunnelProvider.swift index 74c0867..9c1f038 100644 --- a/TunnelKit/Sources/Protocols/OpenVPN/AppExtension/OpenVPNTunnelProvider.swift +++ b/TunnelKit/Sources/Protocols/OpenVPN/AppExtension/OpenVPNTunnelProvider.swift @@ -876,6 +876,9 @@ extension OpenVPNTunnelProvider { case .noRouting: return .routing + + case .serverShutdown: + return .serverShutdown default: return .unexpectedReply diff --git a/TunnelKit/Sources/Protocols/OpenVPN/Authenticator.swift b/TunnelKit/Sources/Protocols/OpenVPN/Authenticator.swift index 219e39b..0973a22 100644 --- a/TunnelKit/Sources/Protocols/OpenVPN/Authenticator.swift +++ b/TunnelKit/Sources/Protocols/OpenVPN/Authenticator.swift @@ -62,9 +62,9 @@ extension OpenVPN { private(set) var serverRandom2: ZeroingData? - let username: ZeroingData? + private(set) var username: ZeroingData? - let password: ZeroingData? + private(set) var password: ZeroingData? var withLocalOptions: Bool @@ -87,6 +87,17 @@ extension OpenVPN { controlBuffer = Z() } + func reset() { + controlBuffer.zero() + preMaster.zero() + random1.zero() + random2.zero() + serverRandom1?.zero() + serverRandom2?.zero() + username = nil + password = nil + } + // MARK: Authentication request // Ruby: on_tls_connect diff --git a/TunnelKit/Sources/Protocols/OpenVPN/OpenVPNError.swift b/TunnelKit/Sources/Protocols/OpenVPN/OpenVPNError.swift index db394c9..d920051 100644 --- a/TunnelKit/Sources/Protocols/OpenVPN/OpenVPNError.swift +++ b/TunnelKit/Sources/Protocols/OpenVPN/OpenVPNError.swift @@ -77,4 +77,7 @@ public enum OpenVPNError: String, Error { /// Missing routing information. case noRouting + + /// Remote server shut down (--explicit-exit-notify). + case serverShutdown } diff --git a/TunnelKit/Sources/Protocols/OpenVPN/OpenVPNSession.swift b/TunnelKit/Sources/Protocols/OpenVPN/OpenVPNSession.swift index db0c3c3..c487d04 100644 --- a/TunnelKit/Sources/Protocols/OpenVPN/OpenVPNSession.swift +++ b/TunnelKit/Sources/Protocols/OpenVPN/OpenVPNSession.swift @@ -745,7 +745,7 @@ public class OpenVPNSession: Session { private func completeConnection() { setupEncryption() - authenticator = nil + authenticator?.reset() negotiationKey.controlState = .connected connectedDate = Date() transitionKeys() @@ -907,6 +907,11 @@ public class OpenVPNSession: Session { // Ruby: handle_ctrl_msg private func handleControlMessage(_ message: String) { + if CoreConfiguration.logsSensitiveData { + log.debug("Received control message: \"\(message)\"") + } + + // disconnect on authentication failure guard !message.hasPrefix("AUTH_FAILED") else { // XXX: retry without client options @@ -921,14 +926,18 @@ public class OpenVPNSession: Session { return } - guard (negotiationKey.controlState == .preIfConfig) else { + // disconnect on remote server restart (--explicit-exit-notify) + guard !message.hasPrefix("RESTART") else { + log.debug("Disconnecting due to server shutdown") + deferStop(.shutdown, OpenVPNError.serverShutdown) + return + } + + // handle authentication from now on + guard negotiationKey.controlState == .preIfConfig else { return } - if CoreConfiguration.logsSensitiveData { - log.debug("Received control message: \"\(message)\"") - } - let completeMessage: String if let continuated = continuatedPushReplyMessage { completeMessage = "\(continuated),\(message)"