diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index d81bb1ece..73df8b416 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -96,7 +96,6 @@ 346129721FD1D74C00532771 /* SignalKeyingStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = 346129591FD1D74B00532771 /* SignalKeyingStorage.m */; }; 346129731FD1E01700532771 /* PromiseKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 451DE9F11DC1585F00810E42 /* PromiseKit.framework */; }; 346129741FD1E02D00532771 /* PromiseKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 451DE9F11DC1585F00810E42 /* PromiseKit.framework */; }; - 346129761FD1E0B500532771 /* WeakTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 346129751FD1E0B500532771 /* WeakTimer.swift */; }; 346129951FD1E30000532771 /* OWSDatabaseMigration.h in Headers */ = {isa = PBXBuildFile; fileRef = 346129931FD1E30000532771 /* OWSDatabaseMigration.h */; settings = {ATTRIBUTES = (Public, ); }; }; 346129961FD1E30000532771 /* OWSDatabaseMigration.m in Sources */ = {isa = PBXBuildFile; fileRef = 346129941FD1E30000532771 /* OWSDatabaseMigration.m */; }; 346129991FD1E4DA00532771 /* SignalApp.m in Sources */ = {isa = PBXBuildFile; fileRef = 346129971FD1E4D900532771 /* SignalApp.m */; }; @@ -697,7 +696,6 @@ 346129571FD1D74B00532771 /* Release.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Release.m; sourceTree = ""; }; 346129581FD1D74B00532771 /* SignalKeyingStorage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SignalKeyingStorage.h; sourceTree = ""; }; 346129591FD1D74B00532771 /* SignalKeyingStorage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SignalKeyingStorage.m; sourceTree = ""; }; - 346129751FD1E0B500532771 /* WeakTimer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WeakTimer.swift; sourceTree = ""; }; 346129931FD1E30000532771 /* OWSDatabaseMigration.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSDatabaseMigration.h; sourceTree = ""; }; 346129941FD1E30000532771 /* OWSDatabaseMigration.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSDatabaseMigration.m; sourceTree = ""; }; 346129971FD1E4D900532771 /* SignalApp.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SignalApp.m; sourceTree = ""; }; @@ -1461,7 +1459,6 @@ B97940251832BD2400BD66CB /* UIUtil.h */, B97940261832BD2400BD66CB /* UIUtil.m */, 45F170D51E315310003FC1F2 /* Weak.swift */, - 346129751FD1E0B500532771 /* WeakTimer.swift */, ); path = utils; sourceTree = ""; @@ -3076,7 +3073,6 @@ 344F249A200FD03300CFB4F4 /* MessageApprovalViewController.swift in Sources */, 450998681FD8C0FF00D89EB3 /* AttachmentSharing.m in Sources */, 347850711FDAEB17007B8332 /* OWSUserProfile.m in Sources */, - 346129761FD1E0B500532771 /* WeakTimer.swift in Sources */, 346129F81FD5F31400532771 /* OWS100RemoveTSRecipientsMigration.m in Sources */, 34074F61203D0CBE004596AE /* OWSSounds.m in Sources */, 346129B51FD1F7E800532771 /* OWSProfileManager.m in Sources */, diff --git a/SignalServiceKit/src/Network/API/TSNetworkManager.m b/SignalServiceKit/src/Network/API/TSNetworkManager.m index 938f57fa7..75aa68dde 100644 --- a/SignalServiceKit/src/Network/API/TSNetworkManager.m +++ b/SignalServiceKit/src/Network/API/TSNetworkManager.m @@ -9,6 +9,7 @@ #import "TSAccountManager.h" #import "TSVerifyCodeRequest.h" #import +#import NSString *const TSNetworkManagerDomain = @"org.whispersystems.signal.networkManager"; @@ -93,6 +94,8 @@ typedef void (^failureBlock)(NSURLSessionDataTask *task, NSError *error); } successBlock(task, responseObject); + + [OutageDetection.sharedManager reportNetworkSuccess]; }; TSNetworkManagerFailure failure = [TSNetworkManager errorPrettifyingForFailureBlock:failureBlock request:request]; @@ -151,6 +154,9 @@ typedef void (^failureBlock)(NSURLSessionDataTask *task, NSError *error); return ^(NSURLSessionDataTask *_Nullable task, NSError *_Nonnull networkError) { NSInteger statusCode = [task statusCode]; + + [OutageDetection.sharedManager reportNetworkFailure]; + NSError *error = [self errorWithHTTPCode:statusCode description:nil failureReason:nil diff --git a/SignalServiceKit/src/Network/OutageDetection.swift b/SignalServiceKit/src/Network/OutageDetection.swift new file mode 100644 index 000000000..e645c6b43 --- /dev/null +++ b/SignalServiceKit/src/Network/OutageDetection.swift @@ -0,0 +1,110 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +import Foundation +import os + +@objc +public class OutageDetection: NSObject { + @objc(sharedManager) + public static let shared = OutageDetection() + + // These properties should only be accessed on the main thread. + private var hasOutage = false { + didSet { + SwiftAssertIsOnMainThread(#function) + } + } + private var mayHaveOutage = false { + didSet { + SwiftAssertIsOnMainThread(#function) + + ensureCheckTimer() + } + } + + private func checkForOutageSync() -> Bool { + let host = CFHostCreateWithName(nil, "uptime.signal.org" as CFString).takeRetainedValue() + CFHostStartInfoResolution(host, .addresses, nil) + var success: DarwinBoolean = false + guard let addresses = CFHostGetAddressing(host, &success)?.takeUnretainedValue() as NSArray? else { + Logger.error("\(logTag) CFHostGetAddressing failed: no addresses.") + return false + } + guard success.boolValue else { + Logger.error("\(logTag) CFHostGetAddressing failed.") + return false + } + var isOutageDetected = false + for case let address as NSData in addresses { + var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST)) + if getnameinfo(address.bytes.assumingMemoryBound(to: sockaddr.self), socklen_t(address.length), + &hostname, socklen_t(hostname.count), nil, 0, NI_NUMERICHOST) == 0 { + let addressString = String(cString: hostname) + if addressString != "127.0.0.1" { + Logger.verbose("\(logTag) addressString: \(addressString)") + isOutageDetected = true + } + } + } + return isOutageDetected + } + + private func checkForOutageAsync() { + DispatchQueue.global().async { + let isOutageDetected = self.checkForOutageSync() + DispatchQueue.main.async { + self.hasOutage = isOutageDetected + } + } + } + + private var checkTimer: Timer? + private func ensureCheckTimer() { + // Only monitor for outages in the main app. + guard CurrentAppContext().isMainApp else { + return + } + + if mayHaveOutage { + if checkTimer != nil { + // Already has timer. + return + } + + // The TTL of the DNS record is 60 seconds. + checkTimer = WeakTimer.scheduledTimer(timeInterval: 60, target: self, userInfo: nil, repeats: true) { [weak self] _ in + SwiftAssertIsOnMainThread(#function) + + guard CurrentAppContext().isMainAppAndActive else { + return + } + + guard let strongSelf = self else { + return + } + + strongSelf.checkForOutageAsync() + } + } else { + checkTimer?.invalidate() + checkTimer = nil + } + } + + @objc + public func reportNetworkSuccess() { + SwiftAssertIsOnMainThread(#function) + + mayHaveOutage = true + hasOutage = false + } + + @objc + public func reportNetworkFailure() { + SwiftAssertIsOnMainThread(#function) + + mayHaveOutage = false + } +} diff --git a/SignalServiceKit/src/Network/WebSockets/TSSocketManager.m b/SignalServiceKit/src/Network/WebSockets/TSSocketManager.m index 0c3d9b2f0..d8b928a5f 100644 --- a/SignalServiceKit/src/Network/WebSockets/TSSocketManager.m +++ b/SignalServiceKit/src/Network/WebSockets/TSSocketManager.m @@ -24,6 +24,7 @@ #import "TextSecureKitEnv.h" #import "Threading.h" #import "WebSocketResources.pb.h" +#import NS_ASSUME_NONNULL_BEGIN @@ -677,6 +678,8 @@ NSString *const kNSNotification_SocketManagerStateDidChange = @"kNSNotification_ // If socket opens, we know we're not de-registered. [TSAccountManager.sharedInstance setIsDeregistered:NO]; + + [OutageDetection.sharedManager reportNetworkSuccess]; } - (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error { @@ -822,6 +825,8 @@ NSString *const kNSNotification_SocketManagerStateDidChange = @"kNSNotification_ // Otherwise clean up and align state. [self applyDesiredSocketState]; } + + [OutageDetection.sharedManager reportNetworkFailure]; } - (void)webSocket:(SRWebSocket *)webSocket diff --git a/SignalMessaging/utils/WeakTimer.swift b/SignalServiceKit/src/Util/WeakTimer.swift similarity index 96% rename from SignalMessaging/utils/WeakTimer.swift rename to SignalServiceKit/src/Util/WeakTimer.swift index 073cdba86..f3553b6f4 100644 --- a/SignalMessaging/utils/WeakTimer.swift +++ b/SignalServiceKit/src/Util/WeakTimer.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. // /**