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";