From 3dacf5a7c92ff4032fa1d8019de02468cdc9ab72 Mon Sep 17 00:00:00 2001 From: Lorenzo Blasa Date: Thu, 23 Sep 2021 05:21:24 -0700 Subject: [PATCH] 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 --- iOS/FlipperKit/FlipperClient.mm | 4 +- iOS/FlipperKit/FlipperPlatformWebSocket.mm | 11 ++++- iOS/FlipperKit/FlipperWebSocket.mm | 11 +++++ .../Flipper/FlipperConnectionManagerImpl.cpp | 46 +++++++++++++++++-- xplat/Flipper/FlipperConnectionManagerImpl.h | 11 +++++ xplat/Flipper/FlipperSocketProvider.cpp | 13 +++++- xplat/Flipper/FlipperSocketProvider.h | 11 +++++ xplat/Flipper/FlipperTransportTypes.h | 6 +-- 8 files changed, 101 insertions(+), 12 deletions(-) diff --git a/iOS/FlipperKit/FlipperClient.mm b/iOS/FlipperKit/FlipperClient.mm index 340b0701a..f776d6208 100644 --- a/iOS/FlipperKit/FlipperClient.mm +++ b/iOS/FlipperKit/FlipperClient.mm @@ -107,8 +107,8 @@ using WrapperPlugin = facebook::flipper::FlipperCppWrapperPlugin; _cppClient = facebook::flipper::FlipperClient::instance(); // To switch to a websocket provider, uncomment the line below. - // facebook::flipper::FlipperSocketProvider::setDefaultProvider( - // std::make_unique()); + facebook::flipper::FlipperSocketProvider::setDefaultProvider( + std::make_unique()); } catch (const std::system_error& e) { // Probably ran out of disk space. diff --git a/iOS/FlipperKit/FlipperPlatformWebSocket.mm b/iOS/FlipperKit/FlipperPlatformWebSocket.mm index 26135ab09..505e99c8c 100644 --- a/iOS/FlipperKit/FlipperPlatformWebSocket.mm +++ b/iOS/FlipperKit/FlipperPlatformWebSocket.mm @@ -195,7 +195,16 @@ static constexpr int connectionKeepaliveSeconds = 10; } - (void)webSocket:(SRWebSocket*)webSocket didFailWithError:(NSError*)error { - _eventHandler(facebook::flipper::SocketEvent::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; } diff --git a/iOS/FlipperKit/FlipperWebSocket.mm b/iOS/FlipperKit/FlipperWebSocket.mm index 6388e0846..2a08d8ec2 100644 --- a/iOS/FlipperKit/FlipperWebSocket.mm +++ b/iOS/FlipperKit/FlipperWebSocket.mm @@ -154,6 +154,17 @@ bool FlipperWebSocket::connect(FlipperConnectionManager* manager) { fullfilled = true; if (event == SocketEvent::OPEN) { promise.set_value(true); + } else if (event == SocketEvent::SSL_ERROR) { + try { + promise.set_exception( + std::make_exception_ptr(folly::AsyncSocketException( + folly::AsyncSocketException::SSL_ERROR, + "SSL handshake failed"))); + } catch (...) { + // set_exception() may throw an exception + // In that case, just set the value to false. + promise.set_value(false); + } } else { promise.set_value(false); } diff --git a/xplat/Flipper/FlipperConnectionManagerImpl.cpp b/xplat/Flipper/FlipperConnectionManagerImpl.cpp index 6d71c46d9..4a73a31b7 100644 --- a/xplat/Flipper/FlipperConnectionManagerImpl.cpp +++ b/xplat/Flipper/FlipperConnectionManagerImpl.cpp @@ -31,6 +31,10 @@ static constexpr int reconnectIntervalSeconds = 2; // To be bumped for every core platform interface change. static constexpr int sdkVersion = 4; +#ifdef __APPLE__ +static constexpr int maxFailedSocketConnectionAttempts = 3; +#endif + using namespace folly; namespace facebook { @@ -64,6 +68,10 @@ class ConnectionEvents { impl->callbacks_->onConnected(); } break; + case SocketEvent::SSL_ERROR: + // SSL errors are not handled as a connection event + // on this handler. + break; case SocketEvent::CLOSE: case SocketEvent::ERROR: if (!impl->isOpen_) @@ -91,6 +99,8 @@ FlipperConnectionManagerImpl::FlipperConnectionManagerImpl( flipperState_(state), insecurePort(config.insecurePort), securePort(config.securePort), + altInsecurePort(config.altInsecurePort), + altSecurePort(config.altSecurePort), flipperEventBase_(config.callbackWorker), connectionEventBase_(config.connectionWorker), contextStore_(contextStore), @@ -190,8 +200,8 @@ void FlipperConnectionManagerImpl::startSync() { } bool FlipperConnectionManagerImpl::connectAndExchangeCertificate() { - auto endpoint = - FlipperConnectionEndpoint(deviceData_.host, insecurePort, false); + auto port = useLegacySocketProvider ? insecurePort : altInsecurePort; + auto endpoint = FlipperConnectionEndpoint(deviceData_.host, port, false); int medium = certProvider_ != nullptr ? certProvider_->getCertificateExchangeMedium() @@ -213,6 +223,7 @@ bool FlipperConnectionManagerImpl::connectAndExchangeCertificate() { connectionIsTrusted_ = false; if (!newClient->connect(this)) { + reevaluateSocketProvider(); connectingInsecurely->fail("Failed to connect"); return false; } @@ -229,7 +240,8 @@ bool FlipperConnectionManagerImpl::connectAndExchangeCertificate() { } bool FlipperConnectionManagerImpl::connectSecurely() { - auto endpoint = FlipperConnectionEndpoint(deviceData_.host, securePort, true); + auto port = useLegacySocketProvider ? securePort : altSecurePort; + auto endpoint = FlipperConnectionEndpoint(deviceData_.host, port, true); int medium = certProvider_ != nullptr ? certProvider_->getCertificateExchangeMedium() @@ -276,6 +288,7 @@ bool FlipperConnectionManagerImpl::connectSecurely() { connectionIsTrusted_ = true; if (!newClient->connect(this)) { + reevaluateSocketProvider(); connectingSecurely->fail("Failed to connect"); return false; } @@ -456,6 +469,33 @@ void FlipperConnectionManagerImpl::sendLegacyCertificateRequest( }); } +/** + Check for the maximum number of failed socket connection attempts. + If exceeded, then swap the default socket provider. If the maximum + number of failed attempts is reached again, swap again the socket provider. + + WebSocket -> RSocket -> WebSocket -> ... + */ +void FlipperConnectionManagerImpl::reevaluateSocketProvider() { +#ifdef __APPLE__ + if (failedSocketConnectionAttempts < maxFailedSocketConnectionAttempts) { + ++failedSocketConnectionAttempts; + } else { + log("Failed to connect with the current socket provider"); + failedSocketConnectionAttempts = 0; + useLegacySocketProvider = !useLegacySocketProvider; + + if (useLegacySocketProvider) { + log("Use legacy socket provider"); + FlipperSocketProvider::shelveDefault(); + } else { + log("Use websocket provider"); + FlipperSocketProvider::unshelveDefault(); + } + } +#endif +} + bool FlipperConnectionManagerImpl::isRunningInOwnThread() { return flipperEventBase_->isInEventBaseThread(); } diff --git a/xplat/Flipper/FlipperConnectionManagerImpl.h b/xplat/Flipper/FlipperConnectionManagerImpl.h index 78807a6a7..9a6708e09 100644 --- a/xplat/Flipper/FlipperConnectionManagerImpl.h +++ b/xplat/Flipper/FlipperConnectionManagerImpl.h @@ -61,6 +61,8 @@ class FlipperConnectionManagerImpl : public FlipperConnectionManager { std::shared_ptr flipperState_; int insecurePort; int securePort; + int altInsecurePort; + int altSecurePort; folly::EventBase* flipperEventBase_; folly::EventBase* connectionEventBase_; @@ -71,6 +73,14 @@ class FlipperConnectionManagerImpl : public FlipperConnectionManager { bool certificateExchangeCompleted_ = false; int failedConnectionAttempts_ = 0; + int failedSocketConnectionAttempts = 0; + +#ifdef __APPLE__ + bool useLegacySocketProvider = false; +#else + bool useLegacySocketProvider = true; +#endif + std::shared_ptr contextStore_; std::shared_ptr implWrapper_; @@ -81,6 +91,7 @@ class FlipperConnectionManagerImpl : public FlipperConnectionManager { void requestSignedCertFromFlipper(); bool isRunningInOwnThread(); void sendLegacyCertificateRequest(folly::dynamic message); + void reevaluateSocketProvider(); std::string getDeviceId(); }; diff --git a/xplat/Flipper/FlipperSocketProvider.cpp b/xplat/Flipper/FlipperSocketProvider.cpp index 88b6b1dd0..5f5d76e2b 100644 --- a/xplat/Flipper/FlipperSocketProvider.cpp +++ b/xplat/Flipper/FlipperSocketProvider.cpp @@ -38,8 +38,10 @@ class FlipperDefaultSocketProvider : public FlipperSocketProvider { std::unique_ptr FlipperSocketProvider::provider_ = std::make_unique(); -std::unique_ptr FlipperSocketProvider::socketCreate( +std::unique_ptr FlipperSocketProvider::shelvedProvider_ = + nullptr; +std::unique_ptr FlipperSocketProvider::socketCreate( FlipperConnectionEndpoint endpoint, std::unique_ptr payload, folly::EventBase* eventBase) { @@ -63,5 +65,14 @@ void FlipperSocketProvider::setDefaultProvider( provider_ = std::move(provider); } +void FlipperSocketProvider::shelveDefault() { + shelvedProvider_ = std::move(provider_); + provider_ = std::make_unique(); +} + +void FlipperSocketProvider::unshelveDefault() { + provider_ = std::move(shelvedProvider_); +} + } // namespace flipper } // namespace facebook diff --git a/xplat/Flipper/FlipperSocketProvider.h b/xplat/Flipper/FlipperSocketProvider.h index 1e4e8069d..360baf64d 100644 --- a/xplat/Flipper/FlipperSocketProvider.h +++ b/xplat/Flipper/FlipperSocketProvider.h @@ -66,8 +66,19 @@ class FlipperSocketProvider { static void setDefaultProvider( std::unique_ptr provider); + /** + Shelves the current default socket provider and promotes the internal + socket provider as default. + */ + static void shelveDefault(); + /** + Restores a previously shelved socket provider. + */ + static void unshelveDefault(); + private: static std::unique_ptr provider_; + static std::unique_ptr shelvedProvider_; }; } // namespace flipper diff --git a/xplat/Flipper/FlipperTransportTypes.h b/xplat/Flipper/FlipperTransportTypes.h index c6836e3d9..bcb2fac5c 100644 --- a/xplat/Flipper/FlipperTransportTypes.h +++ b/xplat/Flipper/FlipperTransportTypes.h @@ -15,11 +15,7 @@ namespace flipper { /** SocketEvent defines the socket states used by Flipper. */ -enum class SocketEvent : int { - OPEN, - CLOSE, - ERROR, -}; +enum class SocketEvent : int { OPEN, CLOSE, ERROR, SSL_ERROR }; /** Defines a socket event handler. Used to notify changes in socket state.