Files
flipper/iOS/FlipperKit/FlipperPlatformWebSocket.mm
Lorenzo Blasa 3dacf5a7c9 WebSocket as default socket provider
Summary:
This change makes WebSockets the default for Flipper on iOS.

Having said that, we are introducing some logic to deal with clients connecting to older Flipper Desktop versions.

The mobile client will first attempt to connect via WebSocket with the Desktop. This connection can either be secure or insecure. If that fails, it will attempt to connect via RSocket.

Connection failure logic:
The mobile client will attempt to connect up-to 3 times via a WebSocket. If it fails to connect, then the socket provider is switched to RSocket.

As before, the mobile client will attempt to connect up-to 3 times via a RSocket. If it fails to connect, then the socket provider is switched back to WebSocket.

Process repeats until a successful connection is established.

Some logs that can be seen from iOS:

   2021-09-15 14:31:51.193503+0100 Sample[92026:92107440] [] nw_protocol_get_quic_image_block_invoke dlopen libquic failed
   2021-09-15 14:31:51.878257+0100 Sample[92026:92107440] [connection] nw_socket_handle_socket_event [C1.1:1] Socket SO_ERROR [61: Connection refused]
   2021-09-15 14:31:52.553729+0100 Sample[92026:92107440] [connection] nw_socket_handle_socket_event [C1.2:1] Socket SO_ERROR [61: Connection refused]
   2021-09-15 14:31:52.899511+0100 Sample[92026:92107442] [connection] nw_connection_get_connected_socket [C1] Client called nw_connection_get_connected_socket on unconnected nw_connection
   2021-09-15 14:31:52.899664+0100 Sample[92026:92107442] TCP Conn 0x600001d384d0 Failed : error 0:61 [61]
   2021-09-15 14:31:57.120120+0100 Sample[92026:92107439] [connection] nw_socket_handle_socket_event [C2.1:1] Socket SO_ERROR [61: Connection refused]
   2021-09-15 14:31:57.141785+0100 Sample[92026:92107439] [connection] nw_socket_handle_socket_event [C2.2:1] Socket SO_ERROR [61: Connection refused]
   2021-09-15 14:31:57.151604+0100 Sample[92026:92107483] [connection] nw_connection_get_connected_socket [C2] Client called nw_connection_get_connected_socket on unconnected nw_connection
   2021-09-15 14:31:57.154312+0100 Sample[92026:92107483] TCP Conn 0x600001d7c0b0 Failed : error 0:61 [61]
   2021-09-15 14:31:59.206079+0100 Sample[92026:92107483] [connection] nw_socket_handle_socket_event [C3.1:1] Socket SO_ERROR [61: Connection refused]
   2021-09-15 14:31:59.236824+0100 Sample[92026:92107483] [connection] nw_socket_handle_socket_event [C3.2:1] Socket SO_ERROR [61: Connection refused]
   2021-09-15 14:31:59.251927+0100 Sample[92026:92107439] [connection] nw_connection_get_connected_socket [C3] Client called nw_connection_get_connected_socket on unconnected nw_connection
   2021-09-15 14:31:59.255963+0100 Sample[92026:92107439] TCP Conn 0x600001d1c210 Failed : error 0:61 [61]
   2021-09-15 14:32:01.291303+0100 Sample[92026:92107439] [connection] nw_socket_handle_socket_event [C4.1:1] Socket SO_ERROR [61: Connection refused]
   2021-09-15 14:32:01.312406+0100 Sample[92026:92107439] [connection] nw_socket_handle_socket_event [C4.2:1] Socket SO_ERROR [61: Connection refused]
   2021-09-15 14:32:01.323099+0100 Sample[92026:92107483] [connection] nw_connection_get_connected_socket [C4] Client called nw_connection_get_connected_socket on unconnected nw_connection
   2021-09-15 14:32:01.326028+0100 Sample[92026:92107483] TCP Conn 0x600001d7c0b0 Failed : error 0:61 [61]
   flipper: Failed to connect with the current socket provider
   flipper: Use legacy socket provider
   flipper: FlipperClient::onConnected

Reviewed By: passy

Differential Revision: D30900471

fbshipit-source-id: 7c242ad71306803b050d0174fc22696bb74fdba5
2021-09-23 05:22:23 -07:00

229 lines
6.5 KiB
Plaintext

/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#ifdef FB_SONARKIT_ENABLED
#import "FlipperPlatformWebSocket.h"
#import <Flipper/Log.h>
#import <SocketRocket/SocketRocket.h>
static constexpr int connectionKeepaliveSeconds = 10;
#pragma mark - FlipperClientCertificateSecurityPolicy
@interface FlipperClientCertificateSecurityPolicy : SRSecurityPolicy
@property(nonatomic)
facebook::flipper::SocketCertificateProvider certificateProvider;
- (id)initWithCertificateProvider:
(facebook::flipper::SocketCertificateProvider)certificateProvider;
@end
@implementation FlipperClientCertificateSecurityPolicy
- (id)initWithCertificateProvider:
(facebook::flipper::SocketCertificateProvider)certificateProvider {
self = [super init];
if (self) {
_certificateProvider = certificateProvider;
}
return self;
}
/**
Updates all the security options for output streams, used for client
certificate authentication.
@param stream Stream to update the options in.
*/
- (void)updateSecurityOptionsInStream:(NSStream*)stream {
if (!_certificateProvider || ![stream isKindOfClass:[NSOutputStream class]]) {
return;
}
NSMutableDictionary* SSLOptions = [[NSMutableDictionary alloc] init];
[stream setProperty:(__bridge id)kCFStreamSocketSecurityLevelNegotiatedSSL
forKey:(__bridge id)kCFStreamPropertySocketSecurityLevel];
char PASSWORD[512] = {};
auto certificatePath = _certificateProvider(&PASSWORD[0], 512);
NSString* certificatePathObjC =
[NSString stringWithUTF8String:certificatePath.c_str()];
NSData* certificateData = [NSData dataWithContentsOfFile:certificatePathObjC];
NSString* password = [NSString stringWithUTF8String:PASSWORD];
NSDictionary* optionsDictionary = [NSDictionary
dictionaryWithObject:password
forKey:(__bridge id)kSecImportExportPassphrase];
CFArrayRef items = NULL;
OSStatus status = SecPKCS12Import(
(__bridge CFDataRef)certificateData,
(__bridge CFDictionaryRef)optionsDictionary,
&items);
if (status != noErr) {
return;
}
CFDictionaryRef identityDictionary =
(CFDictionaryRef)CFArrayGetValueAtIndex(items, 0);
SecIdentityRef identity = (SecIdentityRef)CFDictionaryGetValue(
identityDictionary, kSecImportItemIdentity);
SecCertificateRef certificate = NULL;
status = SecIdentityCopyCertificate(identity, &certificate);
if (status != noErr) {
return;
}
NSArray* certificates = [[NSArray alloc]
initWithObjects:(__bridge id)identity, (__bridge id)certificate, nil];
[SSLOptions setObject:[NSNumber numberWithBool:NO]
forKey:(NSString*)kCFStreamSSLValidatesCertificateChain];
[SSLOptions setObject:(NSString*)kCFStreamSocketSecurityLevelNegotiatedSSL
forKey:(NSString*)kCFStreamSSLLevel];
[SSLOptions setObject:(NSString*)kCFStreamSocketSecurityLevelNegotiatedSSL
forKey:(NSString*)kCFStreamPropertySocketSecurityLevel];
[SSLOptions setObject:certificates
forKey:(NSString*)kCFStreamSSLCertificates];
[SSLOptions setObject:[NSNumber numberWithBool:NO]
forKey:(NSString*)kCFStreamSSLIsServer];
[stream setProperty:SSLOptions
forKey:(__bridge id)kCFStreamPropertySSLSettings];
}
@end
#pragma mark - FlipperPlatformWebSocket
@interface FlipperPlatformWebSocket ()<SRWebSocketDelegate> {
NSURL* _url;
NSTimer* _keepAlive;
FlipperClientCertificateSecurityPolicy* _policy;
}
@property(nonatomic, strong) SRWebSocket* socket;
@end
@implementation FlipperPlatformWebSocket
- (instancetype)initWithURL:(NSURL* _Nonnull)url {
self = [super init];
if (self) {
_url = url;
_policy = [FlipperClientCertificateSecurityPolicy new];
}
return self;
}
- (void)dealloc {
[self disconnect];
}
- (void)connect {
if (_socket) {
return;
}
self.socket = [[SRWebSocket alloc] initWithURL:_url securityPolicy:_policy];
[_socket setDelegate:self];
[_socket open];
}
- (void)disconnect {
if ([_keepAlive isValid]) {
[_keepAlive invalidate];
}
_keepAlive = nil;
if (_socket) {
// Manually trigger a 'close' event as SocketRocket close method will
// not notify the delegate. SocketRocket only triggers the close event
// when the connection is closed from the server.
_eventHandler(facebook::flipper::SocketEvent::CLOSE);
[_socket close];
_socket = nil;
}
}
- (void)send:(NSString*)message error:(NSError**)error {
[_socket sendString:message error:error];
if (error != nil && *error) {
facebook::flipper::log("Unable to send message.");
}
}
- (void)setCertificateProvider:
(facebook::flipper::SocketCertificateProvider)certificateProvider {
_certificateProvider = certificateProvider;
_policy.certificateProvider = certificateProvider;
}
- (void)sendScheduledKeepAlive:(NSTimer*)timer {
[_socket sendPing:nil error:nil];
}
- (void)webSocketDidOpen:(SRWebSocket*)webSocket {
_eventHandler(facebook::flipper::SocketEvent::OPEN);
if (!_keepAlive) {
__weak auto weakSocket = _socket;
_keepAlive =
[NSTimer scheduledTimerWithTimeInterval:connectionKeepaliveSeconds
repeats:YES
block:^(NSTimer* timer) {
auto _Nullable socket = weakSocket;
[socket sendPing:nil error:nil];
}];
}
}
- (void)webSocket:(SRWebSocket*)webSocket didFailWithError:(NSError*)error {
/** Check for the error domain and code. Need to filter out SSL handshake
errors and dispatch them accordingly. CFNetwork SSLHandshake failed:
- Domain: NSOSStatusErrorDomain
- Code: -9806
*/
if ([[error domain] isEqual:NSOSStatusErrorDomain] && [error code] == -9806) {
_eventHandler(facebook::flipper::SocketEvent::SSL_ERROR);
} else {
_eventHandler(facebook::flipper::SocketEvent::ERROR);
}
_socket = nil;
}
- (void)webSocket:(SRWebSocket*)webSocket
didCloseWithCode:(NSInteger)code
reason:(NSString*)reason
wasClean:(BOOL)wasClean {
_eventHandler(facebook::flipper::SocketEvent::CLOSE);
_socket = nil;
}
- (void)webSocket:(SRWebSocket*)webSocket didReceiveMessage:(id)message {
if (message && _messageHandler) {
NSString* response = message;
_messageHandler([response UTF8String]);
}
}
@end
#endif