From 4b8a5f8ccb2942a0bc8533bdb8e5830dee058232 Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Wed, 22 Feb 2017 10:06:01 -0500 Subject: [PATCH] TURN-only option, and for unknown caller Now, by default, we only use TURN for incoming calls from unknown contacts. We will potentially directly connect for outgoing calls and for incoming calls from known contacts. Optionally, the user can disable direct connection altogether, at the cost of some call quality. // FREEBIE --- Signal/src/call/CallService.swift | 16 +++++++- Signal/src/call/PeerConnectionClient.swift | 8 +++- .../src/environment/PropertyListPreferences.h | 5 +++ .../src/environment/PropertyListPreferences.m | 19 ++++++++++ .../PrivacySettingsTableViewController.m | 38 ++++++++++++++++++- .../translations/en.lproj/Localizable.strings | 28 +++++++------- 6 files changed, 94 insertions(+), 20 deletions(-) diff --git a/Signal/src/call/CallService.swift b/Signal/src/call/CallService.swift index 9cf4ba085..77e8f1de5 100644 --- a/Signal/src/call/CallService.swift +++ b/Signal/src/call/CallService.swift @@ -231,6 +231,9 @@ protocol CallServiceObserver: class { self.updateIsVideoEnabled() } + /** + * Choose whether to use CallKit or a Notification backed interface for calling. + */ public func createCallUIAdapter() { AssertIsOnMainThread() @@ -299,7 +302,9 @@ protocol CallServiceObserver: class { return getIceServers().then { iceServers -> Promise in Logger.debug("\(self.TAG) got ice servers:\(iceServers)") - let peerConnectionClient = PeerConnectionClient(iceServers: iceServers, delegate: self, callDirection: .outgoing) + let useTurnOnly = Environment.getCurrent().preferences.doCallsHideIPAddress() + + let peerConnectionClient = PeerConnectionClient(iceServers: iceServers, delegate: self, callDirection: .outgoing, useTurnOnly: useTurnOnly) assert(self.peerConnectionClient == nil, "Unexpected PeerConnectionClient instance") Logger.debug("\(self.TAG) setting peerConnectionClient in \(#function)") @@ -478,8 +483,15 @@ protocol CallServiceObserver: class { throw CallError.assertionError(description: "getIceServers() response for obsolete call") } assert(self.peerConnectionClient == nil, "Unexpected PeerConnectionClient instance") + + // For contacts not stored in our system contacts, we assume they are an unknown caller, and we force + // a TURN connection, so as not to reveal any connectivity information (IP/port) to the caller. + let unknownCaller = self.contactsManager.contact(forPhoneIdentifier: thread.contactIdentifier()) == nil + + let useTurnOnly = unknownCaller || Environment.getCurrent().preferences.doCallsHideIPAddress() + Logger.debug("\(self.self.TAG) setting peerConnectionClient in \(#function)") - self.peerConnectionClient = PeerConnectionClient(iceServers: iceServers, delegate: self, callDirection: .incoming) + self.peerConnectionClient = PeerConnectionClient(iceServers: iceServers, delegate: self, callDirection: .incoming, useTurnOnly: useTurnOnly) let offerSessionDescription = RTCSessionDescription(type: .offer, sdp: callerSessionDescription) let constraints = RTCMediaConstraints(mandatoryConstraints: nil, optionalConstraints: nil) diff --git a/Signal/src/call/PeerConnectionClient.swift b/Signal/src/call/PeerConnectionClient.swift index 676f23c01..ee9db30fe 100644 --- a/Signal/src/call/PeerConnectionClient.swift +++ b/Signal/src/call/PeerConnectionClient.swift @@ -120,7 +120,7 @@ class PeerConnectionClient: NSObject, RTCPeerConnectionDelegate, RTCDataChannelD private var remoteVideoTrack: RTCVideoTrack? private var cameraConstraints: RTCMediaConstraints - init(iceServers: [RTCIceServer], delegate: PeerConnectionClientDelegate, callDirection: CallDirection) { + init(iceServers: [RTCIceServer], delegate: PeerConnectionClientDelegate, callDirection: CallDirection, useTurnOnly: Bool) { AssertIsOnMainThread() self.iceServers = iceServers @@ -130,6 +130,12 @@ class PeerConnectionClient: NSObject, RTCPeerConnectionDelegate, RTCDataChannelD configuration.iceServers = iceServers configuration.bundlePolicy = .maxBundle configuration.rtcpMuxPolicy = .require + if useTurnOnly { + Logger.debug("\(TAG) using iceTransportPolicy: relay") + configuration.iceTransportPolicy = .relay + } else { + Logger.debug("\(TAG) using iceTransportPolicy: default") + } let connectionConstraintsDict = ["DtlsSrtpKeyAgreement": "true"] connectionConstraints = RTCMediaConstraints(mandatoryConstraints: nil, optionalConstraints: connectionConstraintsDict) diff --git a/Signal/src/environment/PropertyListPreferences.h b/Signal/src/environment/PropertyListPreferences.h index 357a6cb0d..c678ba72f 100644 --- a/Signal/src/environment/PropertyListPreferences.h +++ b/Signal/src/environment/PropertyListPreferences.h @@ -71,6 +71,11 @@ extern NSString *const PropertyListPreferencesKeyEnableDebugLog; - (BOOL)isCallKitEnabled; - (void)setIsCallKitEnabled:(BOOL)flag; +#pragma mark direct call connectivity (non-TURN) + +- (BOOL)doCallsHideIPAddress; +- (void)setDoCallsHideIPAddress:(BOOL)flag; + #pragma mark - Block on Identity Change - (BOOL)shouldBlockOnIdentityChange; diff --git a/Signal/src/environment/PropertyListPreferences.m b/Signal/src/environment/PropertyListPreferences.m index 1709f5bbc..f176bf126 100644 --- a/Signal/src/environment/PropertyListPreferences.m +++ b/Signal/src/environment/PropertyListPreferences.m @@ -23,6 +23,7 @@ NSString *const PropertyListPreferencesKeyHasRegisteredVoipPush = @"VOIPPushEnab NSString *const PropertyListPreferencesKeyLastRecordedPushToken = @"LastRecordedPushToken"; NSString *const PropertyListPreferencesKeyLastRecordedVoipToken = @"LastRecordedVoipToken"; NSString *const PropertyListPreferencesKeyCallKitEnabled = @"CallKitEnabled"; +NSString *const PropertyListPreferencesKeyCallsHideIPAddress = @"CallsHideIPAddress"; @implementation PropertyListPreferences @@ -138,6 +139,9 @@ NSString *const PropertyListPreferencesKeyCallKitEnabled = @"CallKitEnabled"; - (void)setLoggingEnabled:(BOOL)flag { + // Logging preferences are stored in UserDefaults instead of the database, so that we can (optionally) start + // logging before the database is initialized. This is important because sometimes there are problems *with* the + // database initialization, and without logging it would be hard to track down. [NSUserDefaults.standardUserDefaults setObject:@(flag) forKey:PropertyListPreferencesKeyEnableDebugLog]; [NSUserDefaults.standardUserDefaults synchronize]; } @@ -182,6 +186,21 @@ NSString *const PropertyListPreferencesKeyCallKitEnabled = @"CallKitEnabled"; [self setValueForKey:PropertyListPreferencesKeyCallKitEnabled toValue:@(flag)]; } +#pragma mark direct call connectivity (non-TURN) + +// Allow callers to connect directly, when desirable, vs. enforcing TURN only proxy connectivity + +- (BOOL)doCallsHideIPAddress +{ + NSNumber *preference = [self tryGetValueForKey:PropertyListPreferencesKeyCallsHideIPAddress]; + return preference ? [preference boolValue] : NO; +} + +- (void)setDoCallsHideIPAddress:(BOOL)flag +{ + [self setValueForKey:PropertyListPreferencesKeyCallsHideIPAddress toValue:@(flag)]; +} + #pragma mark Notification Preferences - (BOOL)soundInForeground diff --git a/Signal/src/view controllers/PrivacySettingsTableViewController.m b/Signal/src/view controllers/PrivacySettingsTableViewController.m index 0313d9d02..0fd3b2d9b 100644 --- a/Signal/src/view controllers/PrivacySettingsTableViewController.m +++ b/Signal/src/view controllers/PrivacySettingsTableViewController.m @@ -14,16 +14,23 @@ NS_ASSUME_NONNULL_BEGIN typedef NS_ENUM(NSInteger, PrivacySettingsTableViewControllerSectionIndex) { PrivacySettingsTableViewControllerSectionIndexScreenSecurity, + PrivacySettingsTableViewControllerSectionIndexCalling, PrivacySettingsTableViewControllerSectionIndexHistoryLog, - PrivacySettingsTableViewControllerSectionIndexBlockOnIdentityChange + PrivacySettingsTableViewControllerSectionIndexBlockOnIdentityChange, + PrivacySettingsTableViewControllerSectionIndex_Count // meta section to track how many sections }; @interface PrivacySettingsTableViewController () @property (nonatomic, strong) UITableViewCell *enableScreenSecurityCell; @property (nonatomic, strong) UISwitch *enableScreenSecuritySwitch; + +@property (nonatomic) UITableViewCell *callsHideIPAddressCell; +@property (nonatomic) UISwitch *callsHideIPAddressSwitch; + @property (nonatomic, strong) UITableViewCell *blockOnIdentityChangeCell; @property (nonatomic, strong) UISwitch *blockOnIdentityChangeSwitch; + @property (nonatomic, strong) UITableViewCell *clearHistoryLogCell; @end @@ -59,6 +66,17 @@ typedef NS_ENUM(NSInteger, PrivacySettingsTableViewControllerSectionIndex) { action:@selector(didToggleScreenSecuritySwitch:) forControlEvents:UIControlEventTouchUpInside]; + // Allow calls to connect directly vs. using TURN exclusively + self.callsHideIPAddressCell = [UITableViewCell new]; + self.callsHideIPAddressCell.textLabel.text + = NSLocalizedString(@"SETTINGS_CALLING_HIDES_IP_ADDRESS_PREFERENCE_TITLE", @"Table cell label"); + self.callsHideIPAddressSwitch = [UISwitch new]; + self.callsHideIPAddressCell.accessoryView = self.callsHideIPAddressSwitch; + [self.callsHideIPAddressSwitch setOn:[Environment.preferences doCallsHideIPAddress]]; + [self.callsHideIPAddressSwitch addTarget:self + action:@selector(didToggleCallsHideIPAddressSwitch:) + forControlEvents:UIControlEventTouchUpInside]; + // Clear History Log Cell self.clearHistoryLogCell = [[UITableViewCell alloc] init]; self.clearHistoryLogCell.textLabel.text = NSLocalizedString(@"SETTINGS_CLEAR_HISTORY", @""); @@ -79,13 +97,15 @@ typedef NS_ENUM(NSInteger, PrivacySettingsTableViewControllerSectionIndex) { #pragma mark - Table view data source - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { - return 3; + return PrivacySettingsTableViewControllerSectionIndex_Count; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { switch (section) { case PrivacySettingsTableViewControllerSectionIndexScreenSecurity: return 1; + case PrivacySettingsTableViewControllerSectionIndexCalling: + return 1; case PrivacySettingsTableViewControllerSectionIndexHistoryLog: return 1; case PrivacySettingsTableViewControllerSectionIndexBlockOnIdentityChange: @@ -100,6 +120,9 @@ typedef NS_ENUM(NSInteger, PrivacySettingsTableViewControllerSectionIndex) { switch (section) { case PrivacySettingsTableViewControllerSectionIndexScreenSecurity: return NSLocalizedString(@"SETTINGS_SCREEN_SECURITY_DETAIL", nil); + case PrivacySettingsTableViewControllerSectionIndexCalling: + return NSLocalizedString(@"SETTINGS_CALLING_HIDES_IP_ADDRESS_PREFERENCE_TITLE_DETAIL", + @"User settings section footer, a detailed explanation"); case PrivacySettingsTableViewControllerSectionIndexBlockOnIdentityChange: return NSLocalizedString( @"SETTINGS_BLOCK_ON_IDENITY_CHANGE_DETAIL", @"User settings section footer, a detailed explanation"); @@ -112,6 +135,8 @@ typedef NS_ENUM(NSInteger, PrivacySettingsTableViewControllerSectionIndex) { switch (indexPath.section) { case PrivacySettingsTableViewControllerSectionIndexScreenSecurity: return self.enableScreenSecurityCell; + case PrivacySettingsTableViewControllerSectionIndexCalling: + return self.callsHideIPAddressCell; case PrivacySettingsTableViewControllerSectionIndexHistoryLog: return self.clearHistoryLogCell; case PrivacySettingsTableViewControllerSectionIndexBlockOnIdentityChange: @@ -128,6 +153,8 @@ typedef NS_ENUM(NSInteger, PrivacySettingsTableViewControllerSectionIndex) { switch (section) { case PrivacySettingsTableViewControllerSectionIndexScreenSecurity: return NSLocalizedString(@"SETTINGS_SECURITY_TITLE", @"Section header"); + case PrivacySettingsTableViewControllerSectionIndexCalling: + return NSLocalizedString(@"SETTINGS_SECTION_TITLE_CALLING", @"settings topic header for table section"); case PrivacySettingsTableViewControllerSectionIndexHistoryLog: return NSLocalizedString(@"SETTINGS_HISTORYLOG_TITLE", @"Section header"); case PrivacySettingsTableViewControllerSectionIndexBlockOnIdentityChange: @@ -182,6 +209,13 @@ typedef NS_ENUM(NSInteger, PrivacySettingsTableViewControllerSectionIndex) { [Environment.preferences setShouldBlockOnIdentityChange:enabled]; } +- (void)didToggleCallsHideIPAddressSwitch:(UISwitch *)sender +{ + BOOL enabled = sender.isOn; + DDLogInfo(@"%@ toggled callsHideIPAddress: %@", self.tag, enabled ? @"ON" : @"OFF"); + [Environment.preferences setDoCallsHideIPAddress:enabled]; +} + #pragma mark - Log util + (NSString *)tag diff --git a/Signal/translations/en.lproj/Localizable.strings b/Signal/translations/en.lproj/Localizable.strings index e38282f0e..c952266d7 100644 --- a/Signal/translations/en.lproj/Localizable.strings +++ b/Signal/translations/en.lproj/Localizable.strings @@ -124,7 +124,7 @@ /* No comment provided by engineer. */ "COUNTRYCODE_SELECT_TITLE" = "Select Country Code"; -/* Accessibility label for the create new group button. */ +/* Accessibility label for the create group new group button */ "CREATE_NEW_GROUP" = "Create new group"; /* {{number of days}} embedded in strings, e.g. 'Alice updated disappearing messages expiration to {{5 days}}'. See other *_TIME_AMOUNT strings */ @@ -253,6 +253,9 @@ /* Generic notice when message failed to send. */ "ERROR_DESCRIPTION_CLIENT_SENDING_FAILURE" = "Failed to send message."; +/* Error mesage indicating that message send is disabled due to prekey update failures */ +"ERROR_DESCRIPTION_MESSAGE_SEND_DISABLED_PREKEY_UPDATE_FAILURES" = "ERROR_DESCRIPTION_MESSAGE_SEND_DISABLED_PREKEY_UPDATE_FAILURES"; + /* Generic error used whenver Signal can't contact the server */ "ERROR_DESCRIPTION_NO_INTERNET" = "Signal was unable to connect to the internet. Please try from another WiFi network or use mobile data."; @@ -349,9 +352,6 @@ /* No comment provided by engineer. */ "GROUP_REMOVING_FAILED" = "Failed to leave group"; -/* Accessibilty label for group settings */ -"GROUP_SETTINGS_LABEL" = "Group settings"; - /* No comment provided by engineer. */ "GROUP_TITLE_CHANGED" = "Title is now '%@'. "; @@ -567,7 +567,8 @@ /* No comment provided by engineer. */ "OK" = "Ok"; -/* Button text which opens the settings app */ +/* Button text which opens the settings app + Label for button which opens the settings UI */ "OPEN_SETTINGS_BUTTON" = "Settings"; /* Info Message when {{other user}} disables or doesn't support disappearing messages */ @@ -768,24 +769,21 @@ /* No comment provided by engineer. */ "SETTINGS_ADVANCED_TITLE" = "Advanced"; -/* This setting is used to switch between new-style WebRTC calling and old-style RedPhone calling. */ -"SETTINGS_ADVANCED_WEBRTC" = "Enable Video Calling"; - -/* The message of the alert shown when updates to the WebRTC property fail. */ -"SETTINGS_ADVANCED_WEBRTC_FAILED_MESSAGE" = "Could not update your preferences."; - -/* The title of the alert shown when updates to the WebRTC property fail. */ -"SETTINGS_ADVANCED_WEBRTC_FAILED_TITLE" = "Error"; - /* User settings section footer, a detailed explanation */ "SETTINGS_BLOCK_ON_IDENITY_CHANGE_DETAIL" = "Requires your approval before communicating with someone who has a new safety number, commonly from a reinstall of Signal."; /* Table cell label */ "SETTINGS_BLOCK_ON_IDENTITY_CHANGE_TITLE" = "Require Approval on Change"; -/* Settings button accessibility hint */ +/* Accessibility hint for the settings button */ "SETTINGS_BUTTON_ACCESSIBILITY" = "Settings"; +/* Table cell label */ +"SETTINGS_CALLING_HIDES_IP_ADDRESS_PREFERENCE_TITLE" = "Hide IP Address"; + +/* User settings section footer, a detailed explanation */ +"SETTINGS_CALLING_HIDES_IP_ADDRESS_PREFERENCE_TITLE_DETAIL" = "Hiding your IP address during audio and video calls will decrease call quality."; + /* No comment provided by engineer. */ "SETTINGS_CLEAR_HISTORY" = "Clear History Logs";