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.