143 lines
4.4 KiB
Swift
143 lines
4.4 KiB
Swift
//
|
|
// Copyright 2022 Signal Messenger, LLC
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
//
|
|
|
|
import Foundation
|
|
import Network
|
|
|
|
extension SignalProxy {
|
|
/// An HTTP Proxy server that relays traffic to a Signal TLS Proxy
|
|
class RelayServer {
|
|
@Atomic private(set) var isStarted = false
|
|
|
|
@Atomic private(set) var isReady = false {
|
|
didSet {
|
|
NotificationCenter.default.postNotificationNameAsync(.isSignalProxyReadyDidChange, object: nil)
|
|
}
|
|
}
|
|
|
|
var connectionProxyDictionary: [AnyHashable: Any]? {
|
|
guard isReady, let port = listener?.port?.rawValue else { return nil }
|
|
|
|
return [
|
|
"HTTPSEnable": true,
|
|
"HTTPSProxy": "localhost",
|
|
"HTTPSPort": port,
|
|
"HTTPEnable": true,
|
|
"HTTPProxy": "localhost",
|
|
"HTTPPort": port
|
|
]
|
|
}
|
|
|
|
@Atomic private var listener: NWListener?
|
|
@Atomic private var clients = [UUID: RelayClient]()
|
|
@Atomic private var backgroundTask: OWSBackgroundTask?
|
|
|
|
private let queue = DispatchQueue(label: "org.signal.proxy.relay-server", attributes: .concurrent)
|
|
|
|
func start() {
|
|
guard !isStarted else { return }
|
|
guard SignalProxy.isEnabled else { return }
|
|
|
|
isStarted = true
|
|
|
|
backgroundTask = OWSBackgroundTask(label: "RelayServer") { [weak self] status in
|
|
guard status == .expired else { return }
|
|
self?.stop(error: OWSAssertionError("Background time expired"))
|
|
}
|
|
|
|
Logger.info("Relay server starting...")
|
|
|
|
do {
|
|
listener = try NWListener(using: .tcp, on: .any)
|
|
listener?.stateUpdateHandler = stateDidChange
|
|
listener?.newConnectionHandler = didAccept
|
|
listener?.start(queue: queue)
|
|
|
|
restartFailureCount = 0
|
|
} catch {
|
|
restartIfNeeded(error: error)
|
|
}
|
|
}
|
|
|
|
func stop(error: Error? = nil) {
|
|
guard isStarted else { return }
|
|
isStarted = false
|
|
isReady = false
|
|
backgroundTask = nil
|
|
|
|
listener?.stateUpdateHandler = nil
|
|
listener?.newConnectionHandler = nil
|
|
listener?.cancel()
|
|
listener = nil
|
|
|
|
for client in clients.values {
|
|
client.didStopCallback = nil
|
|
client.stop()
|
|
}
|
|
clients.removeAll()
|
|
|
|
if let error = error {
|
|
owsFailDebug("Relay server stopped with error \(error)")
|
|
} else {
|
|
restartFailureCount = 0
|
|
}
|
|
}
|
|
|
|
@Atomic private var restartFailureCount: UInt = 0
|
|
@Atomic private var restartBackoffTimer: Timer?
|
|
|
|
func restartIfNeeded(error: Error? = nil, ignoreBackoff: Bool = false) {
|
|
guard isStarted else { return }
|
|
|
|
restartBackoffTimer?.invalidate()
|
|
restartBackoffTimer = nil
|
|
|
|
if error != nil { restartFailureCount += 1 }
|
|
stop(error: error)
|
|
|
|
restartBackoffTimer = .scheduledTimer(
|
|
withTimeInterval: ignoreBackoff ? 0 : OWSOperation.retryIntervalForExponentialBackoff(
|
|
failureCount: restartFailureCount,
|
|
maxBackoff: 15 * kSecondInterval
|
|
),
|
|
repeats: false
|
|
) { [weak self] _ in
|
|
guard let self = self else { return }
|
|
|
|
Logger.info("Restarting relay server...")
|
|
|
|
self.start()
|
|
|
|
self.restartBackoffTimer?.invalidate()
|
|
self.restartBackoffTimer = nil
|
|
}
|
|
}
|
|
|
|
func stateDidChange(to newState: NWListener.State) {
|
|
switch newState {
|
|
case .ready:
|
|
Logger.info("Relay server ready.")
|
|
isReady = true
|
|
case .failed(let error):
|
|
restartIfNeeded(error: error)
|
|
default:
|
|
break
|
|
}
|
|
}
|
|
|
|
private func didAccept(connection: NWConnection) {
|
|
let client = RelayClient(connection: connection)
|
|
clients[client.id] = client
|
|
client.didStopCallback = { [weak self] _ in
|
|
self?.clientDidStop(client)
|
|
}
|
|
client.start()
|
|
}
|
|
|
|
private func clientDidStop(_ client: RelayClient) {
|
|
clients[client.id] = nil
|
|
}
|
|
}
|
|
}
|